diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 205e37ff..eab1dd33 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -39,8 +39,5 @@ jobs: - name: Test with SQLite run: | CI=True pytest -v --exitfirst --cov=now_lms - - name: Test with SQLite and Redis Cache - run: | - CI=True DATABASE_URL=sqlite:// CACHE_REDIS_HOST=localhost CACHE_REDIS_PORT=6379 pytest -v --exitfirst --cov=now_lms - name: Codecov uses: codecov/codecov-action@v1 diff --git a/.github/workflows/refactor.yml b/.github/workflows/refactor.yml index b665b5cc..38118ec6 100644 --- a/.github/workflows/refactor.yml +++ b/.github/workflows/refactor.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Refactor on: push: @@ -16,7 +16,7 @@ jobs: # 3.8 Shared hosting # 3.9 Ubi minimal # 3.12 Last release - python-version: [3.8, 3.9, 3.12] + python-version: [3.8] steps: - uses: actions/checkout@v3 @@ -37,8 +37,5 @@ jobs: - name: Test with SQLite run: | CI=True pytest -v --exitfirst --cov=now_lms - - name: Test with SQLite and Redis Cache - run: | - CI=True DATABASE_URL=sqlite:// CACHE_REDIS_HOST=localhost CACHE_REDIS_PORT=6379 pytest -v --exitfirst --cov=now_lms - name: Codecov uses: codecov/codecov-action@v1 diff --git a/now_lms/__init__.py b/now_lms/__init__.py index 550835c8..1ff5b991 100644 --- a/now_lms/__init__.py +++ b/now_lms/__init__.py @@ -36,32 +36,29 @@ # Libreria estandar # --------------------------------------------------------------------------------------- import sys -from datetime import datetime from os import cpu_count, environ from platform import python_version -from turtle import update # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- import click -from flask import Flask, abort, flash, redirect, render_template, request, url_for +from flask import Flask, flash, render_template from flask.cli import FlaskGroup from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required +from flask_login import LoginManager, current_user from flask_mail import Mail from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads +from flask_uploads import configure_uploads from pg8000.dbapi import ProgrammingError as PGProgrammingError from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError +from sqlalchemy.exc import OperationalError, ProgrammingError +from werkzeug.exceptions import HTTPException # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- -from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global +from now_lms.cache import cache from now_lms.config import ( CONFIGURACION, DESARROLLO, @@ -72,18 +69,7 @@ images, log_messages, ) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) +from now_lms.db import Configuracion, Usuario, database from now_lms.db.info import app_info from now_lms.db.initial_data import ( asignar_cursos_a_categoria, @@ -103,10 +89,6 @@ from now_lms.db.tools import ( crear_configuracion_predeterminada, cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, logo_perzonalizado, obtener_estilo_actual, verifica_docente_asignado_a_curso, @@ -114,33 +96,32 @@ verifica_moderador_asignado_a_curso, verificar_avance_recurso, ) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm from now_lms.logs import log from now_lms.misc import ( ESTILO, ESTILO_ALERTAS, - GENEROS, ICONOS_RECURSOS, INICIO_SESION, concatenar_parametros_a_url, markdown_to_clean_hmtl, ) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.profiles.admin import admin_profile -from now_lms.modules.profiles.instructor import instructor_profile -from now_lms.modules.profiles.moderator import moderator_profile -from now_lms.modules.profiles.user import user_profile -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.modules.home import home from now_lms.version import VERSION -from now_lms.modules.groups import group +from now_lms.vistas.categories import category +from now_lms.vistas.certificates import certificate +from now_lms.vistas.courses import course +from now_lms.vistas.groups import group +from now_lms.vistas.home import home +from now_lms.vistas.messages import msg +from now_lms.vistas.profiles.admin import admin_profile +from now_lms.vistas.profiles.instructor import instructor_profile +from now_lms.vistas.profiles.moderator import moderator_profile +from now_lms.vistas.profiles.user import user_profile +from now_lms.vistas.programs import program +from now_lms.vistas.resources import resource_d +from now_lms.vistas.settings import setting +from now_lms.vistas.tags import tag +from now_lms.vistas.users import user +from now_lms.vistas.web_error_codes import web_error # --------------------------------------------------------------------------------------- # Metadatos @@ -225,6 +206,7 @@ def registrar_modulos_en_la_aplicacion_principal(flask_app: Flask): flask_app.register_blueprint(instructor_profile) flask_app.register_blueprint(moderator_profile) flask_app.register_blueprint(user_profile) + flask_app.register_blueprint(web_error) # --------------------------------------------------------------------------------------- @@ -277,6 +259,30 @@ def no_autorizado(): # pragma: no cover # --------------------------------------------------------------------------------------- # Páginas de error personalizadas. # --------------------------------------------------------------------------------------- + + +# Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402 +class PaymentRequired(HTTPException): + """402 Payment Required""" + + code = 402 + description = """ +The HTTP 402 Payment Required is a nonstandard response status code that is reserved +for future use. This status code was created to enable digital cash or (micro) payment +systems and would indicate that the requested content is not available until the client +makes a payment. +""" + + +# Errores standares +def handle_402(error): + if error: + return render_template("error_pages/403.html") + + +lms_app.register_error_handler(PaymentRequired, handle_402) + + @lms_app.errorhandler(403) @cache.cached() def error_403(error): # pragma: no cover @@ -293,6 +299,14 @@ def error_404(error): # pragma: no cover return render_template("error_pages/404.html"), 404 +@lms_app.errorhandler(405) +@cache.cached() +def error_405(error): # pragma: no cover + """Pagina personalizada para metodos no permitidos.""" + assert error is not None # nosec B101 + return render_template("error_pages/405.html"), 405 + + @lms_app.errorhandler(500) @cache.cached() def error_500(error): # pragma: no cover @@ -349,7 +363,7 @@ def carga_configuracion_del_sitio_web_desde_db(): # pragma: no cover # --------------------------------------------------------------------------------------- # Funciones auxiliares para la administracion y configuración inicial de la aplicacion # --------------------------------------------------------------------------------------- -def initial_setup(with_examples=False): +def initial_setup(with_examples=False, with_test_data=False): """Inicializa una nueva bases de datos""" with lms_app.app_context(): log.info("Creando esquema de base de datos.") @@ -358,9 +372,9 @@ def initial_setup(with_examples=False): log.debug("Esquema de base de datos creado correctamente.") log.info("Cargando datos de muestra.") crear_configuracion_predeterminada() - crear_usuarios_predeterminados(with_examples) crear_curso_predeterminado() - if DESARROLLO or environ.get("CI") or with_examples: + crear_usuarios_predeterminados() + if with_examples: crear_categorias() crear_etiquetas() log.debug("Cargando datos de prueba.") @@ -373,6 +387,11 @@ def initial_setup(with_examples=False): crear_programa() crear_recurso_descargable() log.debug("Datos de muestra cargados correctamente.") + if DESARROLLO or environ.get("CI") or with_test_data: + from now_lms.db.data_test import crear_data_para_pruebas + + crear_data_para_pruebas() + log.info("NOW - LMS iniciado correctamente.") @@ -418,7 +437,7 @@ def init_app(with_examples=False): # pragma: no cover if DB_ACCESS and not DB_INICIALIZADA: log.info("Iniciando nueva base de datos.") - initial_setup(with_examples=False) + initial_setup(with_examples=with_examples) else: log.trace("Acceso a base de datos verificado.") config = Configuracion.query.first() @@ -457,10 +476,15 @@ def command(as_module=False) -> None: # pragma: no cover @lms_app.cli.command() @click.option("--with-examples", is_flag=True, default=False, help="Load example data at setup.") -def setup(with_examples): # pragma: no cover +@click.option("--with-tests", is_flag=True, default=False, help="Load data for testing.") +def setup(with_examples=False, with_tests=False): # pragma: no cover """Inicia al aplicacion.""" lms_app.app_context().push() initial_setup(with_examples) + if with_tests: + from now_lms.db.data_test import crear_data_para_pruebas + + crear_data_para_pruebas() @lms_app.cli.command() @@ -477,12 +501,13 @@ def upgrade_db(): # pragma: no cover @lms_app.cli.command() @click.option("--with-examples", is_flag=True, default=False, help="Load example data at setup.") -def resetdb(with_examples) -> None: # pragma: no cover +@click.option("--with-tests", is_flag=True, default=False, help="Load data for testing.") +def resetdb(with_examples=False, with_tests=False) -> None: # pragma: no cover """Elimina la base de datos actual e inicia una nueva.""" lms_app.app_context().push() cache.clear() database.drop_all() - initial_setup(with_examples) + initial_setup(with_examples, with_tests) @lms_app.cli.command() diff --git a/now_lms/db/__init__.py b/now_lms/db/__init__.py index 86bfc9b2..7897cc9a 100644 --- a/now_lms/db/__init__.py +++ b/now_lms/db/__init__.py @@ -447,6 +447,7 @@ class Certificado(database.Model, BaseTabla): """Plantilla para generar un certificado.""" titulo = database.Column(database.String(50)) + descripcion = database.Column(database.String(500)) habilitado = database.Column(database.Boolean()) diff --git a/now_lms/db/data_test.py b/now_lms/db/data_test.py new file mode 100644 index 00000000..7b113264 --- /dev/null +++ b/now_lms/db/data_test.py @@ -0,0 +1,475 @@ +# Copyright 2022 - 2023 BMO Soluciones, S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Contributors: +# - William José Moreno Reyes + +"""Codigo para crear cursos iniciales.s""" + +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- +from datetime import datetime, time, timedelta +from os import makedirs, path +from shutil import copyfile + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.auth import proteger_passwd +from now_lms.config import DIRECTORIO_ARCHIVOS, DIRECTORIO_BASE_ARCHIVOS_USUARIO +from now_lms.db import ( + Categoria, + Curso, + CursoRecurso, + CursoSeccion, + Etiqueta, + Programa, + Recurso, + Usuario, + UsuarioGrupo, + database, +) +from now_lms.db.initial_data import copy_sample_audio, copy_sample_img, copy_sample_pdf, curse_logo, demo_external_code + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- + + +def crear_etiqueta_prueba(): + """Crea etiquetas de demostración.""" + + etiqueta = Etiqueta(id="01HNP0TTQNTR03J7ZQHR09YMJJ", nombre="Python", color="#FFD43B") + database.session.add(etiqueta) + database.session.commit() + + +def crear_categoria_prueba(): + """Crea categorias de demostración.""" + + categoria = Categoria(id="01HNP0TTQNTR03J7ZQHR09YMJK", nombre="Learning", descripcion="Cursos sobre aprendizaje") + database.session.add(categoria) + database.session.commit() + + +def crear_certificado_prueba(): + """Crea certificado de prueba""" + from now_lms.db import Certificado + + certificado = Certificado(id="01HNP0TTQNTR03J7ZQHR09YMKK", titulo="Certficado Test", descripcion="Certificado Test") + database.session.add(certificado) + database.session.commit() + + +def crear_curso_demo(): + # pylint: disable=too-many-locals + """Crea en la base de datos un curso de demostración.""" + demo = Curso( + id="01HNZY78P2PW3R46BW447FH816", + nombre="Demo Course", + codigo="resources", + descripcion="This course will let you learn resource types.", + estado="open", + certificado=False, + publico=True, + duracion=7, + nivel=1, + auditable=False, + portada=True, + fecha_inicio=datetime.today() + timedelta(days=7), + fecha_fin=datetime.today() + timedelta(days=14), + promocionado=True, + fecha_promocionado=datetime.today(), + ) + + database.session.add(demo) + database.session.commit() + curse_logo("resources", "11372802.jpg") + + seccion_id = "01HNZY7Y81RR4EFMDQX8F2XWHE" + nueva_seccion = CursoSeccion( + id=seccion_id, + curso="resources", + nombre="Demo of type of resources.", + descripcion="Demo of type of resources.", + estado=False, + indice=1, + ) + + database.session.add(nueva_seccion) + database.session.commit() + + copy_sample_audio() + nuevo_recurso6 = CursoRecurso( + id="01HNZYDA9WKT2FHCBZSFV7JQBR", + curso="resources", + seccion=seccion_id, + tipo="mp3", + nombre="A demo audio resource.", + descripcion="Audio is easy to produce that videos.", + base_doc_url="audio", + doc="resources/En-us-hello.ogg", + indice=1, + publico=True, + ) + database.session.add(nuevo_recurso6) + database.session.commit() + + copy_sample_pdf() + nuevo_recurso5 = CursoRecurso( + id="01HNZYDQV2K1FWNKH0R04JTSNV", + curso="resources", + seccion=seccion_id, + tipo="pdf", + nombre="Demo pdf resource.", + descripcion="A exampel of a PDF file to share with yours learners.", + base_doc_url="files", + doc="resources/NOW_Learning_Management_System.pdf", + indice=2, + publico=True, + ) + database.session.add(nuevo_recurso5) + database.session.commit() + + nuevo_recurso3 = CursoRecurso( + id="01HNZYDXSJJ1EC28QW22YNHSGX", + curso="resources", + seccion=seccion_id, + tipo="meet", + nombre="A live meet about course sales.", + descripcion="Live meets will improve your course.", + url="https://en.wikipedia.org/wiki/Web_conferencing", + indice=3, + fecha=datetime.today() + timedelta(days=9), + hora_inicio=time(hour=14, minute=30), + hora_fin=time(hour=15, minute=00), + notes="Google Meet", + publico=False, + requerido=2, + ) + database.session.add(nuevo_recurso3) + database.session.commit() + + copy_sample_img() + nuevo_recurso4 = CursoRecurso( + id="01HNZYECPY2SKBM09GFA4TFWN2", + curso="resources", + seccion=seccion_id, + tipo="img", + nombre="A demo image file.", + descripcion="A image file.", + indice=4, + publico=True, + requerido=3, + base_doc_url="images", + doc="resources/logo_large.png", + ) + database.session.add(nuevo_recurso4) + database.session.commit() + + nuevo_recurso5 = CursoRecurso( + id="01HNZYETGNYGVYN79JB9STQHAM", + curso="resources", + seccion=seccion_id, + tipo="text", + nombre="A demo text resource.", + descripcion="A text in markdown.", + indice=5, + publico=False, + requerido=3, + text="# NOW - Learning Management System.", + ) + database.session.add(nuevo_recurso5) + database.session.commit() + + nuevo_recurso6 = CursoRecurso( + id="01HNZYFK9SX5GE6CEKC4DSSZHD", + curso="resources", + seccion=seccion_id, + tipo="html", + nombre="A demo external resouce resource.", + descripcion="A HTML text.", + indice=6, + publico=False, + external_code=demo_external_code, + ) + database.session.add(nuevo_recurso6) + database.session.commit() + + nuevo_recurso7 = CursoRecurso( + id="01HNZYFZWX2HF6354B5SV4V8V8", + curso="resources", + seccion=seccion_id, + tipo="link", + nombre="A external link.", + descripcion="A external link.", + indice=7, + publico=False, + url="https://es.wikipedia.org/wiki/Wikipedia:Portada", + ) + database.session.add(nuevo_recurso7) + database.session.commit() + + nuevo_recurso8 = CursoRecurso( + id="01HNZYGGKNNDG4NJ949971GMJM", + curso="resources", + seccion=seccion_id, + tipo="slides", + nombre="A demo Slide Show.", + descripcion="A demo Slide Show.", + indice=8, + publico=False, + ) + database.session.add(nuevo_recurso8) + database.session.commit() + + nuevo_recurso9 = CursoRecurso( + id="01HNZYGXRRWXJ8GXVXYZY8S994", + curso="resources", + seccion=seccion_id, + tipo="youtube", + nombre="A demo youtube video.", + descripcion="A demo youtube video..", + url="https://www.youtube.com/watch?v=TWQFHRt3dNg", + indice=9, + publico=False, + requerido=2, + ) + database.session.add(nuevo_recurso9) + database.session.commit() + + +def crear_usuarios_de_prueba(): + student = Usuario( + id="01HNZXJ6Q8CWGC6DXTHK8NC9AT", + usuario="student", + acceso=proteger_passwd("student"), + nombre="Meylin", + apellido="Perez", + correo_electronico="hello@domain.net", + tipo="student", + activo=True, + visible=True, + titulo=None, + genero="female", + nacimiento=datetime(year=1988, month=9, day=21), + bio="Hello there!", + url="google.com.es", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + student1 = Usuario( + id="01HNZXJRD65A55BJACFEFNZ88D", + usuario="student1", + acceso=proteger_passwd("student1"), + nombre="Dania", + apellido="Mendez", + correo_electronico="hello1@domain.net", + tipo="student", + activo=True, + visible=False, + titulo=None, + genero="female", + nacimiento=datetime(year=1988, month=9, day=21), + bio="Hello there!!", + url="www.google.com", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + student2 = Usuario( + id="01HNZXK9VV54MGQ9KANEGN4V5W", + usuario="student2", + acceso=proteger_passwd("student1"), + nombre="Gema", + apellido="Lopez", + correo_electronico="hello3@domain.net", + tipo="student", + activo=True, + visible=True, + titulo=None, + genero="female", + nacimiento=datetime(year=1988, month=9, day=21), + bio="Hello there!!!", + url="google.com.uk", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + student3 = Usuario( + id="01HNZXKTK3KBT63R2QV87X8WVP", + usuario="student3", + acceso=proteger_passwd("student3"), + nombre="Maria", + apellido="Lopez", + correo_electronico="hi@domain.com", + tipo="student", + activo=False, + visible=False, + titulo=None, + genero="female", + nacimiento=datetime(year=1988, month=9, day=21), + bio="Hi there!", + url="www.google.com.es", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + database.session.add(student) + database.session.add(student1) + database.session.add(student2) + database.session.add(student3) + demo_grupo = UsuarioGrupo(nombre="Grupo de Prueba", descripcion="Demo Group", activo=True) + database.session.add(demo_grupo) + database.session.commit() + instructor = Usuario( + id="01HNZXNH8D5DQA9MWHAF599EBV", + usuario="instructor", + acceso=proteger_passwd("instructor"), + nombre="Nemesio", + apellido="Reyes", + correo_electronico="hello2@domain.net", + tipo="instructor", + activo=True, + visible=False, + genero="male", + nacimiento=datetime(year=1988, month=9, day=21), + bio="You can", + url="wikipedia.org", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + database.session.add(instructor) + database.session.commit() + moderator = Usuario( + id="01HNZXP4MJKA0EBH1N0W3YSHVW", + usuario="moderator", + acceso=proteger_passwd("moderator"), + tipo="moderator", + nombre="Abner", + apellido="Romero", + correo_electronico="moderator@mail.com", + activo=True, + visible=False, + genero="male", + nacimiento=datetime(year=1988, month=9, day=21), + bio="You can do it.", + url="redtube.com", + linkedin="user", + facebook="user", + twitter="user", + github="user", + ) + database.session.add(moderator) + database.session.commit() + + +TEXTO_PROGRAMA = """Programa de Prueba""" + + +def crear_programa_prueba(): + """Crea programa de pruebas.""" + + programa = Programa( + id="01HNZXEMSWTSBM4PNSY4R9VMN6", + nombre="Programing 101", + codigo="P000", + descripcion="Introduction to programing", + precio=100, + publico=True, + estado="open", + logo=True, + texto=TEXTO_PROGRAMA, + promocionado=True, + fecha_promocionado=datetime.today(), + ) + curse_logo(curso="P001", image="concepto-collage-html-css-persona.jpg", program=True) + database.session.add(programa) + database.session.commit() + + +def crear_recurso_prueba(): + """Recurso descargable de ejemplo.""" + + recurso = Recurso( + id="01HNZXA1BX9B297CYAAA4MK93V", + nombre="Think Python", + codigo="R000", + descripcion="How to Think Like a Computer Scientist", + precio=0, + publico=True, + logo=True, + file_name="R005.pdf", + tipo="ebook", + usuario="instructor", + ) + database.session.add(recurso) + database.session.commit() + + directorio_destino_archivo = path.join(DIRECTORIO_BASE_ARCHIVOS_USUARIO, "public", "files", "resources_files") + directorio_destino_imagen = path.join(DIRECTORIO_BASE_ARCHIVOS_USUARIO, "public", "images", "resources_files") + try: # pragma: no cover + makedirs(directorio_destino_archivo) + makedirs(directorio_destino_imagen) + except FileExistsError: # pragma: no cover + pass + except FileNotFoundError: # pragma: no cover + pass + + # Copiar pdf de ejemplo. + archivos = [ + ("thinkpython2.pdf", "R005.pdf"), + ] + for archivo in archivos: + origen = path.join(DIRECTORIO_ARCHIVOS, "examples", archivo[0]) + destino = path.join(directorio_destino_archivo, archivo[1]) + try: # pragma: no cover + copyfile(origen, destino) + except FileExistsError: # pragma: no cover + pass + except FileNotFoundError: # pragma: no cover + pass + + # Copiar img de ejemplo. + imagenes = [ + ("thinkpython2.jpg", "R005.jpg"), + ] + for image in imagenes: + origen = path.join(DIRECTORIO_ARCHIVOS, "examples", image[0]) + destino = path.join(directorio_destino_imagen, image[1]) + try: # pragma: no cover + copyfile(origen, destino) + except FileExistsError: # pragma: no cover + pass + except FileNotFoundError: # pragma: no cover + pass + + database.session.commit() + + +def crear_data_para_pruebas(): + crear_etiqueta_prueba() + crear_categoria_prueba() + crear_certificado_prueba() + crear_usuarios_de_prueba() + crear_recurso_prueba() + crear_programa_prueba() diff --git a/now_lms/db/initial_data.py b/now_lms/db/initial_data.py index 5f9de11a..b79ccd4a 100644 --- a/now_lms/db/initial_data.py +++ b/now_lms/db/initial_data.py @@ -49,7 +49,6 @@ Recurso, SystemInfo, Usuario, - UsuarioGrupo, database, ) from now_lms.logs import log @@ -489,7 +488,7 @@ def crear_curso_predeterminado(): PASSWD = environ.get("ADMIN_PSWD") or environ.get("LMS_PSWD") or "lms-admin" -def crear_usuarios_predeterminados(with_examples): +def crear_usuarios_predeterminados(): """Crea en la base de datos los usuarios iniciales.""" log.info("Creando usuario administrador.") administrador = Usuario( @@ -505,135 +504,6 @@ def crear_usuarios_predeterminados(with_examples): database.session.commit() log.debug("Usuario administrador creado correctamente.") - if environ.get("CI") or with_examples: - log.trace("Creando usuarios de demostración.") - student = Usuario( - usuario="student", - acceso=proteger_passwd("student"), - nombre="Meylin", - apellido="Perez", - correo_electronico="hello@domain.net", - tipo="student", - activo=True, - visible=True, - titulo=None, - genero="female", - nacimiento=datetime(year=1988, month=9, day=21), - bio="Hello there!", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - student1 = Usuario( - usuario="student1", - acceso=proteger_passwd("student1"), - nombre="Dania", - apellido="Mendez", - correo_electronico="hello1@domain.net", - tipo="student", - activo=True, - visible=False, - titulo=None, - genero="female", - nacimiento=datetime(year=1988, month=9, day=21), - bio="Hello there!", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - student2 = Usuario( - usuario="student2", - acceso=proteger_passwd("student1"), - nombre="Gema", - apellido="Lopez", - correo_electronico="hello3@domain.net", - tipo="student", - activo=True, - visible=True, - titulo=None, - genero="female", - nacimiento=datetime(year=1988, month=9, day=21), - bio="Hello there!", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - student3 = Usuario( - usuario="student3", - acceso=proteger_passwd("student3"), - nombre="Maria", - apellido="Lopez", - correo_electronico="hi@domain.com", - tipo="student", - activo=False, - visible=False, - titulo=None, - genero="female", - nacimiento=datetime(year=1988, month=9, day=21), - bio="Hello there!", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - database.session.add(student) - database.session.add(student1) - database.session.add(student2) - database.session.add(student3) - demo_grupo1 = UsuarioGrupo(nombre="Usuarios Base", descripcion="Demo Group", activo=True) - demo_grupo2 = UsuarioGrupo(nombre="Usuarios", descripcion="Demo Group", activo=True) - database.session.add(demo_grupo2) - database.session.add(demo_grupo1) - database.session.commit() - instructor = Usuario( - usuario="instructor", - acceso=proteger_passwd("instructor"), - nombre="Nemesio", - apellido="Reyes", - correo_electronico="hello2@domain.net", - tipo="instructor", - activo=True, - visible=False, - genero="male", - nacimiento=datetime(year=1988, month=9, day=21), - bio="You can", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - database.session.add(instructor) - database.session.commit() - moderator = Usuario( - usuario="moderator", - acceso=proteger_passwd("moderator"), - tipo="moderator", - nombre="Abner", - apellido="Romero", - correo_electronico="moderator@mail.com", - activo=True, - visible=False, - genero="male", - nacimiento=datetime(year=1988, month=9, day=21), - bio="You can do it.", - url="google.com", - linkedin="user", - facebook="user", - twitter="user", - github="user", - ) - database.session.add(moderator) - database.session.commit() - log.debug("Usuarios creados correctamente.") - def crear_curso_demo1(): # pylint: disable=too-many-locals diff --git a/now_lms/forms/__init__.py b/now_lms/forms/__init__.py index c05b1139..18d3f8d5 100644 --- a/now_lms/forms/__init__.py +++ b/now_lms/forms/__init__.py @@ -337,3 +337,4 @@ class CertificateForm(FlaskForm): titulo = StringField(validators=[]) descripcion = StringField(validators=[]) + habilitado = BooleanField(validators=[]) diff --git a/now_lms/modules/groups.py b/now_lms/modules/groups.py deleted file mode 100644 index cd7bc176..00000000 --- a/now_lms/modules/groups.py +++ /dev/null @@ -1,155 +0,0 @@ -# --------------------------------------------------------------------------------------- -# Libreria estandar -# --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version - -# --------------------------------------------------------------------------------------- -# Librerias de terceros -# --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError - -# --------------------------------------------------------------------------------------- -# Recursos locales -# --------------------------------------------------------------------------------------- -from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm -from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION - -group = Blueprint("group", __name__, template_folder=DIRECTORIO_PLANTILLAS) - - -@group.route("/group/new", methods=["GET", "POST"]) -@login_required -@perfil_requerido("admin") -def nuevo_grupo(): - """Formulario para crear un nuevo grupo.""" - form = GrupoForm() - if form.validate_on_submit() or request.method == "POST": - grupo_ = UsuarioGrupo( - nombre=form.nombre.data, - descripcion=form.descripcion.data, - ) - - try: - database.session.add(grupo_) - database.session.commit() - cache.delete("view/" + url_for("lista_grupos")) - flash("Grupo creado correctamente", "success") - return redirect("/groups") - except OperationalError: - flash("Error al crear el nuevo grupo.", "warning") - return redirect("/new_group") - else: - return render_template("admin/grupos/nuevo.html", form=form) - - -@group.route( - "/group/set_tutor", - methods=[ - "POST", - ], -) -@login_required -@perfil_requerido("admin") -def agrega_tutor_a_grupo(): - """Asigna como tutor de grupo a un usuario.""" - - id_ = request.args.get("id", type=str) - registro = UsuarioGrupoTutor( - grupo=id_, usuario=request.form["usuario"], creado_por=current_user.usuario, creado=datetime.now() - ) - database.session.add(registro) - url_grupo = url_for("grupo", id=id_) - try: - database.session.commit() - flash("Usuario Agregado Correctamente.", "success") - return redirect(url_grupo) - except OperationalError: - flash("No se pudo agregar al usuario.", "warning") - return redirect(url_grupo) diff --git a/now_lms/modules/home.py b/now_lms/modules/home.py deleted file mode 100644 index 631777a9..00000000 --- a/now_lms/modules/home.py +++ /dev/null @@ -1,150 +0,0 @@ -# --------------------------------------------------------------------------------------- -# Libreria estandar -# --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version - -# --------------------------------------------------------------------------------------- -# Librerias de terceros -# --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError - -# --------------------------------------------------------------------------------------- -# Recursos locales -# --------------------------------------------------------------------------------------- -from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm -from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION - -home = Blueprint("home", __name__, template_folder=DIRECTORIO_PLANTILLAS) - - -# --------------------------------------------------------------------------------------- -# Página de inicio de la aplicación. -# --------------------------------------------------------------------------------------- -@home.route("/") -@home.route("/home") -@cache.cached(timeout=90, unless=no_guardar_en_cache_global) -def pagina_de_inicio(): - """Página principal de la aplicación.""" - - if DESARROLLO: # pragma: no cover - MAX = 3 - else: # pragma: no cover - MAX = MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA - - CURSOS = database.paginate( - database.select(Curso).filter(Curso.publico == True, Curso.estado == "open"), # noqa: E712 - page=request.args.get("page", default=1, type=int), - max_per_page=MAX, - count=True, - ) - - return render_template("inicio/mooc.html", cursos=CURSOS) - - -@home.route("/home/panel") -@login_required -def panel(): - """Panel principal de la aplicacion luego de inicar sesión.""" - if not current_user.is_authenticated: - return redirect("/home") - elif current_user.tipo == "admin": - cursos_actuales = Curso.query.count() - usuarios_registrados = Usuario.query.count() - recursos_creados = CursoRecurso.query.count() - cursos_por_fecha = Curso.query.order_by(Curso.creado).limit(5).all() - return render_template( - "inicio/panel.html", - cursos_actuales=cursos_actuales, - usuarios_registrados=usuarios_registrados, - recursos_creados=recursos_creados, - cursos_por_fecha=cursos_por_fecha, - ) - else: - return redirect("/") diff --git a/now_lms/modules/profiles/moderator.py b/now_lms/modules/profiles/moderator.py deleted file mode 100644 index 72cc9a20..00000000 --- a/now_lms/modules/profiles/moderator.py +++ /dev/null @@ -1,114 +0,0 @@ -# --------------------------------------------------------------------------------------- -# Libreria estandar -# --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version - -# --------------------------------------------------------------------------------------- -# Librerias de terceros -# --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError - -# --------------------------------------------------------------------------------------- -# Recursos locales -# --------------------------------------------------------------------------------------- -from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm -from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION - -moderator_profile = Blueprint("moderator_profile", __name__, template_folder=DIRECTORIO_PLANTILLAS) - - -# --------------------------------------------------------------------------------------- -# Espacio del moderador. -# --------------------------------------------------------------------------------------- -@moderator_profile.route("/moderator") -@login_required -def pagina_moderador(): - """Perfil de usuario moderador.""" - return render_template("perfiles/moderador.html") diff --git a/now_lms/templates/admin/users.html b/now_lms/templates/admin/users.html index 93b4458b..7357d7c7 100644 --- a/now_lms/templates/admin/users.html +++ b/now_lms/templates/admin/users.html @@ -24,7 +24,7 @@

