From b057b70a31d05612eca9392b11d36e3391f349ee Mon Sep 17 00:00:00 2001 From: Pablo Moreno Date: Fri, 22 Nov 2024 21:40:27 +0100 Subject: [PATCH] feat: added seo template and prioduct.slug --- locale/es_ES/LC_MESSAGES/django.po | 535 ++++++++++++++++++ requirements.txt | 2 +- shop/filters.py | 8 +- ...0004_alter_product_options_product_slug.py | 40 ++ shop/models.py | 14 +- shop/templates/components/product_card.html | 4 +- .../components/products/list_filters.html | 1 + shop/templates/shop/index.html | 50 +- shop/templates/shop/list_products.html | 4 +- shop/urls.py | 2 +- shop/views.py | 10 +- theme/templates/theme/base.html | 2 + theme/templates/theme/seo.html | 5 + 13 files changed, 644 insertions(+), 33 deletions(-) create mode 100644 locale/es_ES/LC_MESSAGES/django.po create mode 100644 shop/migrations/0004_alter_product_options_product_slug.py create mode 100644 theme/templates/theme/seo.html diff --git a/locale/es_ES/LC_MESSAGES/django.po b/locale/es_ES/LC_MESSAGES/django.po new file mode 100644 index 0000000..aa55e4c --- /dev/null +++ b/locale/es_ES/LC_MESSAGES/django.po @@ -0,0 +1,535 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-22 21:22+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: shop/api/v1/viewsets.py:72 +msgid "No se puede borrar el producto" +msgstr "No se puede borrar el producto" + +#: shop/filters.py:9 +msgid "Nombre" +msgstr "Nombre" + +#: shop/filters.py:11 +msgid "Etiquetas" +msgstr "Etiquetas" + +#: shop/models.py:11 +msgid "fecha de creación" +msgstr "fecha de creación" + +#: shop/models.py:14 +msgid "fecha de última modificación" +msgstr "fecha de última modificación" + +#: shop/models.py:28 +msgid "etiqueta" +msgstr "etiqueta" + +#: shop/models.py:29 shop/models.py:65 +msgid "etiquetas" +msgstr "etiquetas" + +#: shop/models.py:39 +msgid "código de referencia" +msgstr "código de referencia" + +#: shop/models.py:45 shop/models.py:185 shop/models.py:212 shop/models.py:239 +msgid "nombre" +msgstr "nombre" + +#: shop/models.py:50 +msgid "descripción" +msgstr "descripción" + +#: shop/models.py:56 +msgid "stock" +msgstr "stock" + +#: shop/models.py:59 +msgid "es un activo digital" +msgstr "es un activo digital" + +#: shop/models.py:61 +msgid "URL de descarga" +msgstr "URL de descarga" + +#: shop/models.py:63 +msgid "imágenes" +msgstr "imágenes" + +#: shop/models.py:71 shop/models.py:243 +msgid "marca" +msgstr "marca" + +#: shop/models.py:73 +msgid "Slug" +msgstr "Slug" + +#: shop/models.py:92 shop/models.py:105 shop/models.py:134 shop/models.py:259 +msgid "producto" +msgstr "producto" + +#: shop/models.py:93 +msgid "productos" +msgstr "productos" + +#: shop/models.py:99 shop/models.py:272 +msgid "precio" +msgstr "precio" + +#: shop/models.py:101 +msgid "fecha" +msgstr "fecha" + +#: shop/models.py:111 +msgid "impuesto aplicable" +msgstr "impuesto aplicable" + +#: shop/models.py:113 +msgid "es el precio actual" +msgstr "es el precio actual" + +#: shop/models.py:124 shop/models.py:125 +msgid "precio de producto" +msgstr "precio de producto" + +#: shop/models.py:131 +msgid "código" +msgstr "código" + +#: shop/models.py:140 shop/models.py:267 +msgid "cantidad" +msgstr "cantidad" + +#: shop/models.py:143 +msgid "fecha de caducidad / consumo preferente" +msgstr "fecha de caducidad / consumo preferente" + +#: shop/models.py:150 shop/models.py:233 +msgid "proveedor" +msgstr "proveedor" + +#: shop/models.py:157 +msgid "remesa de producto" +msgstr "remesa de producto" + +#: shop/models.py:158 +msgid "remesas de productos" +msgstr "remesas de productos" + +#: shop/models.py:163 +msgid "código de impuesto" +msgstr "código de impuesto" + +#: shop/models.py:166 +msgid "valor entero (porcentaje)" +msgstr "valor entero (porcentaje)" + +#: shop/models.py:173 +msgid "impuesto" +msgstr "impuesto" + +#: shop/models.py:174 shop/models.py:292 +msgid "impuestos" +msgstr "impuestos" + +#: shop/models.py:183 shop/models.py:210 +msgid "documento de identidad" +msgstr "documento de identidad" + +#: shop/models.py:187 +msgid "apellidos" +msgstr "apellidos" + +#: shop/models.py:189 +msgid "e-mail" +msgstr "e-mail" + +#: shop/models.py:191 shop/models.py:223 shop/models.py:360 +msgid "dirección" +msgstr "dirección" + +#: shop/models.py:192 shop/models.py:224 shop/models.py:363 +msgid "ciudad" +msgstr "ciudad" + +#: shop/models.py:193 shop/models.py:225 shop/models.py:366 +msgid "región" +msgstr "región" + +#: shop/models.py:194 shop/models.py:226 shop/models.py:369 +msgid "país" +msgstr "país" + +#: shop/models.py:195 shop/models.py:227 shop/models.py:372 +msgid "código postal" +msgstr "código postal" + +#: shop/models.py:201 shop/models.py:338 +msgid "cliente" +msgstr "cliente" + +#: shop/models.py:202 +msgid "clientes" +msgstr "clientes" + +#: shop/models.py:214 +msgid "e-mail de contacto" +msgstr "e-mail de contacto" + +#: shop/models.py:220 +msgid "Teléfono de contacto" +msgstr "Teléfono de contacto" + +#: shop/models.py:234 +msgid "proveedores" +msgstr "proveedores" + +#: shop/models.py:244 +msgid "marcas" +msgstr "marcas" + +#: shop/models.py:252 shop/models.py:389 +msgid "pedido" +msgstr "pedido" + +#: shop/models.py:282 +msgid "total sin impuestos" +msgstr "total sin impuestos" + +#: shop/models.py:287 +msgid "valor de impuestos" +msgstr "valor de impuestos" + +#: shop/models.py:297 shop/models.py:333 +msgid "total" +msgstr "total" + +#: shop/models.py:304 +msgid "línea de pedido" +msgstr "línea de pedido" + +#: shop/models.py:305 +msgid "líneas de pedido" +msgstr "líneas de pedido" + +#: shop/models.py:310 +msgid "pendiente de pago" +msgstr "pendiente de pago" + +#: shop/models.py:311 +msgid "pagado" +msgstr "pagado" + +#: shop/models.py:312 +msgid "preparado" +msgstr "preparado" + +#: shop/models.py:313 +msgid "listo para ser enviado" +msgstr"listo para ser enviado" + +#: shop/models.py:314 +msgid "enviado" +msgstr "enviado" + +#: shop/models.py:315 +msgid "entregado" +msgstr "entregado" + +#: shop/models.py:316 +msgid "finalizado" +msgstr "finalizado" + +#: shop/models.py:317 +msgid "cancelado" +msgstr "cancelado" + +#: shop/models.py:318 +msgid "devuelto" +msgstr "devuelto" + +#: shop/models.py:320 +msgid "UUID" +msgstr "UUID" + +#: shop/models.py:323 +msgid "estado" +msgstr "estado" + +#: shop/models.py:330 +msgid "base imponible" +msgstr "base imponible" + +#: shop/models.py:343 +msgid "dirección de facturación" +msgstr "dirección de facturación" + +#: shop/models.py:346 +msgid "ciudad de facturación" +msgstr "ciudad de facturación" + +#: shop/models.py:349 +msgid "región de facturación" +msgstr "región de facturación" + +#: shop/models.py:352 +msgid "país de facturación" +msgstr "país de facturación" + +#: shop/models.py:355 +msgid "código postal de facturación" +msgstr "código postal de facturación" + +#: shop/models.py:375 +msgid "teléfono de contacto" +msgstr "teléfono de contacto" + +#: shop/models.py:390 +msgid "pedidos" +msgstr "pedidos" + +#: shop/templates/shop/index.html:28 +msgid "Mostrar mas" +msgstr "Mostrar mas" + +#: tpv/forms.py:7 +msgid "Introduce tu e-mail" +msgstr "Introduce tu e-mail" + +#: tpv/models.py:14 +msgid "Pagado" +msgstr "Pagado" + +#: tpv/models.py:15 +msgid "Pendiente" +msgstr "Pendiente" + +#: tpv/models.py:16 +msgid "Error" +msgstr "Error" + +#: tpv/models.py:18 +msgid "Hash" +msgstr "Hash" + +#: tpv/models.py:24 +msgid "Estado" +msgstr "Estado" + +#: tpv/models.py:30 +msgid "Cantidad a pagar" +msgstr "Cantidad a pagar" + +#: tpv/models.py:34 +msgid "Fecha de creación" +msgstr "Fecha de creación" + +#: tpv/models.py:37 +msgid "Fecha de última modificación" +msgstr "Fecha de última modificación" + +#: tpv/models.py:41 +msgid "Email de contacto" +msgstr "Email de contacto" + +#: tpv/models.py:48 +msgid "Observaciones" +msgstr "Observaciones" + +#: tpv/models.py:52 +msgid "Metadata" +msgstr "Metadata" + +#: tpv/models.py:71 +msgid "Pago de pedido en Redsys" +msgstr "Pago de pedido en Redsys" + +#: tpv/models.py:72 +msgid "Pagos de pedidos en Redsys" +msgstr "Pagos de pedidos en Redsys" + +#: tpv/settings.py:230 tpv/utils.py:87 +msgid "Error no tipificado" +msgstr "Error no tipificado" + +#: tpv/settings.py:231 +msgid "Transacción autorizada para devoluciones y confirmaciones" +msgstr "Transacción autorizada para devoluciones y confirmaciones" + +#: tpv/settings.py:232 +msgid "Transacción autorizada para anulaciones" +msgstr "Transacción autorizada para anulaciones" + +#: tpv/settings.py:233 +msgid "Tarjeta caducada" +msgstr "Tarjeta caducada" + +#: tpv/settings.py:234 +msgid "Tarjeta en excepción transitoria o bajo sospecha de fraude" +msgstr "Tarjeta en excepción transitoria o bajo sospecha de fraude" + +#: tpv/settings.py:235 +msgid "Intentos de PIN excedidos" +msgstr "Intentos de PIN excedidos" + +#: tpv/settings.py:236 +msgid "Tarjeta no efectiva" +msgstr "Tarjeta no efectiva" + +#: tpv/settings.py:237 +msgid "Código de seguridad (CVV2/CVC2) incorrecto" +msgstr "Código de seguridad (CVV2/CVC2) incorrecto" + +#: tpv/settings.py:238 +msgid "Denegada, no repetir." +msgstr "Denegada, no repetir." + +#: tpv/settings.py:239 +msgid "Denegada, no repetir sin actualizar datos de tarjeta." +msgstr "Denegada, no repetir sin actualizar datos de tarjeta." + +#: tpv/settings.py:240 +msgid "Denegada, no repetir antes de 72 horas." +msgstr "Denegada, no repetir antes de 72 horas." + +#: tpv/settings.py:241 +msgid "Tarjeta ajena al servicio" +msgstr "Tarjeta ajena al servicio" + +#: tpv/settings.py:242 +msgid "Error en la autenticación del titular" +msgstr "Error en la autenticación del titular" + +#: tpv/settings.py:243 +msgid "Denegación del emisor sin especificar motivo" +msgstr "Denegación del emisor sin especificar motivo" + +#: tpv/settings.py:244 +msgid "Fecha de caducidad errónea" +msgstr "Fecha de caducidad errónea" + +#: tpv/settings.py:245 +msgid "Requiere autenticación SCA" +msgstr "Requiere autenticación SCA" + +#: tpv/settings.py:247 +msgid "" +"Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de " +"tarjeta" +msgstr "" +"Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de " +"tarjeta" + +#: tpv/settings.py:249 +msgid "Comercio no registrado en FUC" +msgstr "Comercio no registrado en FUC" + +#: tpv/settings.py:250 +msgid "Error de sistema" +msgstr "Error de sistema" + +#: tpv/settings.py:251 +msgid "Pedido repetido" +msgstr "Pedido repetido" + +#: tpv/settings.py:252 +msgid "Sesión Incorrecta" +msgstr "Sesión Incorrecta" + +#: tpv/settings.py:253 +msgid "Operación de devolución no permitida" +msgstr "Operación de devolución no permitida" + +#: tpv/settings.py:254 tpv/settings.py:255 +msgid "Emisor no disponible" +msgstr "" + +#: tpv/settings.py:256 +msgid "Número de posiciones de la tarjeta incorrecto" +msgstr "Número de posiciones de la tarjeta incorrecto" + +#: tpv/settings.py:257 +msgid "Tipo de operación no permitida para esa tarjeta" +msgstr "Tipo de operación no permitida para esa tarjeta" + +#: tpv/settings.py:258 +msgid "Tarjeta no existente" +msgstr "Tarjeta no existente" + +#: tpv/settings.py:259 +msgid "Rechazo servidores internacionales" +msgstr "Rechazo servidores internacionales" + +#: tpv/settings.py:260 +msgid "Comercio con titular seguro y titular sin clave de compra segura" +msgstr "Comercio con titular seguro y titular sin clave de compra segura" + +#: tpv/settings.py:261 +msgid "El comercio no permite op. seguras por entrada /operaciones" +msgstr "El comercio no permite op. seguras por entrada /operaciones" + +#: tpv/settings.py:262 +msgid "Tarjeta no cumple el check-digit" +msgstr "Tarjeta no cumple el check-digit" + +#: tpv/settings.py:263 +msgid "El comercio no puede realizar preautorizaciones" +msgstr "El comercio no puede realizar preautorizaciones" + +#: tpv/settings.py:264 +msgid "Esta tarjeta no permite operativa de preautorizaciones" +msgstr "Esta tarjeta no permite operativa de preautorizaciones" + +#: tpv/settings.py:266 +msgid "" +"Operación detenida por superar el control de restricciones en la entrada al " +"SIS" +msgstr "" +"Operación detenida por superar el control de restricciones en la entrada al " +"SIS" + +#: tpv/settings.py:268 +msgid "A petición del usuario se ha cancelado el pago" +msgstr "A petición del usuario se ha cancelado el pago" + +#: tpv/settings.py:269 +msgid "Se está procesando otra transacción en SIS con la misma tarjeta" +msgstr "Se está procesando otra transacción en SIS con la misma tarjeta" + +#: tpv/settings.py:270 +msgid "Operación en proceso de solicitud de datos de tarjeta" +msgstr "Operación en proceso de solicitud de datos de tarjeta" + +#: tpv/settings.py:271 +msgid "Operación que ha sido redirigida al emisor a autenticar" +msgstr "Operación que ha sido redirigida al emisor a autenticar" + +#: tpv/utils.py:75 +msgid "No se ha recibido ningún valor para Ds_MerchantParameters" +msgstr "No se ha recibido ningún valor para Ds_MerchantParameters" + +#: tpv/utils.py:89 +#, python-brace-format +msgid "No se ha realizado el pago. Motivo: {reason}" +msgstr "No se ha realizado el pago. Motivo: {reason}" + +#: users/forms/login.py:18 +msgid "Password" +msgstr "Contraseña" diff --git a/requirements.txt b/requirements.txt index b205331..d317f1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ django-cors-headers==3.13.0 django-cryptography==1.1 django-debug-toolbar==4.4.6 django-extensions==3.2.0 -django-filter==23.1 +django-filter==24.3 django-environ==0.10.0 django-watchman==1.3.0 django-silk==5.1.0 diff --git a/shop/filters.py b/shop/filters.py index 47fbd10..645bbfc 100644 --- a/shop/filters.py +++ b/shop/filters.py @@ -1,6 +1,6 @@ import django_filters from django.utils.text import gettext_lazy as _ - +from django_filters import OrderingFilter from shop.models import Product @@ -9,6 +9,11 @@ class ProductFilter(django_filters.FilterSet): field_name="name", lookup_expr="icontains", label=_("Nombre") ) tags = django_filters.BaseInFilter(field_name="tags", label=_("Etiquetas")) + ordering = OrderingFilter( + fields={ + 'name': 'name' + } + ) class Meta: model = Product @@ -17,4 +22,5 @@ class ProductFilter(django_filters.FilterSet): "stock", "is_digital_asset", "tags", + "ordering", ) diff --git a/shop/migrations/0004_alter_product_options_product_slug.py b/shop/migrations/0004_alter_product_options_product_slug.py new file mode 100644 index 0000000..ce3da5d --- /dev/null +++ b/shop/migrations/0004_alter_product_options_product_slug.py @@ -0,0 +1,40 @@ +# Generated by Django 5.1.3 on 2024-11-22 19:53 + +from django.db import migrations, models +from django.utils.text import slugify + + +def add_product_slug(apps, schema_editor): + Product = apps.get_model("shop", "Product") + + for product in Product.objects.all(): + product.slug = slugify(product.name)[:128] + product.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("shop", "0003_alter_brand_creation_date_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="product", + options={ + "ordering": ("id",), + "verbose_name": "producto", + "verbose_name_plural": "productos", + }, + ), + migrations.AddField( + model_name="product", + name="slug", + field=models.SlugField( + blank=True, default="", max_length=128, null=True, verbose_name="Slug" + ), + ), + migrations.RunPython( + add_product_slug, + ) + ] diff --git a/shop/models.py b/shop/models.py index be8cbe7..5cf1bb0 100644 --- a/shop/models.py +++ b/shop/models.py @@ -3,7 +3,7 @@ from uuid import uuid4 from django.db import models from django.utils import timezone -from django.utils.text import gettext_lazy as _ +from django.utils.text import gettext_lazy as _, slugify class TimestampedModel(models.Model): @@ -70,10 +70,20 @@ class Product(TimestampedModel): on_delete=models.SET_NULL, verbose_name=_("marca"), ) + slug = models.SlugField(default="", blank=True, null=True, max_length=128, verbose_name=_('Slug')) def __str__(self): return self.name + def save( + self, + *args, + **kwargs, + ): + if not self.pk: + self.slug = slugify(self.name)[:128] + super().save(*args, **kwargs) + @property def price(self): return self.prices.filter(current=True).first() @@ -81,7 +91,7 @@ class Product(TimestampedModel): class Meta: verbose_name = _("producto") verbose_name_plural = _("productos") - ordering = ("-id",) + ordering = ("id",) class ProductPrice(TimestampedModel): diff --git a/shop/templates/components/product_card.html b/shop/templates/components/product_card.html index 2b54360..d0a605b 100644 --- a/shop/templates/components/product_card.html +++ b/shop/templates/components/product_card.html @@ -2,7 +2,7 @@
- + {{ product.name }}
- {{ product.name }}
diff --git a/shop/templates/components/products/list_filters.html b/shop/templates/components/products/list_filters.html index 759e0e0..4692988 100644 --- a/shop/templates/components/products/list_filters.html +++ b/shop/templates/components/products/list_filters.html @@ -8,6 +8,7 @@ hx-target="#products" hx-trigger="keyup changed delay:500ms" hx-swap="innerHTML" + hx-push="true" >
diff --git a/shop/templates/shop/index.html b/shop/templates/shop/index.html index 4852db2..5619c66 100644 --- a/shop/templates/shop/index.html +++ b/shop/templates/shop/index.html @@ -1,32 +1,34 @@ {% extends 'theme/base.html' %} - -{% load static %} +{% load static i18n %} {% block main %}
{% include 'components/products/list_filters.html' %} -
-
-
+
+
+
    +
+
+ +
-
- -
-
-
+
{% endblock %} diff --git a/shop/templates/shop/list_products.html b/shop/templates/shop/list_products.html index 84fa387..d1b0d49 100644 --- a/shop/templates/shop/list_products.html +++ b/shop/templates/shop/list_products.html @@ -1,5 +1,7 @@ {% load i18n %} {% load static %} {% for product in products %} - {% include 'components/product_card.html' %} +
  • + {% include 'components/product_card.html' %} +
  • {% endfor %} diff --git a/shop/urls.py b/shop/urls.py index 0bee606..f6be08b 100644 --- a/shop/urls.py +++ b/shop/urls.py @@ -7,6 +7,6 @@ app_name = "shop" urlpatterns = [ path("", index, name="index"), - path("products//", product_detail, name="product_detail"), + path("products///", product_detail, name="product_detail"), path("shop/components/products/", list_products, name="list_products"), ] diff --git a/shop/views.py b/shop/views.py index cb46b0e..def8369 100644 --- a/shop/views.py +++ b/shop/views.py @@ -9,6 +9,11 @@ from shop.models import Product class IndexView(TemplateView): template_name = "shop/index.html" + def get_context_data(self, **kwargs): + return { + "title": "Shoppy", + } + class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): template_name = "shop/list_products.html" @@ -27,11 +32,14 @@ class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): class ProductDetail(TemplateView): template_name = "shop/product_detail.html" - def get_context_data(self, pk, **kwargs): + def get_context_data(self, pk, slug, **kwargs): product = get_object_or_404(Product, pk=pk) return { "product": product, + "title": product.name, + "description": product.description, + "image": product.images.first(), } diff --git a/theme/templates/theme/base.html b/theme/templates/theme/base.html index efb3fdb..6590e27 100644 --- a/theme/templates/theme/base.html +++ b/theme/templates/theme/base.html @@ -6,6 +6,8 @@ {{ title }} + {% include "theme/seo.html" %} + {% tailwind_css %} diff --git a/theme/templates/theme/seo.html b/theme/templates/theme/seo.html new file mode 100644 index 0000000..4f4a2f9 --- /dev/null +++ b/theme/templates/theme/seo.html @@ -0,0 +1,5 @@ + + + + +