From 3e95ee22391eda564d7b34b9eb5dab42316b92f1 Mon Sep 17 00:00:00 2001 From: Pablo Moreno Date: Tue, 21 Jul 2026 12:47:01 +0200 Subject: [PATCH] feat: added recover password view --- config/settings/env.py | 1 + crochet/forms.py | 30 +- crochet/locale/en/LC_MESSAGES/django.po | 260 +++++++++++++----- crochet/locale/es/LC_MESSAGES/django.po | 254 ++++++++++++----- .../crochet/password_reset_complete.html | 22 ++ .../crochet/password_reset_confirm.html | 44 +++ .../crochet/password_reset_done.html | 24 ++ .../crochet/password_reset_email.html | 10 + .../crochet/password_reset_form.html | 37 +++ .../crochet/password_reset_subject.txt | 1 + crochet/templates/registration/login.html | 3 + crochet/tests/test_auth.py | 17 +- crochet/tests/test_password_reset.py | 134 +++++++++ crochet/urls.py | 35 ++- 14 files changed, 724 insertions(+), 148 deletions(-) create mode 100644 crochet/templates/crochet/password_reset_complete.html create mode 100644 crochet/templates/crochet/password_reset_confirm.html create mode 100644 crochet/templates/crochet/password_reset_done.html create mode 100644 crochet/templates/crochet/password_reset_email.html create mode 100644 crochet/templates/crochet/password_reset_form.html create mode 100644 crochet/templates/crochet/password_reset_subject.txt create mode 100644 crochet/tests/test_password_reset.py diff --git a/config/settings/env.py b/config/settings/env.py index 28d0e28..603112a 100644 --- a/config/settings/env.py +++ b/config/settings/env.py @@ -43,6 +43,7 @@ EMAIL_HOST = env.str('EMAIL_HOST', '') EMAIL_HOST_USER = env.str('EMAIL_HOST_USER', '') EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD', '') EMAIL_PORT = env.int('EMAIL_PORT', 587) +DEFAULT_FROM_EMAIL = env.str('DEFAULT_FROM_EMAIL', 'webmaster@localhost') # Nivel de log de la app y de Django (ver LOGGING en base.py). En producción # (DEBUG=False) Django, por defecto, solo manda los errores por email a diff --git a/crochet/forms.py b/crochet/forms.py index 5c0e56a..55e7c97 100644 --- a/crochet/forms.py +++ b/crochet/forms.py @@ -1,5 +1,11 @@ from django import forms -from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, UserCreationForm +from django.contrib.auth.forms import ( + AuthenticationForm, + PasswordChangeForm, + PasswordResetForm, + SetPasswordForm, + UserCreationForm, +) from django.utils.translation import gettext_lazy as _ # Los formularios de auth de Django no traen ninguna clase CSS en sus @@ -18,6 +24,14 @@ class StyledAuthenticationForm(AuthenticationForm): class StyledUserCreationForm(UserCreationForm): + # UserCreationForm no pide email por defecto (piensa solo en + # usuario/contraseña); sin él, PasswordResetForm no tiene a qué + # dirección mandar el enlace de recuperación de estos usuarios. + email = forms.EmailField(label=_('Email')) + + class Meta(UserCreationForm.Meta): + fields = ('username', 'email') + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): @@ -31,6 +45,20 @@ class StyledPasswordChangeForm(PasswordChangeForm): field.widget.attrs['class'] = INPUT_CLASSES +class StyledPasswordResetForm(PasswordResetForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs['class'] = INPUT_CLASSES + + +class StyledSetPasswordForm(SetPasswordForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs['class'] = INPUT_CLASSES + + class EmailUpdateForm(forms.Form): email = forms.EmailField(label=_('Email'), widget=forms.EmailInput(attrs={'class': INPUT_CLASSES})) diff --git a/crochet/locale/en/LC_MESSAGES/django.po b/crochet/locale/en/LC_MESSAGES/django.po index 9285b83..046229b 100644 --- a/crochet/locale/en/LC_MESSAGES/django.po +++ b/crochet/locale/en/LC_MESSAGES/django.po @@ -2,13 +2,14 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-20 10:46+0000\n" +"POT-Creation-Date: 2026-07-21 10:20+0000\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: crochet/forms.py:35 crochet/templates/crochet/account_settings.html:22 +#: crochet/forms.py:30 crochet/forms.py:63 +#: crochet/templates/crochet/account_settings.html:22 msgid "Email" msgstr "Email" @@ -26,6 +27,7 @@ msgstr "Password updated." #: crochet/templates/crochet/_account_password_form.html:19 #: crochet/templates/crochet/account_settings.html:29 +#: crochet/templates/crochet/password_reset_confirm.html:30 msgid "Cambiar contraseña" msgstr "Change password" @@ -57,13 +59,12 @@ msgid "Actualizado el %(date)s" msgstr "Updated on %(date)s" #: crochet/templates/crochet/account_home.html:44 -#: crochet/templates/crochet/pattern.html:158 -#: crochet/templates/crochet/pattern.html:204 +#: crochet/templates/crochet/pattern.html:163 msgid "Vista previa" msgstr "Preview" #: crochet/templates/crochet/account_home.html:48 -#: crochet/templates/crochet/pattern.html:50 +#: crochet/templates/crochet/pattern.html:55 msgid "Eliminar" msgstr "Delete" @@ -91,16 +92,12 @@ msgstr "Log out" #: crochet/templates/crochet/base.html:62 #: crochet/templates/crochet/home.html:24 +#: crochet/templates/crochet/password_reset_complete.html:18 #: crochet/templates/registration/login.html:4 #: crochet/templates/registration/login.html:14 msgid "Iniciar sesión" msgstr "Log in" -#: crochet/templates/crochet/base.html:63 -#: crochet/templates/registration/register.html:29 -msgid "Registrarme" -msgstr "Sign up" - #: crochet/templates/crochet/home.html:4 msgid "Crochet — Crea y comparte tus patrones" msgstr "Crochet — Create and share your patterns" @@ -161,233 +158,328 @@ msgstr "" "Send your pattern to anyone with a read-only link: no one else will be able " "to edit it." +#: crochet/templates/crochet/password_reset_complete.html:4 +#: crochet/templates/crochet/password_reset_complete.html:14 +msgid "Contraseña actualizada" +msgstr "Password updated" + +#: crochet/templates/crochet/password_reset_complete.html:16 +msgid "Ya puedes iniciar sesión con tu nueva contraseña." +msgstr "You can now log in with your new password." + +#: crochet/templates/crochet/password_reset_confirm.html:4 +msgid "Elegir nueva contraseña" +msgstr "Choose new password" + +#: crochet/templates/crochet/password_reset_confirm.html:15 +msgid "Elige una contraseña nueva" +msgstr "Choose a new password" + +#: crochet/templates/crochet/password_reset_confirm.html:33 +msgid "Enlace no válido" +msgstr "Invalid link" + +#: crochet/templates/crochet/password_reset_confirm.html:35 +msgid "" +"El enlace para restablecer la contraseña no es válido, puede que ya se haya " +"usado. Solicita uno nuevo." +msgstr "" +"The password reset link is invalid, possibly because it has already been " +"used. Please request a new one." + +#: crochet/templates/crochet/password_reset_confirm.html:38 +msgid "Solicitar un enlace nuevo" +msgstr "Request a new link" + +#: crochet/templates/crochet/password_reset_done.html:4 +#: crochet/templates/crochet/password_reset_form.html:4 +#: crochet/templates/crochet/password_reset_form.html:14 +msgid "Recuperar contraseña" +msgstr "Reset password" + +#: crochet/templates/crochet/password_reset_done.html:14 +msgid "Revisa tu email" +msgstr "Check your email" + +#: crochet/templates/crochet/password_reset_done.html:16 +msgid "" +"Si existe una cuenta con ese email, te hemos enviado un enlace para elegir " +"una contraseña nueva." +msgstr "" +"If an account exists with that email, we've sent you a link to choose a new " +"password." + +#: crochet/templates/crochet/password_reset_done.html:19 +#: crochet/templates/crochet/password_reset_form.html:32 +msgid "Volver a iniciar sesión" +msgstr "Back to login" + +#: crochet/templates/crochet/password_reset_email.html:2 +#, python-format +msgid "" +"Has recibido este email porque alguien ha solicitado restablecer la " +"contraseña de tu cuenta en %(site_name)s." +msgstr "" +"You're receiving this email because someone requested a password reset for " +"your account at %(site_name)s." + +#: crochet/templates/crochet/password_reset_email.html:4 +msgid "Sigue este enlace para elegir una contraseña nueva:" +msgstr "Follow this link to choose a new password:" + +#: crochet/templates/crochet/password_reset_email.html:7 +msgid "Tu usuario, por si lo has olvidado:" +msgstr "Your username, in case you've forgotten:" + +#: crochet/templates/crochet/password_reset_email.html:9 +msgid "Si no has solicitado este cambio, puedes ignorar este email." +msgstr "If you didn't request this change, you can ignore this email." + +#: crochet/templates/crochet/password_reset_form.html:16 +msgid "" +"Escribe tu email y te enviaremos un enlace para elegir una contraseña nueva." +msgstr "Enter your email and we'll send you a link to choose a new password." + +#: crochet/templates/crochet/password_reset_form.html:29 +msgid "Enviar enlace" +msgstr "Send link" + +#: crochet/templates/crochet/password_reset_subject.txt:1 +#, python-format +msgid "Recupera tu contraseña en %(site_name)s" +msgstr "Reset your password on %(site_name)s" + +#: crochet/templates/crochet/pattern.html:26 +msgid "Idioma del patrón" +msgstr "Pattern language" + # crochet/templates/crochet/pattern.html -#: crochet/templates/crochet/pattern.html:33 +#: crochet/templates/crochet/pattern.html:38 msgid "No se ha podido guardar. Inténtalo de nuevo." msgstr "Couldn't save. Please try again." -#: crochet/templates/crochet/pattern.html:33 +#: crochet/templates/crochet/pattern.html:38 msgid "Guardar" msgstr "Save" -#: crochet/templates/crochet/pattern.html:36 +#: crochet/templates/crochet/pattern.html:41 msgid "Ver patrón" msgstr "View pattern" -#: crochet/templates/crochet/pattern.html:37 +#: crochet/templates/crochet/pattern.html:42 msgid "Exportar a PDF" msgstr "Export to PDF" -#: crochet/templates/crochet/pattern.html:44 +#: crochet/templates/crochet/pattern.html:49 msgid "Más opciones" msgstr "More options" -#: crochet/templates/crochet/pattern.html:60 +#: crochet/templates/crochet/pattern.html:65 msgid "Guardado" msgstr "Saved" -#: crochet/templates/crochet/pattern.html:67 +#: crochet/templates/crochet/pattern.html:72 msgid "Personalización de página" msgstr "Page customization" -#: crochet/templates/crochet/pattern.html:71 +#: crochet/templates/crochet/pattern.html:76 msgid "Título del patrón" msgstr "Pattern title" -#: crochet/templates/crochet/pattern.html:72 +#: crochet/templates/crochet/pattern.html:77 msgid "Título del patrón..." msgstr "Pattern title..." -#: crochet/templates/crochet/pattern.html:75 +#: crochet/templates/crochet/pattern.html:80 msgid "Autor" msgstr "Author" -#: crochet/templates/crochet/pattern.html:76 +#: crochet/templates/crochet/pattern.html:81 msgid "Autor..." msgstr "Author..." -#: crochet/templates/crochet/pattern.html:81 +#: crochet/templates/crochet/pattern.html:86 msgid "Imagen de portada" msgstr "Cover image" -#: crochet/templates/crochet/pattern.html:84 +#: crochet/templates/crochet/pattern.html:89 msgid "No se ha podido subir la imagen de portada. Inténtalo de nuevo." msgstr "Couldn't upload the cover image. Please try again." -#: crochet/templates/crochet/pattern.html:98 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:103 +#: crochet/templates/crochet/pattern.html:187 msgid "Color del texto" msgstr "Text color" -#: crochet/templates/crochet/pattern.html:102 +#: crochet/templates/crochet/pattern.html:107 msgid "Color de acento (títulos)" msgstr "Accent color (headings)" -#: crochet/templates/crochet/pattern.html:106 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:111 +#: crochet/templates/crochet/pattern.html:187 msgid "Color de fondo" msgstr "Background color" -#: crochet/templates/crochet/pattern.html:110 +#: crochet/templates/crochet/pattern.html:115 msgid "Tipografía" msgstr "Font" -#: crochet/templates/crochet/pattern.html:116 +#: crochet/templates/crochet/pattern.html:121 msgid "Monoespaciada" msgstr "Monospace" -#: crochet/templates/crochet/pattern.html:120 +#: crochet/templates/crochet/pattern.html:125 msgid "Tamaño de letra" msgstr "Font size" -#: crochet/templates/crochet/pattern.html:122 +#: crochet/templates/crochet/pattern.html:127 msgid "Pequeño" msgstr "Small" -#: crochet/templates/crochet/pattern.html:123 +#: crochet/templates/crochet/pattern.html:128 msgid "Normal" msgstr "Normal" -#: crochet/templates/crochet/pattern.html:124 +#: crochet/templates/crochet/pattern.html:129 msgid "Grande" msgstr "Large" -#: crochet/templates/crochet/pattern.html:128 +#: crochet/templates/crochet/pattern.html:133 msgid "Alineación del texto" msgstr "Text alignment" -#: crochet/templates/crochet/pattern.html:130 +#: crochet/templates/crochet/pattern.html:135 msgid "Izquierda" msgstr "Left" -#: crochet/templates/crochet/pattern.html:131 +#: crochet/templates/crochet/pattern.html:136 msgid "Centrado" msgstr "Center" -#: crochet/templates/crochet/pattern.html:132 +#: crochet/templates/crochet/pattern.html:137 msgid "Justificado" msgstr "Justify" -#: crochet/templates/crochet/pattern.html:136 +#: crochet/templates/crochet/pattern.html:141 msgid "Tamaño de página" msgstr "Page size" -#: crochet/templates/crochet/pattern.html:141 +#: crochet/templates/crochet/pattern.html:146 msgid "Carta (Letter)" msgstr "Letter" -#: crochet/templates/crochet/pattern.html:146 +#: crochet/templates/crochet/pattern.html:151 msgid "Orientación" msgstr "Orientation" -#: crochet/templates/crochet/pattern.html:148 +#: crochet/templates/crochet/pattern.html:153 msgid "Vertical" msgstr "Portrait" -#: crochet/templates/crochet/pattern.html:149 +#: crochet/templates/crochet/pattern.html:154 msgid "Horizontal" msgstr "Landscape" -#: crochet/templates/crochet/pattern.html:157 -#: crochet/templates/crochet/pattern.html:165 +#: crochet/templates/crochet/pattern.html:162 +#: crochet/templates/crochet/pattern.html:170 msgid "Secciones" msgstr "Sections" -#: crochet/templates/crochet/pattern.html:167 +#: crochet/templates/crochet/pattern.html:172 msgid "Colapsar todo" msgstr "Collapse all" -#: crochet/templates/crochet/pattern.html:167 +#: crochet/templates/crochet/pattern.html:172 msgid "Expandir todo" msgstr "Expand all" -#: crochet/templates/crochet/pattern.html:172 +#: crochet/templates/crochet/pattern.html:177 msgid "Añadir sección" msgstr "Add section" -#: crochet/templates/crochet/pattern.html:175 +#: crochet/templates/crochet/pattern.html:180 msgid "Título" msgstr "Title" -#: crochet/templates/crochet/pattern.html:175 +#: crochet/templates/crochet/pattern.html:180 msgid "Título..." msgstr "Title..." -#: crochet/templates/crochet/pattern.html:177 +#: crochet/templates/crochet/pattern.html:182 msgid "Subtítulo" msgstr "Subtitle" -#: crochet/templates/crochet/pattern.html:177 +#: crochet/templates/crochet/pattern.html:182 msgid "Subtítulo..." msgstr "Subtitle..." -#: crochet/templates/crochet/pattern.html:179 +#: crochet/templates/crochet/pattern.html:184 msgid "Texto" msgstr "Text" -#: crochet/templates/crochet/pattern.html:179 +#: crochet/templates/crochet/pattern.html:184 msgid "Escribe aquí..." msgstr "Write here..." -#: crochet/templates/crochet/pattern.html:181 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:186 +#: crochet/templates/crochet/pattern.html:187 msgid "Nota" msgstr "Note" -#: crochet/templates/crochet/pattern.html:181 +#: crochet/templates/crochet/pattern.html:186 msgid "Escribe una nota o consejo..." msgstr "Write a note or tip..." -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Materiales" msgstr "Materials" -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Añadir material..." msgstr "Add material..." -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Añadir línea" msgstr "Add line" -#: crochet/templates/crochet/pattern.html:186 -#: crochet/templates/crochet/pattern.html:188 +#: crochet/templates/crochet/pattern.html:191 +#: crochet/templates/crochet/pattern.html:193 msgid "Imagen" msgstr "Image" -#: crochet/templates/crochet/pattern.html:188 +#: crochet/templates/crochet/pattern.html:193 msgid "No se ha podido subir la imagen. Inténtalo de nuevo." msgstr "Couldn't upload the image. Please try again." -#: crochet/templates/crochet/pattern.html:190 +#: crochet/templates/crochet/pattern.html:195 msgid "Patrón" msgstr "Pattern" -#: crochet/templates/crochet/pattern.html:190 +#: crochet/templates/crochet/pattern.html:195 msgid "Elementos" msgstr "Elements" -#: crochet/templates/crochet/pattern.html:192 +#: crochet/templates/crochet/pattern.html:197 msgid "Grupo" msgstr "Group" -#: crochet/templates/crochet/pattern.html:196 +#: crochet/templates/crochet/pattern.html:201 msgid "Arrastrar para reordenar" msgstr "Drag to reorder" -#: crochet/templates/crochet/pattern.html:197 +#: crochet/templates/crochet/pattern.html:202 msgid "Colapsar / expandir" msgstr "Collapse / expand" -#: crochet/templates/crochet/pattern.html:198 +#: crochet/templates/crochet/pattern.html:203 msgid "Duplicar sección" msgstr "Duplicate section" -#: crochet/templates/crochet/pattern.html:199 +#: crochet/templates/crochet/pattern.html:204 msgid "Eliminar sección" msgstr "Delete section" -#: crochet/templates/crochet/pattern.html:200 +#: crochet/templates/crochet/pattern.html:205 msgid "" "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las " "secciones que contiene." @@ -395,7 +487,7 @@ msgstr "" "Are you sure you want to delete this group? All sections inside it will also " "be deleted." -#: crochet/templates/crochet/pattern.html:206 +#: crochet/templates/crochet/pattern.html:210 msgid "Añade una sección para ver aquí el resultado." msgstr "Add a section to see the result here." @@ -412,6 +504,10 @@ msgid "Entrar" msgstr "Log in" #: crochet/templates/registration/login.html:32 +msgid "¿Has olvidado tu contraseña?" +msgstr "Forgot your password?" + +#: crochet/templates/registration/login.html:35 msgid "¿No tienes cuenta? Regístrate" msgstr "Don't have an account? Sign up" @@ -420,6 +516,10 @@ msgstr "Don't have an account? Sign up" msgid "Crear cuenta" msgstr "Create account" +#: crochet/templates/registration/register.html:29 +msgid "Registrarme" +msgstr "Sign up" + #: crochet/templates/registration/register.html:32 msgid "¿Ya tienes cuenta? Inicia sesión" msgstr "Already have an account? Log in" @@ -484,6 +584,22 @@ msgstr "" msgid "account/logout/" msgstr "" +#: crochet/urls.py:60 +msgid "account/password-reset/" +msgstr "" + +#: crochet/urls.py:75 +msgid "account/password-reset/done/" +msgstr "" + +#: crochet/urls.py:79 +msgid "account/reset///" +msgstr "" + +#: crochet/urls.py:87 +msgid "account/reset/done/" +msgstr "" + # crochet/views.py (PatternDetailView.EMPTY_MESSAGE) #: crochet/views.py:24 msgid "Este patrón todavía no tiene contenido." diff --git a/crochet/locale/es/LC_MESSAGES/django.po b/crochet/locale/es/LC_MESSAGES/django.po index 2e38df8..bd6c29b 100644 --- a/crochet/locale/es/LC_MESSAGES/django.po +++ b/crochet/locale/es/LC_MESSAGES/django.po @@ -2,13 +2,14 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-20 10:46+0000\n" +"POT-Creation-Date: 2026-07-21 10:20+0000\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: crochet/forms.py:35 crochet/templates/crochet/account_settings.html:22 +#: crochet/forms.py:30 crochet/forms.py:63 +#: crochet/templates/crochet/account_settings.html:22 msgid "Email" msgstr "" @@ -26,6 +27,7 @@ msgstr "" #: crochet/templates/crochet/_account_password_form.html:19 #: crochet/templates/crochet/account_settings.html:29 +#: crochet/templates/crochet/password_reset_confirm.html:30 msgid "Cambiar contraseña" msgstr "" @@ -57,13 +59,12 @@ msgid "Actualizado el %(date)s" msgstr "" #: crochet/templates/crochet/account_home.html:44 -#: crochet/templates/crochet/pattern.html:158 -#: crochet/templates/crochet/pattern.html:204 +#: crochet/templates/crochet/pattern.html:163 msgid "Vista previa" msgstr "" #: crochet/templates/crochet/account_home.html:48 -#: crochet/templates/crochet/pattern.html:50 +#: crochet/templates/crochet/pattern.html:55 msgid "Eliminar" msgstr "" @@ -91,16 +92,12 @@ msgstr "" #: crochet/templates/crochet/base.html:62 #: crochet/templates/crochet/home.html:24 +#: crochet/templates/crochet/password_reset_complete.html:18 #: crochet/templates/registration/login.html:4 #: crochet/templates/registration/login.html:14 msgid "Iniciar sesión" msgstr "" -#: crochet/templates/crochet/base.html:63 -#: crochet/templates/registration/register.html:29 -msgid "Registrarme" -msgstr "" - #: crochet/templates/crochet/home.html:4 msgid "Crochet — Crea y comparte tus patrones" msgstr "" @@ -153,238 +150,327 @@ msgid "" "podrá editarlo." msgstr "" -#: crochet/templates/crochet/pattern.html:33 +#: crochet/templates/crochet/password_reset_complete.html:4 +#: crochet/templates/crochet/password_reset_complete.html:14 +msgid "Contraseña actualizada" +msgstr "" + +#: crochet/templates/crochet/password_reset_complete.html:16 +msgid "Ya puedes iniciar sesión con tu nueva contraseña." +msgstr "" + +#: crochet/templates/crochet/password_reset_confirm.html:4 +msgid "Elegir nueva contraseña" +msgstr "" + +#: crochet/templates/crochet/password_reset_confirm.html:15 +msgid "Elige una contraseña nueva" +msgstr "" + +#: crochet/templates/crochet/password_reset_confirm.html:33 +msgid "Enlace no válido" +msgstr "" + +#: crochet/templates/crochet/password_reset_confirm.html:35 +msgid "" +"El enlace para restablecer la contraseña no es válido, puede que ya se haya " +"usado. Solicita uno nuevo." +msgstr "" + +#: crochet/templates/crochet/password_reset_confirm.html:38 +msgid "Solicitar un enlace nuevo" +msgstr "" + +#: crochet/templates/crochet/password_reset_done.html:4 +#: crochet/templates/crochet/password_reset_form.html:4 +#: crochet/templates/crochet/password_reset_form.html:14 +msgid "Recuperar contraseña" +msgstr "" + +#: crochet/templates/crochet/password_reset_done.html:14 +msgid "Revisa tu email" +msgstr "" + +#: crochet/templates/crochet/password_reset_done.html:16 +msgid "" +"Si existe una cuenta con ese email, te hemos enviado un enlace para elegir " +"una contraseña nueva." +msgstr "" + +#: crochet/templates/crochet/password_reset_done.html:19 +#: crochet/templates/crochet/password_reset_form.html:32 +msgid "Volver a iniciar sesión" +msgstr "" + +#: crochet/templates/crochet/password_reset_email.html:2 +#, python-format +msgid "" +"Has recibido este email porque alguien ha solicitado restablecer la " +"contraseña de tu cuenta en %(site_name)s." +msgstr "" + +#: crochet/templates/crochet/password_reset_email.html:4 +msgid "Sigue este enlace para elegir una contraseña nueva:" +msgstr "" + +#: crochet/templates/crochet/password_reset_email.html:7 +msgid "Tu usuario, por si lo has olvidado:" +msgstr "" + +#: crochet/templates/crochet/password_reset_email.html:9 +msgid "Si no has solicitado este cambio, puedes ignorar este email." +msgstr "" + +#: crochet/templates/crochet/password_reset_form.html:16 +msgid "" +"Escribe tu email y te enviaremos un enlace para elegir una contraseña nueva." +msgstr "" + +#: crochet/templates/crochet/password_reset_form.html:29 +msgid "Enviar enlace" +msgstr "" + +#: crochet/templates/crochet/password_reset_subject.txt:1 +#, python-format +msgid "Recupera tu contraseña en %(site_name)s" +msgstr "" + +#: crochet/templates/crochet/pattern.html:26 +msgid "Idioma del patrón" +msgstr "" + +#: crochet/templates/crochet/pattern.html:38 msgid "No se ha podido guardar. Inténtalo de nuevo." msgstr "" -#: crochet/templates/crochet/pattern.html:33 +#: crochet/templates/crochet/pattern.html:38 msgid "Guardar" msgstr "" -#: crochet/templates/crochet/pattern.html:36 +#: crochet/templates/crochet/pattern.html:41 msgid "Ver patrón" msgstr "" -#: crochet/templates/crochet/pattern.html:37 +#: crochet/templates/crochet/pattern.html:42 msgid "Exportar a PDF" msgstr "" -#: crochet/templates/crochet/pattern.html:44 +#: crochet/templates/crochet/pattern.html:49 msgid "Más opciones" msgstr "" -#: crochet/templates/crochet/pattern.html:60 +#: crochet/templates/crochet/pattern.html:65 msgid "Guardado" msgstr "" -#: crochet/templates/crochet/pattern.html:67 +#: crochet/templates/crochet/pattern.html:72 msgid "Personalización de página" msgstr "" -#: crochet/templates/crochet/pattern.html:71 +#: crochet/templates/crochet/pattern.html:76 msgid "Título del patrón" msgstr "" -#: crochet/templates/crochet/pattern.html:72 +#: crochet/templates/crochet/pattern.html:77 msgid "Título del patrón..." msgstr "" -#: crochet/templates/crochet/pattern.html:75 +#: crochet/templates/crochet/pattern.html:80 msgid "Autor" msgstr "" -#: crochet/templates/crochet/pattern.html:76 +#: crochet/templates/crochet/pattern.html:81 msgid "Autor..." msgstr "" -#: crochet/templates/crochet/pattern.html:81 +#: crochet/templates/crochet/pattern.html:86 msgid "Imagen de portada" msgstr "" -#: crochet/templates/crochet/pattern.html:84 +#: crochet/templates/crochet/pattern.html:89 msgid "No se ha podido subir la imagen de portada. Inténtalo de nuevo." msgstr "" -#: crochet/templates/crochet/pattern.html:98 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:103 +#: crochet/templates/crochet/pattern.html:187 msgid "Color del texto" msgstr "" -#: crochet/templates/crochet/pattern.html:102 +#: crochet/templates/crochet/pattern.html:107 msgid "Color de acento (títulos)" msgstr "" -#: crochet/templates/crochet/pattern.html:106 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:111 +#: crochet/templates/crochet/pattern.html:187 msgid "Color de fondo" msgstr "" -#: crochet/templates/crochet/pattern.html:110 +#: crochet/templates/crochet/pattern.html:115 msgid "Tipografía" msgstr "" -#: crochet/templates/crochet/pattern.html:116 +#: crochet/templates/crochet/pattern.html:121 msgid "Monoespaciada" msgstr "" -#: crochet/templates/crochet/pattern.html:120 +#: crochet/templates/crochet/pattern.html:125 msgid "Tamaño de letra" msgstr "" -#: crochet/templates/crochet/pattern.html:122 +#: crochet/templates/crochet/pattern.html:127 msgid "Pequeño" msgstr "" -#: crochet/templates/crochet/pattern.html:123 +#: crochet/templates/crochet/pattern.html:128 msgid "Normal" msgstr "" -#: crochet/templates/crochet/pattern.html:124 +#: crochet/templates/crochet/pattern.html:129 msgid "Grande" msgstr "" -#: crochet/templates/crochet/pattern.html:128 +#: crochet/templates/crochet/pattern.html:133 msgid "Alineación del texto" msgstr "" -#: crochet/templates/crochet/pattern.html:130 +#: crochet/templates/crochet/pattern.html:135 msgid "Izquierda" msgstr "" -#: crochet/templates/crochet/pattern.html:131 +#: crochet/templates/crochet/pattern.html:136 msgid "Centrado" msgstr "" -#: crochet/templates/crochet/pattern.html:132 +#: crochet/templates/crochet/pattern.html:137 msgid "Justificado" msgstr "" -#: crochet/templates/crochet/pattern.html:136 +#: crochet/templates/crochet/pattern.html:141 msgid "Tamaño de página" msgstr "" -#: crochet/templates/crochet/pattern.html:141 +#: crochet/templates/crochet/pattern.html:146 msgid "Carta (Letter)" msgstr "" -#: crochet/templates/crochet/pattern.html:146 +#: crochet/templates/crochet/pattern.html:151 msgid "Orientación" msgstr "" -#: crochet/templates/crochet/pattern.html:148 +#: crochet/templates/crochet/pattern.html:153 msgid "Vertical" msgstr "" -#: crochet/templates/crochet/pattern.html:149 +#: crochet/templates/crochet/pattern.html:154 msgid "Horizontal" msgstr "" -#: crochet/templates/crochet/pattern.html:157 -#: crochet/templates/crochet/pattern.html:165 +#: crochet/templates/crochet/pattern.html:162 +#: crochet/templates/crochet/pattern.html:170 msgid "Secciones" msgstr "" -#: crochet/templates/crochet/pattern.html:167 +#: crochet/templates/crochet/pattern.html:172 msgid "Colapsar todo" msgstr "" -#: crochet/templates/crochet/pattern.html:167 +#: crochet/templates/crochet/pattern.html:172 msgid "Expandir todo" msgstr "" -#: crochet/templates/crochet/pattern.html:172 +#: crochet/templates/crochet/pattern.html:177 msgid "Añadir sección" msgstr "" -#: crochet/templates/crochet/pattern.html:175 +#: crochet/templates/crochet/pattern.html:180 msgid "Título" msgstr "" -#: crochet/templates/crochet/pattern.html:175 +#: crochet/templates/crochet/pattern.html:180 msgid "Título..." msgstr "" -#: crochet/templates/crochet/pattern.html:177 +#: crochet/templates/crochet/pattern.html:182 msgid "Subtítulo" msgstr "" -#: crochet/templates/crochet/pattern.html:177 +#: crochet/templates/crochet/pattern.html:182 msgid "Subtítulo..." msgstr "" -#: crochet/templates/crochet/pattern.html:179 +#: crochet/templates/crochet/pattern.html:184 msgid "Texto" msgstr "" -#: crochet/templates/crochet/pattern.html:179 +#: crochet/templates/crochet/pattern.html:184 msgid "Escribe aquí..." msgstr "" -#: crochet/templates/crochet/pattern.html:181 -#: crochet/templates/crochet/pattern.html:182 +#: crochet/templates/crochet/pattern.html:186 +#: crochet/templates/crochet/pattern.html:187 msgid "Nota" msgstr "" -#: crochet/templates/crochet/pattern.html:181 +#: crochet/templates/crochet/pattern.html:186 msgid "Escribe una nota o consejo..." msgstr "" -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Materiales" msgstr "" -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Añadir material..." msgstr "" -#: crochet/templates/crochet/pattern.html:184 +#: crochet/templates/crochet/pattern.html:189 msgid "Añadir línea" msgstr "" -#: crochet/templates/crochet/pattern.html:186 -#: crochet/templates/crochet/pattern.html:188 +#: crochet/templates/crochet/pattern.html:191 +#: crochet/templates/crochet/pattern.html:193 msgid "Imagen" msgstr "" -#: crochet/templates/crochet/pattern.html:188 +#: crochet/templates/crochet/pattern.html:193 msgid "No se ha podido subir la imagen. Inténtalo de nuevo." msgstr "" -#: crochet/templates/crochet/pattern.html:190 +#: crochet/templates/crochet/pattern.html:195 msgid "Patrón" msgstr "" -#: crochet/templates/crochet/pattern.html:190 +#: crochet/templates/crochet/pattern.html:195 msgid "Elementos" msgstr "" -#: crochet/templates/crochet/pattern.html:192 +#: crochet/templates/crochet/pattern.html:197 msgid "Grupo" msgstr "" -#: crochet/templates/crochet/pattern.html:196 +#: crochet/templates/crochet/pattern.html:201 msgid "Arrastrar para reordenar" msgstr "" -#: crochet/templates/crochet/pattern.html:197 +#: crochet/templates/crochet/pattern.html:202 msgid "Colapsar / expandir" msgstr "" -#: crochet/templates/crochet/pattern.html:198 +#: crochet/templates/crochet/pattern.html:203 msgid "Duplicar sección" msgstr "" -#: crochet/templates/crochet/pattern.html:199 +#: crochet/templates/crochet/pattern.html:204 msgid "Eliminar sección" msgstr "" -#: crochet/templates/crochet/pattern.html:200 +#: crochet/templates/crochet/pattern.html:205 msgid "" "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las " "secciones que contiene." msgstr "" -#: crochet/templates/crochet/pattern.html:206 +#: crochet/templates/crochet/pattern.html:210 msgid "Añade una sección para ver aquí el resultado." msgstr "" @@ -401,6 +487,10 @@ msgid "Entrar" msgstr "" #: crochet/templates/registration/login.html:32 +msgid "¿Has olvidado tu contraseña?" +msgstr "" + +#: crochet/templates/registration/login.html:35 msgid "¿No tienes cuenta? Regístrate" msgstr "" @@ -409,6 +499,10 @@ msgstr "" msgid "Crear cuenta" msgstr "" +#: crochet/templates/registration/register.html:29 +msgid "Registrarme" +msgstr "" + #: crochet/templates/registration/register.html:32 msgid "¿Ya tienes cuenta? Inicia sesión" msgstr "" @@ -476,6 +570,22 @@ msgstr "cuenta/entrar/" msgid "account/logout/" msgstr "cuenta/salir/" +#: crochet/urls.py:60 +msgid "account/password-reset/" +msgstr "cuenta/recuperar-contrasena/" + +#: crochet/urls.py:75 +msgid "account/password-reset/done/" +msgstr "cuenta/recuperar-contrasena/enviado/" + +#: crochet/urls.py:79 +msgid "account/reset///" +msgstr "cuenta/restablecer///" + +#: crochet/urls.py:87 +msgid "account/reset/done/" +msgstr "cuenta/restablecer/hecho/" + #: crochet/views.py:24 msgid "Este patrón todavía no tiene contenido." msgstr "" diff --git a/crochet/templates/crochet/password_reset_complete.html b/crochet/templates/crochet/password_reset_complete.html new file mode 100644 index 0000000..4a19302 --- /dev/null +++ b/crochet/templates/crochet/password_reset_complete.html @@ -0,0 +1,22 @@ +{% extends 'crochet/base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Contraseña actualizada' %}{% endblock %} + +{% block extra_css %} + {% include 'crochet/_platform_assets.html' %} +{% endblock %} + +{% block content %} +
+
+
+

{% trans 'Contraseña actualizada' %}

+

+ {% trans 'Ya puedes iniciar sesión con tu nueva contraseña.' %} +

+ {% trans 'Iniciar sesión' %} +
+
+
+{% endblock %} diff --git a/crochet/templates/crochet/password_reset_confirm.html b/crochet/templates/crochet/password_reset_confirm.html new file mode 100644 index 0000000..86555a0 --- /dev/null +++ b/crochet/templates/crochet/password_reset_confirm.html @@ -0,0 +1,44 @@ +{% extends 'crochet/base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Elegir nueva contraseña' %}{% endblock %} + +{% block extra_css %} + {% include 'crochet/_platform_assets.html' %} +{% endblock %} + +{% block content %} +
+
+
+ {% if validlink %} +

{% trans 'Elige una contraseña nueva' %}

+
+ {% csrf_token %} + {% for field in form %} + + {% endfor %} + +
+ {% else %} +

{% trans 'Enlace no válido' %}

+

+ {% trans 'El enlace para restablecer la contraseña no es válido, puede que ya se haya usado. Solicita uno nuevo.' %} +

+

+ {% trans 'Solicitar un enlace nuevo' %} +

+ {% endif %} +
+
+
+{% endblock %} diff --git a/crochet/templates/crochet/password_reset_done.html b/crochet/templates/crochet/password_reset_done.html new file mode 100644 index 0000000..f9097cf --- /dev/null +++ b/crochet/templates/crochet/password_reset_done.html @@ -0,0 +1,24 @@ +{% extends 'crochet/base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Recuperar contraseña' %}{% endblock %} + +{% block extra_css %} + {% include 'crochet/_platform_assets.html' %} +{% endblock %} + +{% block content %} +
+
+
+

{% trans 'Revisa tu email' %}

+

+ {% trans 'Si existe una cuenta con ese email, te hemos enviado un enlace para elegir una contraseña nueva.' %} +

+

+ {% trans 'Volver a iniciar sesión' %} +

+
+
+
+{% endblock %} diff --git a/crochet/templates/crochet/password_reset_email.html b/crochet/templates/crochet/password_reset_email.html new file mode 100644 index 0000000..d83fba5 --- /dev/null +++ b/crochet/templates/crochet/password_reset_email.html @@ -0,0 +1,10 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Has recibido este email porque alguien ha solicitado restablecer la contraseña de tu cuenta en {{ site_name }}.{% endblocktrans %} + +{% trans 'Sigue este enlace para elegir una contraseña nueva:' %} +{{ protocol }}://{{ domain }}{% url 'crochet:password_reset_confirm' uidb64=uid token=token %} + +{% trans 'Tu usuario, por si lo has olvidado:' %} {{ user.get_username }} + +{% trans 'Si no has solicitado este cambio, puedes ignorar este email.' %} +{% endautoescape %} diff --git a/crochet/templates/crochet/password_reset_form.html b/crochet/templates/crochet/password_reset_form.html new file mode 100644 index 0000000..8ba4aac --- /dev/null +++ b/crochet/templates/crochet/password_reset_form.html @@ -0,0 +1,37 @@ +{% extends 'crochet/base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Recuperar contraseña' %}{% endblock %} + +{% block extra_css %} + {% include 'crochet/_platform_assets.html' %} +{% endblock %} + +{% block content %} +
+
+
+