Usuarios registrados en el sistema.

- Nuevo Usuario + Nuevo Usuario

@@ -51,7 +51,7 @@

Usuarios registrados en el sistema.

- + {{ item.usuario }} diff --git a/now_lms/templates/error_pages/402.html b/now_lms/templates/error_pages/402.html new file mode 100644 index 00000000..4a80df86 --- /dev/null +++ b/now_lms/templates/error_pages/402.html @@ -0,0 +1,43 @@ +{% import "macros.html" as macros %} + + + + + + {{ macros.headertags() }} + Pago requerido. + + + + + +
+
+ + NOW LMS + +
+ +
+

+

402

+

Para acceder al recurso solicitado primero debe registrar un pago.

+

+ + Regresar al inicio +

+
+ + +
+ + + + + + diff --git a/now_lms/templates/error_pages/405.html b/now_lms/templates/error_pages/405.html new file mode 100644 index 00000000..3dca0621 --- /dev/null +++ b/now_lms/templates/error_pages/405.html @@ -0,0 +1,43 @@ +{% import "macros.html" as macros %} + + + + + + {{ macros.headertags() }} + Permisos insuficientes. + + + + + +
+
+ + NOW LMS + +
+ +
+

+

403

+

Su usuario no se encuentra autorizado a acceder al recurso solicitado.

