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__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,
|
||||
DateTimeField,
|
||||
DecimalField,
|
||||
F,
|
||||
FileField,
|
||||
FloatField,
|
||||
ForeignKey,
|
||||
|
|
@ -31,6 +32,7 @@ from django.db.models import (
|
|||
OneToOneField,
|
||||
PositiveIntegerField,
|
||||
QuerySet,
|
||||
Sum,
|
||||
TextField,
|
||||
URLField,
|
||||
)
|
||||
|
|
@ -38,6 +40,7 @@ from django.db.models.indexes import Index
|
|||
from django.http import Http404
|
||||
from django.utils import timezone
|
||||
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.translation import gettext_lazy as _
|
||||
from django_extensions.db.fields import AutoSlugField
|
||||
|
|
@ -177,6 +180,7 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
|
|||
verbose_name_plural = _("vendors")
|
||||
indexes = [
|
||||
GinIndex(fields=["authentication"]),
|
||||
Index(fields=["name"]),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -512,6 +516,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
|||
max_length=255,
|
||||
help_text=_("provide a clear identifying name for the product"),
|
||||
verbose_name=_("product name"),
|
||||
db_index=True,
|
||||
)
|
||||
description = TextField(
|
||||
blank=True,
|
||||
|
|
@ -556,49 +561,37 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): # type: ignore
|
|||
class Meta:
|
||||
verbose_name = _("product")
|
||||
verbose_name_plural = _("products")
|
||||
indexes = [
|
||||
Index(fields=["is_active", "brand", "category"]),
|
||||
Index(fields=["slug"]),
|
||||
Index(fields=["sku"]),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
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)
|
||||
rating = feedbacks.aggregate(Avg("rating"))["rating__avg"] or 0
|
||||
cache.set(cache_key, rating, 86400)
|
||||
feedbacks = Feedback.objects.filter(order_product__product_id=self.pk)
|
||||
rating = feedbacks.aggregate(Avg("rating"))["rating__avg"] or 0
|
||||
return float(round(rating, 2))
|
||||
|
||||
@rating.setter
|
||||
def rating(self, value: float):
|
||||
self.__dict__["rating"] = value
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def feedbacks_count(self) -> int:
|
||||
cache_key = f"product_feedbacks_count_{self.pk}"
|
||||
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
|
||||
return Feedback.objects.filter(order_product__product_id=self.pk).count()
|
||||
|
||||
@property
|
||||
def price(self: Self) -> float:
|
||||
stock = self.stocks.all().order_by("-price").only("price").first()
|
||||
price = stock.price if stock else 0.0
|
||||
return round(price, 2)
|
||||
stock = self.stocks.only("price").order_by("-price").first()
|
||||
return round(stock.price, 2) if stock else 0.0
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def quantity(self) -> int:
|
||||
cache_key = f"product_quantity_{self.pk}"
|
||||
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
|
||||
return self.stocks.aggregate(total=Sum("quantity"))["total"] or 0
|
||||
|
||||
@property
|
||||
def total_orders(self):
|
||||
|
|
@ -1178,6 +1171,10 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
class Meta:
|
||||
verbose_name = _("order")
|
||||
verbose_name_plural = _("orders")
|
||||
indexes = [
|
||||
Index(fields=["user", "status"]),
|
||||
Index(fields=["status", "buy_time"]),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
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
|
||||
def total_price(self) -> float:
|
||||
return (
|
||||
round(
|
||||
sum(
|
||||
(
|
||||
order_product.buy_price * order_product.quantity
|
||||
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
|
||||
)
|
||||
total = self.order_products.exclude(status__in=FAILED_STATUSES).aggregate(
|
||||
total=Sum(F("buy_price") * F("quantity"), output_field=FloatField())
|
||||
)["total"]
|
||||
|
||||
return round(total or 0.0, 2)
|
||||
|
||||
@property
|
||||
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(
|
||||
self,
|
||||
|
|
@ -1676,6 +1665,8 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
|||
verbose_name_plural = _("order products")
|
||||
indexes = [
|
||||
GinIndex(fields=["notifications", "attributes"]),
|
||||
Index(fields=["order", "status"]),
|
||||
Index(fields=["product", "status"]),
|
||||
]
|
||||
|
||||
def return_balance_back(self):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from uuid import UUID
|
|||
|
||||
from constance import config
|
||||
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.shortcuts import get_object_or_404
|
||||
from django.utils.decorators import method_decorator
|
||||
|
|
@ -452,14 +452,23 @@ class ProductViewSet(EvibesViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
||||
qs = qs.select_related("brand", "category")
|
||||
|
||||
if self.request.user.has_perm("core.view_product"):
|
||||
return qs
|
||||
return qs.filter(
|
||||
is_active=True,
|
||||
brand__is_active=True,
|
||||
category__is_active=True,
|
||||
stocks__isnull=False,
|
||||
stocks__vendor__is_active=True,
|
||||
|
||||
active_stocks = Stock.objects.filter(product_id=OuterRef("pk"), vendor__is_active=True)
|
||||
|
||||
return (
|
||||
qs.filter(
|
||||
is_active=True,
|
||||
brand__is_active=True,
|
||||
category__is_active=True,
|
||||
)
|
||||
.annotate(has_active_stocks=Exists(active_stocks))
|
||||
.filter(has_active_stocks=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def get_object(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue