init
This commit is contained in:
155
backend/product/serializers.py
Normal file
155
backend/product/serializers.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueValidator
|
||||
|
||||
from trznice.utils import RoundedDateTimeField
|
||||
from .models import Product, EventProduct
|
||||
from booking.models import Event
|
||||
# from booking.serializers import EventSerializer
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
code = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
allow_blank=True,
|
||||
help_text="Unikátní číselný kód produktu (volitelné)",
|
||||
)
|
||||
events = serializers.SerializerMethodField(help_text="Seznam akcí (eventů), ve kterých se tento produkt prodává.")
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ["id", "name", "code", "events"]
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
"name": {
|
||||
"help_text": "Název zboží (max. 255 znaků).",
|
||||
"required": True,
|
||||
},
|
||||
"code": {
|
||||
"help_text": "Unikátní kód zboží (např. 'FOOD-001'). Volitelné; pokud vyplněno, musí být jedinečný.",
|
||||
"required": False,
|
||||
"allow_null": True,
|
||||
"allow_blank": True,
|
||||
},
|
||||
}
|
||||
|
||||
def validate_name(self, value):
|
||||
value = value.strip()
|
||||
|
||||
if not value:
|
||||
raise serializers.ValidationError("Název Zboží (Produktu) nemůže být prázdný.")
|
||||
|
||||
if len(value) > 255:
|
||||
raise serializers.ValidationError("Název nesmí být delší než 255 znaků.")
|
||||
return value
|
||||
|
||||
def validate_code(self, value):
|
||||
# Accept empty/null
|
||||
if value in (None, ""):
|
||||
return None
|
||||
# Uniqueness manual check (since we removed built-in validator to permit null/blank)
|
||||
qs = Product.objects.filter(code=value)
|
||||
if self.instance:
|
||||
qs = qs.exclude(pk=self.instance.pk)
|
||||
if qs.exists():
|
||||
raise serializers.ValidationError("Produkt s tímto kódem už existuje.")
|
||||
return value
|
||||
|
||||
def get_events(self, obj):
|
||||
# Expect prefetch: event_products__event
|
||||
events = []
|
||||
# Access prefetched related if available to avoid N+1
|
||||
event_products = getattr(obj, 'event_products_all', None)
|
||||
if event_products is None:
|
||||
# Fallback query (should be avoided if queryset is optimized)
|
||||
event_products = obj.event_products.select_related('event').all()
|
||||
for ep in event_products:
|
||||
if ep.event_id and hasattr(ep, 'event'):
|
||||
events.append({"id": ep.event_id, "name": ep.event.name})
|
||||
return events
|
||||
|
||||
|
||||
class EventProductSerializer(serializers.ModelSerializer):
|
||||
product = ProductSerializer(read_only=True)
|
||||
product_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Product.objects.all(), write_only=True
|
||||
)
|
||||
|
||||
start_selling_date = RoundedDateTimeField()
|
||||
end_selling_date = RoundedDateTimeField()
|
||||
|
||||
class Meta:
|
||||
model = EventProduct
|
||||
fields = [
|
||||
'id',
|
||||
'product', # nested read-only
|
||||
'product_id', # required in POST/PUT
|
||||
'event',
|
||||
'start_selling_date',
|
||||
'end_selling_date',
|
||||
]
|
||||
|
||||
read_only_fields = ["id", "product"]
|
||||
extra_kwargs = {
|
||||
"product": {
|
||||
"help_text": "Detail zboží (jen pro čtení).",
|
||||
"required": False,
|
||||
"read_only": True,
|
||||
},
|
||||
"product_id": {
|
||||
"help_text": "ID zboží, které je povoleno prodávat na akci.",
|
||||
"required": True,
|
||||
"write_only": True,
|
||||
},
|
||||
"event": {
|
||||
"help_text": "ID akce (Event), pro kterou je zboží povoleno.",
|
||||
"required": True,
|
||||
},
|
||||
"start_selling_date": {
|
||||
"help_text": "Začátek prodeje v rámci akce (musí spadat do [event.start, event.end]).",
|
||||
"required": True,
|
||||
},
|
||||
"end_selling_date": {
|
||||
"help_text": "Konec prodeje v rámci akce (po start_selling_date, také v rámci [event.start, event.end]).",
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data["product"] = validated_data.pop("product_id")
|
||||
return super().create(validated_data)
|
||||
|
||||
def validate(self, data):
|
||||
product = data.get("product_id")
|
||||
event = data.get("event")
|
||||
start = data.get("start_selling_date")
|
||||
end = data.get("end_selling_date")
|
||||
|
||||
if start >= end:
|
||||
raise serializers.ValidationError("Datum začátku prodeje musí být dříve než jeho konec.")
|
||||
|
||||
if event and (start < event.start or end > event.end):
|
||||
raise serializers.ValidationError("Prodej zboží musí být v rámci trvání akce.")
|
||||
|
||||
# When updating, exclude self instance
|
||||
instance_id = self.instance.id if self.instance else None
|
||||
|
||||
# Check for overlapping EventProducts for the same product/event
|
||||
overlapping = EventProduct.objects.exclude(id=instance_id).filter(
|
||||
event=event,
|
||||
product_id=product,
|
||||
start_selling_date__lt=end,
|
||||
end_selling_date__gt=start,
|
||||
)
|
||||
if overlapping.exists():
|
||||
raise serializers.ValidationError("Toto zboží už se prodává v tomto období na této akci.")
|
||||
|
||||
# # Check for duplicate product-event pair
|
||||
# duplicate = EventProduct.objects.exclude(id=instance_id).filter(
|
||||
# event=event,
|
||||
# product_id=product,
|
||||
# )
|
||||
# if duplicate.exists():
|
||||
# raise serializers.ValidationError(f"V rámci akce {event} už je {product} zaregistrováno.")
|
||||
|
||||
return data
|
||||
Reference in New Issue
Block a user