feat: added recover password view
CI / test (push) Successful in 1m3s
CI / build (push) Successful in 53s

This commit is contained in:
2026-07-21 12:47:01 +02:00
parent 2f58e23cd2
commit 3e95ee2239
14 changed files with 724 additions and 148 deletions
+1
View File
@@ -43,6 +43,7 @@ EMAIL_HOST = env.str('EMAIL_HOST', '')
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER', '') EMAIL_HOST_USER = env.str('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD', '') EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD', '')
EMAIL_PORT = env.int('EMAIL_PORT', 587) 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 # 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 # (DEBUG=False) Django, por defecto, solo manda los errores por email a
+29 -1
View File
@@ -1,5 +1,11 @@
from django import forms 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 _ from django.utils.translation import gettext_lazy as _
# Los formularios de auth de Django no traen ninguna clase CSS en sus # Los formularios de auth de Django no traen ninguna clase CSS en sus
@@ -18,6 +24,14 @@ class StyledAuthenticationForm(AuthenticationForm):
class StyledUserCreationForm(UserCreationForm): 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for field in self.fields.values(): for field in self.fields.values():
@@ -31,6 +45,20 @@ class StyledPasswordChangeForm(PasswordChangeForm):
field.widget.attrs['class'] = INPUT_CLASSES 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): class EmailUpdateForm(forms.Form):
email = forms.EmailField(label=_('Email'), widget=forms.EmailInput(attrs={'class': INPUT_CLASSES})) email = forms.EmailField(label=_('Email'), widget=forms.EmailInput(attrs={'class': INPUT_CLASSES}))
+188 -72
View File
@@ -2,13 +2,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: crochet\n" "Project-Id-Version: crochet\n"
"Report-Msgid-Bugs-To: \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" "Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -26,6 +27,7 @@ msgstr "Password updated."
#: crochet/templates/crochet/_account_password_form.html:19 #: crochet/templates/crochet/_account_password_form.html:19
#: crochet/templates/crochet/account_settings.html:29 #: crochet/templates/crochet/account_settings.html:29
#: crochet/templates/crochet/password_reset_confirm.html:30
msgid "Cambiar contraseña" msgid "Cambiar contraseña"
msgstr "Change password" msgstr "Change password"
@@ -57,13 +59,12 @@ msgid "Actualizado el %(date)s"
msgstr "Updated on %(date)s" msgstr "Updated on %(date)s"
#: crochet/templates/crochet/account_home.html:44 #: crochet/templates/crochet/account_home.html:44
#: crochet/templates/crochet/pattern.html:158 #: crochet/templates/crochet/pattern.html:163
#: crochet/templates/crochet/pattern.html:204
msgid "Vista previa" msgid "Vista previa"
msgstr "Preview" msgstr "Preview"
#: crochet/templates/crochet/account_home.html:48 #: crochet/templates/crochet/account_home.html:48
#: crochet/templates/crochet/pattern.html:50 #: crochet/templates/crochet/pattern.html:55
msgid "Eliminar" msgid "Eliminar"
msgstr "Delete" msgstr "Delete"
@@ -91,16 +92,12 @@ msgstr "Log out"
#: crochet/templates/crochet/base.html:62 #: crochet/templates/crochet/base.html:62
#: crochet/templates/crochet/home.html:24 #: 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:4
#: crochet/templates/registration/login.html:14 #: crochet/templates/registration/login.html:14
msgid "Iniciar sesión" msgid "Iniciar sesión"
msgstr "Log in" 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 #: crochet/templates/crochet/home.html:4
msgid "Crochet — Crea y comparte tus patrones" msgid "Crochet — Crea y comparte tus patrones"
msgstr "Crochet — Create and share your patterns" 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 " "Send your pattern to anyone with a read-only link: no one else will be able "
"to edit it." "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
#: crochet/templates/crochet/pattern.html:33 #: crochet/templates/crochet/pattern.html:38
msgid "No se ha podido guardar. Inténtalo de nuevo." msgid "No se ha podido guardar. Inténtalo de nuevo."
msgstr "Couldn't save. Please try again." msgstr "Couldn't save. Please try again."
#: crochet/templates/crochet/pattern.html:33 #: crochet/templates/crochet/pattern.html:38
msgid "Guardar" msgid "Guardar"
msgstr "Save" msgstr "Save"
#: crochet/templates/crochet/pattern.html:36 #: crochet/templates/crochet/pattern.html:41
msgid "Ver patrón" msgid "Ver patrón"
msgstr "View pattern" msgstr "View pattern"
#: crochet/templates/crochet/pattern.html:37 #: crochet/templates/crochet/pattern.html:42
msgid "Exportar a PDF" msgid "Exportar a PDF"
msgstr "Export to PDF" msgstr "Export to PDF"
#: crochet/templates/crochet/pattern.html:44 #: crochet/templates/crochet/pattern.html:49
msgid "Más opciones" msgid "Más opciones"
msgstr "More options" msgstr "More options"
#: crochet/templates/crochet/pattern.html:60 #: crochet/templates/crochet/pattern.html:65
msgid "Guardado" msgid "Guardado"
msgstr "Saved" msgstr "Saved"
#: crochet/templates/crochet/pattern.html:67 #: crochet/templates/crochet/pattern.html:72
msgid "Personalización de página" msgid "Personalización de página"
msgstr "Page customization" msgstr "Page customization"
#: crochet/templates/crochet/pattern.html:71 #: crochet/templates/crochet/pattern.html:76
msgid "Título del patrón" msgid "Título del patrón"
msgstr "Pattern title" msgstr "Pattern title"
#: crochet/templates/crochet/pattern.html:72 #: crochet/templates/crochet/pattern.html:77
msgid "Título del patrón..." msgid "Título del patrón..."
msgstr "Pattern title..." msgstr "Pattern title..."
#: crochet/templates/crochet/pattern.html:75 #: crochet/templates/crochet/pattern.html:80
msgid "Autor" msgid "Autor"
msgstr "Author" msgstr "Author"
#: crochet/templates/crochet/pattern.html:76 #: crochet/templates/crochet/pattern.html:81
msgid "Autor..." msgid "Autor..."
msgstr "Author..." msgstr "Author..."
#: crochet/templates/crochet/pattern.html:81 #: crochet/templates/crochet/pattern.html:86
msgid "Imagen de portada" msgid "Imagen de portada"
msgstr "Cover image" 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." msgid "No se ha podido subir la imagen de portada. Inténtalo de nuevo."
msgstr "Couldn't upload the cover image. Please try again." msgstr "Couldn't upload the cover image. Please try again."
#: crochet/templates/crochet/pattern.html:98 #: crochet/templates/crochet/pattern.html:103
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Color del texto" msgid "Color del texto"
msgstr "Text color" msgstr "Text color"
#: crochet/templates/crochet/pattern.html:102 #: crochet/templates/crochet/pattern.html:107
msgid "Color de acento (títulos)" msgid "Color de acento (títulos)"
msgstr "Accent color (headings)" msgstr "Accent color (headings)"
#: crochet/templates/crochet/pattern.html:106 #: crochet/templates/crochet/pattern.html:111
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Color de fondo" msgid "Color de fondo"
msgstr "Background color" msgstr "Background color"
#: crochet/templates/crochet/pattern.html:110 #: crochet/templates/crochet/pattern.html:115
msgid "Tipografía" msgid "Tipografía"
msgstr "Font" msgstr "Font"
#: crochet/templates/crochet/pattern.html:116 #: crochet/templates/crochet/pattern.html:121
msgid "Monoespaciada" msgid "Monoespaciada"
msgstr "Monospace" msgstr "Monospace"
#: crochet/templates/crochet/pattern.html:120 #: crochet/templates/crochet/pattern.html:125
msgid "Tamaño de letra" msgid "Tamaño de letra"
msgstr "Font size" msgstr "Font size"
#: crochet/templates/crochet/pattern.html:122 #: crochet/templates/crochet/pattern.html:127
msgid "Pequeño" msgid "Pequeño"
msgstr "Small" msgstr "Small"
#: crochet/templates/crochet/pattern.html:123 #: crochet/templates/crochet/pattern.html:128
msgid "Normal" msgid "Normal"
msgstr "Normal" msgstr "Normal"
#: crochet/templates/crochet/pattern.html:124 #: crochet/templates/crochet/pattern.html:129
msgid "Grande" msgid "Grande"
msgstr "Large" msgstr "Large"
#: crochet/templates/crochet/pattern.html:128 #: crochet/templates/crochet/pattern.html:133
msgid "Alineación del texto" msgid "Alineación del texto"
msgstr "Text alignment" msgstr "Text alignment"
#: crochet/templates/crochet/pattern.html:130 #: crochet/templates/crochet/pattern.html:135
msgid "Izquierda" msgid "Izquierda"
msgstr "Left" msgstr "Left"
#: crochet/templates/crochet/pattern.html:131 #: crochet/templates/crochet/pattern.html:136
msgid "Centrado" msgid "Centrado"
msgstr "Center" msgstr "Center"
#: crochet/templates/crochet/pattern.html:132 #: crochet/templates/crochet/pattern.html:137
msgid "Justificado" msgid "Justificado"
msgstr "Justify" msgstr "Justify"
#: crochet/templates/crochet/pattern.html:136 #: crochet/templates/crochet/pattern.html:141
msgid "Tamaño de página" msgid "Tamaño de página"
msgstr "Page size" msgstr "Page size"
#: crochet/templates/crochet/pattern.html:141 #: crochet/templates/crochet/pattern.html:146
msgid "Carta (Letter)" msgid "Carta (Letter)"
msgstr "Letter" msgstr "Letter"
#: crochet/templates/crochet/pattern.html:146 #: crochet/templates/crochet/pattern.html:151
msgid "Orientación" msgid "Orientación"
msgstr "Orientation" msgstr "Orientation"
#: crochet/templates/crochet/pattern.html:148 #: crochet/templates/crochet/pattern.html:153
msgid "Vertical" msgid "Vertical"
msgstr "Portrait" msgstr "Portrait"
#: crochet/templates/crochet/pattern.html:149 #: crochet/templates/crochet/pattern.html:154
msgid "Horizontal" msgid "Horizontal"
msgstr "Landscape" msgstr "Landscape"
#: crochet/templates/crochet/pattern.html:157 #: crochet/templates/crochet/pattern.html:162
#: crochet/templates/crochet/pattern.html:165 #: crochet/templates/crochet/pattern.html:170
msgid "Secciones" msgid "Secciones"
msgstr "Sections" msgstr "Sections"
#: crochet/templates/crochet/pattern.html:167 #: crochet/templates/crochet/pattern.html:172
msgid "Colapsar todo" msgid "Colapsar todo"
msgstr "Collapse all" msgstr "Collapse all"
#: crochet/templates/crochet/pattern.html:167 #: crochet/templates/crochet/pattern.html:172
msgid "Expandir todo" msgid "Expandir todo"
msgstr "Expand all" msgstr "Expand all"
#: crochet/templates/crochet/pattern.html:172 #: crochet/templates/crochet/pattern.html:177
msgid "Añadir sección" msgid "Añadir sección"
msgstr "Add section" msgstr "Add section"
#: crochet/templates/crochet/pattern.html:175 #: crochet/templates/crochet/pattern.html:180
msgid "Título" msgid "Título"
msgstr "Title" msgstr "Title"
#: crochet/templates/crochet/pattern.html:175 #: crochet/templates/crochet/pattern.html:180
msgid "Título..." msgid "Título..."
msgstr "Title..." msgstr "Title..."
#: crochet/templates/crochet/pattern.html:177 #: crochet/templates/crochet/pattern.html:182
msgid "Subtítulo" msgid "Subtítulo"
msgstr "Subtitle" msgstr "Subtitle"
#: crochet/templates/crochet/pattern.html:177 #: crochet/templates/crochet/pattern.html:182
msgid "Subtítulo..." msgid "Subtítulo..."
msgstr "Subtitle..." msgstr "Subtitle..."
#: crochet/templates/crochet/pattern.html:179 #: crochet/templates/crochet/pattern.html:184
msgid "Texto" msgid "Texto"
msgstr "Text" msgstr "Text"
#: crochet/templates/crochet/pattern.html:179 #: crochet/templates/crochet/pattern.html:184
msgid "Escribe aquí..." msgid "Escribe aquí..."
msgstr "Write here..." msgstr "Write here..."
#: crochet/templates/crochet/pattern.html:181 #: crochet/templates/crochet/pattern.html:186
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Nota" msgid "Nota"
msgstr "Note" msgstr "Note"
#: crochet/templates/crochet/pattern.html:181 #: crochet/templates/crochet/pattern.html:186
msgid "Escribe una nota o consejo..." msgid "Escribe una nota o consejo..."
msgstr "Write a note or tip..." msgstr "Write a note or tip..."
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Materiales" msgid "Materiales"
msgstr "Materials" msgstr "Materials"
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Añadir material..." msgid "Añadir material..."
msgstr "Add material..." msgstr "Add material..."
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Añadir línea" msgid "Añadir línea"
msgstr "Add line" msgstr "Add line"
#: crochet/templates/crochet/pattern.html:186 #: crochet/templates/crochet/pattern.html:191
#: crochet/templates/crochet/pattern.html:188 #: crochet/templates/crochet/pattern.html:193
msgid "Imagen" msgid "Imagen"
msgstr "Image" 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." msgid "No se ha podido subir la imagen. Inténtalo de nuevo."
msgstr "Couldn't upload the image. Please try again." msgstr "Couldn't upload the image. Please try again."
#: crochet/templates/crochet/pattern.html:190 #: crochet/templates/crochet/pattern.html:195
msgid "Patrón" msgid "Patrón"
msgstr "Pattern" msgstr "Pattern"
#: crochet/templates/crochet/pattern.html:190 #: crochet/templates/crochet/pattern.html:195
msgid "Elementos" msgid "Elementos"
msgstr "Elements" msgstr "Elements"
#: crochet/templates/crochet/pattern.html:192 #: crochet/templates/crochet/pattern.html:197
msgid "Grupo" msgid "Grupo"
msgstr "Group" msgstr "Group"
#: crochet/templates/crochet/pattern.html:196 #: crochet/templates/crochet/pattern.html:201
msgid "Arrastrar para reordenar" msgid "Arrastrar para reordenar"
msgstr "Drag to reorder" msgstr "Drag to reorder"
#: crochet/templates/crochet/pattern.html:197 #: crochet/templates/crochet/pattern.html:202
msgid "Colapsar / expandir" msgid "Colapsar / expandir"
msgstr "Collapse / expand" msgstr "Collapse / expand"
#: crochet/templates/crochet/pattern.html:198 #: crochet/templates/crochet/pattern.html:203
msgid "Duplicar sección" msgid "Duplicar sección"
msgstr "Duplicate section" msgstr "Duplicate section"
#: crochet/templates/crochet/pattern.html:199 #: crochet/templates/crochet/pattern.html:204
msgid "Eliminar sección" msgid "Eliminar sección"
msgstr "Delete section" msgstr "Delete section"
#: crochet/templates/crochet/pattern.html:200 #: crochet/templates/crochet/pattern.html:205
msgid "" msgid ""
"¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las " "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las "
"secciones que contiene." "secciones que contiene."
@@ -395,7 +487,7 @@ msgstr ""
"Are you sure you want to delete this group? All sections inside it will also " "Are you sure you want to delete this group? All sections inside it will also "
"be deleted." "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." msgid "Añade una sección para ver aquí el resultado."
msgstr "Add a section to see the result here." msgstr "Add a section to see the result here."
@@ -412,6 +504,10 @@ msgid "Entrar"
msgstr "Log in" msgstr "Log in"
#: crochet/templates/registration/login.html:32 #: 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" msgid "¿No tienes cuenta? Regístrate"
msgstr "Don't have an account? Sign up" msgstr "Don't have an account? Sign up"
@@ -420,6 +516,10 @@ msgstr "Don't have an account? Sign up"
msgid "Crear cuenta" msgid "Crear cuenta"
msgstr "Create account" msgstr "Create account"
#: crochet/templates/registration/register.html:29
msgid "Registrarme"
msgstr "Sign up"
#: crochet/templates/registration/register.html:32 #: crochet/templates/registration/register.html:32
msgid "¿Ya tienes cuenta? Inicia sesión" msgid "¿Ya tienes cuenta? Inicia sesión"
msgstr "Already have an account? Log in" msgstr "Already have an account? Log in"
@@ -484,6 +584,22 @@ msgstr ""
msgid "account/logout/" msgid "account/logout/"
msgstr "" 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/<uidb64>/<token>/"
msgstr ""
#: crochet/urls.py:87
msgid "account/reset/done/"
msgstr ""
# crochet/views.py (PatternDetailView.EMPTY_MESSAGE) # crochet/views.py (PatternDetailView.EMPTY_MESSAGE)
#: crochet/views.py:24 #: crochet/views.py:24
msgid "Este patrón todavía no tiene contenido." msgid "Este patrón todavía no tiene contenido."
+182 -72
View File
@@ -2,13 +2,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: crochet\n" "Project-Id-Version: crochet\n"
"Report-Msgid-Bugs-To: \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" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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" msgid "Email"
msgstr "" msgstr ""
@@ -26,6 +27,7 @@ msgstr ""
#: crochet/templates/crochet/_account_password_form.html:19 #: crochet/templates/crochet/_account_password_form.html:19
#: crochet/templates/crochet/account_settings.html:29 #: crochet/templates/crochet/account_settings.html:29
#: crochet/templates/crochet/password_reset_confirm.html:30
msgid "Cambiar contraseña" msgid "Cambiar contraseña"
msgstr "" msgstr ""
@@ -57,13 +59,12 @@ msgid "Actualizado el %(date)s"
msgstr "" msgstr ""
#: crochet/templates/crochet/account_home.html:44 #: crochet/templates/crochet/account_home.html:44
#: crochet/templates/crochet/pattern.html:158 #: crochet/templates/crochet/pattern.html:163
#: crochet/templates/crochet/pattern.html:204
msgid "Vista previa" msgid "Vista previa"
msgstr "" msgstr ""
#: crochet/templates/crochet/account_home.html:48 #: crochet/templates/crochet/account_home.html:48
#: crochet/templates/crochet/pattern.html:50 #: crochet/templates/crochet/pattern.html:55
msgid "Eliminar" msgid "Eliminar"
msgstr "" msgstr ""
@@ -91,16 +92,12 @@ msgstr ""
#: crochet/templates/crochet/base.html:62 #: crochet/templates/crochet/base.html:62
#: crochet/templates/crochet/home.html:24 #: 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:4
#: crochet/templates/registration/login.html:14 #: crochet/templates/registration/login.html:14
msgid "Iniciar sesión" msgid "Iniciar sesión"
msgstr "" msgstr ""
#: crochet/templates/crochet/base.html:63
#: crochet/templates/registration/register.html:29
msgid "Registrarme"
msgstr ""
#: crochet/templates/crochet/home.html:4 #: crochet/templates/crochet/home.html:4
msgid "Crochet — Crea y comparte tus patrones" msgid "Crochet — Crea y comparte tus patrones"
msgstr "" msgstr ""
@@ -153,238 +150,327 @@ msgid ""
"podrá editarlo." "podrá editarlo."
msgstr "" 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." msgid "No se ha podido guardar. Inténtalo de nuevo."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:33 #: crochet/templates/crochet/pattern.html:38
msgid "Guardar" msgid "Guardar"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:36 #: crochet/templates/crochet/pattern.html:41
msgid "Ver patrón" msgid "Ver patrón"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:37 #: crochet/templates/crochet/pattern.html:42
msgid "Exportar a PDF" msgid "Exportar a PDF"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:44 #: crochet/templates/crochet/pattern.html:49
msgid "Más opciones" msgid "Más opciones"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:60 #: crochet/templates/crochet/pattern.html:65
msgid "Guardado" msgid "Guardado"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:67 #: crochet/templates/crochet/pattern.html:72
msgid "Personalización de página" msgid "Personalización de página"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:71 #: crochet/templates/crochet/pattern.html:76
msgid "Título del patrón" msgid "Título del patrón"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:72 #: crochet/templates/crochet/pattern.html:77
msgid "Título del patrón..." msgid "Título del patrón..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:75 #: crochet/templates/crochet/pattern.html:80
msgid "Autor" msgid "Autor"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:76 #: crochet/templates/crochet/pattern.html:81
msgid "Autor..." msgid "Autor..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:81 #: crochet/templates/crochet/pattern.html:86
msgid "Imagen de portada" msgid "Imagen de portada"
msgstr "" 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." msgid "No se ha podido subir la imagen de portada. Inténtalo de nuevo."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:98 #: crochet/templates/crochet/pattern.html:103
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Color del texto" msgid "Color del texto"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:102 #: crochet/templates/crochet/pattern.html:107
msgid "Color de acento (títulos)" msgid "Color de acento (títulos)"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:106 #: crochet/templates/crochet/pattern.html:111
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Color de fondo" msgid "Color de fondo"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:110 #: crochet/templates/crochet/pattern.html:115
msgid "Tipografía" msgid "Tipografía"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:116 #: crochet/templates/crochet/pattern.html:121
msgid "Monoespaciada" msgid "Monoespaciada"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:120 #: crochet/templates/crochet/pattern.html:125
msgid "Tamaño de letra" msgid "Tamaño de letra"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:122 #: crochet/templates/crochet/pattern.html:127
msgid "Pequeño" msgid "Pequeño"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:123 #: crochet/templates/crochet/pattern.html:128
msgid "Normal" msgid "Normal"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:124 #: crochet/templates/crochet/pattern.html:129
msgid "Grande" msgid "Grande"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:128 #: crochet/templates/crochet/pattern.html:133
msgid "Alineación del texto" msgid "Alineación del texto"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:130 #: crochet/templates/crochet/pattern.html:135
msgid "Izquierda" msgid "Izquierda"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:131 #: crochet/templates/crochet/pattern.html:136
msgid "Centrado" msgid "Centrado"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:132 #: crochet/templates/crochet/pattern.html:137
msgid "Justificado" msgid "Justificado"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:136 #: crochet/templates/crochet/pattern.html:141
msgid "Tamaño de página" msgid "Tamaño de página"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:141 #: crochet/templates/crochet/pattern.html:146
msgid "Carta (Letter)" msgid "Carta (Letter)"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:146 #: crochet/templates/crochet/pattern.html:151
msgid "Orientación" msgid "Orientación"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:148 #: crochet/templates/crochet/pattern.html:153
msgid "Vertical" msgid "Vertical"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:149 #: crochet/templates/crochet/pattern.html:154
msgid "Horizontal" msgid "Horizontal"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:157 #: crochet/templates/crochet/pattern.html:162
#: crochet/templates/crochet/pattern.html:165 #: crochet/templates/crochet/pattern.html:170
msgid "Secciones" msgid "Secciones"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:167 #: crochet/templates/crochet/pattern.html:172
msgid "Colapsar todo" msgid "Colapsar todo"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:167 #: crochet/templates/crochet/pattern.html:172
msgid "Expandir todo" msgid "Expandir todo"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:172 #: crochet/templates/crochet/pattern.html:177
msgid "Añadir sección" msgid "Añadir sección"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:175 #: crochet/templates/crochet/pattern.html:180
msgid "Título" msgid "Título"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:175 #: crochet/templates/crochet/pattern.html:180
msgid "Título..." msgid "Título..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:177 #: crochet/templates/crochet/pattern.html:182
msgid "Subtítulo" msgid "Subtítulo"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:177 #: crochet/templates/crochet/pattern.html:182
msgid "Subtítulo..." msgid "Subtítulo..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:179 #: crochet/templates/crochet/pattern.html:184
msgid "Texto" msgid "Texto"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:179 #: crochet/templates/crochet/pattern.html:184
msgid "Escribe aquí..." msgid "Escribe aquí..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:181 #: crochet/templates/crochet/pattern.html:186
#: crochet/templates/crochet/pattern.html:182 #: crochet/templates/crochet/pattern.html:187
msgid "Nota" msgid "Nota"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:181 #: crochet/templates/crochet/pattern.html:186
msgid "Escribe una nota o consejo..." msgid "Escribe una nota o consejo..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Materiales" msgid "Materiales"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Añadir material..." msgid "Añadir material..."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:184 #: crochet/templates/crochet/pattern.html:189
msgid "Añadir línea" msgid "Añadir línea"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:186 #: crochet/templates/crochet/pattern.html:191
#: crochet/templates/crochet/pattern.html:188 #: crochet/templates/crochet/pattern.html:193
msgid "Imagen" msgid "Imagen"
msgstr "" 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." msgid "No se ha podido subir la imagen. Inténtalo de nuevo."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:190 #: crochet/templates/crochet/pattern.html:195
msgid "Patrón" msgid "Patrón"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:190 #: crochet/templates/crochet/pattern.html:195
msgid "Elementos" msgid "Elementos"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:192 #: crochet/templates/crochet/pattern.html:197
msgid "Grupo" msgid "Grupo"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:196 #: crochet/templates/crochet/pattern.html:201
msgid "Arrastrar para reordenar" msgid "Arrastrar para reordenar"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:197 #: crochet/templates/crochet/pattern.html:202
msgid "Colapsar / expandir" msgid "Colapsar / expandir"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:198 #: crochet/templates/crochet/pattern.html:203
msgid "Duplicar sección" msgid "Duplicar sección"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:199 #: crochet/templates/crochet/pattern.html:204
msgid "Eliminar sección" msgid "Eliminar sección"
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:200 #: crochet/templates/crochet/pattern.html:205
msgid "" msgid ""
"¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las " "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las "
"secciones que contiene." "secciones que contiene."
msgstr "" msgstr ""
#: crochet/templates/crochet/pattern.html:206 #: crochet/templates/crochet/pattern.html:210
msgid "Añade una sección para ver aquí el resultado." msgid "Añade una sección para ver aquí el resultado."
msgstr "" msgstr ""
@@ -401,6 +487,10 @@ msgid "Entrar"
msgstr "" msgstr ""
#: crochet/templates/registration/login.html:32 #: 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" msgid "¿No tienes cuenta? Regístrate"
msgstr "" msgstr ""
@@ -409,6 +499,10 @@ msgstr ""
msgid "Crear cuenta" msgid "Crear cuenta"
msgstr "" msgstr ""
#: crochet/templates/registration/register.html:29
msgid "Registrarme"
msgstr ""
#: crochet/templates/registration/register.html:32 #: crochet/templates/registration/register.html:32
msgid "¿Ya tienes cuenta? Inicia sesión" msgid "¿Ya tienes cuenta? Inicia sesión"
msgstr "" msgstr ""
@@ -476,6 +570,22 @@ msgstr "cuenta/entrar/"
msgid "account/logout/" msgid "account/logout/"
msgstr "cuenta/salir/" 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/<uidb64>/<token>/"
msgstr "cuenta/restablecer/<uidb64>/<token>/"
#: crochet/urls.py:87
msgid "account/reset/done/"
msgstr "cuenta/restablecer/hecho/"
#: crochet/views.py:24 #: crochet/views.py:24
msgid "Este patrón todavía no tiene contenido." msgid "Este patrón todavía no tiene contenido."
msgstr "" msgstr ""
@@ -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 %}
<main class="flex justify-center px-4 py-12">
<div class="card w-full max-w-sm bg-base-100 shadow-xl border border-base-300">
<div class="card-body">
<h1 class="card-title">{% trans 'Contraseña actualizada' %}</h1>
<p class="text-sm text-base-content/70">
{% trans 'Ya puedes iniciar sesión con tu nueva contraseña.' %}
</p>
<a href="{% url 'crochet:login' %}" class="btn btn-primary mt-2">{% trans 'Iniciar sesión' %}</a>
</div>
</div>
</main>
{% endblock %}
@@ -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 %}
<main class="flex justify-center px-4 py-12">
<div class="card w-full max-w-sm bg-base-100 shadow-xl border border-base-300">
<div class="card-body">
{% if validlink %}
<h1 class="card-title">{% trans 'Elige una contraseña nueva' %}</h1>
<form method="post" class="flex flex-col gap-3">
{% csrf_token %}
{% for field in form %}
<label class="flex flex-col gap-1">
<span class="text-sm">{{ field.label }}</span>
{{ field }}
{% for error in field.errors %}
<span class="text-error text-xs">{{ error }}</span>
{% endfor %}
{% if field.help_text %}
<span class="text-xs text-base-content/60">{{ field.help_text }}</span>
{% endif %}
</label>
{% endfor %}
<button type="submit" class="btn btn-primary mt-2">{% trans 'Cambiar contraseña' %}</button>
</form>
{% else %}
<h1 class="card-title">{% trans 'Enlace no válido' %}</h1>
<p class="text-sm text-base-content/70">
{% trans 'El enlace para restablecer la contraseña no es válido, puede que ya se haya usado. Solicita uno nuevo.' %}
</p>
<p class="text-sm text-center mt-2">
<a href="{% url 'crochet:password_reset' %}" class="link link-primary">{% trans 'Solicitar un enlace nuevo' %}</a>
</p>
{% endif %}
</div>
</div>
</main>
{% endblock %}
@@ -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 %}
<main class="flex justify-center px-4 py-12">
<div class="card w-full max-w-sm bg-base-100 shadow-xl border border-base-300">
<div class="card-body">
<h1 class="card-title">{% trans 'Revisa tu email' %}</h1>
<p class="text-sm text-base-content/70">
{% trans 'Si existe una cuenta con ese email, te hemos enviado un enlace para elegir una contraseña nueva.' %}
</p>
<p class="text-sm text-center mt-2">
<a href="{% url 'crochet:login' %}" class="link link-primary">{% trans 'Volver a iniciar sesión' %}</a>
</p>
</div>
</div>
</main>
{% endblock %}
@@ -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 %}
@@ -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 %}
<main class="flex justify-center px-4 py-12">
<div class="card w-full max-w-sm bg-base-100 shadow-xl border border-base-300">
<div class="card-body">
<h1 class="card-title">{% trans 'Recuperar contraseña' %}</h1>
<p class="text-sm text-base-content/70">
{% trans 'Escribe tu email y te enviaremos un enlace para elegir una contraseña nueva.' %}
</p>
<form method="post" class="flex flex-col gap-3">
{% csrf_token %}
{% for field in form %}
<label class="flex flex-col gap-1">
<span class="text-sm">{{ field.label }}</span>
{{ field }}
{% for error in field.errors %}
<span class="text-error text-xs">{{ error }}</span>
{% endfor %}
</label>
{% endfor %}
<button type="submit" class="btn btn-primary mt-2">{% trans 'Enviar enlace' %}</button>
</form>
<p class="text-sm text-center mt-2">
<a href="{% url 'crochet:login' %}" class="link link-primary">{% trans 'Volver a iniciar sesión' %}</a>
</p>
</div>
</div>
</main>
{% endblock %}
@@ -0,0 +1 @@
{% load i18n %}{% blocktrans %}Recupera tu contraseña en {{ site_name }}{% endblocktrans %}
@@ -29,6 +29,9 @@
<button type="submit" class="btn btn-primary mt-2">{% trans 'Entrar' %}</button> <button type="submit" class="btn btn-primary mt-2">{% trans 'Entrar' %}</button>
</form> </form>
<p class="text-sm text-center mt-2"> <p class="text-sm text-center mt-2">
<a href="{% url 'crochet:password_reset' %}" class="link link-primary">{% trans '¿Has olvidado tu contraseña?' %}</a>
</p>
<p class="text-sm text-center">
<a href="{% url 'crochet:register' %}" class="link link-primary">{% trans '¿No tienes cuenta? Regístrate' %}</a> <a href="{% url 'crochet:register' %}" class="link link-primary">{% trans '¿No tienes cuenta? Regístrate' %}</a>
</p> </p>
</div> </div>
+16 -1
View File
@@ -20,17 +20,20 @@ class RegisterViewTests(TestCase):
def test_post_creates_user_and_logs_in(self): def test_post_creates_user_and_logs_in(self):
response = self.client.post(self.url, { response = self.client.post(self.url, {
'username': 'crocheter', 'username': 'crocheter',
'email': 'crocheter@example.com',
'password1': 'a-very-uncommon-pw-1', 'password1': 'a-very-uncommon-pw-1',
'password2': '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.assertRedirects(response, reverse('crochet:account_home'))
self.assertIn('_auth_user_id', self.client.session) self.assertIn('_auth_user_id', self.client.session)
def test_post_with_mismatched_passwords_does_not_create_user(self): def test_post_with_mismatched_passwords_does_not_create_user(self):
response = self.client.post(self.url, { response = self.client.post(self.url, {
'username': 'crocheter', 'username': 'crocheter',
'email': 'crocheter@example.com',
'password1': 'a-very-uncommon-pw-1', 'password1': 'a-very-uncommon-pw-1',
'password2': 'something-else', 'password2': 'something-else',
}) })
@@ -38,6 +41,18 @@ class RegisterViewTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertFalse(User.objects.filter(username='crocheter').exists()) 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): class LoginViewTests(TestCase):
def setUp(self): def setUp(self):
+134
View File
@@ -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}"')
+33 -2
View File
@@ -1,8 +1,8 @@
from django.contrib.auth import views as auth_views 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 django.utils.translation import gettext_lazy as _
from crochet.forms import StyledAuthenticationForm from crochet.forms import StyledAuthenticationForm, StyledPasswordResetForm, StyledSetPasswordForm
from crochet.views import ( from crochet.views import (
AccountEmailUpdateView, AccountEmailUpdateView,
AccountHomeView, AccountHomeView,
@@ -56,4 +56,35 @@ urlpatterns = [
), ),
), ),
path(_('account/logout/'), name='logout', view=auth_views.LogoutView.as_view()), 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/<uidb64>/<token>/'), 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'),
),
] ]