diff --git a/config/urls.py b/config/urls.py index 85781d5..0c7368f 100644 --- a/config/urls.py +++ b/config/urls.py @@ -18,10 +18,27 @@ from django.conf import settings from django.conf.urls.i18n import i18n_patterns from django.contrib import admin from django.urls import include, path +from django.views.generic import TemplateView from django.views.static import serve urlpatterns = [ path('admin/', admin.site.urls), + # Sin prefijo de idioma (fuera de i18n_patterns, ver más abajo) ni bajo + # STATIC_URL: sw.js necesita servirse desde la raíz para que su ámbito + # (qué rutas puede interceptar un Service Worker) cubra el origen + # entero, no solo /static/ o /es/ (ver crochet/templates/sw.js). Se + # renderiza como plantilla (no un archivo estático de whitenoise) para + # que {% static %}/{% url %} resuelvan ahí mismo las rutas (con hash) + # vigentes en cada respuesta. + path( + 'sw.js', name='service_worker', + view=TemplateView.as_view(template_name='sw.js', content_type='application/javascript'), + ), + path( + 'manifest.json', name='manifest', + view=TemplateView.as_view(template_name='manifest.json', content_type='application/manifest+json'), + ), + path('offline/', name='offline', view=TemplateView.as_view(template_name='offline.html')), ] # i18n_patterns antepone el prefijo de idioma (/es/..., /en/...) a cada URL, diff --git a/conftest.py b/conftest.py index 9181ab0..ec99e7f 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,21 @@ from django.core.management import call_command from django.test import override_settings +@pytest.fixture(scope='session', autouse=True) +def fast_password_hasher(): + """PBKDF2PasswordHasher (por defecto, ver AUTH_PASSWORD_VALIDATORS) es + deliberadamente lento -es justo lo que lo hace seguro en producción-, + pero eso mismo hace bastante caro cada create_user()/check_password()/ + login de los tests: con cientos de ellos (y los de rate limiting, que + encima repiten el login 10-11 veces seguidas) esto es la mayor parte + del tiempo de toda la suite. MD5PasswordHasher no vale para producción, + pero aquí no protege nada real -las contraseñas son literales fijos + como 'a-very-uncommon-pw-1'-, así que no hay ninguna pérdida de + seguridad en usarlo solo para tests.""" + with override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.MD5PasswordHasher']): + yield + + @pytest.fixture(autouse=True) def clear_ratelimit_cache(): """django-ratelimit (ver LOGIN_RATE/REGISTER_RATE/PASSWORD_RESET_RATE en diff --git a/crochet/locale/en/LC_MESSAGES/django.po b/crochet/locale/en/LC_MESSAGES/django.po index 1599d98..3ee3494 100644 --- a/crochet/locale/en/LC_MESSAGES/django.po +++ b/crochet/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 11:16+0000\n" +"POT-Creation-Date: 2026-09-16 14:05+0000\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -46,7 +46,7 @@ msgstr "" "made in a row from here." #: crochet/templates/403.html:24 crochet/templates/404.html:19 -#: crochet/templates/500.html:64 +#: crochet/templates/500.html:65 #: crochet/templates/crochet/cookie_policy.html:54 msgid "Volver al inicio" msgstr "Back to home" @@ -63,11 +63,11 @@ msgstr "The page you're looking for doesn't exist or has moved." msgid "Error del servidor" msgstr "Server error" -#: crochet/templates/500.html:62 +#: crochet/templates/500.html:63 msgid "Vaya, algo ha ido mal" msgstr "Oops, something went wrong" -#: crochet/templates/500.html:63 +#: crochet/templates/500.html:64 msgid "Ha ocurrido un error inesperado. Prueba a recargar en un momento." msgstr "An unexpected error occurred. Try reloading in a moment." @@ -225,9 +225,8 @@ msgid "Actualizado el %(date)s" msgstr "Updated on %(date)s" #: crochet/templates/crochet/account_home.html:44 -#: crochet/templates/crochet/pattern.html:156 -msgid "Vista previa" -msgstr "Preview" +msgid "Editar" +msgstr "Edit" #: crochet/templates/crochet/account_home.html:48 #: crochet/templates/crochet/pattern.html:46 @@ -240,7 +239,7 @@ msgstr "You don't have any patterns yet." #: crochet/templates/crochet/account_settings.html:4 #: crochet/templates/crochet/account_settings.html:18 -#: crochet/templates/crochet/base.html:62 +#: crochet/templates/crochet/base.html:71 msgid "Ajustes de la cuenta" msgstr "Account settings" @@ -274,19 +273,19 @@ msgstr "" msgid "Esto borrará tu cuenta y todos tus patrones de forma permanente." msgstr "This will permanently delete your account and all your patterns." -#: crochet/templates/crochet/base.html:37 +#: crochet/templates/crochet/base.html:46 msgid "Cambiar tema" msgstr "Change theme" -#: crochet/templates/crochet/base.html:49 +#: crochet/templates/crochet/base.html:58 msgid "Cambiar idioma" msgstr "Change language" -#: crochet/templates/crochet/base.html:66 +#: crochet/templates/crochet/base.html:75 msgid "Cerrar sesión" msgstr "Log out" -#: crochet/templates/crochet/base.html:72 +#: crochet/templates/crochet/base.html:81 #: crochet/templates/crochet/home.html:25 #: crochet/templates/crochet/password_reset_complete.html:18 #: crochet/templates/registration/login.html:4 @@ -758,6 +757,10 @@ msgstr "Landscape" msgid "Secciones" msgstr "Sections" +#: crochet/templates/crochet/pattern.html:156 +msgid "Vista previa" +msgstr "Preview" + #: crochet/templates/crochet/pattern.html:166 msgid "Idioma del patrón" msgstr "Pattern language" @@ -906,6 +909,22 @@ msgstr "" msgid "Confirmar" msgstr "Confirm" +#: crochet/templates/offline.html:20 crochet/templates/offline.html:70 +msgid "Sin conexión" +msgstr "No connection" + +#: crochet/templates/offline.html:71 +msgid "" +"No se ha podido cargar esta página porque no tienes conexión a internet. " +"Comprueba tu conexión e inténtalo de nuevo." +msgstr "" +"This page couldn't be loaded because you're not connected to the internet. " +"Check your connection and try again." + +#: crochet/templates/offline.html:72 +msgid "Reintentar" +msgstr "Retry" + #: crochet/templates/registration/login.html:17 msgid "Usuario o contraseña incorrectos." msgstr "Incorrect username or password." @@ -1109,8 +1128,5 @@ msgstr "Your account has been deleted successfully." #~ msgid "Idioma" #~ msgstr "Language" -#~ msgid "Editar" -#~ msgstr "Edit" - #~ msgid "Eliminar patrón" #~ msgstr "Delete pattern" diff --git a/crochet/locale/es/LC_MESSAGES/django.po b/crochet/locale/es/LC_MESSAGES/django.po index a396d52..9399c4c 100644 --- a/crochet/locale/es/LC_MESSAGES/django.po +++ b/crochet/locale/es/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 11:16+0000\n" +"POT-Creation-Date: 2026-09-16 14:05+0000\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -44,7 +44,7 @@ msgid "" msgstr "" #: crochet/templates/403.html:24 crochet/templates/404.html:19 -#: crochet/templates/500.html:64 +#: crochet/templates/500.html:65 #: crochet/templates/crochet/cookie_policy.html:54 msgid "Volver al inicio" msgstr "" @@ -61,11 +61,11 @@ msgstr "" msgid "Error del servidor" msgstr "" -#: crochet/templates/500.html:62 +#: crochet/templates/500.html:63 msgid "Vaya, algo ha ido mal" msgstr "" -#: crochet/templates/500.html:63 +#: crochet/templates/500.html:64 msgid "Ha ocurrido un error inesperado. Prueba a recargar en un momento." msgstr "" @@ -215,8 +215,7 @@ msgid "Actualizado el %(date)s" msgstr "" #: crochet/templates/crochet/account_home.html:44 -#: crochet/templates/crochet/pattern.html:156 -msgid "Vista previa" +msgid "Editar" msgstr "" #: crochet/templates/crochet/account_home.html:48 @@ -230,7 +229,7 @@ msgstr "" #: crochet/templates/crochet/account_settings.html:4 #: crochet/templates/crochet/account_settings.html:18 -#: crochet/templates/crochet/base.html:62 +#: crochet/templates/crochet/base.html:71 msgid "Ajustes de la cuenta" msgstr "" @@ -262,19 +261,19 @@ msgstr "" msgid "Esto borrará tu cuenta y todos tus patrones de forma permanente." msgstr "" -#: crochet/templates/crochet/base.html:37 +#: crochet/templates/crochet/base.html:46 msgid "Cambiar tema" msgstr "" -#: crochet/templates/crochet/base.html:49 +#: crochet/templates/crochet/base.html:58 msgid "Cambiar idioma" msgstr "" -#: crochet/templates/crochet/base.html:66 +#: crochet/templates/crochet/base.html:75 msgid "Cerrar sesión" msgstr "" -#: crochet/templates/crochet/base.html:72 +#: crochet/templates/crochet/base.html:81 #: crochet/templates/crochet/home.html:25 #: crochet/templates/crochet/password_reset_complete.html:18 #: crochet/templates/registration/login.html:4 @@ -718,6 +717,10 @@ msgstr "" msgid "Secciones" msgstr "" +#: crochet/templates/crochet/pattern.html:156 +msgid "Vista previa" +msgstr "" + #: crochet/templates/crochet/pattern.html:166 msgid "Idioma del patrón" msgstr "" @@ -860,6 +863,20 @@ msgstr "" msgid "Confirmar" msgstr "" +#: crochet/templates/offline.html:20 crochet/templates/offline.html:70 +msgid "Sin conexión" +msgstr "" + +#: crochet/templates/offline.html:71 +msgid "" +"No se ha podido cargar esta página porque no tienes conexión a internet. " +"Comprueba tu conexión e inténtalo de nuevo." +msgstr "" + +#: crochet/templates/offline.html:72 +msgid "Reintentar" +msgstr "" + #: crochet/templates/registration/login.html:17 msgid "Usuario o contraseña incorrectos." msgstr "" diff --git a/crochet/static/img/favicon-32.png b/crochet/static/img/favicon-32.png new file mode 100644 index 0000000..c353a04 Binary files /dev/null and b/crochet/static/img/favicon-32.png differ diff --git a/crochet/static/img/favicon.ico b/crochet/static/img/favicon.ico new file mode 100644 index 0000000..ac704f6 Binary files /dev/null and b/crochet/static/img/favicon.ico differ diff --git a/crochet/static/img/icon-192.png b/crochet/static/img/icon-192.png new file mode 100644 index 0000000..41c4c1d Binary files /dev/null and b/crochet/static/img/icon-192.png differ diff --git a/crochet/static/img/icon-512.png b/crochet/static/img/icon-512.png new file mode 100644 index 0000000..0a441cd Binary files /dev/null and b/crochet/static/img/icon-512.png differ diff --git a/crochet/static/js/sw-register.js b/crochet/static/js/sw-register.js new file mode 100644 index 0000000..aed958e --- /dev/null +++ b/crochet/static/js/sw-register.js @@ -0,0 +1,18 @@ +// Registra el Service Worker de la PWA (ver crochet/templates/sw.js) para +// que la app sea instalable y tenga soporte offline básico. La ruta va a +// pelo ('/sw.js', no bajo /static/ ni con prefijo de idioma) porque tiene +// que coincidir exactamente con dónde lo sirve config/urls.py: el ámbito +// de un Service Worker es la carpeta de su propia URL y las de debajo, así +// que solo sirviéndolo (y registrándolo) desde la raíz cubre el origen +// entero en vez de quedarse limitado a /static/ o /es/. +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(() => { + // Sin conexión, en un navegador sin soporte real pese a exponer el + // objeto, o serviceWorker.register() con documento no seguro (http + // sin ser localhost): la app sigue funcionando con normalidad, solo + // sin lo que aporta la PWA (instalación, caché offline), así que no + // hace falta avisar a quien la usa. + }); + }); +} diff --git a/crochet/templates/500.html b/crochet/templates/500.html index 7f46908..eb52617 100644 --- a/crochet/templates/500.html +++ b/crochet/templates/500.html @@ -14,6 +14,7 @@
{% trans 'No se ha podido cargar esta página porque no tienes conexión a internet. Comprueba tu conexión e inténtalo de nuevo.' %}
+ +