From 09213dd616ff8130f2823f08b05a0eb81fed3e1b Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 28 May 2025 19:58:40 +0300 Subject: [PATCH 01/15] Fixes: 1) Correct Nominatim API URL by appending '/search' to avoid endpoint errors. --- core/managers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/managers.py b/core/managers.py index 19d78527..96bd2af3 100644 --- a/core/managers.py +++ b/core/managers.py @@ -22,7 +22,7 @@ class AddressManager(models.Manager): "addressdetails": 1, "q": raw_data, } - resp = requests.get(config.NOMINATIM_URL, params=params) + resp = requests.get(config.NOMINATIM_URL.rstrip("/") + "/search", params=params) resp.raise_for_status() results = resp.json() if not results: From 79c97b7e5a3371e4746c5f03498ddaabca770a6f Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 28 May 2025 22:07:06 +0300 Subject: [PATCH 02/15] Features: 1) Add `address_line` field to `Address` model for enhanced customer address representation; 2) Extend serializers with `address_line_1` and `address_line_2` fields; 3) Update address parsing logic to include house number in street and support `address_line` storage; Fixes: None; Extra: Refactor address manager to construct `address_line` from parsed data; --- core/managers.py | 3 ++- core/migrations/0023_address_address_line.py | 18 ++++++++++++++++++ core/models.py | 6 ++++++ core/serializers/__init__.py | 10 ++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 core/migrations/0023_address_address_line.py diff --git a/core/managers.py b/core/managers.py index 96bd2af3..8b502c53 100644 --- a/core/managers.py +++ b/core/managers.py @@ -31,7 +31,7 @@ class AddressManager(models.Manager): # Parse address components addr = data.get("address", {}) - street = addr.get("road") or addr.get("pedestrian") or "" + street = f"{addr.get('road', '') or addr.get('pedestrian', '')}, {addr.get('house_number', '')}" district = addr.get("city_district") or addr.get("suburb") or "" city = addr.get("city") or addr.get("town") or addr.get("village") or "" region = addr.get("state") or addr.get("region") or "" @@ -49,6 +49,7 @@ class AddressManager(models.Manager): # Create the model instance, storing both the input string and full API response return super().create( raw_data=raw_data, + address_line=f"{kwargs.get('address_line_1')}, {kwargs.get('address_line_2')}", street=street, district=district, city=city, diff --git a/core/migrations/0023_address_address_line.py b/core/migrations/0023_address_address_line.py new file mode 100644 index 00000000..55654c18 --- /dev/null +++ b/core/migrations/0023_address_address_line.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-05-28 19:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('core', '0022_category_slug'), + ] + + operations = [ + migrations.AddField( + model_name='address', + name='address_line', + field=models.TextField(blank=True, help_text='address line for the customer', null=True, + verbose_name='address line'), + ), + ] diff --git a/core/models.py b/core/models.py index 3b1b64e9..e36436d8 100644 --- a/core/models.py +++ b/core/models.py @@ -1259,6 +1259,12 @@ class Documentary(NiceModel): class Address(NiceModel): is_publicly_visible = False + address_line = TextField( # noqa: DJ001 + blank=True, + null=True, + help_text=_("address line for the customer"), + verbose_name=_("address line"), + ) street = CharField(_("street"), max_length=255, null=True) # noqa: DJ001 district = CharField(_("district"), max_length=255, null=True) # noqa: DJ001 city = CharField(_("city"), max_length=100, null=True) # noqa: DJ001 diff --git a/core/serializers/__init__.py b/core/serializers/__init__.py index 95e372bf..7e38070b 100644 --- a/core/serializers/__init__.py +++ b/core/serializers/__init__.py @@ -158,6 +158,16 @@ class AddressCreateSerializer(ModelSerializer): # noqa: F405 write_only=True, max_length=512, ) + address_line_1 = CharField( + write_only=True, + max_length=128, + required=False + ) + address_line_2 = CharField( + write_only=True, + max_length=128, + required=False + ) class Meta: model = Address From bc12dcf35c04d974ddf3587483fc325fc7418192 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 13:33:06 +0300 Subject: [PATCH 03/15] Features: 1) Extend permissions to include user-based scoping for instances and querysets; 2) Enable admin override with appropriate model-level permissions; 3) Refine queryset filtering logic to enforce user ownership rules. Fixes: 1) Address incorrect scoping for user-based permissions; 2) Correct queryset handling for actions like "list" and "retrieve." Extra: Refactor permission checks for clarity and maintainability; simplify conditional logic in queryset filters. --- core/permissions.py | 54 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/core/permissions.py b/core/permissions.py index 2a906c36..415585ac 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -22,6 +22,8 @@ class EvibesPermission(permissions.BasePermission): - Standard model perms ('add', 'view', 'change', 'delete') are enforced for all other actions, including for staff users. - Publicly visible models allow anonymous list/retrieve. + - If an instance or queryset has a "user" attribute, ensure that the request.user is the same, + unless the user is an admin with the required django permission. """ ACTION_PERM_MAP = { @@ -64,19 +66,58 @@ class EvibesPermission(permissions.BasePermission): if request.user.has_perm(f"{app_label}.{codename}"): return True - return bool(action in ("list", "retrieve") and getattr(model, "is_publicly_visible", False)) + return bool( + action in ("list", "retrieve") + and getattr(model, "is_publicly_visible", False) + ) + + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + + if hasattr(obj, "user"): + if obj.user == request.user: + return True + # Allow admins who hold the required model permission + app_label = obj._meta.app_label + model_name = obj._meta.model_name + action = getattr(view, "action", None) + perm_prefix = self.ACTION_PERM_MAP.get(action) + return bool(perm_prefix and request.user.has_perm(f"{app_label}.{perm_prefix}_{model_name}")) + + model = view.queryset.model + app_label = model._meta.app_label + model_name = model._meta.model_name + action = getattr(view, "action", None) + perm_prefix = self.ACTION_PERM_MAP.get(action) + return bool(perm_prefix and request.user.has_perm(f"{app_label}.{perm_prefix}_{model_name}")) def has_queryset_permission(self, request, view, queryset): """ Filter the base queryset according to the action and user. - Staff users still require view permissions to see records. + For models with a "user" field, restrict access to records belonging to the request user + unless the admin holds the needed permissions. """ model = view.queryset.model app_label = model._meta.app_label model_name = model._meta.model_name - if view.action in self.USER_SCOPED_ACTIONS: - return queryset.filter(user=request.user) + if hasattr(model, "user"): + if view.action in self.USER_SCOPED_ACTIONS: + return queryset.filter(user=request.user) + if view.action in ("list", "retrieve"): + if request.user.has_perm(f"{app_label}.view_{model_name}"): + if request.user.is_staff: + return queryset + return queryset.filter(user=request.user, is_active=True) + return queryset.none() + + base = queryset.filter(is_active=True, user=request.user) + if request.user.is_staff and request.user.has_perm( + f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}" + ): + return queryset.filter(is_active=True) + return base if view.action in ("list", "retrieve"): if request.user.has_perm(f"{app_label}.view_{model_name}"): @@ -87,10 +128,7 @@ class EvibesPermission(permissions.BasePermission): base = queryset.filter(is_active=True) match view.action: - case "update": - if request.user.has_perm(f"{app_label}.change_{model_name}"): - return base - case "partial_update": + case "update" | "partial_update": if request.user.has_perm(f"{app_label}.change_{model_name}"): return base case "destroy": From e1c7197d49d1d4c32403db80c11988befb7d74be Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 14:08:09 +0300 Subject: [PATCH 04/15] Features: 1) Add `request`, `*args`, and `**kwargs` parameters to `confirm_password_reset` method for compatibility. Fixes: 1) Adjust password reset serializer to use `request.data` instead of `self.request.data`; 2) Remove unnecessary language scoping from category query. Extra: None; --- core/graphene/object_types.py | 2 +- vibes_auth/viewsets.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index ae06b024..141c0322 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -140,7 +140,7 @@ class CategoryType(DjangoObjectType): if depth <= 0: return Category.objects.none() - categories = Category.objects.language(info.context.locale).filter(parent=self) + categories = Category.objects.filter(parent=self) if info.context.user.has_perm("core.view_category"): return categories return categories.filter(is_active=True) diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index ec4a2620..86a8a81b 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -64,9 +64,9 @@ class UserViewSet( @action(detail=False, methods=["post"]) @method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h")) - def confirm_password_reset(self): + def confirm_password_reset(self, request, *args, **kwargs): try: - data = ConfirmPasswordResetSerializer(self.request.data).data + data = ConfirmPasswordResetSerializer(request.data).data if not compare_digest(data.get("password"), data.get("confirm_password")): return Response( From eb48c78b9d829f7f22542d34c81480127d4cf432 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 15:54:01 +0300 Subject: [PATCH 05/15] Fixes: 1) Rename `id` field to `uuid` in `BulkActionOrderProductInput` for clarity and consistency; Extra: align field naming with project standards. --- core/graphene/object_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index 141c0322..dcd0b1ee 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -511,5 +511,5 @@ class SearchResultsType(ObjectType): class BulkActionOrderProductInput(InputObjectType): - id = UUID(required=True) + uuid = UUID(required=True) attributes = GenericScalar(required=False) From 7520184e5622ffb40afda52daa2f59a593aa3eb5 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 17:48:19 +0300 Subject: [PATCH 06/15] Features: 1) Add `filter_parent_uuid` method in `CategoryFilter` to handle parent UUID filtering; 2) Expand `CategoryFilter` fields to include `parent_uuid` and `slug`. Fixes: 1) Update `resolve_categories` in schema to include all categories in query. Extra: 1) Add missing `uuid` import in `filters.py`; 2) Improve handling for null and invalid parent UUID values in `filter_parent_uuid`. --- core/filters.py | 20 ++++++++++++++++++-- core/graphene/schema.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/filters.py b/core/filters.py index 8f250640..044eb905 100644 --- a/core/filters.py +++ b/core/filters.py @@ -1,5 +1,6 @@ import json import logging +import uuid from django.db.models import Avg, FloatField, OuterRef, Q, Subquery, Value from django.db.models.functions import Coalesce @@ -251,7 +252,7 @@ class WishlistFilter(FilterSet): class CategoryFilter(FilterSet): uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") name = CharFilter(field_name="name", lookup_expr="icontains") - parent_uuid = UUIDFilter(field_name="parent__uuid", lookup_expr="exact") + parent_uuid = CharFilter(method="filter_parent_uuid") slug = CharFilter(field_name="slug", lookup_expr="exact") order_by = OrderingFilter( @@ -264,7 +265,22 @@ class CategoryFilter(FilterSet): class Meta: model = Category - fields = ["uuid", "name"] + fields = ["uuid", "name", "parent_uuid", "slug"] + + def filter_parent_uuid(self, queryset, name, value): + """ + If ?parent_uuid= or ?parent_uuid=null, return items with parent=None. + Otherwise treat `value` as a real UUID and filter parent__uuid=value. + """ + if value in ("", "null", "None"): + return queryset.filter(parent=None) + + try: + uuid_val = uuid.UUID(value) + except (ValueError, TypeError): + return queryset + + return queryset.filter(parent__uuid=uuid_val) class BrandFilter(FilterSet): diff --git a/core/graphene/schema.py b/core/graphene/schema.py index 223731fc..9dad47f4 100644 --- a/core/graphene/schema.py +++ b/core/graphene/schema.py @@ -184,7 +184,7 @@ class Query(ObjectType): @staticmethod def resolve_categories(_parent, info, **kwargs): - categories = Category.objects.filter(parent=None) + categories = Category.objects.all() if info.context.user.has_perm("core.view_category"): return categories return categories.filter(is_active=True) From 7e40596cb3e2c8efc7db2d8c80da16e4bdf62c7d Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 18:08:44 +0300 Subject: [PATCH 07/15] Features: 1) Add address_line_1 and address_line_2 fields to Address serializer; 2) Include detailed traceback information in error responses when DEBUG is enabled. Fixes: 1) Correct mismanagement of instance and serializer in update method of viewset. Extra: 1) Minor organizational improvement and clearer formatting in views. --- core/serializers/__init__.py | 6 +++++- vibes_auth/viewsets.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/serializers/__init__.py b/core/serializers/__init__.py index 7e38070b..96f2fe80 100644 --- a/core/serializers/__init__.py +++ b/core/serializers/__init__.py @@ -171,7 +171,11 @@ class AddressCreateSerializer(ModelSerializer): # noqa: F405 class Meta: model = Address - fields = ["raw_data"] + fields = [ + "raw_data", + "address_line_1", + "address_line_2" + ] def create(self, validated_data): raw = validated_data.pop("raw_data") diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index 86a8a81b..7f3739b3 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -86,7 +86,10 @@ class UserViewSet( return Response({"message": _("password reset successfully")}, status=status.HTTP_200_OK) except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: - return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + data = {"error": str(e)} + if DEBUG: + data["detail"] = str(traceback.format_exc()) + return Response(data, status=status.HTTP_400_BAD_REQUEST) @method_decorator(ratelimit(key="ip", rate="3/h" if not DEBUG else "888/h")) def create(self, request, *args, **kwargs): @@ -142,6 +145,9 @@ class UserViewSet( return Response(serializer.data) def update(self, request, pk=None, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + instance = serializer.update(instance=self.get_object(), validated_data=request.data) return Response( - self.get_serializer(self.get_object()).update(instance=self.get_object(), validated_data=request.data).data + self.get_serializer(instance.data) ) From baf165ddd7d9ac5ca6fbde39e69b5435f953be48 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 18:22:44 +0300 Subject: [PATCH 08/15] Features: 1) Add `ProductTagType` to GraphQL schema with associated queries and resolvers; 2) Register `ProductTagViewSet` in API URLs and core router; 3) Implement detailed and simple serializers for `ProductTag`. Fixes: 1) Correct `serializer_class` assignments for `ProductImageViewSet`, `PromoCodeViewSet`, `PromotionViewSet`, `StockViewSet`, and `WishlistViewSet`. Extra: 1) General code cleanup and reorganization for viewsets. --- core/api_urls.py | 2 ++ core/graphene/object_types.py | 11 +++++++++++ core/graphene/schema.py | 9 +++++++++ core/viewsets.py | 35 ++++++++++++++++++++--------------- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/core/api_urls.py b/core/api_urls.py index fa2d3fd5..51bc983d 100644 --- a/core/api_urls.py +++ b/core/api_urls.py @@ -20,6 +20,7 @@ from core.viewsets import ( CategoryViewSet, FeedbackViewSet, OrderViewSet, + ProductTagViewSet, ProductViewSet, PromoCodeViewSet, PromotionViewSet, @@ -41,6 +42,7 @@ core_router.register(r"stocks", StockViewSet, basename="stocks") core_router.register(r"promo_codes", PromoCodeViewSet, basename="promo_codes") core_router.register(r"promotions", PromotionViewSet, basename="promotions") core_router.register(r"addresses", AddressViewSet, basename="addresses") +core_router.register(r"product_tags", ProductTagViewSet, basename="product_tags") sitemaps = { "products": ProductSitemap, diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index dcd0b1ee..2aff9c9a 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -21,6 +21,7 @@ from core.models import ( OrderProduct, Product, ProductImage, + ProductTag, PromoCode, Promotion, Stock, @@ -455,6 +456,16 @@ class WishlistType(DjangoObjectType): description = _("wishlists") +class ProductTagType(DjangoObjectType): + product_set = DjangoFilterConnectionField(ProductType, description=_("tagged products")) + + class Meta: + model = ProductTag + interfaces = (relay.Node,) + fields = ("uuid", "tag_name", "name", "product_set") + description = _("product tags") + + class ConfigType(ObjectType): project_name = String(description=_("project name")) base_domain = String(description=_("company email")) diff --git a/core/graphene/schema.py b/core/graphene/schema.py index 9dad47f4..f8f2520e 100644 --- a/core/graphene/schema.py +++ b/core/graphene/schema.py @@ -48,6 +48,7 @@ from core.graphene.object_types import ( OrderProductType, OrderType, ProductImageType, + ProductTagType, ProductType, PromoCodeType, PromotionType, @@ -64,6 +65,7 @@ from core.models import ( OrderProduct, Product, ProductImage, + ProductTag, PromoCode, Promotion, Stock, @@ -108,6 +110,7 @@ class Query(ObjectType): product_images = DjangoFilterConnectionField(ProductImageType) stocks = DjangoFilterConnectionField(StockType) wishlists = DjangoFilterConnectionField(WishlistType, filterset_class=WishlistFilter) + product_tags = DjangoFilterConnectionField(ProductTagType) promotions = DjangoFilterConnectionField(PromotionType) promocodes = DjangoFilterConnectionField(PromoCodeType) brands = DjangoFilterConnectionField(BrandType, filterset_class=BrandFilter) @@ -280,6 +283,12 @@ class Query(ObjectType): return promocodes.filter(user__uuid=kwargs.get("user_uuid")) or promocodes.all() return promocodes.filter(is_active=True, user=info.context.user) + @staticmethod + def resolve_product_tags(_parent, info, **kwargs): + if info.context.user.has_perm("core.view_producttag"): + return ProductTag.objects.all() + return ProductTag.objects.filter(is_active=True) + class Mutation(ObjectType): search = Search.Field() diff --git a/core/viewsets.py b/core/viewsets.py index 122c7dad..756edd55 100644 --- a/core/viewsets.py +++ b/core/viewsets.py @@ -73,13 +73,18 @@ from core.serializers import ( OrderProductSimpleSerializer, OrderSimpleSerializer, ProductDetailSerializer, + ProductImageDetailSerializer, ProductImageSimpleSerializer, ProductSimpleSerializer, + ProductTagDetailSerializer, ProductTagSimpleSerializer, + PromoCodeDetailSerializer, PromoCodeSimpleSerializer, + PromotionDetailSerializer, PromotionSimpleSerializer, RemoveOrderProductSerializer, RemoveWishlistProductSerializer, + StockDetailSerializer, StockSimpleSerializer, VendorSimpleSerializer, WishlistDetailSerializer, @@ -364,21 +369,11 @@ class OrderProductViewSet(EvibesViewSet): } -class ProductTagViewSet(EvibesViewSet): - queryset = ProductTag.objects.all() - filter_backends = [DjangoFilterBackend] - filterset_fields = ["tag_name", "is_active"] - serializer_class = AttributeGroupDetailSerializer - action_serializer_classes = { - "list": ProductTagSimpleSerializer, - } - - class ProductImageViewSet(EvibesViewSet): queryset = ProductImage.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["product", "priority", "is_active"] - serializer_class = AttributeGroupDetailSerializer + serializer_class = ProductImageDetailSerializer action_serializer_classes = { "list": ProductImageSimpleSerializer, } @@ -388,7 +383,7 @@ class PromoCodeViewSet(EvibesViewSet): queryset = PromoCode.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["code", "discount_amount", "discount_percent", "start_time", "end_time", "used_on", "is_active"] - serializer_class = AttributeGroupDetailSerializer + serializer_class = PromoCodeDetailSerializer action_serializer_classes = { "list": PromoCodeSimpleSerializer, } @@ -398,7 +393,7 @@ class PromotionViewSet(EvibesViewSet): queryset = Promotion.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["name", "discount_percent", "is_active"] - serializer_class = AttributeGroupDetailSerializer + serializer_class = PromotionDetailSerializer action_serializer_classes = { "list": PromotionSimpleSerializer, } @@ -408,7 +403,7 @@ class StockViewSet(EvibesViewSet): queryset = Stock.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["vendor", "product", "sku", "is_active"] - serializer_class = AttributeGroupDetailSerializer + serializer_class = StockDetailSerializer action_serializer_classes = { "list": StockSimpleSerializer, } @@ -419,7 +414,7 @@ class WishlistViewSet(EvibesViewSet): queryset = Wishlist.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["user", "is_active"] - serializer_class = AttributeGroupDetailSerializer + serializer_class = WishlistDetailSerializer action_serializer_classes = { "list": WishlistSimpleSerializer, } @@ -535,3 +530,13 @@ class AddressViewSet(EvibesViewSet): ) return Response(suggestions, status=status.HTTP_200_OK) + + +class ProductTagViewSet(EvibesViewSet): + queryset = ProductTag.objects.all() + filter_backends = [DjangoFilterBackend] + filterset_fields = ["tag_name", "is_active"] + serializer_class = ProductTagDetailSerializer + action_serializer_classes = { + "list": ProductTagSimpleSerializer, + } From f3ebf029ef17f377e945de0b9af506fd3bf8702a Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 19:18:07 +0300 Subject: [PATCH 09/15] Features: 1) Add `filter_fields` to product tags in graphene object types; Fixes: 1) None; Extra: 1) None; --- core/graphene/object_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index 2aff9c9a..2465ce0a 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -463,6 +463,7 @@ class ProductTagType(DjangoObjectType): model = ProductTag interfaces = (relay.Node,) fields = ("uuid", "tag_name", "name", "product_set") + filter_fields = ["uuid", "tag_name", "name"] description = _("product tags") From 86e2d787b1196921b478b030bf07b7c3f70fee82 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:23:43 +0300 Subject: [PATCH 10/15] Features: 1) Limit distinct values to 128 elements when count exceeds threshold; 2) Replace CategoryDetailSerializer with CategorySimpleSerializer in BrandDetailSerializer; Fixes: 1) Add conditional caching based on user permissions; Extra: 1) Remove redundant condition on distinct values count; 2) Minor readability improvements within detail serializer logic; --- core/serializers/detail.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/core/serializers/detail.py b/core/serializers/detail.py index 0ef62c6f..b4ecc9a3 100644 --- a/core/serializers/detail.py +++ b/core/serializers/detail.py @@ -89,20 +89,19 @@ class CategoryDetailSerializer(ModelSerializer): .distinct() ) - distinct_vals_list = list(distinct_vals) + distinct_vals_list = list(distinct_vals)[0:128] if len(list(distinct_vals)) > 128 else list(distinct_vals) - if len(distinct_vals_list) <= 256: - filterable_results.append( - { - "attribute_name": attr.name, - "possible_values": distinct_vals_list, - "value_type": attr.value_type, - } - ) - else: - continue + filterable_results.append( + { + "attribute_name": attr.name, + "possible_values": distinct_vals_list, + "value_type": attr.value_type, + } + ) + + if not user.has_perm("view_attribute"): + cache.set(f"{obj.uuid}_filterable_results", filterable_results, 86400) - cache.set(f"{obj.uuid}_filterable_results", filterable_results, 86400) return filterable_results def get_children(self, obj) -> list[dict]: @@ -123,7 +122,7 @@ class CategoryDetailSerializer(ModelSerializer): class BrandDetailSerializer(ModelSerializer): - categories = CategoryDetailSerializer(many=True) + categories = CategorySimpleSerializer(many=True) small_logo = SerializerMethodField() big_logo = SerializerMethodField() From d3e9775d5499ce7924495ec054ddee9f9eed473d Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:24:21 +0300 Subject: [PATCH 11/15] Features: None; Fixes: None; Extra: 1) Bump version to 2.7.1 in pyproject.toml and settings/base.py. --- evibes/settings/base.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/evibes/settings/base.py b/evibes/settings/base.py index cb00076f..5e93a7fc 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -2,7 +2,7 @@ import logging from os import getenv from pathlib import Path -EVIBES_VERSION = "2.7.0" +EVIBES_VERSION = "2.7.1" BASE_DIR = Path(__file__).resolve().parent.parent.parent diff --git a/pyproject.toml b/pyproject.toml index 551ae696..535e2eef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "eVibes" -version = "2.7.0" +version = "2.7.1" description = "eVibes is an open-source eCommerce backend service built with Django. It’s designed for flexibility, making it ideal for various use cases and learning Django skills. The project is easy to customize, allowing for straightforward editing and extension." authors = ["fureunoir "] readme = "README.md" From 73b1e99dc1a024d9cd505bf84f660120630e229c Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:28:36 +0300 Subject: [PATCH 12/15] Features: 1) Add response logging of received serializer data in debug mode for enhanced error diagnostics; Fixes: 1) Resolve inconsistent variable usage in `confirm_password_reset` by replacing `data` with `serializer_data` across the method; Extra: 1) Minor cleanup for improved clarity and maintainability. --- vibes_auth/viewsets.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index 7f3739b3..ce13b17a 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -65,23 +65,24 @@ class UserViewSet( @action(detail=False, methods=["post"]) @method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h")) def confirm_password_reset(self, request, *args, **kwargs): + serializer_data = None try: - data = ConfirmPasswordResetSerializer(request.data).data + serializer_data = ConfirmPasswordResetSerializer(request.data).data - if not compare_digest(data.get("password"), data.get("confirm_password")): + if not compare_digest(data.get("password"), serializer_data.get("confirm_password")): return Response( {"error": _("passwords do not match")}, status=status.HTTP_400_BAD_REQUEST, ) - uuid = urlsafe_base64_decode(data.get("uidb64")).decode() + uuid = urlsafe_base64_decode(serializer_data.get("uidb64")).decode() user = User.objects.get(pk=uuid) password_reset_token = PasswordResetTokenGenerator() - if not password_reset_token.check_token(user, data.get("token")): + if not password_reset_token.check_token(user, serializer_data.get("token")): return Response({"error": _("token is invalid!")}, status=status.HTTP_400_BAD_REQUEST) - user.set_password(data.get("password")) + user.set_password(serializer_data.get("password")) user.save() return Response({"message": _("password reset successfully")}, status=status.HTTP_200_OK) @@ -89,6 +90,7 @@ class UserViewSet( data = {"error": str(e)} if DEBUG: data["detail"] = str(traceback.format_exc()) + data["received"] = str(serializer_data) return Response(data, status=status.HTTP_400_BAD_REQUEST) @method_decorator(ratelimit(key="ip", rate="3/h" if not DEBUG else "888/h")) From 0db69018e24da79c50f22776f89e953c33a98a14 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:28:58 +0300 Subject: [PATCH 13/15] Fixes: 1) Correct key used to access password field in `compare_digest`. --- vibes_auth/viewsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index ce13b17a..8cff1da1 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -69,7 +69,7 @@ class UserViewSet( try: serializer_data = ConfirmPasswordResetSerializer(request.data).data - if not compare_digest(data.get("password"), serializer_data.get("confirm_password")): + if not compare_digest(serializer_data.get("password"), serializer_data.get("confirm_password")): return Response( {"error": _("passwords do not match")}, status=status.HTTP_400_BAD_REQUEST, From 7b5585ea9f32034ae8fbce26c731d31dc9c9ff40 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:42:13 +0300 Subject: [PATCH 14/15] Features: 1) Enforced password validation using Django's `validate_password` in password reset flows; 2) Added handling for `ValidationError` during password validation; Fixes: 1) Removed redundant import for `ValidationError` from `rest_framework.exceptions`; 2) Fixed request data handling in `confirm_password_reset` to directly use incoming data instead of serialized data; Extra: 1) Minor adjustments to error messages for consistency; 2) Cleaned up unused variables in `confirm_password_reset`. --- vibes_auth/graphene/mutations.py | 11 ++++++----- vibes_auth/viewsets.py | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/vibes_auth/graphene/mutations.py b/vibes_auth/graphene/mutations.py index 28750979..fae1bb69 100644 --- a/vibes_auth/graphene/mutations.py +++ b/vibes_auth/graphene/mutations.py @@ -3,7 +3,7 @@ from hmac import compare_digest from django.contrib.auth.password_validation import validate_password from django.contrib.auth.tokens import PasswordResetTokenGenerator -from django.core.exceptions import BadRequest, PermissionDenied +from django.core.exceptions import BadRequest, PermissionDenied, ValidationError from django.db import IntegrityError from django.http import Http404 from django.utils.http import urlsafe_base64_decode @@ -11,7 +11,6 @@ from django.utils.translation import gettext_lazy as _ from graphene import UUID, Boolean, Field, List, String from graphene.types.generic import GenericScalar from graphene_file_upload.scalars import Upload -from rest_framework.exceptions import ValidationError from core.graphene import BaseMutation from core.utils.messages import permission_denied_message @@ -123,8 +122,8 @@ class UpdateUser(BaseMutation): password = kwargs.get("password", "") confirm_password = kwargs.get("confirm_password", "") - if compare_digest(password.lower(), email.lower()): - raise BadRequest(_("password too weak")) + if password: + validate_password(password=password, user=user) if not compare_digest(password, "") and compare_digest(password, confirm_password): user.set_password(password) @@ -314,13 +313,15 @@ class ConfirmResetPassword(BaseMutation): if not password_reset_token.check_token(user, token): raise BadRequest(_("token is invalid!")) + validate_password(password=password, user=user) + user.set_password(password) user.save() return ConfirmResetPassword(success=True) - except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: + except (TypeError, ValueError, OverflowError, ValidationError, User.DoesNotExist) as e: raise BadRequest(_(f"something went wrong: {e!s}")) diff --git a/vibes_auth/viewsets.py b/vibes_auth/viewsets.py index 8cff1da1..8c4fae3d 100644 --- a/vibes_auth/viewsets.py +++ b/vibes_auth/viewsets.py @@ -3,7 +3,9 @@ import traceback from contextlib import suppress from secrets import compare_digest +from django.contrib.auth.password_validation import validate_password from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.core.exceptions import ValidationError from django.utils.decorators import method_decorator from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext_lazy as _ @@ -20,7 +22,6 @@ from evibes.settings import DEBUG from vibes_auth.docs.drf.viewsets import USER_SCHEMA from vibes_auth.models import User from vibes_auth.serializers import ( - ConfirmPasswordResetSerializer, UserSerializer, ) from vibes_auth.utils.emailing import send_reset_password_email_task @@ -65,32 +66,32 @@ class UserViewSet( @action(detail=False, methods=["post"]) @method_decorator(ratelimit(key="ip", rate="2/h" if not DEBUG else "888/h")) def confirm_password_reset(self, request, *args, **kwargs): - serializer_data = None try: - serializer_data = ConfirmPasswordResetSerializer(request.data).data - if not compare_digest(serializer_data.get("password"), serializer_data.get("confirm_password")): + if not compare_digest(request.data.get("password"), request.data.get("confirm_password")): return Response( {"error": _("passwords do not match")}, status=status.HTTP_400_BAD_REQUEST, ) - uuid = urlsafe_base64_decode(serializer_data.get("uidb64")).decode() + uuid = urlsafe_base64_decode(request.data.get("uidb64")).decode() user = User.objects.get(pk=uuid) + validate_password(password=request.data.get("password"), user=user) + password_reset_token = PasswordResetTokenGenerator() - if not password_reset_token.check_token(user, serializer_data.get("token")): + if not password_reset_token.check_token(user, request.data.get("token")): return Response({"error": _("token is invalid!")}, status=status.HTTP_400_BAD_REQUEST) - user.set_password(serializer_data.get("password")) + user.set_password(request.data.get("password")) user.save() return Response({"message": _("password reset successfully")}, status=status.HTTP_200_OK) - except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: + except (TypeError, ValueError, OverflowError, ValidationError, User.DoesNotExist) as e: data = {"error": str(e)} if DEBUG: data["detail"] = str(traceback.format_exc()) - data["received"] = str(serializer_data) + data["received"] = str(request.data) return Response(data, status=status.HTTP_400_BAD_REQUEST) @method_decorator(ratelimit(key="ip", rate="3/h" if not DEBUG else "888/h")) From 04656ea223a1213758225b5b9802734ba340d807 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 29 May 2025 22:56:19 +0300 Subject: [PATCH 15/15] Features: 1) Add "list" and "retrieve" to `USER_SCOPED_ACTIONS`; 2) Include new image asset `evibes-big-simple.png`; Fixes: None; Extra: 1) Simplify queryset filtering logic for "list" and "retrieve" actions; --- core/docs/images/evibes-big-simple.png | Bin 0 -> 62415 bytes core/permissions.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 core/docs/images/evibes-big-simple.png diff --git a/core/docs/images/evibes-big-simple.png b/core/docs/images/evibes-big-simple.png new file mode 100644 index 0000000000000000000000000000000000000000..08ba5f8c76520878375d3c963e57a3b9c3682d9a GIT binary patch literal 62415 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 z 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%0D~J_n{`<3=XQ&% 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 literal 0 HcmV?d00001 diff --git a/core/permissions.py b/core/permissions.py index 415585ac..c033af30 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -36,6 +36,8 @@ class EvibesPermission(permissions.BasePermission): } USER_SCOPED_ACTIONS = { + "list", + "retrieve", "buy", "buy_unregistered", "current", @@ -107,9 +109,7 @@ class EvibesPermission(permissions.BasePermission): return queryset.filter(user=request.user) if view.action in ("list", "retrieve"): if request.user.has_perm(f"{app_label}.view_{model_name}"): - if request.user.is_staff: - return queryset - return queryset.filter(user=request.user, is_active=True) + return queryset return queryset.none() base = queryset.filter(is_active=True, user=request.user)