+

+ + Regresar al inicio +

+
+ + +
+ + + + + + diff --git a/now_lms/templates/inicio/cursos.html b/now_lms/templates/inicio/cursos.html index 0427bcf7..4698e09b 100644 --- a/now_lms/templates/inicio/cursos.html +++ b/now_lms/templates/inicio/cursos.html @@ -82,7 +82,7 @@

- Ver Curso @@ -103,7 +103,7 @@

- {{ macros.rendizar_paginacion(consulta=cursos, vista="lista_cursos") }} + {{ macros.rendizar_paginacion(consulta=cursos, vista="course.lista_cursos") }}
diff --git a/now_lms/templates/inicio/panel.html b/now_lms/templates/inicio/panel.html index 6eec9c94..cab0785d 100644 --- a/now_lms/templates/inicio/panel.html +++ b/now_lms/templates/inicio/panel.html @@ -35,10 +35,10 @@

Información del sistema.

- Nuevo Usuario + Nuevo Usuario - Nuevo Curso + Nuevo Curso
@@ -129,7 +129,7 @@
Cursos Recientes
diff --git a/now_lms/templates/learning/certificados/editar_certificado.html b/now_lms/templates/learning/certificados/editar_certificado.html new file mode 100644 index 00000000..2fc98307 --- /dev/null +++ b/now_lms/templates/learning/certificados/editar_certificado.html @@ -0,0 +1,57 @@ +{% import "macros.html" as macros %} + + + + + + + {{ macros.headertags() }} + {{ title }} + + {{ macros.local_style() }} + + + + + {{ macros.navbar() }} + +
+ + {{ macros.notify() }} + +
+

Editar Certificado.

+
+ {{ form.csrf_token }} +
+
+ + {{ form.titulo(class="form-control", id="titulo", placeholder="Certificado") }} +
+ +
+ + {{ form.descripcion(class="form-control", id="descripcion", placeholder="Descripcion") }} +
+
+ +
+
+
+ + {{ form.habilitado() }} +
+
+ +
+ + +
+
+ +
+ + + + + diff --git a/now_lms/templates/learning/certificados/lista_certificados.html b/now_lms/templates/learning/certificados/lista_certificados.html index 86ff0d20..dc6fc9bf 100644 --- a/now_lms/templates/learning/certificados/lista_certificados.html +++ b/now_lms/templates/learning/certificados/lista_certificados.html @@ -17,6 +17,70 @@
+ {{ macros.notify() }} + +
+ +
+ +

Lista de Certificados Disponibles.

+ +

+ + Nuevo Certificado + +

+ +
+ + {% if consulta %} + +
+ + + + + + + + + + + {% for item in consulta.items -%} + + + + + + + + {% endfor %} +
Lista de certificados disponibles en el sistema.
CertificadoHabilitado
+ + {{ item.titulo }} + + + {{ item.habilitado }} + + + Eliminar + Certificado + + + Editar + Certificado + +
+
+ {% else %} +

Usted no ha creado ninguna etiqueta todavia.

+ {% endif %} + +
+ {{ macros.rendizar_paginacion(consulta=consulta, vista="program.programs") }} +
+ +
diff --git a/now_lms/templates/learning/certificados/nuevo_certificado.html b/now_lms/templates/learning/certificados/nuevo_certificado.html index 86ff0d20..d463511f 100644 --- a/now_lms/templates/learning/certificados/nuevo_certificado.html +++ b/now_lms/templates/learning/certificados/nuevo_certificado.html @@ -17,6 +17,29 @@
+ {{ macros.notify() }} + +
+

Crear nuevo Certificado.

+
+ {{ form.csrf_token }} +
+
+ + {{ form.titulo(class="form-control", id="titulo", placeholder="Certificado") }} +
+ +
+ + {{ form.descripcion(class="form-control", id="descripcion", placeholder="Descripcion") }} +
+
+ +
+ + +
+
diff --git a/now_lms/templates/learning/curso/curso.html b/now_lms/templates/learning/curso/curso.html index 08b0ea21..d7ea093f 100644 --- a/now_lms/templates/learning/curso/curso.html +++ b/now_lms/templates/learning/curso/curso.html @@ -26,7 +26,7 @@

@@ -57,7 +57,7 @@

{{ curso.nombre }}

{% endif %}
{% if curso.fecha_inicio and curso.fecha_fin %} @@ -95,7 +95,7 @@

{{ curso.nombre }}

Recursos disponibles con este curso:
{% for item in descargas %} - {{ tipo[item[0].tipo] | safe }} {{ item[0].nombre }}
{% endfor %} {% endif %} @@ -103,20 +103,22 @@

{{ curso.nombre }}


{% if current_user.is_authenticated %} + {% if current_user.tipo == "user" %}
- + Inscribirse al Curso
+ {% endif %} {% else %}
- + Iniciar Sesión - + Crear Cuenta
@@ -154,7 +156,7 @@

{% if recurso.publico == True %} + href="{{ url_for("course.pagina_recurso", curso_id=recurso.curso, codigo=recurso.id, resource_type=recurso.tipo) }} ">
{{ recurso.nombre }}
diff --git a/now_lms/templates/perfiles/admin.html b/now_lms/templates/perfiles/admin.html index 4ba26052..35b86ea8 100644 --- a/now_lms/templates/perfiles/admin.html +++ b/now_lms/templates/perfiles/admin.html @@ -45,15 +45,15 @@

Usuarios

- Usuarios + Usuarios - - Usuarios Inactivos + Usuarios Inactivos {% if inactivos > 0 %} ({{ inactivos | int }}) {% endif %} - - Nuevo Usuario + Nuevo Usuario

@@ -65,8 +65,8 @@

Grupos

- Grupos - Nuevo Grupo + Grupos - Nuevo Grupo

@@ -81,9 +81,9 @@

Configuración

- Configuración del Sitio + Configuración del Sitio - - Tema del Sitio + Tema del Sitio

@@ -96,7 +96,7 @@

Correo electronico

- Configuración + Configuración

@@ -108,7 +108,7 @@

Pagos

- Configuración + Configuración

diff --git a/now_lms/templates/perfiles/instructor.html b/now_lms/templates/perfiles/instructor.html index 54fb2851..97741810 100644 --- a/now_lms/templates/perfiles/instructor.html +++ b/now_lms/templates/perfiles/instructor.html @@ -43,9 +43,9 @@

Cursos

- Cursos + Cursos - - Nuevo Curso + Nuevo Curso

@@ -54,9 +54,9 @@

Programas

- Programas + Programas - - Nuevo Progama + Nuevo Progama

@@ -65,9 +65,9 @@

Certificados

- Certificados + Certificados - - Nuevo Certificado + Nuevo Certificado

@@ -78,9 +78,9 @@

Etiquetas

- Etiquetas + Etiquetas - - Nueva Etiqueta + Nueva Etiqueta

@@ -89,9 +89,9 @@

Categorias

- Categorias + Categorias - - Nueva Categoria + Nueva Categoria

@@ -100,9 +100,9 @@

Recuros Descargables

- Recursos + Recursos - - Nuevo Recurso + Nuevo Recurso

diff --git a/now_lms/templates/perfiles/moderador.html b/now_lms/templates/perfiles/moderador.html index 6b06ece1..f9eefcf7 100644 --- a/now_lms/templates/perfiles/moderador.html +++ b/now_lms/templates/perfiles/moderador.html @@ -43,7 +43,7 @@

Mensajes

- Mensajes + Mensajes

diff --git a/now_lms/modules/README.md b/now_lms/vistas/README.md similarity index 100% rename from now_lms/modules/README.md rename to now_lms/vistas/README.md diff --git a/now_lms/modules/__init__.py b/now_lms/vistas/__init__.py similarity index 100% rename from now_lms/modules/__init__.py rename to now_lms/vistas/__init__.py diff --git a/now_lms/modules/categories.py b/now_lms/vistas/categories.py similarity index 100% rename from now_lms/modules/categories.py rename to now_lms/vistas/categories.py diff --git a/now_lms/modules/certificates.py b/now_lms/vistas/certificates.py similarity index 84% rename from now_lms/modules/certificates.py rename to now_lms/vistas/certificates.py index a39b7e6c..e01dcf99 100644 --- a/now_lms/modules/certificates.py +++ b/now_lms/vistas/certificates.py @@ -29,7 +29,7 @@ # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -from flask import Blueprint, flash, redirect, render_template, request +from flask import Blueprint, flash, redirect, render_template, request, url_for from flask_login import login_required from sqlalchemy.exc import OperationalError @@ -51,7 +51,7 @@ @certificate.route("/certificate/new", methods=["GET", "POST"]) @login_required -@perfil_requerido("instructor") +@perfil_requerido("admin") def new_certificate(): """Nuevo certificado.""" form = CertificateForm() @@ -59,6 +59,7 @@ def new_certificate(): certificado = Certificado( titulo=form.titulo.data, descripcion=form.descripcion.data, + habilitado=False, ) database.session.add(certificado) try: @@ -87,7 +88,7 @@ def certificados(): @certificate.route("/certificate//delete") @login_required -@perfil_requerido("instructor") +@perfil_requerido("admin") def delete_certificate(ulid: str): """Elimina certificado.""" Certificado.query.filter(Certificado.id == ulid).delete() @@ -97,20 +98,20 @@ def delete_certificate(ulid: str): @certificate.route("/certificate//edit", methods=["GET", "POST"]) @login_required -@perfil_requerido("instructor") -def edit_certificate(tag: str): +@perfil_requerido("admin") +def edit_certificate(ulid: str): """Editar categoria.""" - certificado = Certificado.query.filter(Certificado.id == tag).first() - form = CertificateForm(nombre=certificado.titulo, descripcion=certificado.descripcion) + certificado = Certificado.query.filter(Certificado.id == ulid).first() + form = CertificateForm(titulo=certificado.titulo, descripcion=certificado.descripcion, habilitado=certificado.habilitado) if form.validate_on_submit() or request.method == "POST": - certificado.titulo = form.certificado.data + certificado.titulo = form.titulo.data certificado.descripcion = form.descripcion.data try: database.session.add(certificado) database.session.commit() - flash("Categoria editada correctamente.", "success") + flash("Certificado editado correctamente.", "success") except OperationalError: - flash("No se puedo editar la categoria.", "warning") - return redirect("/categories") + flash("No se puedo editar el certificado.", "warning") + return redirect(url_for("certificate.certificados")) - return render_template("learning/categorias/editar_categoria.html", form=form) + return render_template("learning/certificados/editar_certificado.html", form=form) diff --git a/now_lms/modules/courses.py b/now_lms/vistas/courses.py similarity index 96% rename from now_lms/modules/courses.py rename to now_lms/vistas/courses.py index 1ad78e75..5b4ab174 100644 --- a/now_lms/modules/courses.py +++ b/now_lms/vistas/courses.py @@ -97,52 +97,123 @@ course = Blueprint("course", __name__, template_folder=DIRECTORIO_PLANTILLAS) -@course.route("/course//edit", methods=["GET", "POST"]) +@course.route("/course//view") +@cache.cached(unless=no_guardar_en_cache_global) +def curso(course_code): + """Pagina principal del curso.""" + + _curso = Curso.query.filter_by(codigo=course_code).first() + + if current_user.is_authenticated and request.args.get("inspect"): + if current_user.tipo == "admin": + acceso = True + editable = True + else: + _consulta = database.select(DocenteCurso).filter( + DocenteCurso.curso == course_code, DocenteCurso.usuario == current_user.usuario + ) + acceso = database.session.execute(_consulta).first() + if acceso: + editable = True + elif _curso.publico: + acceso = _curso.estado == "open" and _curso.publico is True + editable = False + else: + acceso = False + + if acceso: + return render_template( + "learning/curso/curso.html", + curso=_curso, + secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), + recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), + descargas=database.session.execute( + database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) + ).all(), # El join devuelve una tuple. + nivel=CURSO_NIVEL, + tipo=TIPOS_RECURSOS, + editable=editable, + ) + + else: + abort(403) + + +@course.route("/course//enroll") @login_required -@perfil_requerido("instructor") -def editar_curso(course_code): - """Editar pagina del curso.""" +@perfil_requerido("student") +def course_enroll(course_code): + """Pagina para inscribirse a un curso.""" - curso_a_editar = Curso.query.filter_by(codigo=course_code).first() - form = CurseForm( - nivel=curso_a_editar.nivel, descripcion=curso_a_editar.descripcion, promocionado=curso_a_editar.promocionado - ) - curso_url = url_for("administrar_curso", course_code=course_code) - if form.validate_on_submit() or request.method == "POST": - if curso_a_editar.promocionado is False and form.promocionado.data is True: - curso_a_editar.promocionado = datetime.today() - curso_a_editar.nombre = form.nombre.data - curso_a_editar.descripcion = form.descripcion.data - curso_a_editar.publico = form.publico.data - curso_a_editar.auditable = form.auditable.data - curso_a_editar.certificado = form.certificado.data - curso_a_editar.precio = form.precio.data - curso_a_editar.capacidad = form.capacidad.data - curso_a_editar.fecha_inicio = form.fecha_inicio.data - curso_a_editar.fecha_fin = form.fecha_fin.data - curso_a_editar.duracion = form.duracion.data - curso_a_editar.modificado_por = current_user.usuario - curso_a_editar.nivel = form.nivel.data - curso_a_editar.promocionado = form.promocionado.data + _curso = Curso.query.filter_by(codigo=course_code).first() - try: - database.session.commit() - if "logo" in request.files: - try: - picture_file = images.save(request.files["logo"], folder=curso_a_editar.codigo, name="logo.jpg") - if picture_file: - curso_a_editar.portada = True - database.session.commit() - except UploadNotAllowed: - log.warning("No se pudo actualizar la portada del curso.") - flash("Curso actualizado exitosamente.", "success") - return redirect(curso_url) + return render_template("learning/curso/enroll.html", curso=_curso) - except OperationalError: # pragma: no cover - flash("Hubo en error al actualizar el curso.", "warning") - return redirect(curso_url) - return render_template("learning/edit_curso.html", form=form, curso=curso_a_editar) +@course.route("/course//take") +@login_required +@perfil_requerido("student") +@cache.cached(unless=no_guardar_en_cache_global) +def tomar_curso(course_code): + """Pagina principal del curso.""" + + if current_user.tipo == "student": + return render_template( + "learning/curso.html", + curso=Curso.query.filter_by(codigo=course_code).first(), + secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), + recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), + descargas=database.session.execute( + database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) + ).all(), # El join devuelve una tuple. + nivel=CURSO_NIVEL, + tipo=TIPOS_RECURSOS, + ) + else: + return redirect(url_for(course.curso, codigo=course_code)) + + +@course.route("/course//moderate") +@login_required +@perfil_requerido("moderator") +@cache.cached(unless=no_guardar_en_cache_global) +def moderar_curso(course_code): + """Pagina principal del curso.""" + + if current_user.tipo == "moderator": + return render_template( + "learning/curso.html", + curso=Curso.query.filter_by(codigo=course_code).first(), + secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), + recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), + descargas=database.session.execute( + database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) + ).all(), # El join devuelve una tuple. + nivel=CURSO_NIVEL, + tipo=TIPOS_RECURSOS, + ) + else: + return redirect(url_for(course.curso, codigo=course_code)) + + +@course.route("/course//admin") +@login_required +@perfil_requerido("instructor") +@cache.cached(unless=no_guardar_en_cache_global) +def administrar_curso(course_code): + """Pagina principal del curso.""" + + return render_template( + "learning/curso/admin.html", + curso=Curso.query.filter_by(codigo=course_code).first(), + secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), + recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), + descargas=database.session.execute( + database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) + ).all(), # El join devuelve una tuple. + nivel=CURSO_NIVEL, + tipo=TIPOS_RECURSOS, + ) @course.route("/course/new_curse", methods=["GET", "POST"]) @@ -196,6 +267,54 @@ def nuevo_curso(): return render_template("learning/nuevo_curso.html", form=form) +@course.route("/course//edit", methods=["GET", "POST"]) +@login_required +@perfil_requerido("instructor") +def editar_curso(course_code): + """Editar pagina del curso.""" + + curso_a_editar = Curso.query.filter_by(codigo=course_code).first() + form = CurseForm( + nivel=curso_a_editar.nivel, descripcion=curso_a_editar.descripcion, promocionado=curso_a_editar.promocionado + ) + curso_url = url_for("administrar_curso", course_code=course_code) + if form.validate_on_submit() or request.method == "POST": + if curso_a_editar.promocionado is False and form.promocionado.data is True: + curso_a_editar.promocionado = datetime.today() + curso_a_editar.nombre = form.nombre.data + curso_a_editar.descripcion = form.descripcion.data + curso_a_editar.publico = form.publico.data + curso_a_editar.auditable = form.auditable.data + curso_a_editar.certificado = form.certificado.data + curso_a_editar.precio = form.precio.data + curso_a_editar.capacidad = form.capacidad.data + curso_a_editar.fecha_inicio = form.fecha_inicio.data + curso_a_editar.fecha_fin = form.fecha_fin.data + curso_a_editar.duracion = form.duracion.data + curso_a_editar.modificado_por = current_user.usuario + curso_a_editar.nivel = form.nivel.data + curso_a_editar.promocionado = form.promocionado.data + + try: + database.session.commit() + if "logo" in request.files: + try: + picture_file = images.save(request.files["logo"], folder=curso_a_editar.codigo, name="logo.jpg") + if picture_file: + curso_a_editar.portada = True + database.session.commit() + except UploadNotAllowed: + log.warning("No se pudo actualizar la portada del curso.") + flash("Curso actualizado exitosamente.", "success") + return redirect(curso_url) + + except OperationalError: # pragma: no cover + flash("Hubo en error al actualizar el curso.", "warning") + return redirect(curso_url) + + return render_template("learning/edit_curso.html", form=form, curso=curso_a_editar) + + @course.route("/course//new_seccion", methods=["GET", "POST"]) @login_required @perfil_requerido("instructor") @@ -735,119 +854,6 @@ def nuevo_recurso_html(course_code, seccion): ) -@course.route("/course//view") -@cache.cached(unless=no_guardar_en_cache_global) -def curso(course_code): - """Pagina principal del curso.""" - - _curso = Curso.query.filter_by(codigo=course_code).first() - - if current_user.is_authenticated and request.args.get("inspect"): - if current_user.tipo == "admin": - acceso = True - editable = True - else: - _consulta = database.select(DocenteCurso).filter( - DocenteCurso.curso == course_code, DocenteCurso.usuario == current_user.usuario - ) - acceso = database.session.execute(_consulta).first() - if acceso: - editable = True - elif _curso.publico: - acceso = _curso.estado == "open" and _curso.publico is True - editable = False - else: - acceso = False - - if acceso: - return render_template( - "learning/curso/curso.html", - curso=_curso, - secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), - recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), - descargas=database.session.execute( - database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) - ).all(), # El join devuelve una tuple. - nivel=CURSO_NIVEL, - tipo=TIPOS_RECURSOS, - editable=editable, - ) - - else: - abort(403) - - -@course.route("/course//enroll") -@login_required -@perfil_requerido("student") -def course_enroll(course_code): - """Pagina para inscribirse a un curso.""" - - _curso = Curso.query.filter_by(codigo=course_code).first() - - return render_template("learning/curso/enroll.html", curso=_curso) - - -@course.route("/course//take") -@login_required -@perfil_requerido("student") -@cache.cached(unless=no_guardar_en_cache_global) -def tomar_curso(course_code): - """Pagina principal del curso.""" - - return render_template( - "learning/curso.html", - curso=Curso.query.filter_by(codigo=course_code).first(), - secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), - recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), - descargas=database.session.execute( - database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) - ).all(), # El join devuelve una tuple. - nivel=CURSO_NIVEL, - tipo=TIPOS_RECURSOS, - ) - - -@course.route("/course//moderate") -@login_required -@perfil_requerido("moderator") -@cache.cached(unless=no_guardar_en_cache_global) -def moderar_curso(course_code): - """Pagina principal del curso.""" - - return render_template( - "learning/curso.html", - curso=Curso.query.filter_by(codigo=course_code).first(), - secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), - recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), - descargas=database.session.execute( - database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) - ).all(), # El join devuelve una tuple. - nivel=CURSO_NIVEL, - tipo=TIPOS_RECURSOS, - ) - - -@course.route("/course//admin") -@login_required -@perfil_requerido("instructor") -@cache.cached(unless=no_guardar_en_cache_global) -def administrar_curso(course_code): - """Pagina principal del curso.""" - - return render_template( - "learning/curso/admin.html", - curso=Curso.query.filter_by(codigo=course_code).first(), - secciones=CursoSeccion.query.filter_by(curso=course_code).order_by(CursoSeccion.indice).all(), - recursos=CursoRecurso.query.filter_by(curso=course_code).order_by(CursoRecurso.indice).all(), - descargas=database.session.execute( - database.select(Recurso).join(CursoRecursoDescargable).filter(CursoRecursoDescargable.curso == course_code) - ).all(), # El join devuelve una tuple. - nivel=CURSO_NIVEL, - tipo=TIPOS_RECURSOS, - ) - - # --------------------------------------------------------------------------------------- # Vistas auxiliares para servir el contenido de los cursos por tipo de recurso. # - Enviar archivo. diff --git a/now_lms/vistas/groups.py b/now_lms/vistas/groups.py new file mode 100644 index 00000000..d8e0b6f2 --- /dev/null +++ b/now_lms/vistas/groups.py @@ -0,0 +1,73 @@ +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- +from datetime import datetime + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- +from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask_login import current_user, login_required +from sqlalchemy.exc import OperationalError + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.auth import perfil_requerido +from now_lms.cache import cache +from now_lms.config import DIRECTORIO_PLANTILLAS +from now_lms.db import UsuarioGrupo, UsuarioGrupoTutor, database +from now_lms.forms import GrupoForm + +group = Blueprint("group", __name__, template_folder=DIRECTORIO_PLANTILLAS) + + +@group.route("/group/new", methods=["GET", "POST"]) +@login_required +@perfil_requerido("admin") +def nuevo_grupo(): + """Formulario para crear un nuevo grupo.""" + form = GrupoForm() + if form.validate_on_submit() or request.method == "POST": + grupo_ = UsuarioGrupo( + nombre=form.nombre.data, + descripcion=form.descripcion.data, + ) + + try: + database.session.add(grupo_) + database.session.commit() + cache.delete("view/" + url_for("lista_grupos")) + flash("Grupo creado correctamente", "success") + return redirect("/groups") + except OperationalError: + flash("Error al crear el nuevo grupo.", "warning") + return redirect("/new_group") + else: + return render_template("admin/grupos/nuevo.html", form=form) + + +@group.route( + "/group/set_tutor", + methods=[ + "POST", + ], +) +@login_required +@perfil_requerido("admin") +def agrega_tutor_a_grupo(): + """Asigna como tutor de grupo a un usuario.""" + + id_ = request.args.get("id", type=str) + registro = UsuarioGrupoTutor( + grupo=id_, usuario=request.form["usuario"], creado_por=current_user.usuario, creado=datetime.now() + ) + database.session.add(registro) + url_grupo = url_for("grupo", id=id_) + try: + database.session.commit() + flash("Usuario Agregado Correctamente.", "success") + return redirect(url_grupo) + except OperationalError: + flash("No se pudo agregar al usuario.", "warning") + return redirect(url_grupo) diff --git a/now_lms/vistas/home.py b/now_lms/vistas/home.py new file mode 100644 index 00000000..4c880b7e --- /dev/null +++ b/now_lms/vistas/home.py @@ -0,0 +1,64 @@ +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- +from flask import Blueprint, redirect, render_template, request +from flask_login import current_user, login_required + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.cache import cache, no_guardar_en_cache_global +from now_lms.config import DESARROLLO, DIRECTORIO_PLANTILLAS +from now_lms.db import MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, Curso, CursoRecurso, Usuario, database + +home = Blueprint("home", __name__, template_folder=DIRECTORIO_PLANTILLAS) + + +# --------------------------------------------------------------------------------------- +# Página de inicio de la aplicación. +# --------------------------------------------------------------------------------------- +@home.route("/") +@home.route("/home") +@cache.cached(timeout=90, unless=no_guardar_en_cache_global) +def pagina_de_inicio(): + """Página principal de la aplicación.""" + + if DESARROLLO: # pragma: no cover + MAX = 3 + else: # pragma: no cover + MAX = MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA + + CURSOS = database.paginate( + database.select(Curso).filter(Curso.publico == True, Curso.estado == "open"), # noqa: E712 + page=request.args.get("page", default=1, type=int), + max_per_page=MAX, + count=True, + ) + + return render_template("inicio/mooc.html", cursos=CURSOS) + + +@home.route("/home/panel") +@login_required +def panel(): + """Panel principal de la aplicacion luego de inicar sesión.""" + if not current_user.is_authenticated: + return redirect("/home") + elif current_user.tipo == "admin": + cursos_actuales = Curso.query.count() + usuarios_registrados = Usuario.query.count() + recursos_creados = CursoRecurso.query.count() + cursos_por_fecha = Curso.query.order_by(Curso.creado).limit(5).all() + return render_template( + "inicio/panel.html", + cursos_actuales=cursos_actuales, + usuarios_registrados=usuarios_registrados, + recursos_creados=recursos_creados, + cursos_por_fecha=cursos_por_fecha, + ) + else: + return redirect("/") diff --git a/now_lms/modules/messages.py b/now_lms/vistas/messages.py similarity index 100% rename from now_lms/modules/messages.py rename to now_lms/vistas/messages.py diff --git a/now_lms/modules/profiles/__init__.py b/now_lms/vistas/profiles/__init__.py similarity index 100% rename from now_lms/modules/profiles/__init__.py rename to now_lms/vistas/profiles/__init__.py diff --git a/now_lms/modules/profiles/admin.py b/now_lms/vistas/profiles/admin.py similarity index 61% rename from now_lms/modules/profiles/admin.py rename to now_lms/vistas/profiles/admin.py index b4b1a74a..8dae2a5c 100644 --- a/now_lms/modules/profiles/admin.py +++ b/now_lms/vistas/profiles/admin.py @@ -1,105 +1,21 @@ # --------------------------------------------------------------------------------------- # Libreria estandar # --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError +from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask_login import current_user, login_required # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- from now_lms.auth import perfil_requerido from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm -from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION +from now_lms.cache import cache +from now_lms.config import DIRECTORIO_PLANTILLAS +from now_lms.db import MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, Usuario, database admin_profile = Blueprint("admin_profile", __name__, template_folder=DIRECTORIO_PLANTILLAS) diff --git a/now_lms/modules/profiles/instructor.py b/now_lms/vistas/profiles/instructor.py similarity index 64% rename from now_lms/modules/profiles/instructor.py rename to now_lms/vistas/profiles/instructor.py index 5b3d320e..fc97a7f8 100644 --- a/now_lms/modules/profiles/instructor.py +++ b/now_lms/vistas/profiles/instructor.py @@ -1,106 +1,30 @@ # --------------------------------------------------------------------------------------- # Libreria estandar # --------------------------------------------------------------------------------------- -import sys from datetime import datetime -from os import cpu_count, environ -from platform import python_version # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError -import ulid +from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask_login import current_user, login_required +from sqlalchemy.exc import ArgumentError, OperationalError # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) +from now_lms.cache import cache +from now_lms.config import DIRECTORIO_PLANTILLAS from now_lms.db import ( MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, Curso, - CursoRecurso, DocenteCurso, Usuario, UsuarioGrupo, UsuarioGrupoMiembro, - UsuarioGrupoTutor, database, ) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm -from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION instructor_profile = Blueprint("instructor_profile", __name__, template_folder=DIRECTORIO_PLANTILLAS) diff --git a/now_lms/vistas/profiles/moderator.py b/now_lms/vistas/profiles/moderator.py new file mode 100644 index 00000000..0c286e22 --- /dev/null +++ b/now_lms/vistas/profiles/moderator.py @@ -0,0 +1,26 @@ +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- +from flask import Blueprint, render_template +from flask_login import login_required + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.config import DIRECTORIO_PLANTILLAS + +moderator_profile = Blueprint("moderator_profile", __name__, template_folder=DIRECTORIO_PLANTILLAS) + + +# --------------------------------------------------------------------------------------- +# Espacio del moderador. +# --------------------------------------------------------------------------------------- +@moderator_profile.route("/moderator") +@login_required +def pagina_moderador(): + """Perfil de usuario moderador.""" + return render_template("perfiles/moderador.html") diff --git a/now_lms/modules/profiles/user.py b/now_lms/vistas/profiles/user.py similarity index 63% rename from now_lms/modules/profiles/user.py rename to now_lms/vistas/profiles/user.py index 0ec9db6b..32a774a7 100644 --- a/now_lms/modules/profiles/user.py +++ b/now_lms/vistas/profiles/user.py @@ -1,105 +1,25 @@ # --------------------------------------------------------------------------------------- # Libreria estandar # --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -import click from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError +from flask_login import current_user, login_required +from flask_uploads import UploadNotAllowed +from sqlalchemy.exc import OperationalError # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- -from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm +from now_lms.cache import cache +from now_lms.config import DIRECTORIO_PLANTILLAS, images +from now_lms.db import Usuario, database +from now_lms.db.tools import elimina_imagen_usuario +from now_lms.forms import UserForm from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.settings import setting -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION +from now_lms.misc import GENEROS user_profile = Blueprint("user_profile", __name__, template_folder=DIRECTORIO_PLANTILLAS) diff --git a/now_lms/modules/programs.py b/now_lms/vistas/programs.py similarity index 99% rename from now_lms/modules/programs.py rename to now_lms/vistas/programs.py index 5ae69a09..c3f6f62f 100644 --- a/now_lms/modules/programs.py +++ b/now_lms/vistas/programs.py @@ -56,7 +56,7 @@ @program.route("/program/new", methods=["GET", "POST"]) @login_required @perfil_requerido("instructor") -def new_program(): +def nuevo_programa(): """Nueva programa.""" form = ProgramaForm() if form.validate_on_submit() or request.method == "POST": @@ -86,7 +86,7 @@ def new_program(): @login_required @perfil_requerido("instructor") @cache.cached(timeout=60) -def programs(): +def programas(): """Lista de programas""" if current_user.tipo == "admin": diff --git a/now_lms/modules/resources.py b/now_lms/vistas/resources.py similarity index 100% rename from now_lms/modules/resources.py rename to now_lms/vistas/resources.py diff --git a/now_lms/modules/settings.py b/now_lms/vistas/settings.py similarity index 67% rename from now_lms/modules/settings.py rename to now_lms/vistas/settings.py index 6fda4ec2..afb0e89a 100644 --- a/now_lms/modules/settings.py +++ b/now_lms/vistas/settings.py @@ -24,104 +24,25 @@ # --------------------------------------------------------------------------------------- # Libreria estandar # --------------------------------------------------------------------------------------- -import sys -from datetime import datetime -from os import cpu_count, environ -from platform import python_version # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -import click -from flask import Blueprint, abort, flash, redirect, render_template, request, url_for -from flask.cli import FlaskGroup -from flask_alembic import Alembic -from flask_login import LoginManager, current_user, login_required -from flask_mail import Mail -from flask_mde import Mde -from flask_uploads import UploadNotAllowed, configure_uploads -from pg8000.dbapi import ProgrammingError as PGProgrammingError -from pg8000.exceptions import DatabaseError -from sqlalchemy.exc import ArgumentError, OperationalError, ProgrammingError +from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask_login import login_required +from flask_uploads import UploadNotAllowed +from sqlalchemy.exc import OperationalError # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- from now_lms.auth import perfil_requerido -from now_lms.bi import cambia_tipo_de_usuario_por_id -from now_lms.cache import cache, no_guardar_en_cache_global -from now_lms.config import ( - CONFIGURACION, - DESARROLLO, - DIRECTORIO_ARCHIVOS, - DIRECTORIO_PLANTILLAS, - audio, - files, - images, - log_messages, -) -from now_lms.db import ( - MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, - Configuracion, - Curso, - CursoRecurso, - DocenteCurso, - Usuario, - UsuarioGrupo, - UsuarioGrupoMiembro, - UsuarioGrupoTutor, - database, -) -from now_lms.db.info import app_info -from now_lms.db.initial_data import ( - asignar_cursos_a_categoria, - asignar_cursos_a_etiquetas, - crear_categorias, - crear_curso_demo, - crear_curso_demo1, - crear_curso_demo2, - crear_curso_demo3, - crear_curso_predeterminado, - crear_etiquetas, - crear_programa, - crear_recurso_descargable, - crear_usuarios_predeterminados, - system_info, -) -from now_lms.db.tools import ( - crear_configuracion_predeterminada, - cuenta_cursos_por_programa, - elimina_imagen_usuario, - elimina_logo_perzonalizado, - elimina_logo_perzonalizado_curso, - elimina_logo_perzonalizado_programa, - logo_perzonalizado, - obtener_estilo_actual, - verifica_docente_asignado_a_curso, - verifica_estudiante_asignado_a_curso, - verifica_moderador_asignado_a_curso, - verificar_avance_recurso, -) -from now_lms.forms import ConfigForm, GrupoForm, MailForm, ThemeForm, UserForm +from now_lms.cache import cache +from now_lms.config import DIRECTORIO_PLANTILLAS, images +from now_lms.db import Configuracion, database +from now_lms.db.tools import elimina_logo_perzonalizado +from now_lms.forms import ConfigForm, MailForm, ThemeForm from now_lms.logs import log -from now_lms.misc import ( - ESTILO, - ESTILO_ALERTAS, - GENEROS, - ICONOS_RECURSOS, - INICIO_SESION, - concatenar_parametros_a_url, - markdown_to_clean_hmtl, -) -from now_lms.modules.categories import category -from now_lms.modules.certificates import certificate -from now_lms.modules.courses import course -from now_lms.modules.messages import msg -from now_lms.modules.programs import program -from now_lms.modules.resources import resource_d -from now_lms.modules.tags import tag -from now_lms.modules.users import user -from now_lms.version import VERSION # --------------------------------------------------------------------------------------- # Administración de la configuración del sistema. diff --git a/now_lms/modules/tags.py b/now_lms/vistas/tags.py similarity index 100% rename from now_lms/modules/tags.py rename to now_lms/vistas/tags.py diff --git a/now_lms/modules/users.py b/now_lms/vistas/users.py similarity index 100% rename from now_lms/modules/users.py rename to now_lms/vistas/users.py diff --git a/now_lms/vistas/web_error_codes.py b/now_lms/vistas/web_error_codes.py new file mode 100644 index 00000000..121054fa --- /dev/null +++ b/now_lms/vistas/web_error_codes.py @@ -0,0 +1,58 @@ +# Copyright 2022 -2023 BMO Soluciones, S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Contributors: +# - William José Moreno Reyes + +""" +NOW Learning Management System. + +Gestión de certificados. +""" + +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- + + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- +from flask import Blueprint, flash, redirect, render_template, request +from flask_login import login_required +from sqlalchemy.exc import OperationalError + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.auth import perfil_requerido +from now_lms.config import DIRECTORIO_PLANTILLAS +from now_lms.db import MAXIMO_RESULTADOS_EN_CONSULTA_PAGINADA, Etiqueta, database +from now_lms.db.tools import cursos_por_etiqueta +from now_lms.forms import EtiquetaForm + +# --------------------------------------------------------------------------------------- +# Administración de Etiquetas. +# --------------------------------------------------------------------------------------- + +web_error = Blueprint("error", __name__, template_folder=DIRECTORIO_PLANTILLAS) + + +@web_error.route("/http/error/") +def error_page(code): + """HTTP error code pages.""" + + url = "error_pages/" + code + ".html" + + return render_template(url) diff --git a/tests/test_acciones.py b/tests/acciones.py similarity index 100% rename from tests/test_acciones.py rename to tests/acciones.py diff --git a/tests/test_forms.py b/tests/forms.py similarity index 100% rename from tests/test_forms.py rename to tests/forms.py diff --git a/tests/test_funciones.py b/tests/funciones.py similarity index 100% rename from tests/test_funciones.py rename to tests/funciones.py diff --git a/tests/test_unitarios.py b/tests/test_unitarios.py index fa9c371b..809140ae 100644 --- a/tests/test_unitarios.py +++ b/tests/test_unitarios.py @@ -86,3 +86,188 @@ def test_BaseTable(self): assert issubclass(Usuario, UserMixin) assert issubclass(Usuario, BaseTabla) assert issubclass(Usuario, database.Model) + + +# Source: https://gist.github.com/allysonsilva/85fff14a22bbdf55485be947566cc09e +MARKDOWN_EXAMPLE = """ +# Headers + +``` +# h1 Heading 8-) +## h2 Heading +### h3 Heading +#### h4 Heading +##### h5 Heading +###### h6 Heading + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ +``` + +# h1 Heading 8-) +## h2 Heading +### h3 Heading +#### h4 Heading +##### h5 Heading +###### h6 Heading + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ + +------ + +# Emphasis + +``` +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ + +**This is bold text** + +__This is bold text__ + +*This is italic text* + +_This is italic text_ + +~~Strikethrough~~ +``` + +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ + +**This is bold text** + +__This is bold text__ + +*This is italic text* + +_This is italic text_ + +~~Strikethrough~~ + +------ + +# Lists + +``` +1. First ordered list item +2. Another item +⋅⋅* Unordered sub-list. +1. Actual numbers don't matter, just that it's a number +⋅⋅1. Ordered sub-list +4. And another item. + +⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). + +⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ +⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ +⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) + +* Unordered list can use asterisks +- Or minuses ++ Or pluses + +1. Make my changes + 1. Fix bug + 2. Improve formatting + - Make the headings bigger +2. Push my commits to GitHub +3. Open a pull request + * Describe my changes + * Mention all the members of my team + * Ask for feedback + ++ Create a list by starting a line with `+`, `-`, or `*` ++ Sub-lists are made by indenting 2 spaces: + - Marker character change forces new list start: + * Ac tristique libero volutpat at + + Facilisis in pretium nisl aliquet + - Nulla volutpat aliquam velit ++ Very easy! +``` + +1. First ordered list item +2. Another item +⋅⋅* Unordered sub-list. +1. Actual numbers don't matter, just that it's a number +⋅⋅1. Ordered sub-list +4. And another item. + +⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). + +⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ +⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ +⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) + +* Unordered list can use asterisks +- Or minuses ++ Or pluses + +1. Make my changes + 1. Fix bug + 2. Improve formatting + - Make the headings bigger +2. Push my commits to GitHub +3. Open a pull request + * Describe my changes + * Mention all the members of my team + * Ask for feedback + ++ Create a list by starting a line with `+`, `-`, or `*` ++ Sub-lists are made by indenting 2 spaces: + - Marker character change forces new list start: + * Ac tristique libero volutpat at + + Facilisis in pretium nisl aliquet + - Nulla volutpat aliquam velit ++ Very easy! + +------ + +# Task lists + +``` +- [x] Finish my changes +- [ ] Push my commits to GitHub +- [ ] Open a pull request +- [x] @mentions, #refs, [links](), **formatting**, and tags supported +- [x] list syntax required (any unordered or ordered list supported) +- [x] this is a complete item +- [ ] this is an incomplete item +``` + +- [x] Finish my changes +- [ ] Push my commits to GitHub +- [ ] Open a pull request +- [x] @mentions, #refs, [links](), **formatting**, and tags supported +- [x] list syntax required (any unordered or ordered list supported) +- [ ] this is a complete item +- [ ] this is an incomplete item + +""" + + +def test_clean_markdown(): + from now_lms.misc import markdown_to_clean_hmtl + + markdown_to_clean_hmtl(MARKDOWN_EXAMPLE) diff --git a/tests/test_vistas.py b/tests/test_vistas.py index 656864ea..13f6ae79 100644 --- a/tests/test_vistas.py +++ b/tests/test_vistas.py @@ -65,7 +65,7 @@ def test_visit_all_views_with_anonimus_user(lms_application): from now_lms import database, initial_setup database.drop_all() - initial_setup() + initial_setup(with_test_data=True, with_examples=False) with lms_application.test_client() as client: for ruta in rutas_estaticas: @@ -84,7 +84,7 @@ def test_visit_all_views_with_admin_user(lms_application): from flask_login import current_user database.drop_all() - initial_setup() + initial_setup(with_test_data=True, with_examples=False) with lms_application.test_client() as client: # Keep the session alive until the with clausule closes @@ -113,7 +113,7 @@ def test_visit_all_views_with_student_user(lms_application): from flask_login import current_user database.drop_all() - initial_setup(with_examples=True) + initial_setup(with_test_data=True, with_examples=False) with lms_application.test_client() as client: # Keep the session alive until the with clausule closes @@ -142,7 +142,7 @@ def test_visit_all_views_with_moderator_user(lms_application): from flask_login import current_user database.drop_all() - initial_setup(with_examples=True) + initial_setup(with_test_data=True, with_examples=False) with lms_application.test_client() as client: # Keep the session alive until the with clausule closes @@ -171,7 +171,7 @@ def test_visit_all_views_with_instructor_user(lms_application): from flask_login import current_user database.drop_all() - initial_setup(with_examples=True) + initial_setup(with_test_data=True, with_examples=False) with lms_application.test_client() as client: # Keep the session alive until the with clausule closes @@ -190,3 +190,12 @@ def test_visit_all_views_with_instructor_user(lms_application): for t in ruta.como_instructor: assert t in consulta.data client.get("/user/logout") + + +def test_visit_custom_error_pages(lms_application): + + error_codes = [402, 403, 404, 405, 500] + with lms_application.test_client() as client: + for error in error_codes: + url = "/http/error/" + str(error) + client.get(url) diff --git a/tests/x_rutas_estaticas.py b/tests/x_rutas_estaticas.py index e9feea84..dd48ccca 100644 --- a/tests/x_rutas_estaticas.py +++ b/tests/x_rutas_estaticas.py @@ -16,6 +16,112 @@ # - William José Moreno Reyes +""" +url_map: 2024-02-05 + + ' (HEAD, OPTIONS, GET) -> static>, + ' (HEAD, OPTIONS, GET) -> flask_mde.static>, + category.new_category>, + category.categories>, + /delete' (HEAD, OPTIONS, GET) -> category.delete_category>, + /edit' (HEAD, POST, OPTIONS, GET) -> category.edit_category>, + certificate.new_certificate>, + certificate.certificados>, + /delete' (HEAD, OPTIONS, GET) -> certificate.delete_certificate>, + /edit' (HEAD, POST, OPTIONS, GET) -> certificate.edit_certificate>, + /view' (HEAD, OPTIONS, GET) -> course.curso>, + /enroll' (HEAD, OPTIONS, GET) -> course.course_enroll>, + /take' (HEAD, OPTIONS, GET) -> course.tomar_curso>, + /moderate' (HEAD, OPTIONS, GET) -> course.moderar_curso>, + /admin' (HEAD, OPTIONS, GET) -> course.administrar_curso>, + course.nuevo_curso>, + /edit' (HEAD, POST, OPTIONS, GET) -> course.editar_curso>, + /new_seccion' (HEAD, POST, OPTIONS, GET) -> course.nuevo_seccion>, + //edit' (HEAD, POST, OPTIONS, GET) -> course.editar_seccion>, + /seccion/increment/' (HEAD, OPTIONS, GET) -> course.incrementar_indice_seccion>, + /seccion/decrement/' (HEAD, OPTIONS, GET) -> course.reducir_indice_seccion>, + ///' (HEAD, OPTIONS, GET) -> course.modificar_orden_recurso>, + /delete_recurso//' (HEAD, OPTIONS, GET) -> course.eliminar_recurso>, + /delete_seccion/' (HEAD, OPTIONS, GET) -> course.eliminar_seccion>, + /delete_curse' (HEAD, OPTIONS, GET) -> course.eliminar_curso>, + course.cambiar_estatus_curso>, + course.cambiar_curso_publico>, + course.cambiar_seccion_publico>, + /resource//' (HEAD, OPTIONS, GET) -> course.pagina_recurso>, + /resource///complete' (HEAD, OPTIONS, GET) -> course.marcar_recurso_completado>, + /alternative//' (HEAD, OPTIONS, GET) -> course.pagina_recurso_alternativo>, + //new_resource' (HEAD, OPTIONS, GET) -> course.nuevo_recurso>, + //youtube/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_youtube_video>, + //text/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_text>, + //link/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_link>, + //pdf/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_pdf>, + //meet/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_meet>, + //img/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_img>, + //audio/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_audio>, + //html/new' (HEAD, POST, OPTIONS, GET) -> course.nuevo_recurso_html>, + /files/' (HEAD, OPTIONS, GET) -> course.recurso_file>, + /external_code/' (HEAD, OPTIONS, GET) -> course.external_code>, + ' (HEAD, OPTIONS, GET) -> course.slide_show>, + /md_to_html/' (HEAD, OPTIONS, GET) -> course.markdown_a_html>, + /description' (HEAD, OPTIONS, GET) -> course.curso_descripcion_a_html>, + /description/' (HEAD, OPTIONS, GET) -> course.recurso_descripcion_a_html>, + course.lista_cursos>, + group.nuevo_grupo>, + group.agrega_tutor_a_grupo>, + home.pagina_de_inicio>, + home.pagina_de_inicio>, + home.panel>, + ' (HEAD, OPTIONS, GET) -> msg.mensaje>, + msg.nuevo_mensaje>, + program.nuevo_programa>, + program.programas>, + /delete' (HEAD, OPTIONS, GET) -> program.delete_program>, + /edit' (HEAD, POST, OPTIONS, GET) -> program.edit_program>, + /courses' (HEAD, OPTIONS, GET) -> program.programa_cursos>, + ' (HEAD, OPTIONS, GET) -> program.pagina_programa>, + program.lista_programas>, + resource.new_resource>, + resource.lista_de_recursos>, + /donwload' (HEAD, OPTIONS, GET) -> resource.descargar_recurso>, + /delete' (HEAD, OPTIONS, GET) -> resource.delete_resource>, + /update' (HEAD, POST, OPTIONS, GET) -> resource.edit_resource>, + ' (HEAD, OPTIONS, GET) -> resource.vista_recurso>, + resource.lista_recursos>, + setting.personalizacion>, + setting.configuracion>, + setting.mail>, + setting.elimina_logo>, + tag.new_tag>, + tag.tags>, + /delete' (HEAD, OPTIONS, GET) -> tag.delete_tag>, + /edit' (HEAD, POST, OPTIONS, GET) -> tag.edit_tag>, + user.inicio_sesion>, + user.cerrar_sesion>, + user.crear_cuenta>, + user.crear_usuario>, + admin_profile.pagina_admin>, + admin_profile.usuarios>, + ' (HEAD, OPTIONS, GET) -> admin_profile.activar_usuario>, + ' (HEAD, OPTIONS, GET) -> admin_profile.inactivar_usuario>, + ' (HEAD, OPTIONS, GET) -> admin_profile.eliminar_usuario>, + admin_profile.usuarios_inactivos>, + admin_profile.cambiar_tipo_usario>, + instructor_profile.pagina_instructor>, + instructor_profile.cursos>, + instructor_profile.lista_grupos>, + ' (HEAD, OPTIONS, GET) -> instructor_profile.grupo>, + /' (HEAD, OPTIONS, GET) -> instructor_profile.elimina_usuario__grupo>, + instructor_profile.agrega_usuario_a_grupo>, + moderator_profile.pagina_moderador>, + user_profile.pagina_estudiante>, + user_profile.perfil>, + ' (HEAD, OPTIONS, GET) -> user_profile.usuario>, + ' (HEAD, POST, OPTIONS, GET) -> user_profile.edit_perfil>, + /delete_logo' (HEAD, OPTIONS, GET) -> user_profile.elimina_logo_usuario>, + /' (HEAD, OPTIONS, GET) -> _uploads.uploaded_file>]) +""" + + from collections import namedtuple Ruta = namedtuple( @@ -117,7 +223,7 @@ como_admin=[], ), Ruta( - ruta="/certificate/new", + ruta="/category/01HNP0TTQNTR03J7ZQHR09YMJK/edit", # Hard coded ULID in test data creation no_session=302, admin=200, user=403, @@ -129,6 +235,38 @@ como_instructor=[], como_admin=[], ), + Ruta( + ruta="/category/01HNP0TTQNTR03J7ZQHR09YMJK/delete", + no_session=302, + admin=302, # Delete return a redirect + user=403, + moderator=403, + instructor=302, + texto=[], + como_user=[], + como_moderador=[], + como_instructor=[], + como_admin=[], + ), + Ruta( + ruta="/certificate/new", + no_session=302, + admin=200, + user=403, + moderator=403, + instructor=403, + texto=[], + como_user=[], + como_moderador=[], + como_instructor=[], + como_admin=[ + b"Crear nuevo Certificado.", + b"Certificado", + b"Descripcion", + b"Crear Certificado", + b"""""", + ], + ), Ruta( ruta="/certificate/list", no_session=302, @@ -138,6 +276,61 @@ instructor=200, texto=[], como_user=[], + como_moderador=[ + b"Lista de Certificados Disponibles.", + b"Lista de certificados disponibles en el sistema.", + b"Certficado Test", + b"Nuevo Certificado", + ], + como_instructor=[], + como_admin=[ + b"Lista de Certificados Disponibles.", + b"Lista de certificados disponibles en el sistema.", + b"Certficado Test", + b"Nuevo Certificado", + ], + ), + Ruta( + ruta="/certificate/01HNP0TTQNTR03J7ZQHR09YMKK/edit", + no_session=302, + admin=200, + user=403, + moderator=403, + instructor=403, + texto=[], + como_user=[], + como_moderador=[], + como_instructor=[], + como_admin=[ + b"Editar Certificado.", + b"Certficado Test", + b"Actualizar Certificado", + b"Habilitado", + b"Certificado Test", + ], + ), + Ruta( + ruta="/certificate/01HNP0TTQNTR03J7ZQHR09YMKK/delete", + no_session=302, + admin=302, + user=403, + moderator=403, + instructor=403, + texto=[], + como_user=[], + como_moderador=[], + como_instructor=[], + como_admin=[], + ), + Ruta( + ruta="/course/now/view", + no_session=200, + admin=200, + user=200, + moderator=200, + instructor=200, + texto=[], + como_user=[b"Inscribirse al Curso"], como_moderador=[], como_instructor=[], como_admin=[],