+ VIP CC +
+ ++ Portal Educativo +
+ +diff --git a/.gitignore b/.gitignore index 80704f4378..12e3b32354 100755 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ database.database database.db diagram.png __pycache__/ +migrations \ No newline at end of file diff --git a/docs/assets/logo.jpg b/docs/assets/logo.jpg new file mode 100644 index 0000000000..917d074903 Binary files /dev/null and b/docs/assets/logo.jpg differ diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/0763d677d453_.py deleted file mode 100644 index 88964176f1..0000000000 --- a/migrations/versions/0763d677d453_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: 0763d677d453 -Revises: -Create Date: 2025-02-25 14:47:16.337069 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '0763d677d453' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..398b3c017c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -877,19 +877,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -997,14 +984,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1307,14 +1286,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3915,29 +3886,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -4074,35 +4022,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4944,18 +4863,6 @@ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -5044,14 +4951,6 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -5252,14 +5151,6 @@ "update-browserslist-db": "^1.1.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6982,28 +6873,6 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -7094,30 +6963,6 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/src/api/commands.py b/src/api/commands.py index 19806164d3..cff1a785fb 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -1,34 +1,34 @@ -import click -from api.models import db, User +# import click +# from api.models import db -""" -In this file, you can add as many commands as you want using the @app.cli.command decorator -Flask commands are usefull to run cronjobs or tasks outside of the API but sill in integration -with youy database, for example: Import the price of bitcoin every night as 12am -""" -def setup_commands(app): +# """ +# In this file, you can add as many commands as you want using the @app.cli.command decorator +# Flask commands are usefull to run cronjobs or tasks outside of the API but sill in integration +# with youy database, for example: Import the price of bitcoin every night as 12am +# """ +# def setup_commands(app): - """ - This is an example command "insert-test-users" that you can run from the command line - by typing: $ flask insert-test-users 5 - Note: 5 is the number of users to add - """ - @app.cli.command("insert-test-users") # name of our command - @click.argument("count") # argument of out command - def insert_test_users(count): - print("Creating test users") - for x in range(1, int(count) + 1): - user = User() - user.email = "test_user" + str(x) + "@test.com" - user.password = "123456" - user.is_active = True - db.session.add(user) - db.session.commit() - print("User: ", user.email, " created.") +# """ +# This is an example command "insert-test-users" that you can run from the command line +# by typing: $ flask insert-test-users 5 +# Note: 5 is the number of users to add +# """ +# @app.cli.command("insert-test-users") # name of our command +# @click.argument("count") # argument of out command +# def insert_test_users(count): +# print("Creating test users") +# for x in range(1, int(count) + 1): +# user = User() +# user.email = "test_user" + str(x) + "@test.com" +# user.password = "123456" +# user.is_active = True +# db.session.add(user) +# db.session.commit() +# print("User: ", user.email, " created.") - print("All test users created") +# print("All test users created") - @app.cli.command("insert-test-data") - def insert_test_data(): - pass \ No newline at end of file +# @app.cli.command("insert-test-data") +# def insert_test_data(): +# pass \ No newline at end of file diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..c643c47b5e 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,234 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import Table, Column, Integer, String, ForeignKey, Enum +from sqlalchemy.orm import Mapped, mapped_column, relationship +import enum db = SQLAlchemy() -class User(db.Model): +evento_estudiantes = Table( + "evento_estudiantes", + db.Model.metadata, + Column("evento_id", Integer, ForeignKey( + "eventos.evento_id"), primary_key=True), + Column("alumnos_id", Integer, ForeignKey( + "estudiantes.id"), primary_key=True), +) + +tutor_estudiantes = Table( + "tutor_estudiantes", + db.Model.metadata, + Column("estudiantes_id", Integer, ForeignKey( + "estudiantes.id"), primary_key=True), + Column("tutor_id", Integer, ForeignKey( + "tutor_legal.id"), primary_key=True), + Column("parentesco", String(255)) +) + + +class SuperAdmin(db.Model): + __tablename__ = "super_admin" + id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + password: Mapped[str] = mapped_column(String(255), nullable=False) + nombre_colegio: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + rol_id: Mapped[int] = mapped_column(Integer, default=1, nullable=False) + + aulas = relationship("Aula", back_populates="colegio", foreign_keys="[Aula.colegio_id]") + + def serialize(self): + return { + "id": self.id, + "email": self.email, + "nombre_colegio": self.nombre_colegio, + "rol_id": self.rol_id + } + +class TutorLegal(db.Model): + __tablename__ = "tutor_legal" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=True) + email: Mapped[str] = mapped_column(String(120), nullable=True) + password: Mapped[str] = mapped_column(String(120), nullable=True) + telephone: Mapped[str] = mapped_column(String(80), nullable=True) + rol_id: Mapped[int] = mapped_column(Integer, default=3, nullable=False) + estudiantes = relationship( + "Estudiantes", + secondary=tutor_estudiantes, + back_populates="tutores" + ) def serialize(self): return { "id": self.id, + "name": self.name, "email": self.email, - # do not serialize the password, its a security breach - } \ No newline at end of file + "telephone": self.telephone, + "rol_id": self.rol_id + } + + +class Profesor(db.Model): + __tablename__ = "profesor" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=True) + email: Mapped[str] = mapped_column(String(120), nullable=True) + password: Mapped[str] = mapped_column(String(120), nullable=True) + telephone: Mapped[str] = mapped_column(String(120), nullable=True) + rol_id: Mapped[int] = mapped_column(Integer, default=2, nullable=False) + + estudiantes = relationship("Estudiantes", back_populates="profesor") + eventos = relationship("Eventos", back_populates="profesor") + aulas = relationship("Aula", back_populates="profesor") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "email": self.email, + "telephone": self.telephone, + "rol_id": self.rol_id + } + + +class Aula(db.Model): + __tablename__ = "aula" + + aula_id: Mapped[int] = mapped_column(primary_key=True) + curso: Mapped[str] = mapped_column(String(80), nullable=True) + clase: Mapped[str] = mapped_column(String(40), nullable=True) + + profesor_id: Mapped[int] = mapped_column( + ForeignKey("profesor.id"), + nullable=True + ) + colegio_id: Mapped[int] = mapped_column( + ForeignKey("super_admin.id"), + nullable=True + ) + + profesor = relationship("Profesor", back_populates="aulas") + colegio = relationship("SuperAdmin", back_populates="aulas", foreign_keys=[colegio_id]) + estudiantes = relationship("Estudiantes", back_populates="aula") + + def serialize(self): + return { + "aula_id": self.aula_id, + "curso": self.curso, + "clase": self.clase, + } + +class Estudiantes(db.Model): + __tablename__ = "estudiantes" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), nullable=True) + + profesor_id: Mapped[int] = mapped_column( + ForeignKey("profesor.id"), + nullable=True + ) + aula_id: Mapped[int] = mapped_column( + ForeignKey("aula.aula_id"), + nullable=True + ) + profesor = relationship("Profesor", back_populates="estudiantes") + aula = relationship("Aula", back_populates="estudiantes") + eventos = relationship( + "Eventos", + secondary=evento_estudiantes, + back_populates="alumnos" + ) + tutores = relationship( + "TutorLegal", + secondary=tutor_estudiantes, + back_populates="estudiantes" + ) + calificaciones = relationship( + "Calificaciones", back_populates="estudiante") + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "profesor_id": self.profesor_id + } + + +class Asignaturas(db.Model): + __tablename__ = "asignaturas" + + asignatura_id: Mapped[int] = mapped_column(primary_key=True) + nombre_asignatura: Mapped[str] = mapped_column(String(120), nullable=True) + + calificaciones = relationship( + "Calificaciones", back_populates="asignatura") + + def serialize(self): + return { + "nombre_asignatura": self.nombre_asignatura, + "asignatura_id": self.asignatura_id, + } + + +class Calificaciones(db.Model): + __tablename__ = "calificaciones" + + calificacion_id: Mapped[int] = mapped_column(primary_key=True) + calificacion: Mapped[int] = mapped_column(nullable=True) + + asignatura_id: Mapped[int] = mapped_column( + ForeignKey("asignaturas.asignatura_id"), + nullable=True + ) + estudiante_id: Mapped[int] = mapped_column( + ForeignKey("estudiantes.id"), + nullable=True + ) + asignatura = relationship("Asignaturas", back_populates="calificaciones") + estudiante = relationship("Estudiantes", back_populates="calificaciones") + + def serialize(self): + return { + "calificacion_id": self.calificacion_id, + "calificacion": self.calificacion, + } + + +class tipo_evento(enum.Enum): + EXCURSION = "excursion" + EXAMEN = "examen" + REUNION = "reunion" + EVENTO_SOLIDARIO = "evento solidario" + + +class Eventos(db.Model): + __tablename__ = "eventos" + + evento_id: Mapped[int] = mapped_column(primary_key=True) + nombre_evento: Mapped[str] = mapped_column(String(80), nullable=True) + localizacion: Mapped[str] = mapped_column(String(80), nullable=True) + tipo_de_evento: Mapped[tipo_evento] = mapped_column( + Enum(tipo_evento), nullable=False) + profesor_id: Mapped[int] = mapped_column( + ForeignKey("profesor.id"), + nullable=True + ) + + profesor = relationship("Profesor", back_populates="eventos") + alumnos = relationship( + "Estudiantes", + secondary=evento_estudiantes, + back_populates="eventos" + ) + + def serialize(self): + return { + "evento_id": self.evento_id, + "nombre_evento": self.nombre_evento, + "localizacion": self.localizacion, + "tipo_de_evento": self.tipo_de_evento.value, + } diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..a78a3675da 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -2,7 +2,7 @@ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ from flask import Flask, request, jsonify, url_for, Blueprint -from api.models import db, User +from api.models import db from api.utils import generate_sitemap, APIException from flask_cors import CORS diff --git a/src/app.py b/src/app.py index 1b3340c0fa..f543642f0c 100644 --- a/src/app.py +++ b/src/app.py @@ -9,7 +9,7 @@ from api.models import db from api.routes import api from api.admin import setup_admin -from api.commands import setup_commands +# from api.commands import setup_commands # from models import Person @@ -35,7 +35,7 @@ setup_admin(app) # add the admin -setup_commands(app) +# setup_commands(app) # Add all endpoints form the API with a "api" prefix app.register_blueprint(api, url_prefix='/api') diff --git a/src/front/pages/LandingPagePT.jsx b/src/front/pages/LandingPagePT.jsx new file mode 100644 index 0000000000..bb75422ca4 --- /dev/null +++ b/src/front/pages/LandingPagePT.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +export const LandingPagePT = () => { + const primaryColor = "#6200e8"; + const navigate = useNavigate(); + + return ( +
+ Portal Educativo +
+ +