From 3ec0991aa6372f5940233a05dee5306a4feef006 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 30 Apr 2025 16:31:54 +0300 Subject: [PATCH] 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. --- core/filters.py | 4 ++++ core/graphene/object_types.py | 1 + core/migrations/0013_product_slug.py | 19 +++++++++++++++++++ core/models.py | 9 ++++++++- core/serializers/detail.py | 1 + core/serializers/simple.py | 1 + core/sitemaps.py | 5 ++--- evibes/settings/base.py | 4 ++++ .../0004_alter_transaction_payment_method.py | 18 ++++++++++++++++++ 9 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 core/migrations/0013_product_slug.py create mode 100644 payments/migrations/0004_alter_transaction_payment_method.py diff --git a/core/filters.py b/core/filters.py index c6d85bec..f36915cb 100644 --- a/core/filters.py +++ b/core/filters.py @@ -34,12 +34,15 @@ class ProductFilter(FilterSet): brand = CharFilter(field_name="brand__name", lookup_expr="iexact", label="Brand") attributes = CharFilter(method="filter_attributes", label="Attributes") 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( fields=( ("uuid", "uuid"), ("rating", "rating"), ("name", "name"), + ("slug", "slug"), ("created", "created"), ("modified", "modified"), ("stocks__price", "price"), @@ -61,6 +64,7 @@ class ProductFilter(FilterSet): "is_digital", "is_active", "tags", + "slug", "min_price", "max_price", "brand", diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index 40a80fbc..7b4570e0 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -329,6 +329,7 @@ class ProductType(DjangoObjectType): "brand", "tags", "name", + "slug", "description", "feedbacks", "images", diff --git a/core/migrations/0013_product_slug.py b/core/migrations/0013_product_slug.py new file mode 100644 index 00000000..f1180bfe --- /dev/null +++ b/core/migrations/0013_product_slug.py @@ -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), + ), + ] diff --git a/core/models.py b/core/models.py index dec04a26..855dc903 100644 --- a/core/models.py +++ b/core/models.py @@ -33,6 +33,7 @@ from django.utils import timezone from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext_lazy as _ +from django_extensions.db.fields import AutoSlugField from mptt.fields import TreeForeignKey from mptt.models import MPTTModel @@ -300,7 +301,13 @@ class Product(NiceModel): blank=False, null=True, 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: diff --git a/core/serializers/detail.py b/core/serializers/detail.py index 346b30d5..5ca9b5a5 100644 --- a/core/serializers/detail.py +++ b/core/serializers/detail.py @@ -306,6 +306,7 @@ class ProductDetailSerializer(ModelSerializer): "feedbacks_count", "quantity", "tags", + "slug", "images", "attributes", "rating", diff --git a/core/serializers/simple.py b/core/serializers/simple.py index 7af23a28..0d142679 100644 --- a/core/serializers/simple.py +++ b/core/serializers/simple.py @@ -168,6 +168,7 @@ class ProductSimpleSerializer(ModelSerializer): "uuid", "name", "is_digital", + "slug", "description", "partnumber", "brand", diff --git a/core/sitemaps.py b/core/sitemaps.py index 18ae5824..c443ef42 100644 --- a/core/sitemaps.py +++ b/core/sitemaps.py @@ -16,14 +16,13 @@ class ProductSitemap(Sitemap): is_active=True, brand__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): return obj.modified def location(self, obj): - slug = slugify(obj.name) - return f"/{LANGUAGE_CODE}/product/{obj.uuid}/{slug}" + return f"/{LANGUAGE_CODE}/product/{obj.uuid}/{obj.slug}" class CategorySitemap(Sitemap): diff --git a/evibes/settings/base.py b/evibes/settings/base.py index f703a2ec..954c8ca3 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -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 diff --git a/payments/migrations/0004_alter_transaction_payment_method.py b/payments/migrations/0004_alter_transaction_payment_method.py new file mode 100644 index 00000000..241ecc4a --- /dev/null +++ b/payments/migrations/0004_alter_transaction_payment_method.py @@ -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), + ), + ]