Features: 1) Add Stock existence filters for has_price and has_stock annotations in queries; 2) Introduce personal_order_tail for improved ordering logic; 3) Support advanced filtering in DjangoFilterConnectionField with augmented filters and types.

Fixes: 1) Correct import order in multiple files; 2) Remove unnecessary migration file metadata changes.

Extra: 1) Refactor ordering logic to streamline query annotations; 2) Improve code readability with explicit list annotations and consistent formatting adjustments.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-10-07 17:49:07 +03:00
parent 0752221d22
commit f646d5ab09
3 changed files with 39 additions and 49 deletions

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("blog", "0005_post_content_fa_ir_post_content_he_il_and_more"), ("blog", "0005_post_content_fa_ir_post_content_he_il_and_more"),
] ]

View file

@ -5,10 +5,10 @@ import uuid
from django.core.exceptions import BadRequest from django.core.exceptions import BadRequest
from django.db.models import ( from django.db.models import (
Avg, Avg,
BooleanField,
Case, Case,
Exists, Exists,
FloatField, FloatField,
IntegerField,
Max, Max,
OuterRef, OuterRef,
Prefetch, Prefetch,
@ -33,7 +33,7 @@ from django_filters import (
) )
from core.elasticsearch import process_query from core.elasticsearch import process_query
from core.models import Address, Brand, Category, Feedback, Order, Product, Wishlist from core.models import Address, Brand, Category, Feedback, Order, Product, Stock, Wishlist
logger = logging.getLogger("django") logger = logging.getLogger("django")
@ -276,41 +276,43 @@ class ProductFilter(FilterSet):
ordering_param = self.data.get("order_by", "") ordering_param = self.data.get("order_by", "")
stock_with_price = Stock.objects.filter(product_uuid=OuterRef("pk"), price__gt=0)
stock_with_qty = Stock.objects.filter(product_uuid=OuterRef("pk"), quantity__gt=0)
qs = qs.annotate( qs = qs.annotate(
has_stock=Case( has_price=Exists(stock_with_price),
When(stocks__quantity__gt=0, then=Value(True)), has_stock=Exists(stock_with_qty),
default=Value(False),
output_field=BooleanField(),
),
has_price=Case(
When(stocks__price__gt=0, then=Value(True)),
default=Value(False),
output_field=BooleanField(),
),
).annotate( ).annotate(
personal_orders_only=Case( personal_order_tail=Case(
When(has_stock=False, has_price=False, then=Value(True)), When(Q(has_price=False) | Q(has_stock=False), then=Value(1)),
default=Value(False), default=Value(0),
output_field=BooleanField(), output_field=IntegerField(),
) )
) )
requested = [part.strip() for part in ordering_param.split(",") if part.strip()] if ordering_param else [] requested = [part.strip() for part in ordering_param.split(",") if part.strip()] if ordering_param else []
mapped_requested = [] mapped_requested: list[str] = []
for part in requested: for part in requested:
desc = part.startswith("-") desc = part.startswith("-")
key = part.lstrip("-") key = part.lstrip("-")
if key == "price": if key == "price":
key = "price_order" key = "price_order"
if key == "random": if key == "random":
key = "?" mapped_requested.append("?")
mapped_requested.append(key)
continue continue
if key == "personal_orders_only":
if key in {"personal_orders_only", "personal_order_only", "personal_order_tail"}:
continue continue
mapped_requested.append(f"-{key}" if desc else key) mapped_requested.append(f"-{key}" if desc else key)
final_ordering = mapped_requested + ["personal_orders_only"] if "?" in mapped_requested:
final_ordering = ["personal_order_tail", "?"]
else:
final_ordering = mapped_requested + ["personal_order_tail"]
if final_ordering: if final_ordering:
qs = qs.order_by(*final_ordering) qs = qs.order_by(*final_ordering)

View file

@ -1,22 +1,22 @@
import logging import logging
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Max, Case, When, Value, IntegerField, BooleanField
from django.utils import timezone
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Case, Exists, IntegerField, OuterRef, Q, Value, When
from django.utils import timezone
from graphene import Field, List, ObjectType, Schema from graphene import Field, List, ObjectType, Schema
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from blog.filters import PostFilter from blog.filters import PostFilter
from blog.graphene.object_types import PostType from blog.graphene.object_types import PostType
from core.filters import ( from core.filters import (
AddressFilter,
BrandFilter, BrandFilter,
CategoryFilter, CategoryFilter,
FeedbackFilter, FeedbackFilter,
OrderFilter, OrderFilter,
ProductFilter, ProductFilter,
WishlistFilter, WishlistFilter,
AddressFilter,
) )
from core.graphene.mutations import ( from core.graphene.mutations import (
AddOrderProduct, AddOrderProduct,
@ -44,6 +44,7 @@ from core.graphene.mutations import (
UpdateProduct, UpdateProduct,
) )
from core.graphene.object_types import ( from core.graphene.object_types import (
AddressType,
AttributeGroupType, AttributeGroupType,
BrandType, BrandType,
CategoryTagType, CategoryTagType,
@ -61,9 +62,9 @@ from core.graphene.object_types import (
StockType, StockType,
VendorType, VendorType,
WishlistType, WishlistType,
AddressType,
) )
from core.models import ( from core.models import (
Address,
AttributeGroup, AttributeGroup,
Brand, Brand,
Category, Category,
@ -79,7 +80,6 @@ from core.models import (
Stock, Stock,
Vendor, Vendor,
Wishlist, Wishlist,
Address,
) )
from core.utils import get_project_parameters from core.utils import get_project_parameters
from core.utils.languages import get_flag_by_language from core.utils.languages import get_flag_by_language
@ -163,35 +163,24 @@ class Query(ObjectType):
.prefetch_related("images", "stocks") .prefetch_related("images", "stocks")
) )
base_qs = ( stock_with_price = Stock.objects.filter(product_uuid=OuterRef("pk"), price__gt=0)
stock_with_qty = Stock.objects.filter(product_uuid=OuterRef("pk"), quantity__gt=0)
return (
base_qs.annotate( base_qs.annotate(
has_stock=Max( has_price=Exists(stock_with_price),
Case( has_stock=Exists(stock_with_qty),
When(stocks__quantity__gt=0, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
),
has_price=Max(
Case(
When(stocks__price__gt=0, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
),
) )
.annotate( .annotate(
personal_order_only=Case( personal_order_tail=Case(
When(has_stock=0, has_price=1, then=Value(True)), When(Q(has_price=False) | Q(has_stock=False), then=Value(1)),
default=Value(False), default=Value(0),
output_field=BooleanField(), output_field=IntegerField(),
) )
) )
.order_by("personal_order_only") .order_by("personal_order_tail")
) )
return base_qs
@staticmethod @staticmethod
def resolve_orders(_parent, info, **kwargs): def resolve_orders(_parent, info, **kwargs):
orders = Order.objects orders = Order.objects