Features: 1) Ensure querysets return distinct results in filters;

Fixes: 1) Address potential duplication issue in querysets;

Extra:
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-19 09:42:02 +03:00
parent d40d7d3c28
commit d72c3947d4

View file

@ -48,19 +48,31 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
class ProductFilter(FilterSet): class ProductFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
categories = CaseInsensitiveListFilter(field_name="category__name", label=_("Categories")) categories = CaseInsensitiveListFilter(
field_name="category__name", label=_("Categories")
)
category_uuid = CharFilter(method="filter_category", label="Category (UUID)") category_uuid = CharFilter(method="filter_category", label="Category (UUID)")
categories_slugs = CaseInsensitiveListFilter(field_name="category__slug", label=_("Categories Slugs")) categories_slugs = CaseInsensitiveListFilter(
field_name="category__slug", label=_("Categories Slugs")
)
tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags"))
min_price = NumberFilter(field_name="stocks__price", lookup_expr="gte", label=_("Min Price")) min_price = NumberFilter(
max_price = NumberFilter(field_name="stocks__price", lookup_expr="lte", label=_("Max Price")) field_name="stocks__price", lookup_expr="gte", label=_("Min Price")
)
max_price = NumberFilter(
field_name="stocks__price", lookup_expr="lte", label=_("Max Price")
)
is_active = BooleanFilter(field_name="is_active", label=_("Is Active")) is_active = BooleanFilter(field_name="is_active", label=_("Is Active"))
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")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug"))
is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital")) is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital"))
include_subcategories = BooleanFilter(method="filter_include_flag", label=_("Include sub-categories")) include_subcategories = BooleanFilter(
method="filter_include_flag", label=_("Include sub-categories")
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(
@ -111,13 +123,16 @@ class ProductFilter(FilterSet):
) )
self.queryset = self.queryset.annotate( self.queryset = self.queryset.annotate(
rating=Coalesce( rating=Coalesce(
Subquery(feedback_qs, output_field=FloatField()), Value(0, output_field=FloatField()) Subquery(feedback_qs, output_field=FloatField()),
Value(0, output_field=FloatField()),
) )
) )
def filter_include_flag(self, queryset, **_kwargs): def filter_include_flag(self, queryset, **_kwargs):
if not self.data.get("category_uuid"): if not self.data.get("category_uuid"):
raise BadRequest(_("there must be a category_uuid to use include_subcategories flag")) raise BadRequest(
_("there must be a category_uuid to use include_subcategories flag")
)
return queryset return queryset
def filter_attributes(self, queryset, _name, value): def filter_attributes(self, queryset, _name, value):
@ -240,7 +255,8 @@ class ProductFilter(FilterSet):
) )
qs = qs.annotate( qs = qs.annotate(
rating=Coalesce( rating=Coalesce(
Subquery(feedback_qs, output_field=FloatField()), Value(0, output_field=FloatField()) Subquery(feedback_qs, output_field=FloatField()),
Value(0, output_field=FloatField()),
) )
) )
return qs.distinct() return qs.distinct()
@ -252,14 +268,26 @@ class OrderFilter(FilterSet):
label=_("Search (ID, product name or part number)"), label=_("Search (ID, product name or part number)"),
) )
min_buy_time = DateTimeFilter(field_name="buy_time", lookup_expr="gte", label=_("Bought after (inclusive)")) min_buy_time = DateTimeFilter(
max_buy_time = DateTimeFilter(field_name="buy_time", lookup_expr="lte", label=_("Bought before (inclusive)")) field_name="buy_time", lookup_expr="gte", label=_("Bought after (inclusive)")
)
max_buy_time = DateTimeFilter(
field_name="buy_time", lookup_expr="lte", label=_("Bought before (inclusive)")
)
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email")) user_email = CharFilter(
user = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID")) field_name="user__email", lookup_expr="iexact", label=_("User email")
)
user = UUIDFilter(
field_name="user__uuid", lookup_expr="exact", label=_("User UUID")
)
status = CharFilter(field_name="status", lookup_expr="icontains", label=_("Status")) status = CharFilter(field_name="status", lookup_expr="icontains", label=_("Status"))
human_readable_id = CharFilter(field_name="human_readable_id", lookup_expr="exact", label=_("Human Readable ID")) human_readable_id = CharFilter(
field_name="human_readable_id",
lookup_expr="exact",
label=_("Human Readable ID"),
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(
@ -299,11 +327,20 @@ class OrderFilter(FilterSet):
class WishlistFilter(FilterSet): class WishlistFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email")) user_email = CharFilter(
user = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID")) field_name="user__email", lookup_expr="iexact", label=_("User email")
)
user = UUIDFilter(
field_name="user__uuid", lookup_expr="exact", label=_("User UUID")
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=(("uuid", "uuid"), ("created", "created"), ("modified", "modified"), ("?", "random")) fields=(
("uuid", "uuid"),
("created", "created"),
("modified", "modified"),
("?", "random"),
)
) )
class Meta: class Meta:
@ -317,7 +354,9 @@ class CategoryFilter(FilterSet):
parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent")) parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent"))
slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug"))
whole = BooleanFilter( whole = BooleanFilter(
field_name="whole", label=_("Whole category(has at least 1 product or not)"), method="filter_whole_categories" field_name="whole",
label=_("Whole category(has at least 1 product or not)"),
method="filter_whole_categories",
) )
tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags"))
level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level")) level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level"))
@ -332,7 +371,16 @@ class CategoryFilter(FilterSet):
class Meta: class Meta:
model = Category model = Category
fields = ["uuid", "name", "parent_uuid", "slug", "tags", "level", "order_by", "whole"] fields = [
"uuid",
"name",
"parent_uuid",
"slug",
"tags",
"level",
"order_by",
"whole",
]
def filter_whole_categories(self, queryset, _name, value): def filter_whole_categories(self, queryset, _name, value):
has_own_products = Exists(Product.objects.filter(category=OuterRef("pk"))) has_own_products = Exists(Product.objects.filter(category=OuterRef("pk")))
@ -364,7 +412,9 @@ class CategoryFilter(FilterSet):
class BrandFilter(FilterSet): class BrandFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
categories = CaseInsensitiveListFilter(field_name="categories__uuid", lookup_expr="exact", label=_("Categories")) categories = CaseInsensitiveListFilter(
field_name="categories__uuid", lookup_expr="exact", label=_("Categories")
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(
@ -381,8 +431,16 @@ class BrandFilter(FilterSet):
class FeedbackFilter(FilterSet): class FeedbackFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
product_uuid = UUIDFilter(field_name="order_product__product__uuid", lookup_expr="exact", label=_("Product UUID")) product_uuid = UUIDFilter(
user_uuid = UUIDFilter(field_name="order_product__order__user__uuid", lookup_expr="exact", label=_("User UUID")) field_name="order_product__product__uuid",
lookup_expr="exact",
label=_("Product UUID"),
)
user_uuid = UUIDFilter(
field_name="order_product__order__user__uuid",
lookup_expr="exact",
label=_("User UUID"),
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(
@ -402,8 +460,12 @@ class FeedbackFilter(FilterSet):
class AddressFilter(FilterSet): class AddressFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
user_uuid = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID")) user_uuid = UUIDFilter(
user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email")) field_name="user__uuid", lookup_expr="exact", label=_("User UUID")
)
user_email = CharFilter(
field_name="user__email", lookup_expr="iexact", label=_("User email")
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(