From fef078f8faad7aa23d1faaa4212e7d7a88226d2f Mon Sep 17 00:00:00 2001 From: William Moreno Date: Sun, 3 Mar 2024 20:40:19 -0600 Subject: [PATCH 1/5] release: v0.0.1a14.dev20240303 (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Next release (#41) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * feat: Configure mail backend with Flask-Mailman (#39) * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Use flask-mailman Signed-off-by: William Moreno Reyes * feat: Setup email backend with Flask-Mailman Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes * build: update Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * build: Update entrypoint Signed-off-by: William José Moreno Reyes * test: Update test Signed-off-by: William José Moreno Reyes * build: Clean Signed-off-by: William José Moreno Reyes * build: Update test Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 Signed-off-by: William José Moreno Reyes * build: Add huey and gevent as dependencies Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * Calendar (#43) * build: Remove setup.py Signed-off-by: William José Moreno Reyes * feat: initial work in calendar view Signed-off-by: William José Moreno Reyes * Fix: import errors Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 (#38) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * feat: Configure mail backend with Flask-Mailman (#39) * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Use flask-mailman Signed-off-by: William Moreno Reyes * feat: Setup email backend with Flask-Mailman Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes * build: update Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * build: Update entrypoint Signed-off-by: William José Moreno Reyes * test: Update test Signed-off-by: William José Moreno Reyes * build: Clean Signed-off-by: William José Moreno Reyes * build: Update test Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * Pagos (#42) * feat: initial work in payments Signed-off-by: William José Moreno Reyes * feat: Inital work in payments Signed-off-by: William José Moreno Reyes * Update python-package.yml Signed-off-by: William Moreno * Update python-package.yml Signed-off-by: William Moreno * Update publish.yml Signed-off-by: William Moreno --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno * test: Update test Signed-off-by: William José Moreno Reyes * build: Update coverage Signed-off-by: William José Moreno Reyes * build: Update coverage Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240303 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno --- .github/workflows/publish.yml | 5 +- .github/workflows/python-package.yml | 4 +- .lint.sh | 12 +-- .mypy.ini | 4 +- MANIFEST.in | 1 - now_lms/__init__.py | 2 + now_lms/auth.py | 2 +- now_lms/db/__init__.py | 35 ++++++-- now_lms/misc.py | 4 +- now_lms/static/package-lock.json | 66 ++++++++++++-- now_lms/static/package.json | 6 +- now_lms/version.py | 6 +- now_lms/vistas/calendar.py | 73 ++++++++++++++++ package-lock.json | 123 +++++++++++++++++++++++++++ package.json | 9 ++ requirements.txt | 4 + tests/test_vistas.py | 6 ++ tests/x_forms_data.py | 1 - 18 files changed, 328 insertions(+), 35 deletions(-) create mode 100644 now_lms/vistas/calendar.py create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1474c2fb..09a3387a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,8 +1,9 @@ name: Publish to PyPi on: - push: - branches: [main] + workflow_run: + workflows: ["CI"] + types: [completed] jobs: build: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a95a4252..a7b73154 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,13 +1,13 @@ # 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: CI on: push: branches: [main, development] pull_request: - branches: [main, development] + branches: [main, development, next-release] jobs: build: diff --git a/.lint.sh b/.lint.sh index 48921c3f..014d0ea9 100644 --- a/.lint.sh +++ b/.lint.sh @@ -1,18 +1,18 @@ #!/bin/bash -echo ------------------------------------------------- +echo ---------------------------------------------------- echo Check python code with ruff -echo ------------------------------------------------- +echo ---------------------------------------------------- echo python -m ruff now_lms echo -echo ------------------------------------------------- +echo ---------------------------------------------------- echo Check python types -echo ------------------------------------------------- +echo ---------------------------------------------------- echo python -m mypy now_lms --install-types --non-interactive echo -echo ------------------------------------------------- +echo ---------------------------------------------------- echo Lint html files wiht curlylint -echo ------------------------------------------------- +echo ---------------------------------------------------- echo # python -m curlylint now_lms/templates/ diff --git a/.mypy.ini b/.mypy.ini index fd6d8805..c46bd932 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,6 +1,6 @@ [mypy] -exclude = (?x)(^test\. # or files starting with "three." - ) +exclude = (?x)(^test\. # or files starting with "test." + ignore_missing_imports = True show_error_codes = True disable_error_code = union-attr, name-defined diff --git a/MANIFEST.in b/MANIFEST.in index ed2000d2..792b2069 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,5 @@ include LICENSE include wsgi.py -include passenger_wsgi.py include Procfile include pyproject.toml graft now_lms/static diff --git a/now_lms/__init__.py b/now_lms/__init__.py index 91ee7f8a..351e5e0f 100644 --- a/now_lms/__init__.py +++ b/now_lms/__init__.py @@ -107,6 +107,7 @@ markdown_to_clean_hmtl, ) from now_lms.version import VERSION +from now_lms.vistas.calendar import calendar from now_lms.vistas.categories import category from now_lms.vistas.certificates import certificate from now_lms.vistas.courses import course @@ -180,6 +181,7 @@ def registrar_modulos_en_la_aplicacion_principal(flask_app: Flask): flask_app.register_blueprint(moderator_profile) flask_app.register_blueprint(user_profile) flask_app.register_blueprint(web_error) + flask_app.register_blueprint(calendar) # --------------------------------------------------------------------------------------- diff --git a/now_lms/auth.py b/now_lms/auth.py index 15a7ff21..e737e6f8 100644 --- a/now_lms/auth.py +++ b/now_lms/auth.py @@ -116,7 +116,7 @@ def proteger_secreto(password): ) key = base64.urlsafe_b64encode(kdf.derive(current_app.config.get("SECRET_KEY").encode())) f = Fernet(key) - return f.encrypt(password.encode()) + return f.encrypt(password) def descifrar_secreto(hash): diff --git a/now_lms/db/__init__.py b/now_lms/db/__init__.py index b1b168ee..18771e5e 100644 --- a/now_lms/db/__init__.py +++ b/now_lms/db/__init__.py @@ -154,13 +154,16 @@ class Curso(database.Model, BaseTabla): auditable = database.Column(database.Boolean()) precio = database.Column(database.Numeric()) capacidad = database.Column(database.Integer()) - fecha_inicio = database.Column(database.Date()) - fecha_fin = database.Column(database.Date()) - duracion = database.Column(database.Integer()) portada = database.Column(database.Boolean()) nivel = database.Column(database.Integer()) + # CEO promocionado = database.Column(database.Boolean()) fecha_promocionado = database.Column(database.DateTime, nullable=True) + # Duración del evento. + temporalidad = database.Column(database.String(10), nullable=True) + fecha_inicio = database.Column(database.Date()) + fecha_fin = database.Column(database.Date()) + duracion = database.Column(database.Integer()) class CursoRecursoDescargable(database.Model, BaseTabla): @@ -197,9 +200,6 @@ class CursoRecurso(database.Model, BaseTabla): # 1: Requerido, 2: Optional, 3: Alternativo requerido = database.Column(database.Integer(), default=1) url = database.Column(database.String(250), unique=False) - fecha = database.Column(database.Date()) - hora_inicio = database.Column(database.Time()) - hora_fin = database.Column(database.Time()) publico = database.Column(database.Boolean()) base_doc_url = database.Column(database.String(50), unique=False) doc = database.Column(database.String(50), unique=True) @@ -207,6 +207,10 @@ class CursoRecurso(database.Model, BaseTabla): text = database.Column(database.String(750)) external_code = database.Column(database.String(500)) notes = database.Column(database.String(20)) + # Temporalidad + fecha = database.Column(database.Date()) + hora_inicio = database.Column(database.Time()) + hora_fin = database.Column(database.Time()) class CursoRecursoAvance(database.Model, BaseTabla): @@ -473,9 +477,26 @@ class Mensaje(database.Model, BaseTabla): parent = database.Column(database.String(26), database.ForeignKey("mensaje.id"), nullable=True, index=True) -class PagosConfig(database.Model): +class StripeUserKey(database.Model): + """Datos de acceso de usuario a la plataforma de Stripe.""" + + id = database.Column( + database.String(26), primary_key=True, nullable=False, index=True, default=generador_de_codigos_unicos + ) + acceso = database.Column(database.LargeBinary(), nullable=False) + usuario = database.Column(database.String(26), database.ForeignKey(LLAVE_FORANEA_USUARIO), nullable=False, index=True) + + +class StripePaymentsConfig(database.Model): """Configuración de pagos.""" id = database.Column( database.String(26), primary_key=True, nullable=False, index=True, default=generador_de_codigos_unicos ) + # Relaciones foraneas + curso = database.Column(database.String(26), database.ForeignKey("curso.id"), nullable=False, index=True) + programa = database.Column(database.String(26), database.ForeignKey("programa.id"), nullable=False, index=True) + # Configuración de la API de Stripe + stripe_price_id = database.Column(database.String(60)) + stripe_product_id = database.Column(database.String(60)) + stripe_payment_url = database.Column(database.String(60)) diff --git a/now_lms/misc.py b/now_lms/misc.py index f171cdb8..62acc70b 100644 --- a/now_lms/misc.py +++ b/now_lms/misc.py @@ -47,7 +47,7 @@ def concatenar_parametros_a_url( argumentos: str = char - if parametros: + if parametros: # pragma: no cover if arg and val: parametros[arg] = val @@ -56,7 +56,7 @@ def concatenar_parametros_a_url( elif arg and val: argumentos = argumentos + "&" + arg + "=" + val - if char is not None and argumentos.find("&") == 1: + if char is not None and argumentos.find("&") == 1: # pragma: no cover argumentos = char + argumentos[2:] return argumentos diff --git a/now_lms/static/package-lock.json b/now_lms/static/package-lock.json index ad8c6996..7e77d4c5 100644 --- a/now_lms/static/package-lock.json +++ b/now_lms/static/package-lock.json @@ -5,13 +5,50 @@ "packages": { "": { "dependencies": { + "@fullcalendar/bootstrap5": "^6.1.11", + "@fullcalendar/core": "^6.1.11", + "@fullcalendar/icalendar": "^6.1.11", + "@fullcalendar/interaction": "^6.1.11", "bootstrap": "^5.2.3", - "bootstrap-icons": "^1.10.3", + "bootstrap-icons": "^1.11.3", "jquery": "^3.6.4", "reveal.js": "^4.4.0", "select2": "^4.1.0-rc.0" } }, + "node_modules/@fullcalendar/bootstrap5": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-6.1.11.tgz", + "integrity": "sha512-wZ2eYtZoi8VS2tcoe2oeca8wkkY85Nn+rH35JpFx35H77EMB5xBTtGAAEk9AClFkn+QscAwoOz3nyy1z0EqNpA==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11" + } + }, + "node_modules/@fullcalendar/core": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.11.tgz", + "integrity": "sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/icalendar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/icalendar/-/icalendar-6.1.11.tgz", + "integrity": "sha512-a1kFIUs/G1ic9kkblL08n8Vwqw+jkBExhgFjbARVQrvyTwx0/SDmRtt0crqlkUYUOnu5nofj3xXPNupdxgPSwg==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11", + "ical.js": "^1.4.0" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.11.tgz", + "integrity": "sha512-ynOKjzuPwEAMgTQ6R/Z2zvzIIqG4p8/Qmnhi1q0vzPZZxSIYx3rlZuvpEK2WGBZZ1XEafDOP/LGfbWoNZe+qdg==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -23,9 +60,9 @@ } }, "node_modules/bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "funding": [ { "type": "github", @@ -41,9 +78,9 @@ } }, "node_modules/bootstrap-icons": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.0.tgz", - "integrity": "sha512-bLTbtACfUqwZf6f/xUYUb7bTRZC68QaQwwy9h1b96NPKfnwqzSatHqDypW6R2CBW7zUE7lP+O93GdZuPY3RIHA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", "funding": [ { "type": "github", @@ -55,11 +92,26 @@ } ] }, + "node_modules/ical.js": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.5.0.tgz", + "integrity": "sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==", + "peer": true + }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/reveal.js": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.6.1.tgz", diff --git a/now_lms/static/package.json b/now_lms/static/package.json index 2d6513f2..2f454f1e 100644 --- a/now_lms/static/package.json +++ b/now_lms/static/package.json @@ -1,7 +1,11 @@ { "dependencies": { + "@fullcalendar/bootstrap5": "^6.1.11", + "@fullcalendar/core": "^6.1.11", + "@fullcalendar/icalendar": "^6.1.11", + "@fullcalendar/interaction": "^6.1.11", "bootstrap": "^5.2.3", - "bootstrap-icons": "^1.10.3", + "bootstrap-icons": "^1.11.3", "jquery": "^3.6.4", "reveal.js": "^4.4.0", "select2": "^4.1.0-rc.0" diff --git a/now_lms/version.py b/now_lms/version.py index b5bcebd3..3ef449d1 100644 --- a/now_lms/version.py +++ b/now_lms/version.py @@ -54,10 +54,10 @@ # Release string # Refences: # - https://peps.python.org/pep-0440/ -# 0.0.1a14.dev20240302 -if PRERELEASE: +# 0.0.1a14.dev20240303 +if PRERELEASE: # pragma: no cover VERSION = MAYOR + "." + MENOR + "." + PATCH + PRERELEASE + ".dev" + REVISION -else: +else: # pragma: no cover if not POST: VERSION = MAYOR + "." + MENOR + "." + PATCH else: diff --git a/now_lms/vistas/calendar.py b/now_lms/vistas/calendar.py new file mode 100644 index 00000000..aa10aac6 --- /dev/null +++ b/now_lms/vistas/calendar.py @@ -0,0 +1,73 @@ +# 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 eventos en una sesión +""" + +# --------------------------------------------------------------------------------------- +# Libreria estandar +# --------------------------------------------------------------------------------------- + + +# --------------------------------------------------------------------------------------- +# Librerias de terceros +# --------------------------------------------------------------------------------------- +from flask import Blueprint +from flask_login import login_required + +# --------------------------------------------------------------------------------------- +# Recursos locales +# --------------------------------------------------------------------------------------- +from now_lms.auth import perfil_requerido +from now_lms.config import DIRECTORIO_PLANTILLAS + +# --------------------------------------------------------------------------------------- +# Interfaz de calendario. +# --------------------------------------------------------------------------------------- + +calendar = Blueprint("calendar", __name__, template_folder=DIRECTORIO_PLANTILLAS) + + +@calendar.route("/calendar/") +@login_required +@perfil_requerido("user") +def calendario(course_ulid: str): + """Genera un calendario ical a partir de los eventos de un curso.""" + + +@calendar.route("/calendar//export") +@login_required +@perfil_requerido("user") +def calendario_export(course_ulid: str): + """Exporta un calendario ical a partir de los eventos de un curso.""" + + +@calendar.route("/calendar//event/") +@login_required +@perfil_requerido("user") +def calendario_evento(course_ulid: str, resource_id: str): + """Genera un evento a partir de un recurso del calendario.""" + + +@calendar.route("/calendar//event//export") +@login_required +@perfil_requerido("user") +def calendario_evento_export(course_ulid: str, resource_id: str): + """Exporta un evento a ical.""" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f06dcbbb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,123 @@ +{ + "name": "now-lms", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "@fullcalendar/bootstrap5": "^6.1.11", + "@fullcalendar/core": "^6.1.11", + "@fullcalendar/icalendar": "^6.1.11", + "@fullcalendar/interaction": "^6.1.11", + "bootstrap-icons": "^1.11.3" + } + }, + "node_modules/@fullcalendar/bootstrap5": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-6.1.11.tgz", + "integrity": "sha512-wZ2eYtZoi8VS2tcoe2oeca8wkkY85Nn+rH35JpFx35H77EMB5xBTtGAAEk9AClFkn+QscAwoOz3nyy1z0EqNpA==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11" + } + }, + "node_modules/@fullcalendar/core": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.11.tgz", + "integrity": "sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/icalendar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/icalendar/-/icalendar-6.1.11.tgz", + "integrity": "sha512-a1kFIUs/G1ic9kkblL08n8Vwqw+jkBExhgFjbARVQrvyTwx0/SDmRtt0crqlkUYUOnu5nofj3xXPNupdxgPSwg==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11", + "ical.js": "^1.4.0" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.11.tgz", + "integrity": "sha512-ynOKjzuPwEAMgTQ6R/Z2zvzIIqG4p8/Qmnhi1q0vzPZZxSIYx3rlZuvpEK2WGBZZ1XEafDOP/LGfbWoNZe+qdg==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.11" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] + }, + "node_modules/ical.js": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.5.0.tgz", + "integrity": "sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==", + "peer": true + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + } + }, + "dependencies": { + "@fullcalendar/bootstrap5": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-6.1.11.tgz", + "integrity": "sha512-wZ2eYtZoi8VS2tcoe2oeca8wkkY85Nn+rH35JpFx35H77EMB5xBTtGAAEk9AClFkn+QscAwoOz3nyy1z0EqNpA==", + "requires": {} + }, + "@fullcalendar/core": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.11.tgz", + "integrity": "sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==", + "requires": { + "preact": "~10.12.1" + } + }, + "@fullcalendar/icalendar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/icalendar/-/icalendar-6.1.11.tgz", + "integrity": "sha512-a1kFIUs/G1ic9kkblL08n8Vwqw+jkBExhgFjbARVQrvyTwx0/SDmRtt0crqlkUYUOnu5nofj3xXPNupdxgPSwg==", + "requires": {} + }, + "@fullcalendar/interaction": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.11.tgz", + "integrity": "sha512-ynOKjzuPwEAMgTQ6R/Z2zvzIIqG4p8/Qmnhi1q0vzPZZxSIYx3rlZuvpEK2WGBZZ1XEafDOP/LGfbWoNZe+qdg==", + "requires": {} + }, + "bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==" + }, + "ical.js": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.5.0.tgz", + "integrity": "sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==", + "peer": true + }, + "preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..1a7d21a1 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@fullcalendar/bootstrap5": "^6.1.11", + "@fullcalendar/core": "^6.1.11", + "@fullcalendar/icalendar": "^6.1.11", + "@fullcalendar/interaction": "^6.1.11", + "bootstrap-icons": "^1.11.3" + } +} diff --git a/requirements.txt b/requirements.txt index 86dcd53b..ef31e17e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,10 @@ flask-mde flask-reuploaded flask-sqlalchemy flask-wtf +icalendar +gevent +greenlet +huey loguru markdown python-ulid diff --git a/tests/test_vistas.py b/tests/test_vistas.py index 94c0e463..67601580 100644 --- a/tests/test_vistas.py +++ b/tests/test_vistas.py @@ -255,5 +255,11 @@ def test_email_backend(request, lms_application): follow_redirects=True, ) + from now_lms.auth import proteger_secreto, descifrar_secreto + + password = b"testing123abcqwerty" + + assert password == descifrar_secreto(proteger_secreto(password)) + else: pytest.skip("Not running slow test.") diff --git a/tests/x_forms_data.py b/tests/x_forms_data.py index c81b14ca..bf94cc56 100644 --- a/tests/x_forms_data.py +++ b/tests/x_forms_data.py @@ -1,6 +1,5 @@ from collections import namedtuple from io import BytesIO -from tkinter.messagebox import NO Form = namedtuple("form", ["ruta", "data", "file", "flash"]) From ed556faefdf0a5d13b66fc3c1e30548a63f90fea Mon Sep 17 00:00:00 2001 From: William Moreno Date: Mon, 1 Apr 2024 13:07:02 -0600 Subject: [PATCH 2/5] Create CODE_OF_CONDUCT.md Signed-off-by: William Moreno --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1450f0f5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +soluciones.bmo@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 009150ba06c82f68f2236ed5c1d98a3c342e5fbf Mon Sep 17 00:00:00 2001 From: William Moreno Date: Fri, 5 Apr 2024 21:20:02 -0600 Subject: [PATCH 3/5] release: v0.0.1a15.dev20240405 (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Next release (#41) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * feat: Configure mail backend with Flask-Mailman (#39) * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Use flask-mailman Signed-off-by: William Moreno Reyes * feat: Setup email backend with Flask-Mailman Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes * build: update Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * build: Update entrypoint Signed-off-by: William José Moreno Reyes * test: Update test Signed-off-by: William José Moreno Reyes * build: Clean Signed-off-by: William José Moreno Reyes * build: Update test Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 Signed-off-by: William José Moreno Reyes * build: Add huey and gevent as dependencies Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * Calendar (#43) * build: Remove setup.py Signed-off-by: William José Moreno Reyes * feat: initial work in calendar view Signed-off-by: William José Moreno Reyes * Fix: import errors Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 (#38) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * feat: Configure mail backend with Flask-Mailman (#39) * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes * Test: Validate more tests Signed-off-by: William José Moreno Reyes * test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test Signed-off-by: William José Moreno Reyes * Test: validate more views Signed-off-by: William José Moreno Reyes * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes * docs: Update docs Signed-off-by: William José Moreno Reyes * Test: Update test Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Remove setup.py file Signed-off-by: William José Moreno Reyes * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes * build: Update dev dependencies Signed-off-by: William José Moreno Reyes * build: cleanup Signed-off-by: William José Moreno Reyes * build: update requeriments Signed-off-by: William José Moreno Reyes * build: Update build metadata Signed-off-by: William José Moreno Reyes * docs: update config Signed-off-by: William José Moreno Reyes * build: update docker base image Signed-off-by: William José Moreno Reyes * build: Update coverga report Signed-off-by: William José Moreno Reyes * buil: clean Signed-off-by: William José Moreno Reyes * build: update atributes Signed-off-by: William José Moreno Reyes * build: Update setup Signed-off-by: William José Moreno Reyes * build: Test coverage update Signed-off-by: William José Moreno Reyes * test: More tests Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes * build: Use flask-mailman Signed-off-by: William Moreno Reyes * feat: Setup email backend with Flask-Mailman Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes * build: update Signed-off-by: William José Moreno Reyes * build: Update config Signed-off-by: William José Moreno Reyes * build: Update entrypoint Signed-off-by: William José Moreno Reyes * test: Update test Signed-off-by: William José Moreno Reyes * build: Clean Signed-off-by: William José Moreno Reyes * build: Update test Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240302 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * build: Update config Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno * Pagos (#42) * feat: initial work in payments Signed-off-by: William José Moreno Reyes * feat: Inital work in payments Signed-off-by: William José Moreno Reyes * Update python-package.yml Signed-off-by: William Moreno * Update python-package.yml Signed-off-by: William Moreno * Update publish.yml Signed-off-by: William Moreno --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno * test: Update test Signed-off-by: William José Moreno Reyes * build: Update coverage Signed-off-by: William José Moreno Reyes * build: Update coverage Signed-off-by: William José Moreno Reyes * release: v0.0.1a14.dev20240303 Signed-off-by: William José Moreno Reyes * refactor: Update database schema Signed-off-by: William José Moreno Reyes * fix: Mapper Mapper[Configuracion(configuracion)] could not assemble any primary key columns for mapped table 'configuracion' Signed-off-by: William José Moreno Reyes * fix: Errors in user profile Signed-off-by: William José Moreno Reyes * buil: Fix date format Signed-off-by: William José Moreno Reyes * refactor: Fernet key Signed-off-by: William Moreno Reyes * refactor: isort imports Signed-off-by: William Moreno Reyes * refactor: Mail check Signed-off-by: William José Moreno Reyes * build: Clean out Signed-off-by: William José Moreno Reyes * refactor: Mail setup Signed-off-by: William José Moreno Reyes * fix: Update mail config Signed-off-by: William José Moreno Reyes * build: clean out Signed-off-by: William José Moreno Reyes * fix: Set smtp mail backend Signed-off-by: William José Moreno Reyes * chore: Find smtp error Signed-off-by: William Moreno Reyes * tests: Update tests Signed-off-by: William José Moreno Reyes * feat: Update project Signed-off-by: William José Moreno Reyes * Clean Signed-off-by: William José Moreno Reyes * release: v0.0.1a15.dev20240405 Signed-off-by: William José Moreno Reyes --------- Signed-off-by: William José Moreno Reyes Signed-off-by: William Moreno Reyes Signed-off-by: William Moreno --- docs/email.md | 1 + docs/setup-conf.md | 2 + now_lms/__init__.py | 34 +--- now_lms/auth.py | 92 ++++++---- now_lms/db/__init__.py | 15 +- now_lms/db/tools.py | 1 - now_lms/forms/__init__.py | 2 +- now_lms/templates/inicio/perfil.html | 4 +- now_lms/templates/inicio/perfil_editar.html | 5 +- now_lms/version.py | 5 +- now_lms/vistas/profiles/user.py | 2 +- now_lms/vistas/settings.py | 54 +++--- now_lms/vistas/users.py | 4 +- requirements.txt | 2 +- tests/test_basicos.py | 188 ++++++++++++++++---- tests/test_use_cases.py | 1 + tests/test_vistas.py | 1 + tests/test_webforms.py | 2 +- 18 files changed, 262 insertions(+), 153 deletions(-) create mode 100644 docs/email.md diff --git a/docs/email.md b/docs/email.md new file mode 100644 index 00000000..88e4d47a --- /dev/null +++ b/docs/email.md @@ -0,0 +1 @@ +# Email diff --git a/docs/setup-conf.md b/docs/setup-conf.md index cb151bf1..c735864c 100644 --- a/docs/setup-conf.md +++ b/docs/setup-conf.md @@ -90,3 +90,5 @@ You can use the following options to configure NOW-LMS: as cache backend. - **UPLOAD_FILES_DIR** (recomended): Directory to save user uploaded files, must be writable by the main app proccess. Note that this variable can NOT be set AD-HOC because the order we parse the configuration options, so you must set this options before the app firts run, any overwritte before this can lead to unexpected results like file not found errors.It is better to set this option as enviroment variable before the firts run of the app. Note that if you migrate your instalation to a diferent host must edit this value so database records can match fisical file storage. +- **LMS_FERNET_KEY** (recomended): If available vía system enviroment + this option will be used to encrypt sencitive data, if not available a secret key will be generated and stored in the private directory of the app. diff --git a/now_lms/__init__.py b/now_lms/__init__.py index 351e5e0f..378b07cc 100644 --- a/now_lms/__init__.py +++ b/now_lms/__init__.py @@ -47,7 +47,6 @@ from flask.cli import FlaskGroup from flask_alembic import Alembic from flask_login import LoginManager, current_user -from flask_mailman import Mail from flask_mde import Mde from flask_uploads import configure_uploads from pg8000.dbapi import ProgrammingError as PGProgrammingError @@ -138,7 +137,6 @@ alembic: Alembic = Alembic() administrador_sesion: LoginManager = LoginManager() mde: Mde = Mde() -mail: Mail = Mail() # --------------------------------------------------------------------------------------- @@ -154,7 +152,6 @@ def inicializa_extenciones_terceros(flask_app: Flask): administrador_sesion.init_app(flask_app) cache.init_app(flask_app) mde.init_app(flask_app) - mail.init_app(flask_app) log.trace("Extensiones de terceros iniciadas correctamente.") @@ -293,7 +290,14 @@ def error_500(error): # pragma: no cover # --------------------------------------------------------------------------------------- # Carga configuración del sitio web desde la base de datos. # --------------------------------------------------------------------------------------- -@cache.cached(timeout=60, key_prefix="site_config") + +if DESARROLLO: + timeout = 1 +else: + timeout = 60 + + +@cache.cached(timeout=timeout, key_prefix="site_config") def carga_configuracion_del_sitio_web_desde_db(): # pragma: no cover """Obtiene configuración del sitio web desde la base de datos.""" @@ -415,28 +419,6 @@ def init_app(with_examples=False): initial_setup(with_examples=with_examples) else: log.trace("Acceso a base de datos verificado.") - config = Configuracion.query.first() - - if ( - config.email - and config.MAIL_HOST is not None - and config.MAIL_PORT is not None - and config.MAIL_USERNAME is not None - and config.MAIL_PASSWORD is not None - ): - log.trace("Cargando configuración de correo electronico.") - lms_app.config.update( - { - "MAIL_HOST": config.MAIL_HOST, - "MAIL_PORT": config.MAIL_PORT, - "MAIL_USERNAME": config.MAIL_USERNAME, - "MAIL_PASSWORD": descifrar_secreto(config.MAIL_PASSWORD), - "MAIL_USE_TLS": config.MAIL_USE_TLS, - "MAIL_USE_SSL": config.MAIL_USE_SSL, - } - ) - if DESARROLLO: - lms_app.config.update({"MAIL_BACKEND": "dummy"}) # --------------------------------------------------------------------------------------- diff --git a/now_lms/auth.py b/now_lms/auth.py index e737e6f8..308e42ae 100644 --- a/now_lms/auth.py +++ b/now_lms/auth.py @@ -24,6 +24,8 @@ import base64 from datetime import datetime from functools import wraps +from os import environ, path +from pathlib import Path # --------------------------------------------------------------------------------------- # Librerias de terceros @@ -33,16 +35,16 @@ from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from flask import abort, flash, current_app +from flask import abort, current_app, flash from flask_login import current_user # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- +from now_lms.config import DIRECTORIO_ARCHIVOS_PRIVADOS from now_lms.db import Usuario, database from now_lms.logs import log - ph = PasswordHasher() @@ -94,51 +96,75 @@ def wrapper(*args, **kwargs): return decorator_verifica_acceso +def obtener_clave_de_cifrado(): + """ + Retorna una clave en bytes para utilizarla para proteger información sencible. + + Los usuario de NOW_LMS pueden almacenar información sencible como contraseñas y claves + de acceso a otros servicios en sus perfiles, para proteger la privacidad del usuario + estos valores no se almacen como texto plano en la base de datos si no que se almacen como + hashes. + + La implementación actual esta inspirada por la forma en que Apache Airflow almacena los datos + accesos que sus usuarios guardan. + + El sistema utiliza una llave segura para cifrar y desifrar estos valores, de esta forma un tercero + requerira tanto acceso a la base de datos como a la clave de cifrado para acceder a la información + cifrada. + + Se recomienda utilizar una clave de cifrado guardada como clave de entorno, en caso de proveerse una + el sistema generara una clave aleatoria y la almacenara en la carpeta de archivos privados de su + implementación. + + Referencias: + - https://airflow.apache.org/docs/apache-airflow/stable/security/secrets/fernet.html + - https://cryptography.io/en/latest/fernet/ + """ + + if environ.get("LMS_FERNET_KEY"): + return environ.get("LMS_FERNET_KEY").encode() + else: + SECURE_KEY_FILE: Path = Path(path.join(DIRECTORIO_ARCHIVOS_PRIVADOS, "secret.key")) + if Path.exists(SECURE_KEY_FILE): + with open(SECURE_KEY_FILE) as f: + return f.readline().encode() + else: + SECURE_KEY = Fernet.generate_key() + with open(SECURE_KEY_FILE, "x") as f: + f.write(SECURE_KEY.decode()) + return SECURE_KEY + + def proteger_secreto(password): """ - Devuelve el hash de una contraseña. + Devuelve el hash de un valor sencible a almancenar en la base de datos. - Se requiere que el parametro "SECRET_KEY" este establecido en la configuración de la aplicacion, - si cambia el valor de este parametro debera actualizar la configuración ya se utiliza el mismo - parametro para obtener la contraseña original. + Si la variable de entorno "LMS_FERNET_KEY" esta definida se utiliza este valor por defecto, en + caso contrario se utilizara una clave generada aleatoriamente que sera almacenada en el directorio + de archivos privados de su implementación. """ - with current_app.app_context(): - from now_lms.db import database, Configuracion + f = Fernet(obtener_clave_de_cifrado()) - config = database.session.execute(database.select(Configuracion)).first()[0] + if isinstance(password, bytes): - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=config.r, - iterations=480000, - ) - key = base64.urlsafe_b64encode(kdf.derive(current_app.config.get("SECRET_KEY").encode())) - f = Fernet(key) return f.encrypt(password) + else: + + return f.encrypt(password.encode()) + def descifrar_secreto(hash): """ Devuelve el valor de una contraseña protegida. - Se utiliza el valor del parametro "SECRET_KEY" de la configuración de la aplicación para decodificar - la contraseña original, si el parametro "SECRET_KEY" cambia en la configuración no se posible desifrar - la contraseña original, debera generar una nueva. + Si la variable de entorno "LMS_FERNET_KEY" esta definida se utiliza este valor por defecto, en + caso contrario se utilizara una clave generada aleatoriamente que sera almacenada en el directorio + de archivos privados de su implementación. """ - with current_app.app_context(): - from now_lms.db import database, Configuracion - - config = database.session.execute(database.select(Configuracion)).first()[0] + f = Fernet(obtener_clave_de_cifrado()) + b = f.decrypt(hash).decode("utf-8") - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=config.r, - iterations=480000, - ) - key = base64.urlsafe_b64encode(kdf.derive(current_app.config.get("SECRET_KEY").encode())) - f = Fernet(key) - return f.decrypt(hash) + return b diff --git a/now_lms/db/__init__.py b/now_lms/db/__init__.py index 18771e5e..4466ea0c 100644 --- a/now_lms/db/__init__.py +++ b/now_lms/db/__init__.py @@ -337,7 +337,7 @@ class EstudianteCurso(database.Model, BaseTabla): vigente = database.Column(database.Boolean()) -class Configuracion(database.Model, BaseTabla): +class Configuracion(database.Model): """ Repositorio Central para la configuración de la aplicacion. @@ -345,6 +345,7 @@ class Configuracion(database.Model, BaseTabla): va a estar disponible como la variable global config. """ + id = database.Column(database.Integer, primary_key=True, autoincrement=True) titulo = database.Column(database.String(150), nullable=False) descripcion = database.Column(database.String(500), nullable=False) # Uno de mooc, school, training @@ -355,21 +356,13 @@ class Configuracion(database.Model, BaseTabla): custom_logo = database.Column(database.Boolean()) # Email settings email = database.Column(database.Boolean()) - MAIL_HOST = database.Column(database.String(50)) - MAIL_PORT = database.Column(database.String(50)) + MAIL_SERVER = database.Column(database.String(50)) + MAIL_PORT = database.Column(database.Integer()) MAIL_USERNAME = database.Column(database.String(50)) MAIL_PASSWORD = database.Column(database.LargeBinary()) MAIL_USE_TLS = database.Column(database.Boolean()) MAIL_USE_SSL = database.Column(database.Boolean()) email_verificado = database.Column(database.Boolean()) - # These are ramdon bytes to protect passwords like SMTP mail password or others - # than the users of the system will estore in the database as configuration parameters - # those password are not goint to be saved in plain text, we will save them in hashed version - # with your app SECRET_KEY - # So, if any one have access to your database it will access to the hashed - # version of the password and this 16 bytes, but will need your app SECRET_KEY - # in order to decode those passwords. - r = database.Column(database.LargeBinary()) class Categoria(database.Model, BaseTabla): diff --git a/now_lms/db/tools.py b/now_lms/db/tools.py index a9f9fa88..8b555da5 100644 --- a/now_lms/db/tools.py +++ b/now_lms/db/tools.py @@ -98,7 +98,6 @@ def crear_configuracion_predeterminada(): MAIL_USE_TLS=False, MAIL_USE_SSL=False, moneda="C$", - r=urandom(16), ) database.session.add(config) database.session.commit() diff --git a/now_lms/forms/__init__.py b/now_lms/forms/__init__.py index ac0687d1..368f3b94 100644 --- a/now_lms/forms/__init__.py +++ b/now_lms/forms/__init__.py @@ -108,7 +108,7 @@ class MailForm(FlaskForm): email = BooleanField(validators=[]) MAIL_HOST = StringField(validators=[DataRequired()]) - MAIL_PORT = StringField(validators=[DataRequired()]) + MAIL_PORT = IntegerField(validators=[DataRequired()]) MAIL_USERNAME = StringField(validators=[DataRequired()]) MAIL_PASSWORD = PasswordField() MAIL_USE_TLS = BooleanField(validators=[]) diff --git a/now_lms/templates/inicio/perfil.html b/now_lms/templates/inicio/perfil.html index b1dddf64..95af23f7 100644 --- a/now_lms/templates/inicio/perfil.html +++ b/now_lms/templates/inicio/perfil.html @@ -44,8 +44,8 @@

Perfil de Usuario

{% endif %} {% if current_user.id == perfil.id %} {% endif %}

diff --git a/now_lms/templates/inicio/perfil_editar.html b/now_lms/templates/inicio/perfil_editar.html index f55d0e45..dd5341a3 100644 --- a/now_lms/templates/inicio/perfil_editar.html +++ b/now_lms/templates/inicio/perfil_editar.html @@ -22,7 +22,8 @@

Editar perfil del usuario: {{ usuario.usuario }}

-
+ {{ form.csrf_token }} @@ -118,7 +119,7 @@
Información de Contacto:
- Cancelar + Cancelar
diff --git a/now_lms/version.py b/now_lms/version.py index 3ef449d1..a45024e0 100644 --- a/now_lms/version.py +++ b/now_lms/version.py @@ -21,7 +21,6 @@ # --------------------------------------------------------------------------------------- # Libreria estandar # --------------------------------------------------------------------------------------- -from datetime import datetime # --------------------------------------------------------------------------------------- # Librerias de terceros @@ -47,8 +46,8 @@ # <--------------------------------------------------------------------------> # # Pre release data. -PRERELEASE = "a14" -REVISION = datetime.today().strftime("%Y%m%d") +PRERELEASE = "a15" +REVISION = "20240405" # <--------------------------------------------------------------------------> # # Release string diff --git a/now_lms/vistas/profiles/user.py b/now_lms/vistas/profiles/user.py index 19463156..34ff9771 100644 --- a/now_lms/vistas/profiles/user.py +++ b/now_lms/vistas/profiles/user.py @@ -88,7 +88,7 @@ def edit_perfil(ulid: str): try: # pragma: no cover database.session.commit() - cache.delete("view/" + url_for("perfil")) + cache.delete("view/" + url_for("user_profile.perfil")) flash("Pefil actualizado.", "success") if "logo" in request.files: try: diff --git a/now_lms/vistas/settings.py b/now_lms/vistas/settings.py index ab2552cf..5a530f24 100644 --- a/now_lms/vistas/settings.py +++ b/now_lms/vistas/settings.py @@ -28,8 +28,8 @@ # --------------------------------------------------------------------------------------- # Librerias de terceros # --------------------------------------------------------------------------------------- -from flask import Blueprint, flash, redirect, render_template, request, url_for, current_app -from flask_login import login_required, current_user +from flask import Blueprint, current_app, flash, redirect, render_template, request, url_for +from flask_login import current_user, login_required from flask_uploads import UploadNotAllowed from sqlalchemy.exc import OperationalError @@ -123,7 +123,7 @@ def mail(): config = database.session.execute(database.select(Configuracion)).first()[0] form = MailForm( email=config.email, - MAIL_HOST=config.MAIL_HOST, + MAIL_HOST=config.MAIL_SERVER, MAIL_PORT=config.MAIL_PORT, MAIL_USERNAME=config.MAIL_USERNAME, MAIL_PASSWORD=config.MAIL_PASSWORD, @@ -134,7 +134,7 @@ def mail(): if form.validate_on_submit() or request.method == "POST": config.email = form.email.data - config.MAIL_HOST = form.MAIL_HOST.data + config.MAIL_SERVER = form.MAIL_HOST.data config.MAIL_PORT = form.MAIL_PORT.data config.MAIL_USE_TLS = form.MAIL_USE_TLS.data config.MAIL_USE_SSL = form.MAIL_USE_SSL.data @@ -156,35 +156,27 @@ def mail(): @setting.route("/setting/mail_check", methods=["GET", "POST"]) @login_required @perfil_requerido("admin") -def test_mail(): +async def test_mail(): """Envia un correo de prueba.""" - from flask_mailman import EmailMessage - - msg = EmailMessage( - "Hello", - "Body goes here", - "from@example.com", - [current_user.correo_electronico], - [], - reply_to=["another@example.com"], - headers={"Message-ID": "foo"}, - ) - - with current_app.app_context(): + from flask_mailing import Message + from now_lms.mail import cargar_configuracion_correo_desde_db + + mail = cargar_configuracion_correo_desde_db() + + if current_user.correo_electronico: + message = Message( + subject="NOW-LMS mail setup confirmation.", + recipients=[current_user.correo_electronico], + body="Your email is working.", + ) + await mail.send_message(message) + flash( + "Verifique su casilla de correo electronico, incluso los spam. Si no recibio un correo de confirmación favor verifique su configuración.", + "success", + ) - if current_user.correo_electronico: - try: - msg.send() - config = database.session.execute(database.select(Configuracion)).first()[0] - config.email_verificado = True - database.session.commit() - flash("Correo de prueba enviado correctamente.", "success") - except RuntimeError: - flash("Error, no se pudo enviar el correo electronico.", "error") - except ConnectionRefusedError: - flash("Su sistema operativo denego el acceso a red.", "error") - else: - flash("Error, no ha configurado su correo electronico.", "error") + else: + flash("Error, no ha configurado su correo electronico.", "error") return redirect(url_for("setting.mail")) diff --git a/now_lms/vistas/users.py b/now_lms/vistas/users.py index a0ae0149..28e62d0b 100644 --- a/now_lms/vistas/users.py +++ b/now_lms/vistas/users.py @@ -29,13 +29,13 @@ # Librerias de terceros # --------------------------------------------------------------------------------------- from flask import Blueprint, flash, redirect, render_template, request, url_for -from flask_login import current_user, login_user, logout_user, login_required +from flask_login import current_user, login_required, login_user, logout_user from sqlalchemy.exc import OperationalError # --------------------------------------------------------------------------------------- # Recursos locales # --------------------------------------------------------------------------------------- -from now_lms.auth import proteger_passwd, validar_acceso, perfil_requerido +from now_lms.auth import perfil_requerido, proteger_passwd, validar_acceso from now_lms.config import DIRECTORIO_PLANTILLAS from now_lms.db import Usuario, database from now_lms.forms import LoginForm, LogonForm diff --git a/requirements.txt b/requirements.txt index ef31e17e..266e2a22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ flask-alembic flask-babel flask-caching flask-login -flask-mailman +flask-mailing flask-mde flask-reuploaded flask-sqlalchemy diff --git a/tests/test_basicos.py b/tests/test_basicos.py index 876161be..eb081e01 100644 --- a/tests/test_basicos.py +++ b/tests/test_basicos.py @@ -19,74 +19,186 @@ from unittest import TestCase -class TestBasicos(TestCase): - def setUp(self): - from now_lms import app - - self.app = app - self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - self.app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" - self.app.app_context().push() - - def test_importable(self): - """El proyecto debe poder importarse sin errores.""" - - assert self.app - - def test_cli(self): - self.app.test_cli_runner() - - class TestInstanciasDeClases(TestCase): - def setUp(self): - from now_lms import app - - self.app = app - self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - self.app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" - self.app.app_context().push() def test_Flask(self): from flask import Flask - self.assertIsInstance(self.app, Flask) + from now_lms import app + + self.assertIsInstance(app, Flask) def test_SQLAlchemy(self): - from now_lms import database from flask_sqlalchemy import SQLAlchemy + from now_lms import database + self.assertIsInstance(database, SQLAlchemy) def test_Alembic(self): - from now_lms import alembic from flask_alembic import Alembic + from now_lms import alembic + self.assertIsInstance(alembic, Alembic) def test_Login(self): - from now_lms import administrador_sesion from flask_login import LoginManager + from now_lms import administrador_sesion + self.assertIsInstance(administrador_sesion, LoginManager) def test_FlaskForm(self): from flask_wtf import FlaskForm - from now_lms.forms import LoginForm, LogonForm, CurseForm, CursoRecursoVideoYoutube, CursoSeccionForm - assert issubclass(LoginForm, FlaskForm) - assert issubclass(CurseForm, FlaskForm) - assert issubclass(LogonForm, FlaskForm) - assert issubclass(CursoRecursoVideoYoutube, FlaskForm) - assert issubclass(CursoSeccionForm, FlaskForm) + from now_lms.forms import ( + BaseForm, + CategoriaForm, + CertificateForm, + CurseForm, + CursoRecursoArchivoAudio, + CursoRecursoArchivoImagen, + CursoRecursoArchivoPDF, + CursoRecursoArchivoText, + CursoRecursoExternalLink, + CursoRecursoForm, + CursoRecursoMeet, + CursoRecursoSlides, + CursoRecursoVideoYoutube, + CursoSeccionForm, + EtiquetaForm, + GrupoForm, + LoginForm, + LogonForm, + MailForm, + MsgForm, + ProgramaForm, + RecursoForm, + ThemeForm, + UserForm, + ) + + forms = [ + ThemeForm, + LoginForm, + MailForm, + LogonForm, + BaseForm, + GrupoForm, + CurseForm, + CursoSeccionForm, + CursoRecursoForm, + CursoRecursoVideoYoutube, + CursoRecursoArchivoPDF, + CursoRecursoArchivoAudio, + CursoRecursoArchivoImagen, + CursoRecursoArchivoText, + CursoRecursoExternalLink, + CursoRecursoSlides, + CursoRecursoMeet, + CategoriaForm, + EtiquetaForm, + ProgramaForm, + RecursoForm, + UserForm, + MsgForm, + CertificateForm, + ] + + for form in forms: + assert issubclass(form, FlaskForm) def test_BaseTable(self): - from now_lms.db import BaseTabla, database from flask_login import UserMixin + from now_lms import Usuario + from now_lms.db import ( + Categoria, + CategoriaCurso, + Certificado, + Configuracion, + Curso, + CursoRecurso, + CursoRecursoAvance, + CursoRecursoConsulta, + CursoRecursoDescargable, + CursoRecursoPregunta, + CursoRecursoPreguntaOpcion, + CursoRecursoPreguntaRespuesta, + CursoRecursoSlides, + CursoRecursoSlideShow, + CursoSeccion, + DocenteCurso, + EstudianteCurso, + Etiqueta, + EtiquetaCurso, + Files, + Mensaje, + ModeradorCurso, + Programa, + ProgramaCurso, + ProgramaEstudiante, + Recurso, + StripePaymentsConfig, + StripeUserKey, + SystemInfo, + Usuario, + UsuarioGrupo, + UsuarioGrupoMiembro, + UsuarioGrupoTutor, + database, + ) assert issubclass(Usuario, UserMixin) - assert issubclass(Usuario, BaseTabla) - assert issubclass(Usuario, database.Model) + + tablas = ( + SystemInfo, + Usuario, + UsuarioGrupo, + UsuarioGrupoMiembro, + UsuarioGrupoTutor, + Curso, + CursoRecursoDescargable, + CursoSeccion, + CursoRecurso, + CursoRecursoAvance, + CursoRecursoPregunta, + CursoRecursoPreguntaOpcion, + CursoRecursoPreguntaRespuesta, + CursoRecursoConsulta, + CursoRecursoSlideShow, + CursoRecursoSlides, + Files, + DocenteCurso, + ModeradorCurso, + EstudianteCurso, + Configuracion, + Categoria, + CategoriaCurso, + Etiqueta, + EtiquetaCurso, + Programa, + ProgramaCurso, + ProgramaEstudiante, + Recurso, + Certificado, + Mensaje, + StripeUserKey, + StripePaymentsConfig, + ) + + for tabla in tablas: + assert issubclass(tabla, database.Model) + + +def tests_secretos(): + from now_lms.auth import descifrar_secreto, proteger_secreto + + password = b"testing123abcqwerty" + hash = proteger_secreto(password=password) + + assert password.decode() == descifrar_secreto(hash=hash) # Source: https://gist.github.com/allysonsilva/85fff14a22bbdf55485be947566cc09e diff --git a/tests/test_use_cases.py b/tests/test_use_cases.py index 148d257a..dba2a30d 100644 --- a/tests/test_use_cases.py +++ b/tests/test_use_cases.py @@ -18,6 +18,7 @@ import os import sys + import pytest from now_lms import log diff --git a/tests/test_vistas.py b/tests/test_vistas.py index 67601580..03113647 100644 --- a/tests/test_vistas.py +++ b/tests/test_vistas.py @@ -18,6 +18,7 @@ import os import sys + import pytest from now_lms import log diff --git a/tests/test_webforms.py b/tests/test_webforms.py index 752a2885..fc6b00d9 100644 --- a/tests/test_webforms.py +++ b/tests/test_webforms.py @@ -18,8 +18,8 @@ import os import sys -import pytest +import pytest from flask import session from now_lms import log From fe21e8f889da91eeca988e8801d3c2a4b1f3bb5b Mon Sep 17 00:00:00 2001 From: William Moreno Date: Fri, 5 Apr 2024 21:31:35 -0600 Subject: [PATCH 4/5] Update publish.yml Signed-off-by: William Moreno --- .github/workflows/publish.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 09a3387a..e9431ff5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,9 @@ name: Publish to PyPi on: + push: + branches: + - main workflow_run: workflows: ["CI"] types: [completed] From adab6b44657a5e2feefbbdf02151d6fad29e2b36 Mon Sep 17 00:00:00 2001 From: William Moreno Date: Fri, 5 Apr 2024 21:34:04 -0600 Subject: [PATCH 5/5] release: v0.0.1a15.dev20240406 Signed-off-by: William Moreno --- now_lms/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/now_lms/version.py b/now_lms/version.py index a45024e0..80067e3b 100644 --- a/now_lms/version.py +++ b/now_lms/version.py @@ -47,7 +47,7 @@ # <--------------------------------------------------------------------------> # # Pre release data. PRERELEASE = "a15" -REVISION = "20240405" +REVISION = "20240406" # <--------------------------------------------------------------------------> # # Release string