{% trans 'Recuperar contraseña' %}

+

+ {% trans 'Escribe tu email y te enviaremos un enlace para elegir una contraseña nueva.' %} +

+
+ {% csrf_token %} + {% for field in form %} + + {% endfor %} + +
+

+ {% trans 'Volver a iniciar sesión' %} +

+
+
+
+{% endblock %} diff --git a/crochet/templates/crochet/password_reset_subject.txt b/crochet/templates/crochet/password_reset_subject.txt new file mode 100644 index 0000000..0e8c4f5 --- /dev/null +++ b/crochet/templates/crochet/password_reset_subject.txt @@ -0,0 +1 @@ +{% load i18n %}{% blocktrans %}Recupera tu contraseña en {{ site_name }}{% endblocktrans %} diff --git a/crochet/templates/registration/login.html b/crochet/templates/registration/login.html index d2f0d3e..822852e 100644 --- a/crochet/templates/registration/login.html +++ b/crochet/templates/registration/login.html @@ -29,6 +29,9 @@

+ {% trans '¿Has olvidado tu contraseña?' %} +

+

{% trans '¿No tienes cuenta? Regístrate' %}

diff --git a/crochet/tests/test_auth.py b/crochet/tests/test_auth.py index 9b73c38..ee64fce 100644 --- a/crochet/tests/test_auth.py +++ b/crochet/tests/test_auth.py @@ -20,17 +20,20 @@ class RegisterViewTests(TestCase): def test_post_creates_user_and_logs_in(self): response = self.client.post(self.url, { 'username': 'crocheter', + 'email': 'crocheter@example.com', 'password1': 'a-very-uncommon-pw-1', 'password2': 'a-very-uncommon-pw-1', }) - self.assertTrue(User.objects.filter(username='crocheter').exists()) + user = User.objects.get(username='crocheter') + self.assertEqual(user.email, 'crocheter@example.com') self.assertRedirects(response, reverse('crochet:account_home')) self.assertIn('_auth_user_id', self.client.session) def test_post_with_mismatched_passwords_does_not_create_user(self): response = self.client.post(self.url, { 'username': 'crocheter', + 'email': 'crocheter@example.com', 'password1': 'a-very-uncommon-pw-1', 'password2': 'something-else', }) @@ -38,6 +41,18 @@ class RegisterViewTests(TestCase): self.assertEqual(response.status_code, 200) self.assertFalse(User.objects.filter(username='crocheter').exists()) + def test_post_without_email_does_not_create_user(self): + # Sin email no habría a dónde mandar el enlace de recuperación de + # contraseña (ver PasswordResetForm/StyledUserCreationForm). + response = self.client.post(self.url, { + 'username': 'crocheter', + 'password1': 'a-very-uncommon-pw-1', + 'password2': 'a-very-uncommon-pw-1', + }) + + self.assertEqual(response.status_code, 200) + self.assertFalse(User.objects.filter(username='crocheter').exists()) + class LoginViewTests(TestCase): def setUp(self): diff --git a/crochet/tests/test_password_reset.py b/crochet/tests/test_password_reset.py new file mode 100644 index 0000000..c217741 --- /dev/null +++ b/crochet/tests/test_password_reset.py @@ -0,0 +1,134 @@ +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from django.core import mail +from django.test import TestCase +from django.urls import reverse +from django.utils import translation +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode + + +class PasswordResetRequestViewTests(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username='crocheter', password='the-old-password-1', email='crocheter@example.com', + ) + with translation.override('es'): + self.url = reverse('crochet:password_reset') + + def test_get_renders_the_form(self): + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'crochet/password_reset_form.html') + + def test_known_email_sends_a_reset_link(self): + response = self.client.post(self.url, {'email': 'crocheter@example.com'}) + + with translation.override('es'): + done_url = reverse('crochet:password_reset_done') + self.assertRedirects(response, done_url) + self.assertEqual(len(mail.outbox), 1) + self.assertIn('crocheter@example.com', mail.outbox[0].to) + self.assertIn('http://testserver', mail.outbox[0].body) + + def test_unknown_email_does_not_send_anything_but_still_redirects(self): + # Ni la vista ni la plantilla deben delatar si el email existe o no + # en el sistema (comportamiento por defecto de PasswordResetForm): + # misma redirección tanto si existe como si no, para no permitir + # enumerar cuentas registradas probando emails. + response = self.client.post(self.url, {'email': 'nobody@example.com'}) + + with translation.override('es'): + done_url = reverse('crochet:password_reset_done') + self.assertRedirects(response, done_url) + self.assertEqual(len(mail.outbox), 0) + + +class PasswordResetDoneViewTests(TestCase): + def test_get_renders_the_confirmation_message(self): + with translation.override('es'): + url = reverse('crochet:password_reset_done') + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'crochet/password_reset_done.html') + + +class PasswordResetConfirmViewTests(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username='crocheter', password='the-old-password-1', email='crocheter@example.com', + ) + uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) + token = default_token_generator.make_token(self.user) + with translation.override('es'): + self.confirm_url = reverse( + 'crochet:password_reset_confirm', kwargs={'uidb64': uidb64, 'token': token}, + ) + + def test_valid_link_allows_setting_a_new_password(self): + # PasswordResetConfirmView consume el token de la URL en el primer + # GET (redirige a una URL "set-password" en sesión) precisamente + # para que no quede reutilizable ni visible en el historial del + # navegador; hay que seguir esa redirección antes de poder enviar + # el formulario. + get_response = self.client.get(self.confirm_url, follow=True) + self.assertContains(get_response, 'id_new_password1') + + response = self.client.post(get_response.redirect_chain[-1][0], { + 'new_password1': 'the-new-password-2', + 'new_password2': 'the-new-password-2', + }) + + with translation.override('es'): + complete_url = reverse('crochet:password_reset_complete') + self.assertRedirects(response, complete_url) + self.user.refresh_from_db() + self.assertTrue(self.user.check_password('the-new-password-2')) + + def test_mismatched_passwords_do_not_change_the_password(self): + get_response = self.client.get(self.confirm_url, follow=True) + + response = self.client.post(get_response.redirect_chain[-1][0], { + 'new_password1': 'the-new-password-2', + 'new_password2': 'something-else-3', + }) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'class="text-error text-xs"') + self.user.refresh_from_db() + self.assertTrue(self.user.check_password('the-old-password-1')) + + def test_invalid_token_shows_the_invalid_link_message(self): + uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) + with translation.override('es'): + bogus_url = reverse( + 'crochet:password_reset_confirm', kwargs={'uidb64': uidb64, 'token': 'not-a-real-token'}, + ) + + response = self.client.get(bogus_url, follow=True) + + self.assertContains(response, 'Enlace no válido') + self.assertNotContains(response, 'id_new_password1') + + +class PasswordResetCompleteViewTests(TestCase): + def test_get_renders_the_success_message(self): + with translation.override('es'): + url = reverse('crochet:password_reset_complete') + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'crochet/password_reset_complete.html') + + +class LoginPageForgotPasswordLinkTests(TestCase): + def test_login_page_links_to_password_reset(self): + with translation.override('es'): + login_url = reverse('crochet:login') + reset_url = reverse('crochet:password_reset') + + response = self.client.get(login_url) + + self.assertContains(response, f'href="{reset_url}"') diff --git a/crochet/urls.py b/crochet/urls.py index e4834e2..9c6c851 100644 --- a/crochet/urls.py +++ b/crochet/urls.py @@ -1,8 +1,8 @@ from django.contrib.auth import views as auth_views -from django.urls import path +from django.urls import path, reverse_lazy from django.utils.translation import gettext_lazy as _ -from crochet.forms import StyledAuthenticationForm +from crochet.forms import StyledAuthenticationForm, StyledPasswordResetForm, StyledSetPasswordForm from crochet.views import ( AccountEmailUpdateView, AccountHomeView, @@ -56,4 +56,35 @@ urlpatterns = [ ), ), path(_('account/logout/'), name='logout', view=auth_views.LogoutView.as_view()), + path( + _('account/password-reset/'), name='password_reset', + view=auth_views.PasswordResetView.as_view( + # No en registration/ (a diferencia de login.html/register.html): + # django.contrib.admin trae sus propias plantillas con estos + # mismos nombres bajo registration/ y, al ir antes que crochet + # en INSTALLED_APPS, el buscador de plantillas por APP_DIRS + # encontraría siempre las suyas primero, tapando las nuestras. + template_name='crochet/password_reset_form.html', + email_template_name='crochet/password_reset_email.html', + subject_template_name='crochet/password_reset_subject.txt', + form_class=StyledPasswordResetForm, + success_url=reverse_lazy('crochet:password_reset_done'), + ), + ), + path( + _('account/password-reset/done/'), name='password_reset_done', + view=auth_views.PasswordResetDoneView.as_view(template_name='crochet/password_reset_done.html'), + ), + path( + _('account/reset///'), name='password_reset_confirm', + view=auth_views.PasswordResetConfirmView.as_view( + template_name='crochet/password_reset_confirm.html', + form_class=StyledSetPasswordForm, + success_url=reverse_lazy('crochet:password_reset_complete'), + ), + ), + path( + _('account/reset/done/'), name='password_reset_complete', + view=auth_views.PasswordResetCompleteView.as_view(template_name='crochet/password_reset_complete.html'), + ), ]