diff --git a/crochet/locale/en/LC_MESSAGES/django.po b/crochet/locale/en/LC_MESSAGES/django.po index 89ae263..dc26bac 100644 --- a/crochet/locale/en/LC_MESSAGES/django.po +++ b/crochet/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-21 11:59+0000\n" +"POT-Creation-Date: 2026-07-21 12:09+0000\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -31,6 +31,23 @@ msgstr "Password updated." msgid "Cambiar contraseña" msgstr "Change password" +#: crochet/templates/crochet/_cookie_banner.html:8 +msgid "" +"Usamos únicamente las cookies necesarias para que la web funcione (mantener " +"tu sesión iniciada y proteger los formularios). No usamos cookies de " +"analítica ni de publicidad." +msgstr "" +"We only use the cookies necessary for the site to work (keeping you logged " +"in and protecting forms). We don't use analytics or advertising cookies." + +#: crochet/templates/crochet/_cookie_banner.html:9 +msgid "Más información" +msgstr "More information" + +#: crochet/templates/crochet/_cookie_banner.html:11 +msgid "Entendido" +msgstr "Got it" + #: crochet/templates/crochet/account_home.html:4 msgid "Mis patrones" msgstr "My patterns" @@ -98,6 +115,75 @@ msgstr "Log out" msgid "Iniciar sesión" msgstr "Log in" +#: crochet/templates/crochet/cookie_policy.html:4 +#: crochet/templates/crochet/cookie_policy.html:12 +msgid "Política de cookies" +msgstr "Cookie policy" + +#: crochet/templates/crochet/cookie_policy.html:15 +msgid "" +"Una cookie es un pequeño archivo que una web guarda en tu navegador. Aquí " +"usamos únicamente las que hacen falta para que la página funcione; no usamos " +"cookies de analítica ni de publicidad, ni de terceros." +msgstr "" +"A cookie is a small file that a website stores in your browser. Here we only " +"use the ones needed for the page to work; we don't use analytics, " +"advertising, or third-party cookies." + +#: crochet/templates/crochet/cookie_policy.html:20 +msgid "Cookies que usamos" +msgstr "Cookies we use" + +#: crochet/templates/crochet/cookie_policy.html:25 +msgid "Nombre" +msgstr "Name" + +#: crochet/templates/crochet/cookie_policy.html:26 +msgid "Finalidad" +msgstr "Purpose" + +#: crochet/templates/crochet/cookie_policy.html:27 +msgid "Duración" +msgstr "Duration" + +#: crochet/templates/crochet/cookie_policy.html:33 +msgid "Mantiene tu sesión iniciada." +msgstr "Keeps you logged in." + +#: crochet/templates/crochet/cookie_policy.html:34 +msgid "Hasta que cierras sesión o caduca." +msgstr "Until you log out or it expires." + +#: crochet/templates/crochet/cookie_policy.html:38 +msgid "" +"Protege los formularios frente a ataques de falsificación de petición (CSRF)." +msgstr "Protects forms against cross-site request forgery (CSRF) attacks." + +#: crochet/templates/crochet/cookie_policy.html:39 +msgid "1 año." +msgstr "1 year." + +#: crochet/templates/crochet/cookie_policy.html:45 +msgid "" +"Además, tu navegador guarda localmente (no como cookie) que ya has visto el " +"aviso de cookies, para no volver a mostrártelo." +msgstr "" +"Your browser also stores locally (not as a cookie) that you've already seen " +"the cookie notice, so it isn't shown again." + +#: crochet/templates/crochet/cookie_policy.html:51 +msgid "" +"Ambas son cookies técnicas necesarias para el funcionamiento del sitio " +"(iniciar sesión y proteger los formularios), así que no requieren tu " +"consentimiento previo." +msgstr "" +"Both are technical cookies necessary for the site to work (logging in and " +"protecting forms), so they don't require your prior consent." + +#: crochet/templates/crochet/cookie_policy.html:54 +msgid "Volver al inicio" +msgstr "Back to home" + #: crochet/templates/crochet/email/base.html:43 msgid "" "Este email se ha enviado automáticamente, no respondas a esta dirección." @@ -548,7 +634,7 @@ msgstr "" msgid "Añade una sección para ver aquí el resultado." msgstr "Add a section to see the result here." -#: crochet/templates/crochet/pattern_detail.html:24 +#: crochet/templates/crochet/pattern_detail.html:26 msgid "Descargar PDF" msgstr "Download PDF" @@ -581,79 +667,83 @@ msgstr "Sign up" msgid "¿Ya tienes cuenta? Inicia sesión" msgstr "Already have an account? Log in" -#: crochet/urls.py:36 -msgid "pattern//" -msgstr "" - #: crochet/urls.py:37 -msgid "pattern//edit/" +msgid "cookies/" msgstr "" #: crochet/urls.py:38 -msgid "pattern//save/" +msgid "pattern//" msgstr "" #: crochet/urls.py:39 -msgid "pattern//images/" +msgid "pattern//edit/" msgstr "" #: crochet/urls.py:40 -msgid "pattern//cover/" +msgid "pattern//save/" msgstr "" #: crochet/urls.py:41 -msgid "pattern//pdf/" +msgid "pattern//images/" msgstr "" #: crochet/urls.py:42 -msgid "pattern//delete/" +msgid "pattern//cover/" msgstr "" #: crochet/urls.py:43 -msgid "pattern/new/" +msgid "pattern//pdf/" msgstr "" #: crochet/urls.py:44 -msgid "account/" +msgid "pattern//delete/" msgstr "" #: crochet/urls.py:45 -msgid "account/settings/" +msgid "pattern/new/" msgstr "" #: crochet/urls.py:46 -msgid "account/settings/email/" +msgid "account/" +msgstr "" + +#: crochet/urls.py:47 +msgid "account/settings/" msgstr "" #: crochet/urls.py:48 +msgid "account/settings/email/" +msgstr "" + +#: crochet/urls.py:50 msgid "account/settings/password/" msgstr "" -#: crochet/urls.py:51 +#: crochet/urls.py:53 msgid "account/register/" msgstr "" -#: crochet/urls.py:53 +#: crochet/urls.py:55 msgid "account/login/" msgstr "" -#: crochet/urls.py:58 +#: crochet/urls.py:60 msgid "account/logout/" msgstr "" -#: crochet/urls.py:60 +#: crochet/urls.py:62 msgid "account/password-reset/" msgstr "" -#: crochet/urls.py:79 +#: crochet/urls.py:81 msgid "account/password-reset/done/" msgstr "" -#: crochet/urls.py:83 +#: crochet/urls.py:85 msgid "account/reset///" msgstr "" -#: crochet/urls.py:91 +#: crochet/urls.py:93 msgid "account/reset/done/" msgstr "" diff --git a/crochet/locale/es/LC_MESSAGES/django.po b/crochet/locale/es/LC_MESSAGES/django.po index 47759c9..a4a645e 100644 --- a/crochet/locale/es/LC_MESSAGES/django.po +++ b/crochet/locale/es/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: crochet\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-21 11:59+0000\n" +"POT-Creation-Date: 2026-07-21 12:09+0000\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -31,6 +31,21 @@ msgstr "" msgid "Cambiar contraseña" msgstr "" +#: crochet/templates/crochet/_cookie_banner.html:8 +msgid "" +"Usamos únicamente las cookies necesarias para que la web funcione (mantener " +"tu sesión iniciada y proteger los formularios). No usamos cookies de " +"analítica ni de publicidad." +msgstr "" + +#: crochet/templates/crochet/_cookie_banner.html:9 +msgid "Más información" +msgstr "" + +#: crochet/templates/crochet/_cookie_banner.html:11 +msgid "Entendido" +msgstr "" + #: crochet/templates/crochet/account_home.html:4 msgid "Mis patrones" msgstr "" @@ -98,6 +113,68 @@ msgstr "" msgid "Iniciar sesión" msgstr "" +#: crochet/templates/crochet/cookie_policy.html:4 +#: crochet/templates/crochet/cookie_policy.html:12 +msgid "Política de cookies" +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:15 +msgid "" +"Una cookie es un pequeño archivo que una web guarda en tu navegador. Aquí " +"usamos únicamente las que hacen falta para que la página funcione; no usamos " +"cookies de analítica ni de publicidad, ni de terceros." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:20 +msgid "Cookies que usamos" +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:25 +msgid "Nombre" +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:26 +msgid "Finalidad" +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:27 +msgid "Duración" +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:33 +msgid "Mantiene tu sesión iniciada." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:34 +msgid "Hasta que cierras sesión o caduca." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:38 +msgid "" +"Protege los formularios frente a ataques de falsificación de petición (CSRF)." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:39 +msgid "1 año." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:45 +msgid "" +"Además, tu navegador guarda localmente (no como cookie) que ya has visto el " +"aviso de cookies, para no volver a mostrártelo." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:51 +msgid "" +"Ambas son cookies técnicas necesarias para el funcionamiento del sitio " +"(iniciar sesión y proteger los formularios), así que no requieren tu " +"consentimiento previo." +msgstr "" + +#: crochet/templates/crochet/cookie_policy.html:54 +msgid "Volver al inicio" +msgstr "" + #: crochet/templates/crochet/email/base.html:43 msgid "" "Este email se ha enviado automáticamente, no respondas a esta dirección." @@ -527,7 +604,7 @@ msgstr "" msgid "Añade una sección para ver aquí el resultado." msgstr "" -#: crochet/templates/crochet/pattern_detail.html:24 +#: crochet/templates/crochet/pattern_detail.html:26 msgid "Descargar PDF" msgstr "" @@ -560,82 +637,86 @@ msgstr "" msgid "¿Ya tienes cuenta? Inicia sesión" msgstr "" +#: crochet/urls.py:37 +msgid "cookies/" +msgstr "" + # Rutas traducibles de crochet/urls.py: la base "pattern/" se traduce como # "patron/" (sin tilde, para no meter caracteres acentuados en la URL). -#: crochet/urls.py:36 +#: crochet/urls.py:38 msgid "pattern//" msgstr "patron//" -#: crochet/urls.py:37 +#: crochet/urls.py:39 msgid "pattern//edit/" msgstr "patron//editar/" -#: crochet/urls.py:38 +#: crochet/urls.py:40 msgid "pattern//save/" msgstr "patron//guardar/" -#: crochet/urls.py:39 +#: crochet/urls.py:41 msgid "pattern//images/" msgstr "patron//imagenes/" -#: crochet/urls.py:40 +#: crochet/urls.py:42 msgid "pattern//cover/" msgstr "patron//portada/" -#: crochet/urls.py:41 +#: crochet/urls.py:43 msgid "pattern//pdf/" msgstr "patron//pdf/" -#: crochet/urls.py:42 +#: crochet/urls.py:44 msgid "pattern//delete/" msgstr "patron//eliminar/" -#: crochet/urls.py:43 +#: crochet/urls.py:45 msgid "pattern/new/" msgstr "patron/nuevo/" # Igual que "pattern/" arriba: "account/" se traduce como "cuenta/". -#: crochet/urls.py:44 +#: crochet/urls.py:46 msgid "account/" msgstr "cuenta/" -#: crochet/urls.py:45 +#: crochet/urls.py:47 msgid "account/settings/" msgstr "cuenta/ajustes/" -#: crochet/urls.py:46 +#: crochet/urls.py:48 msgid "account/settings/email/" msgstr "cuenta/ajustes/email/" -#: crochet/urls.py:48 +#: crochet/urls.py:50 msgid "account/settings/password/" msgstr "cuenta/ajustes/contrasena/" -#: crochet/urls.py:51 +#: crochet/urls.py:53 msgid "account/register/" msgstr "cuenta/registro/" -#: crochet/urls.py:53 +#: crochet/urls.py:55 msgid "account/login/" msgstr "cuenta/entrar/" -#: crochet/urls.py:58 +#: crochet/urls.py:60 msgid "account/logout/" msgstr "cuenta/salir/" -#: crochet/urls.py:60 +#: crochet/urls.py:62 msgid "account/password-reset/" msgstr "cuenta/recuperar-contrasena/" -#: crochet/urls.py:79 +#: crochet/urls.py:81 msgid "account/password-reset/done/" msgstr "cuenta/recuperar-contrasena/enviado/" -#: crochet/urls.py:83 +#: crochet/urls.py:85 msgid "account/reset///" msgstr "cuenta/restablecer///" -#: crochet/urls.py:91 +#: crochet/urls.py:93 msgid "account/reset/done/" msgstr "cuenta/restablecer/hecho/" diff --git a/crochet/static/js/cookie-consent.js b/crochet/static/js/cookie-consent.js new file mode 100644 index 0000000..5a02c36 --- /dev/null +++ b/crochet/static/js/cookie-consent.js @@ -0,0 +1,18 @@ +// Aviso de cookies (ver crochet/_cookie_banner.html): la preferencia se +// guarda en localStorage, no en una cookie propia, para no tener que crear +// una cookie solo para recordar que se ha descartado el aviso de cookies. +const STORAGE_KEY = 'cookieConsentAccepted'; + +const banner = document.getElementById('cookie-banner'); +const acceptButton = document.getElementById('cookie-banner-accept'); + +if (banner && acceptButton) { + if (!localStorage.getItem(STORAGE_KEY)) { + banner.classList.remove('hidden'); + } + + acceptButton.addEventListener('click', () => { + localStorage.setItem(STORAGE_KEY, '1'); + banner.classList.add('hidden'); + }); +} diff --git a/crochet/templates/crochet/_cookie_banner.html b/crochet/templates/crochet/_cookie_banner.html new file mode 100644 index 0000000..db08e98 --- /dev/null +++ b/crochet/templates/crochet/_cookie_banner.html @@ -0,0 +1,13 @@ +{% load i18n %} + + diff --git a/crochet/templates/crochet/base.html b/crochet/templates/crochet/base.html index 6b85ccb..a81dc90 100644 --- a/crochet/templates/crochet/base.html +++ b/crochet/templates/crochet/base.html @@ -65,6 +65,13 @@ {% endblock %} {% block content %}{% endblock %} + + {% block cookie_banner %} + {% include 'crochet/_cookie_banner.html' %} + + {% endblock %} {% block extra_js %}{% endblock %} diff --git a/crochet/templates/crochet/cookie_policy.html b/crochet/templates/crochet/cookie_policy.html new file mode 100644 index 0000000..f6dbf62 --- /dev/null +++ b/crochet/templates/crochet/cookie_policy.html @@ -0,0 +1,56 @@ +{% extends 'crochet/base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Política de cookies' %}{% endblock %} + +{% block extra_css %} + {% include 'crochet/_platform_assets.html' %} +{% endblock %} + +{% block content %} +
+

