Compare commits
No commits in common. "6955e6bec688b480bb95b3799b83da1fb268937c" and "3859445c782dbdcc9b2475366066b7ddef4e5273" have entirely different histories.
6955e6bec6
...
3859445c78
11 changed files with 73 additions and 55 deletions
|
|
@ -469,21 +469,21 @@ class Category(NiceModel, MPTTModel):
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def min_price(self) -> Decimal:
|
def min_price(self) -> float:
|
||||||
return (
|
return (
|
||||||
self.products.filter(is_active=True, stocks__is_active=True).aggregate(
|
self.products.filter(is_active=True, stocks__is_active=True).aggregate(
|
||||||
Min("stocks__price")
|
Min("stocks__price")
|
||||||
)["stocks__price__min"]
|
)["stocks__price__min"]
|
||||||
or Decimal("0.00")
|
or 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def max_price(self) -> Decimal:
|
def max_price(self) -> float:
|
||||||
return (
|
return (
|
||||||
self.products.filter(is_active=True, stocks__is_active=True).aggregate(
|
self.products.filter(is_active=True, stocks__is_active=True).aggregate(
|
||||||
Max("stocks__price")
|
Max("stocks__price")
|
||||||
)["stocks__price__max"]
|
)["stocks__price__max"]
|
||||||
or Decimal("0.00")
|
or 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
@ -776,7 +776,7 @@ class Product(NiceModel):
|
||||||
).order_by("-discount_percent")
|
).order_by("-discount_percent")
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def discount_price(self) -> Decimal | None:
|
def discount_price(self) -> float | None:
|
||||||
promo = self.promos.first()
|
promo = self.promos.first()
|
||||||
return (self.price / 100) * promo.discount_percent if promo else None
|
return (self.price / 100) * promo.discount_percent if promo else None
|
||||||
|
|
||||||
|
|
@ -795,11 +795,11 @@ class Product(NiceModel):
|
||||||
return Feedback.objects.filter(order_product__product_id=self.pk).count()
|
return Feedback.objects.filter(order_product__product_id=self.pk).count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price(self: Self) -> Decimal:
|
def price(self: Self) -> float:
|
||||||
stock = (
|
stock = (
|
||||||
self.stocks.filter(is_active=True).only("price").order_by("-price").first()
|
self.stocks.filter(is_active=True).only("price").order_by("-price").first()
|
||||||
)
|
)
|
||||||
return Decimal(round(stock.price, 2)) if stock else Decimal("0.00")
|
return round(stock.price, 2) if stock else 0.0
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def quantity(self) -> int:
|
def quantity(self) -> int:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from contextlib import suppress
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework.fields import DecimalField, JSONField, SerializerMethodField
|
from rest_framework.fields import JSONField, SerializerMethodField
|
||||||
from rest_framework.serializers import ListSerializer, ModelSerializer
|
from rest_framework.serializers import ListSerializer, ModelSerializer
|
||||||
from rest_framework_recursive.fields import RecursiveField
|
from rest_framework_recursive.fields import RecursiveField
|
||||||
|
|
||||||
|
|
@ -64,12 +64,8 @@ class CategoryDetailSerializer(ModelSerializer):
|
||||||
description = SerializerMethodField()
|
description = SerializerMethodField()
|
||||||
filterable_attributes = SerializerMethodField()
|
filterable_attributes = SerializerMethodField()
|
||||||
brands = BrandSimpleSerializer(many=True, read_only=True)
|
brands = BrandSimpleSerializer(many=True, read_only=True)
|
||||||
min_price = DecimalField(
|
min_price = SerializerMethodField()
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
max_price = SerializerMethodField()
|
||||||
)
|
|
||||||
max_price = DecimalField(
|
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = Category
|
||||||
|
|
@ -111,6 +107,11 @@ class CategoryDetailSerializer(ModelSerializer):
|
||||||
return list(serializer.data)
|
return list(serializer.data)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_min_price(self, obj: Category) -> float:
|
||||||
|
return obj.min_price
|
||||||
|
|
||||||
|
def get_max_price(self, obj: Category) -> float:
|
||||||
|
return obj.max_price
|
||||||
|
|
||||||
|
|
||||||
class BrandDetailSerializer(ModelSerializer):
|
class BrandDetailSerializer(ModelSerializer):
|
||||||
|
|
@ -280,15 +281,10 @@ class ProductDetailSerializer(ModelSerializer):
|
||||||
|
|
||||||
description = SerializerMethodField()
|
description = SerializerMethodField()
|
||||||
rating = SerializerMethodField()
|
rating = SerializerMethodField()
|
||||||
price = DecimalField(
|
price = SerializerMethodField()
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
|
||||||
)
|
|
||||||
quantity = SerializerMethodField()
|
quantity = SerializerMethodField()
|
||||||
feedbacks_count = SerializerMethodField()
|
feedbacks_count = SerializerMethodField()
|
||||||
discount_price = DecimalField(
|
discount_price = SerializerMethodField()
|
||||||
max_digits=12, decimal_places=2, read_only=True, allow_null=True,
|
|
||||||
coerce_to_string=False,
|
|
||||||
)
|
|
||||||
personal_orders_only = SerializerMethodField()
|
personal_orders_only = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -323,6 +319,9 @@ class ProductDetailSerializer(ModelSerializer):
|
||||||
def get_rating(self, obj: Product) -> float:
|
def get_rating(self, obj: Product) -> float:
|
||||||
return obj.rating
|
return obj.rating
|
||||||
|
|
||||||
|
def get_price(self, obj: Product) -> float:
|
||||||
|
return obj.price
|
||||||
|
|
||||||
def get_feedbacks_count(self, obj: Product) -> int:
|
def get_feedbacks_count(self, obj: Product) -> int:
|
||||||
return obj.feedbacks_count
|
return obj.feedbacks_count
|
||||||
|
|
||||||
|
|
@ -332,6 +331,9 @@ class ProductDetailSerializer(ModelSerializer):
|
||||||
def get_quantity(self, obj: Product) -> int:
|
def get_quantity(self, obj: Product) -> int:
|
||||||
return obj.quantity
|
return obj.quantity
|
||||||
|
|
||||||
|
def get_discount_price(self, obj: Product) -> float | None:
|
||||||
|
return obj.discount_price
|
||||||
|
|
||||||
|
|
||||||
class PromotionDetailSerializer(ModelSerializer):
|
class PromotionDetailSerializer(ModelSerializer):
|
||||||
products = ProductDetailSerializer(
|
products = ProductDetailSerializer(
|
||||||
|
|
@ -414,9 +416,7 @@ class OrderDetailSerializer(ModelSerializer):
|
||||||
order_products = OrderProductDetailSerializer(
|
order_products = OrderProductDetailSerializer(
|
||||||
many=True,
|
many=True,
|
||||||
)
|
)
|
||||||
total_price = DecimalField(
|
total_price = SerializerMethodField(read_only=True)
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
|
|
@ -433,3 +433,6 @@ class OrderDetailSerializer(ModelSerializer):
|
||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_total_price(self, obj: Order) -> float:
|
||||||
|
return obj.total_price # ty: ignore[invalid-return-type]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from rest_framework.fields import DecimalField, JSONField, SerializerMethodField
|
from rest_framework.fields import JSONField, SerializerMethodField
|
||||||
from rest_framework.relations import PrimaryKeyRelatedField
|
from rest_framework.relations import PrimaryKeyRelatedField
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
|
||||||
|
|
@ -140,16 +140,11 @@ class ProductSimpleSerializer(ModelSerializer):
|
||||||
|
|
||||||
description = SerializerMethodField()
|
description = SerializerMethodField()
|
||||||
rating = SerializerMethodField()
|
rating = SerializerMethodField()
|
||||||
price = DecimalField(
|
price = SerializerMethodField()
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
|
||||||
)
|
|
||||||
quantity = SerializerMethodField()
|
quantity = SerializerMethodField()
|
||||||
feedbacks_count = SerializerMethodField()
|
feedbacks_count = SerializerMethodField()
|
||||||
personal_orders_only = SerializerMethodField()
|
personal_orders_only = SerializerMethodField()
|
||||||
discount_price = DecimalField(
|
discount_price = SerializerMethodField()
|
||||||
max_digits=12, decimal_places=2, read_only=True, allow_null=True,
|
|
||||||
coerce_to_string=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Product
|
model = Product
|
||||||
|
|
@ -181,6 +176,9 @@ class ProductSimpleSerializer(ModelSerializer):
|
||||||
def get_rating(self, obj: Product) -> float:
|
def get_rating(self, obj: Product) -> float:
|
||||||
return obj.rating
|
return obj.rating
|
||||||
|
|
||||||
|
def get_price(self, obj: Product) -> float:
|
||||||
|
return obj.price
|
||||||
|
|
||||||
def get_feedbacks_count(self, obj: Product) -> int:
|
def get_feedbacks_count(self, obj: Product) -> int:
|
||||||
return obj.feedbacks_count
|
return obj.feedbacks_count
|
||||||
|
|
||||||
|
|
@ -190,6 +188,9 @@ class ProductSimpleSerializer(ModelSerializer):
|
||||||
def get_personal_orders_only(self, obj: Product) -> bool:
|
def get_personal_orders_only(self, obj: Product) -> bool:
|
||||||
return obj.personal_orders_only
|
return obj.personal_orders_only
|
||||||
|
|
||||||
|
def get_discount_price(self, obj: Product) -> float | None:
|
||||||
|
return obj.discount_price
|
||||||
|
|
||||||
|
|
||||||
class VendorSimpleSerializer(ModelSerializer):
|
class VendorSimpleSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -285,9 +286,7 @@ class OrderSimpleSerializer(ModelSerializer):
|
||||||
billing_address = AddressSerializer(read_only=True, required=False)
|
billing_address = AddressSerializer(read_only=True, required=False)
|
||||||
shipping_address = AddressSerializer(read_only=True, required=False)
|
shipping_address = AddressSerializer(read_only=True, required=False)
|
||||||
attributes = JSONField(required=False)
|
attributes = JSONField(required=False)
|
||||||
total_price = DecimalField(
|
total_price = SerializerMethodField(read_only=True)
|
||||||
max_digits=12, decimal_places=2, read_only=True, coerce_to_string=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
|
|
@ -306,3 +305,6 @@ class OrderSimpleSerializer(ModelSerializer):
|
||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_total_price(self, obj: Order) -> float:
|
||||||
|
return obj.total_price # ty: ignore[invalid-return-type]
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ def product_schema(product, images, rating=None):
|
||||||
offers.append(
|
offers.append(
|
||||||
{
|
{
|
||||||
"@type": "Offer",
|
"@type": "Offer",
|
||||||
"price": float(round(stock.price, 2)),
|
"price": round(stock.price, 2),
|
||||||
"priceCurrency": settings.CURRENCY_CODE,
|
"priceCurrency": settings.CURRENCY_CODE,
|
||||||
"availability": "https://schema.org/InStock"
|
"availability": "https://schema.org/InStock"
|
||||||
if stock.quantity > 0
|
if stock.quantity > 0
|
||||||
|
|
|
||||||
|
|
@ -135,22 +135,6 @@ class CustomGraphQLView(FileUploadGraphQLView):
|
||||||
def get_context(self, request):
|
def get_context(self, request):
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def json_encode(request, d, pretty=False):
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import orjson
|
|
||||||
|
|
||||||
def _default(obj):
|
|
||||||
if isinstance(obj, Decimal):
|
|
||||||
return float(obj)
|
|
||||||
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
||||||
|
|
||||||
opts = orjson.OPT_NON_STR_KEYS
|
|
||||||
if pretty or request.GET.get("pretty"):
|
|
||||||
opts |= orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
|
|
||||||
return orjson.dumps(d, default=_default, option=opts).decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**CUSTOM_OPENAPI_SCHEMA)
|
@extend_schema_view(**CUSTOM_OPENAPI_SCHEMA)
|
||||||
class CustomSpectacularAPIView(SpectacularAPIView):
|
class CustomSpectacularAPIView(SpectacularAPIView):
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ UNFOLD: dict[str, Any] = {
|
||||||
"SITE_URL": STOREFRONT_DOMAIN,
|
"SITE_URL": STOREFRONT_DOMAIN,
|
||||||
"SITE_TITLE": f"{PROJECT_NAME} Dashboard",
|
"SITE_TITLE": f"{PROJECT_NAME} Dashboard",
|
||||||
"SITE_HEADER": PROJECT_NAME,
|
"SITE_HEADER": PROJECT_NAME,
|
||||||
"SITE_LOGO": lambda request: static("favicon.png"),
|
"SITE_LOGO": "favicon.png",
|
||||||
"SITE_ICON": lambda request: static("favicon.ico"),
|
"SITE_ICON": "favicon.ico",
|
||||||
"SITE_SYMBOL": "money",
|
"SITE_SYMBOL": "money",
|
||||||
"SITE_DROPDOWN": [
|
"SITE_DROPDOWN": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
uv run manage.py await_services
|
uv run manage.py await_services
|
||||||
uv run manage.py collectstatic --noinput --verbosity 0
|
|
||||||
|
|
||||||
if [ "${DEBUG:-0}" = "1" ]; then
|
if [ "${DEBUG:-0}" = "1" ]; then
|
||||||
uv run uvicorn --host 0.0.0.0 --port 8000 --reload --log-level debug --ws-ping-interval 20 --ws-ping-timeout 20 schon.asgi:application
|
uv run uvicorn --host 0.0.0.0 --port 8000 --reload --log-level debug --ws-ping-interval 20 --ws-ping-timeout 20 schon.asgi:application
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,13 @@ if ! output=$(docker compose exec -T app uv run manage.py search_index --rebuild
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
log_info " → Collecting static files..."
|
||||||
|
if ! output=$(docker compose exec -T app uv run manage.py collectstatic --clear --no-input --verbosity 0 2>&1); then
|
||||||
|
log_error "Static files collection failed"
|
||||||
|
echo "$output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_success "Pre-run tasks completed successfully!"
|
log_success "Pre-run tasks completed successfully!"
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,13 @@ if ! output=$(docker compose exec -T app uv run manage.py search_index --rebuild
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
log_info " → Collecting static files..."
|
||||||
|
if ! output=$(docker compose exec -T app uv run manage.py collectstatic --clear --no-input --verbosity 0 2>&1); then
|
||||||
|
log_error "Static files collection failed"
|
||||||
|
echo "$output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_success "Pre-run tasks completed successfully!"
|
log_success "Pre-run tasks completed successfully!"
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,14 @@ if ($LASTEXITCODE -ne 0) {
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Write-Info " Collecting static files..."
|
||||||
|
$output = docker compose exec -T app uv run manage.py collectstatic --clear --no-input --verbosity 0 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error-Custom "Static files collection failed"
|
||||||
|
Write-Host $output
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
Write-Success "Pre-run tasks completed successfully!"
|
Write-Success "Pre-run tasks completed successfully!"
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,14 @@ if ($LASTEXITCODE -ne 0) {
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Write-Info " Collecting static files..."
|
||||||
|
$output = docker compose exec -T app uv run manage.py collectstatic --clear --no-input --verbosity 0 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error-Custom "Static files collection failed"
|
||||||
|
Write-Host $output
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
Write-Success "Pre-run tasks completed successfully!"
|
Write-Success "Pre-run tasks completed successfully!"
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue