feat: added seo template and prioduct.slug
This commit is contained in:
+7
-1
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
]
|
||||
+12
-2
@@ -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):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="h-56 w-full">
|
||||
<a href="#">
|
||||
<a href="{% url 'shop:product_detail' pk=product.pk slug=product.slug %}">
|
||||
<img class="mx-auto h-full dark:hidden" src="{{ product.images.all|first }}"
|
||||
alt="{{ product.name }}"/>
|
||||
<img class="mx-auto hidden h-full dark:block" src="{{ product.images.all|first }}"
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<div class="pt-6">
|
||||
|
||||
<a href="{% url 'shop:product_detail' pk=product.pk %}"
|
||||
<a href="{% url 'shop:product_detail' pk=product.pk slug=product.slug %}"
|
||||
class="text-lg font-semibold leading-tight text-gray-900 hover:underline dark:text-white">{{ product.name }}</a>
|
||||
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
hx-target="#products"
|
||||
hx-trigger="keyup changed delay:500ms"
|
||||
hx-swap="innerHTML"
|
||||
hx-push="true"
|
||||
>
|
||||
<label for="simple-search" class="sr-only">Buscar</label>
|
||||
<div class="relative w-full">
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
{% extends 'theme/base.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block main %}
|
||||
<div>
|
||||
{% include 'components/products/list_filters.html' %}
|
||||
<section
|
||||
class="p-8"
|
||||
hx-get="{% url 'shop:list_products' %}{% querystring request.GET %}"
|
||||
hx-trigger="load"
|
||||
hx-target="#products"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<div>
|
||||
<div id="products" class="mb-4 grid gap-4 sm:grid-cols-2 md:mb-8 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<section
|
||||
class="p-8"
|
||||
hx-get="{% url 'shop:list_products' %}{% querystring request.GET %}"
|
||||
hx-trigger="load"
|
||||
hx-target="#products"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<div>
|
||||
<ul id="products" class="mb-4 grid gap-4 sm:grid-cols-2 md:mb-8 lg:grid-cols-3 xl:grid-cols-4">
|
||||
</ul>
|
||||
<div class="w-full text-center">
|
||||
<button type="button"
|
||||
class="rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900
|
||||
hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4
|
||||
focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700
|
||||
dark:hover:text-white dark:focus:ring-gray-700"
|
||||
hx-get="{% url 'shop:list_products' %}{% querystring request.GET %}{% querystring page=9 %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#products"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
{% translate 'Mostrar mas' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full text-center">
|
||||
<button type="button"
|
||||
class="rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
|
||||
hx-get="{% url 'shop:list_products' %}{% querystring request.GET %}{% querystring page=9 %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#products"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Mostrar mas
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% for product in products %}
|
||||
{% include 'components/product_card.html' %}
|
||||
<li>
|
||||
{% include 'components/product_card.html' %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@ app_name = "shop"
|
||||
|
||||
urlpatterns = [
|
||||
path("", index, name="index"),
|
||||
path("products/<int:pk>/", product_detail, name="product_detail"),
|
||||
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
|
||||
path("shop/components/products/", list_products, name="list_products"),
|
||||
]
|
||||
|
||||
+9
-1
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user