{% trans 'Política de cookies' %}

+ +

+ {% blocktrans %}Una cookie es un pequeño archivo que una web guarda en tu navegador. Aquí usamos únicamente las que hacen falta para que la página funcione; no usamos cookies de analítica ni de publicidad, ni de terceros.{% endblocktrans %} +

+ +
+
+

{% trans 'Cookies que usamos' %}

+
+ + + + + + + + + + + + + + + + + + + + +
{% trans 'Nombre' %}{% trans 'Finalidad' %}{% trans 'Duración' %}
sessionid{% trans 'Mantiene tu sesión iniciada.' %}{% trans 'Hasta que cierras sesión o caduca.' %}
csrftoken{% trans 'Protege los formularios frente a ataques de falsificación de petición (CSRF).' %}{% trans '1 año.' %}
+
+

+ {% blocktrans %}Además, tu navegador guarda localmente (no como cookie) que ya has visto el aviso de cookies, para no volver a mostrártelo.{% endblocktrans %} +

+
+
+ +

+ {% blocktrans %}Ambas son cookies técnicas necesarias para el funcionamiento del sitio (iniciar sesión y proteger los formularios), así que no requieren tu consentimiento previo.{% endblocktrans %} +

+ + {% trans 'Volver al inicio' %} +
+{% endblock %} diff --git a/crochet/templates/crochet/pattern_detail.html b/crochet/templates/crochet/pattern_detail.html index 5654c4a..8d1ad92 100644 --- a/crochet/templates/crochet/pattern_detail.html +++ b/crochet/templates/crochet/pattern_detail.html @@ -15,8 +15,10 @@ {% endblock %} + ningún estilo que aplicarle: se deja vacío a propósito. Lo mismo con + el aviso de cookies: es una vista de solo lectura sin JS propio. --> {% block navbar %}{% endblock %} +{% block cookie_banner %}{% endblock %} {% block content %}
diff --git a/crochet/tests/test_views.py b/crochet/tests/test_views.py index 91cc3c6..ade4f57 100644 --- a/crochet/tests/test_views.py +++ b/crochet/tests/test_views.py @@ -53,6 +53,39 @@ class HomeViewTests(TestCase): self.assertEqual(response_en.status_code, 200) +class CookiePolicyViewTests(TestCase): + def test_get_renders_the_policy_page(self): + with translation.override('es'): + url = reverse('crochet:cookie_policy') + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'crochet/cookie_policy.html') + self.assertContains(response, 'sessionid') + self.assertContains(response, 'csrftoken') + + +class CookieBannerTests(TestCase): + def test_regular_pages_include_the_cookie_banner(self): + with translation.override('es'): + url = reverse('crochet:home') + policy_url = reverse('crochet:cookie_policy') + response = self.client.get(url) + + self.assertContains(response, 'id="cookie-banner"') + self.assertContains(response, f'href="{policy_url}"') + self.assertContains(response, 'cookie-consent.js') + + def test_pattern_detail_does_not_include_the_cookie_banner(self): + pattern = Pattern.objects.create() + with translation.override('es'): + url = reverse('crochet:pattern_detail', args=[pattern.uuid]) + response = self.client.get(url) + + self.assertNotContains(response, 'id="cookie-banner"') + self.assertNotContains(response, 'cookie-consent.js') + + class LanguageSwitcherTests(TestCase): def test_home_page_switcher_offers_the_same_page_in_english(self): response = self.client.get('/es/') diff --git a/crochet/urls.py b/crochet/urls.py index 038af09..82a335d 100644 --- a/crochet/urls.py +++ b/crochet/urls.py @@ -8,6 +8,7 @@ from crochet.views import ( AccountHomeView, AccountPasswordChangeView, AccountSettingsView, + CookiePolicyView, HomeView, PatternCoverImageUploadView, PatternCreateView, @@ -33,6 +34,7 @@ app_name = 'crochet' # /es/patron//editar/. urlpatterns = [ path('', name='home', view=HomeView.as_view()), + path(_('cookies/'), name='cookie_policy', view=CookiePolicyView.as_view()), path(_('pattern//'), name='pattern_detail', view=PatternDetailView.as_view()), path(_('pattern//edit/'), name='pattern_edit', view=PatternEditView.as_view()), path(_('pattern//save/'), name='pattern_save', view=PatternSaveView.as_view()), diff --git a/crochet/views.py b/crochet/views.py index 603ebe4..b8c3a64 100644 --- a/crochet/views.py +++ b/crochet/views.py @@ -67,6 +67,10 @@ class HomeView(TemplateView): template_name = 'crochet/home.html' +class CookiePolicyView(TemplateView): + template_name = 'crochet/cookie_policy.html' + + class PatternEditView(LoginRequiredMixin, DetailView): model = Pattern template_name = 'crochet/pattern.html'