Add slug field to Product and update related functionalities
Introduce a unique, auto-generated slug field to the Product model, populated from category, brand, and name. Update core filters, serializers, sitemaps, and GraphQL object types to support the new slug functionality. Also, enforce HTTP-only cookies for session, CSRF, and language for improved security.
This commit is contained in:
parent
bbde0a4654
commit
3ec0991aa6
9 changed files with 58 additions and 4 deletions
|
|
@ -34,12 +34,15 @@ class ProductFilter(FilterSet):
|
||||||
brand = CharFilter(field_name="brand__name", lookup_expr="iexact", label="Brand")
|
brand = CharFilter(field_name="brand__name", lookup_expr="iexact", label="Brand")
|
||||||
attributes = CharFilter(method="filter_attributes", label="Attributes")
|
attributes = CharFilter(method="filter_attributes", label="Attributes")
|
||||||
quantity = NumberFilter(field_name="stocks__quantity", lookup_expr="gt", label="Quantity")
|
quantity = NumberFilter(field_name="stocks__quantity", lookup_expr="gt", label="Quantity")
|
||||||
|
slug = CharFilter(field_name="slug", lookup_expr="exact", label="Slug")
|
||||||
|
is_digital = BooleanFilter(field_name="is_digital", label="Is Digital")
|
||||||
|
|
||||||
order_by = OrderingFilter(
|
order_by = OrderingFilter(
|
||||||
fields=(
|
fields=(
|
||||||
("uuid", "uuid"),
|
("uuid", "uuid"),
|
||||||
("rating", "rating"),
|
("rating", "rating"),
|
||||||
("name", "name"),
|
("name", "name"),
|
||||||
|
("slug", "slug"),
|
||||||
("created", "created"),
|
("created", "created"),
|
||||||
("modified", "modified"),
|
("modified", "modified"),
|
||||||
("stocks__price", "price"),
|
("stocks__price", "price"),
|
||||||
|
|
@ -61,6 +64,7 @@ class ProductFilter(FilterSet):
|
||||||
"is_digital",
|
"is_digital",
|
||||||
"is_active",
|
"is_active",
|
||||||
"tags",
|
"tags",
|
||||||
|
"slug",
|
||||||
"min_price",
|
"min_price",
|
||||||
"max_price",
|
"max_price",
|
||||||
"brand",
|
"brand",
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,7 @@ class ProductType(DjangoObjectType):
|
||||||
"brand",
|
"brand",
|
||||||
"tags",
|
"tags",
|
||||||
"name",
|
"name",
|
||||||
|
"slug",
|
||||||
"description",
|
"description",
|
||||||
"feedbacks",
|
"feedbacks",
|
||||||
"images",
|
"images",
|
||||||
|
|
|
||||||
19
core/migrations/0013_product_slug.py
Normal file
19
core/migrations/0013_product_slug.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 5.1.8 on 2025-04-30 13:29
|
||||||
|
|
||||||
|
import django_extensions.db.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0012_alter_order_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='slug',
|
||||||
|
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, populate_from=('category.name', 'brand.name', 'name'), unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -33,6 +33,7 @@ from django.utils import timezone
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_extensions.db.fields import AutoSlugField
|
||||||
from mptt.fields import TreeForeignKey
|
from mptt.fields import TreeForeignKey
|
||||||
from mptt.models import MPTTModel
|
from mptt.models import MPTTModel
|
||||||
|
|
||||||
|
|
@ -300,7 +301,13 @@ class Product(NiceModel):
|
||||||
blank=False,
|
blank=False,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("part number for this product"),
|
help_text=_("part number for this product"),
|
||||||
verbose_name=_("part number")
|
verbose_name=_("part number"),
|
||||||
|
)
|
||||||
|
slug = AutoSlugField(
|
||||||
|
populate_from=("category.name", "brand.name", "name"),
|
||||||
|
allow_unicode=True,
|
||||||
|
unique=True,
|
||||||
|
editable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,7 @@ class ProductDetailSerializer(ModelSerializer):
|
||||||
"feedbacks_count",
|
"feedbacks_count",
|
||||||
"quantity",
|
"quantity",
|
||||||
"tags",
|
"tags",
|
||||||
|
"slug",
|
||||||
"images",
|
"images",
|
||||||
"attributes",
|
"attributes",
|
||||||
"rating",
|
"rating",
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ class ProductSimpleSerializer(ModelSerializer):
|
||||||
"uuid",
|
"uuid",
|
||||||
"name",
|
"name",
|
||||||
"is_digital",
|
"is_digital",
|
||||||
|
"slug",
|
||||||
"description",
|
"description",
|
||||||
"partnumber",
|
"partnumber",
|
||||||
"brand",
|
"brand",
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,13 @@ class ProductSitemap(Sitemap):
|
||||||
is_active=True,
|
is_active=True,
|
||||||
brand__is_active=True,
|
brand__is_active=True,
|
||||||
category__is_active=True,
|
category__is_active=True,
|
||||||
).only("uuid", "name", "modified").order_by("-modified")
|
).only("uuid", "name", "modified", "slug").order_by("-modified")
|
||||||
|
|
||||||
def lastmod(self, obj):
|
def lastmod(self, obj):
|
||||||
return obj.modified
|
return obj.modified
|
||||||
|
|
||||||
def location(self, obj):
|
def location(self, obj):
|
||||||
slug = slugify(obj.name)
|
return f"/{LANGUAGE_CODE}/product/{obj.uuid}/{obj.slug}"
|
||||||
return f"/{LANGUAGE_CODE}/product/{obj.uuid}/{slug}"
|
|
||||||
|
|
||||||
|
|
||||||
class CategorySitemap(Sitemap):
|
class CategorySitemap(Sitemap):
|
||||||
|
|
|
||||||
|
|
@ -299,4 +299,8 @@ if getenv("SENTRY_DSN"):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
CSRF_COOKIE_HTTPONLY = True
|
||||||
|
LANGUAGE_COOKIE_HTTPONLY = True
|
||||||
|
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 8888
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 8888
|
||||||
|
|
|
||||||
18
payments/migrations/0004_alter_transaction_payment_method.py
Normal file
18
payments/migrations/0004_alter_transaction_payment_method.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.8 on 2025-04-30 13:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('payments', '0003_alter_transaction_balance'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='payment_method',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue