+
402
+Para acceder al recurso solicitado primero debe registrar un pago.
+ +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 @@
- Nuevo Usuario + Nuevo Usuario
@@ -51,7 +51,7 @@Para acceder al recurso solicitado primero debe registrar un pago.
+ +Su usuario no se encuentra autorizado a acceder al recurso solicitado.
+ ++ + Nuevo Certificado + +
+ +Certificado | +Habilitado | ++ |
---|---|---|
+ + {{ item.titulo }} + + | ++ {{ item.habilitado }} + | ++ + Eliminar + Certificado + + + Editar + Certificado + + | +
Usted no ha creado ninguna etiqueta todavia.
+ {% endif %} + +- Usuarios + Usuarios - - Usuarios Inactivos + Usuarios Inactivos {% if inactivos > 0 %} ({{ inactivos | int }}) {% endif %} - - Nuevo Usuario + Nuevo Usuario
@@ -65,8 +65,8 @@- Grupos - Nuevo Grupo + Grupos - Nuevo Grupo
@@ -81,9 +81,9 @@
- Configuración del Sitio + Configuración del Sitio - - Tema del Sitio + Tema del Sitio
@@ -96,7 +96,7 @@- Configuración + Configuración
@@ -108,7 +108,7 @@- 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 - - Nuevo Curso + Nuevo Curso
- Programas + Programas - - Nuevo Progama + Nuevo Progama
- Certificados + Certificados - - Nuevo Certificado + Nuevo Certificado
- Etiquetas + Etiquetas - - Nueva Etiqueta + Nueva Etiqueta
- Categorias + Categorias - - Nueva Categoria + Nueva Categoria
- Recursos + Recursos - - Nuevo Recurso + Nuevo Recurso
")
+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=[],