Features: 1) Add with_related method to managers for optimized prefetching; 2) Add indexes to models for enhanced query performance; 3) Introduce cached_property for feedback count and product quantity; 4) Implement Exists and OuterRef filters for active stock validation in viewsets.
Fixes: 1) Simplify rating and feedback count logic by removing caching layers; 2) Refactor price and quantity calculations for improved accuracy and simplicity; 3) Optimize total price and quantity aggregations in orders by leveraging Django ORM tools. Extra: Adjusted import statements, removed redundant cache logic, and cleaned up methods for better readability and maintainability.
This commit is contained in:
parent
00e94a2b29
commit
e894affad7
3 changed files with 52 additions and 47 deletions
|
|
@ -84,3 +84,8 @@ class ProductManager(MultilingualManager):
|
||||||
stocks__vendor__is_active=True,
|
stocks__vendor__is_active=True,
|
||||||
stocks__quantity__gt=0,
|
stocks__quantity__gt=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def with_related(self):
|
||||||
|
return self.select_related("category", "brand").prefetch_related(
|
||||||
|
"tags", "stocks", "images", "attributes__attribute__group"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from django.db.models import (
|
||||||
CharField,
|
CharField,
|
||||||
DateTimeField,
|
DateTimeField,
|
||||||
DecimalField,
|
DecimalField,
|
||||||
|
F,
|
||||||
FileField,
|
FileField,
|
||||||
FloatField,
|
FloatField,
|
||||||
ForeignKey,
|
ForeignKey,
|
||||||
|
|
@ -31,6 +32,7 @@ from django.db.models import (
|
||||||
OneToOneField,
|
OneToOneField,
|
||||||
PositiveIntegerField,
|
PositiveIntegerField,
|
||||||
QuerySet,
|
QuerySet,
|
||||||
|
Sum,
|
||||||
TextField,
|
TextField,
|
||||||
URLField,
|
URLField,
|
||||||
)
|
)
|
||||||
|
|
@ -38,6 +40,7 @@ from django.db.models.indexes import Index
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
from django.utils.functional import cached_property
|
||||||
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 django_extensions.db.fields import AutoSlugField
|
||||||
|
|
@ -177,6 +180,7 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
|
||||||
verbose_name_plural = _("vendors")
|
verbose_name_plural = _("vendors")
|
||||||
indexes = [
|
indexes = [
|
||||||
GinIndex(fields=["authentication"]),
|
GinIndex(fields=["authentication"]),
|
||||||
|
Index(fields=["name"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -512,6 +516,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("provide a clear identifying name for the product"),
|
help_text=_("provide a clear identifying name for the product"),
|
||||||
verbose_name=_("product name"),
|
verbose_name=_("product name"),
|
||||||
|
db_index=True,
|
||||||
)
|
)
|
||||||
description = TextField(
|
description = TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -556,49 +561,37 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("product")
|
verbose_name = _("product")
|
||||||
verbose_name_plural = _("products")
|
verbose_name_plural = _("products")
|
||||||
|
indexes = [
|
||||||
|
Index(fields=["is_active", "brand", "category"]),
|
||||||
|
Index(fields=["slug"]),
|
||||||
|
Index(fields=["sku"]),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rating(self) -> float:
|
def rating(self) -> float:
|
||||||
cache_key = f"product_rating_{self.pk}"
|
|
||||||
rating = cache.get(cache_key)
|
|
||||||
if rating is None:
|
|
||||||
feedbacks = Feedback.objects.filter(order_product__product_id=self.pk)
|
feedbacks = Feedback.objects.filter(order_product__product_id=self.pk)
|
||||||
rating = feedbacks.aggregate(Avg("rating"))["rating__avg"] or 0
|
rating = feedbacks.aggregate(Avg("rating"))["rating__avg"] or 0
|
||||||
cache.set(cache_key, rating, 86400)
|
|
||||||
return float(round(rating, 2))
|
return float(round(rating, 2))
|
||||||
|
|
||||||
@rating.setter
|
@rating.setter
|
||||||
def rating(self, value: float):
|
def rating(self, value: float):
|
||||||
self.__dict__["rating"] = value
|
self.__dict__["rating"] = value
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def feedbacks_count(self) -> int:
|
def feedbacks_count(self) -> int:
|
||||||
cache_key = f"product_feedbacks_count_{self.pk}"
|
return Feedback.objects.filter(order_product__product_id=self.pk).count()
|
||||||
feedbacks_count = cache.get(cache_key)
|
|
||||||
if feedbacks_count is None:
|
|
||||||
feedbacks_count = Feedback.objects.filter(order_product__product_id=self.pk).count()
|
|
||||||
cache.set(cache_key, feedbacks_count, 604800)
|
|
||||||
return feedbacks_count
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price(self: Self) -> float:
|
def price(self: Self) -> float:
|
||||||
stock = self.stocks.all().order_by("-price").only("price").first()
|
stock = self.stocks.only("price").order_by("-price").first()
|
||||||
price = stock.price if stock else 0.0
|
return round(stock.price, 2) if stock else 0.0
|
||||||
return round(price, 2)
|
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def quantity(self) -> int:
|
def quantity(self) -> int:
|
||||||
cache_key = f"product_quantity_{self.pk}"
|
return self.stocks.aggregate(total=Sum("quantity"))["total"] or 0
|
||||||
quantity = cache.get(cache_key, 0)
|
|
||||||
if not quantity:
|
|
||||||
stocks = self.stocks.only("quantity")
|
|
||||||
for stock in stocks:
|
|
||||||
quantity += stock.quantity
|
|
||||||
cache.set(cache_key, quantity, 3600)
|
|
||||||
return quantity
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_orders(self):
|
def total_orders(self):
|
||||||
|
|
@ -1178,6 +1171,10 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("order")
|
verbose_name = _("order")
|
||||||
verbose_name_plural = _("orders")
|
verbose_name_plural = _("orders")
|
||||||
|
indexes = [
|
||||||
|
Index(fields=["user", "status"]),
|
||||||
|
Index(fields=["status", "buy_time"]),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"#{self.human_readable_id} for {self.user.email if self.user else 'unregistered user'}"
|
return f"#{self.human_readable_id} for {self.user.email if self.user else 'unregistered user'}"
|
||||||
|
|
@ -1225,24 +1222,16 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_price(self) -> float:
|
def total_price(self) -> float:
|
||||||
return (
|
total = self.order_products.exclude(status__in=FAILED_STATUSES).aggregate(
|
||||||
round(
|
total=Sum(F("buy_price") * F("quantity"), output_field=FloatField())
|
||||||
sum(
|
)["total"]
|
||||||
(
|
|
||||||
order_product.buy_price * order_product.quantity
|
return round(total or 0.0, 2)
|
||||||
if order_product.status not in FAILED_STATUSES and order_product.buy_price is not None
|
|
||||||
else 0.0
|
|
||||||
)
|
|
||||||
for order_product in self.order_products.all()
|
|
||||||
),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
or 0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_quantity(self) -> int:
|
def total_quantity(self) -> int:
|
||||||
return sum([op.quantity for op in self.order_products.all()])
|
total = self.order_products.aggregate(total=Sum("quantity"))["total"]
|
||||||
|
return total or 0
|
||||||
|
|
||||||
def add_product(
|
def add_product(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1676,6 +1665,8 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
||||||
verbose_name_plural = _("order products")
|
verbose_name_plural = _("order products")
|
||||||
indexes = [
|
indexes = [
|
||||||
GinIndex(fields=["notifications", "attributes"]),
|
GinIndex(fields=["notifications", "attributes"]),
|
||||||
|
Index(fields=["order", "status"]),
|
||||||
|
Index(fields=["product", "status"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
def return_balance_back(self):
|
def return_balance_back(self):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from uuid import UUID
|
||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Prefetch, Q
|
from django.db.models import Prefetch, Q, OuterRef, Exists
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
|
@ -452,14 +452,23 @@ class ProductViewSet(EvibesViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
|
|
||||||
|
qs = qs.select_related("brand", "category")
|
||||||
|
|
||||||
if self.request.user.has_perm("core.view_product"):
|
if self.request.user.has_perm("core.view_product"):
|
||||||
return qs
|
return qs
|
||||||
return qs.filter(
|
|
||||||
|
active_stocks = Stock.objects.filter(product_id=OuterRef("pk"), vendor__is_active=True)
|
||||||
|
|
||||||
|
return (
|
||||||
|
qs.filter(
|
||||||
is_active=True,
|
is_active=True,
|
||||||
brand__is_active=True,
|
brand__is_active=True,
|
||||||
category__is_active=True,
|
category__is_active=True,
|
||||||
stocks__isnull=False,
|
)
|
||||||
stocks__vendor__is_active=True,
|
.annotate(has_active_stocks=Exists(active_stocks))
|
||||||
|
.filter(has_active_stocks=True)
|
||||||
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue