603 lines
29 KiB
Python
603 lines
29 KiB
Python
from rest_framework import serializers
|
|
from datetime import timedelta
|
|
from booking.models import Event, MarketSlot
|
|
import logging
|
|
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
|
|
try:
|
|
from commerce.serializers import PriceCalculationSerializer
|
|
except ImportError:
|
|
PriceCalculationSerializer = None
|
|
|
|
from trznice.utils import RoundedDateTimeField
|
|
from .models import Event, MarketSlot, Reservation, Square, ReservationCheck
|
|
from account.models import CustomUser
|
|
from product.serializers import EventProductSerializer
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
#----------------------SHORT SERIALIZERS---------------------------------
|
|
|
|
class EventShortSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Square
|
|
fields = ["id", "name"]
|
|
extra_kwargs = {
|
|
"id": {"read_only": True},
|
|
"name": {"read_only": True, "help_text": "Název náměstí"}
|
|
}
|
|
|
|
class UserShortSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = CustomUser
|
|
fields = ["id", "username"]
|
|
extra_kwargs = {
|
|
"id": {"read_only": True},
|
|
"username": {"read_only": True, "help_text": "username uživatele"}
|
|
}
|
|
|
|
class SquareShortSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Square
|
|
fields = ["id", "name"]
|
|
extra_kwargs = {
|
|
"id": {"read_only": True},
|
|
"name": {"read_only": True, "help_text": "Název náměstí"}
|
|
}
|
|
|
|
class ReservationShortSerializer(serializers.ModelSerializer):
|
|
user = UserShortSerializer(read_only=True)
|
|
event = EventShortSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = Reservation
|
|
fields = ["id", "user", "event"]
|
|
extra_kwargs = {
|
|
"id": {"read_only": True},
|
|
"user": {"read_only": True, "help_text": "Majitel rezervace"},
|
|
"event": {"read_only": True, "help_text": "Akce na které je vytvořena rezervace"}
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#------------------------NORMAL SERIALIZERS------------------------------
|
|
|
|
class ReservationCheckSerializer(serializers.ModelSerializer):
|
|
reservation = serializers.PrimaryKeyRelatedField(
|
|
queryset=Reservation.objects.all(),
|
|
write_only=True,
|
|
help_text="ID rezervace, která se kontroluje."
|
|
)
|
|
reservation_info = ReservationShortSerializer(source="reservation", read_only=True)
|
|
|
|
checker = serializers.HiddenField(default=serializers.CurrentUserDefault())
|
|
checker_info = UserShortSerializer(source="checker", read_only=True)
|
|
|
|
class Meta:
|
|
model = ReservationCheck
|
|
fields = [
|
|
"id", "reservation", "reservation_info",
|
|
"checker", "checker_info", "checked_at"
|
|
]
|
|
read_only_fields = ["id", "checked_at"]
|
|
|
|
def validate_reservation(self, value):
|
|
if value.status != "reserved":
|
|
raise serializers.ValidationError("Rezervaci lze kontrolovat pouze pokud je ve stavu 'reserved'.")
|
|
return value
|
|
|
|
def validate_checker(self, value):
|
|
user = self.context["request"].user
|
|
if not user.is_staff and value != user:
|
|
raise serializers.ValidationError("Pouze administrátor může nastavit jiného uživatele jako kontrolora.")
|
|
return value
|
|
|
|
|
|
class ReservationSerializer(serializers.ModelSerializer):
|
|
reserved_from = serializers.DateField()
|
|
reserved_to = serializers.DateField()
|
|
|
|
event = EventShortSerializer(read_only=True)
|
|
user = UserShortSerializer(read_only=True)
|
|
market_slot = serializers.PrimaryKeyRelatedField(
|
|
queryset=MarketSlot.objects.filter(is_deleted=False), required=True
|
|
)
|
|
|
|
last_checked_by = UserShortSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = Reservation
|
|
fields = [
|
|
"id", "market_slot",
|
|
"used_extension", "reserved_from", "reserved_to",
|
|
"created_at", "status", "note", "final_price",
|
|
"event", "user", "is_checked", "last_checked_by", "last_checked_at"
|
|
]
|
|
read_only_fields = ["id", "created_at", "is_checked", "last_checked_by", "last_checked_at"]
|
|
extra_kwargs = {
|
|
"event": {"help_text": "ID (Event), ke které rezervace patří", "required": True},
|
|
"market_slot": {"help_text": "ID konkrétního prodejního místa (MarketSlot)", "required": True},
|
|
"user": {"help_text": "ID a název uživatele, který rezervaci vytváří", "required": True},
|
|
"used_extension": {"help_text": "Velikost rozšíření v m², které chce uživatel využít", "required": True},
|
|
"reserved_from": {"help_text": "Datum a čas začátku rezervace", "required": True},
|
|
"reserved_to": {"help_text": "Datum a čas konce rezervace", "required": True},
|
|
"status": {"help_text": "Stav rezervace (reserved / cancelled)", "required": False, "default": "reserved"},
|
|
"note": {"help_text": "Poznámka k rezervaci (volitelné)", "required": False},
|
|
"final_price": {"help_text": "Cena za Rezervaci, počítá se podle plochy prodejního místa a počtů dní.", "required": False, "default": 0},
|
|
|
|
"is_checked": {"help_text": "Stav je True, pokud již byla provedena aspoň jedna kontrola.", "required": False, "read_only": True},
|
|
"last_checked_by": {"help_text": "Kontrolor, který provedl poslední kontrolu.", "required": False, "read_only": True},
|
|
"last_checked_at": {"help_text": "Čas kdy byla provedena poslední kontrola.", "required": False, "read_only": True}
|
|
}
|
|
|
|
def to_internal_value(self, data):
|
|
# Accept both "market_slot" and legacy "marketSlot" keys for compatibility
|
|
if "marketSlot" in data and "market_slot" not in data:
|
|
data["market_slot"] = data["marketSlot"]
|
|
# Debug: log incoming data for troubleshooting
|
|
logger.debug(f"ReservationSerializer.to_internal_value input data: {data}")
|
|
return super().to_internal_value(data)
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
# Accept both "market_slot" and legacy "marketSlot" keys for compatibility
|
|
if "marketSlot" in data and "market_slot" not in data:
|
|
data["market_slot"] = data["marketSlot"]
|
|
# Debug: log incoming data for troubleshooting
|
|
logger.debug(f"ReservationSerializer.to_internal_value input data: {data}")
|
|
return super().to_internal_value(data)
|
|
|
|
def validate(self, data):
|
|
logger.debug(f"ReservationSerializer.validate market_slot: {data.get('market_slot')}, event: {data.get('event')}")
|
|
# Get the event object from the provided event id (if present)
|
|
event_id = self.initial_data.get("event")
|
|
if event_id:
|
|
try:
|
|
event = Event.objects.get(pk=event_id)
|
|
data["event"] = event
|
|
except Event.DoesNotExist:
|
|
raise serializers.ValidationError({"event": "Zadaná akce (event) neexistuje."})
|
|
else:
|
|
event = data.get("event")
|
|
|
|
market_slot = data.get("market_slot")
|
|
# --- FIX: Ensure event is set before permission check in views ---
|
|
if event is None and market_slot is not None:
|
|
event = market_slot.event
|
|
data["event"] = event
|
|
logger.debug(f"ReservationSerializer.validate auto-filled event from market_slot: {event}")
|
|
|
|
|
|
|
|
user = data.get("user")
|
|
request_user = self.context["request"].user if "request" in self.context else None
|
|
|
|
# If user is not specified, use the logged-in user
|
|
if user is None and request_user is not None:
|
|
user = request_user
|
|
data["user"] = user
|
|
|
|
# If user is specified and differs from logged-in user, check permissions
|
|
if user is not None and request_user is not None and user != request_user:
|
|
if request_user.role not in ["admin", "cityClerk", "squareManager"]:
|
|
raise serializers.ValidationError("Pouze administrátor, úředník nebo správce tržiště může vytvářet rezervace pro jiné uživatele.")
|
|
|
|
|
|
|
|
if user is None:
|
|
raise serializers.ValidationError("Rezervace musí mít přiřazeného uživatele.")
|
|
if user.user_reservations.filter(status="reserved").count() >= 5:
|
|
raise serializers.ValidationError("Uživatel už má 5 aktivních rezervací.")
|
|
|
|
reserved_from = data.get("reserved_from")
|
|
reserved_to = data.get("reserved_to")
|
|
used_extension = data.get("used_extension", 0)
|
|
final_price = data.get("final_price", 0)
|
|
|
|
if "status" in data:
|
|
if self.instance: # update
|
|
if data["status"] != self.instance.status and user.role not in ["admin", "cityClerk"]:
|
|
raise serializers.ValidationError({
|
|
"status": "Pouze administrátor nebo úředník může upravit status rezervace."
|
|
})
|
|
else:
|
|
data["status"] = "reserved"
|
|
|
|
privileged_roles = ["admin", "cityClerk"]
|
|
|
|
# Define max allowed price based on model's decimal constraints (8 digits, 2 decimal places)
|
|
MAX_FINAL_PRICE = Decimal("999999.99")
|
|
|
|
if user and getattr(user, "role", None) in privileged_roles:
|
|
# 🧠 Automatický výpočet ceny rezervace pokud není zadána
|
|
if not final_price or final_price == 0:
|
|
market_slot = data.get("market_slot")
|
|
event = data.get("event")
|
|
reserved_from = data.get("reserved_from")
|
|
reserved_to = data.get("reserved_to")
|
|
used_extension = data.get("used_extension", 0)
|
|
# --- Prefer PriceCalculationSerializer if available ---
|
|
if PriceCalculationSerializer:
|
|
try:
|
|
price_serializer = PriceCalculationSerializer(data={
|
|
"market_slot": market_slot.id if market_slot else None,
|
|
"used_extension": used_extension,
|
|
"reserved_from": reserved_from,
|
|
"reserved_to": reserved_to,
|
|
"event": event.id if event else None,
|
|
"user": user.id if user else None,
|
|
})
|
|
price_serializer.is_valid(raise_exception=True)
|
|
calculated_price = price_serializer.validated_data.get("final_price")
|
|
if calculated_price is not None:
|
|
try:
|
|
# Always quantize to two decimals
|
|
decimal_price = Decimal(str(calculated_price)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
# Clamp value to max allowed and raise error if exceeded
|
|
if decimal_price > MAX_FINAL_PRICE:
|
|
logger.error(f"ReservationSerializer: final_price ({decimal_price}) exceeds max allowed ({MAX_FINAL_PRICE})")
|
|
data["final_price"] = MAX_FINAL_PRICE
|
|
raise serializers.ValidationError({"final_price": f"Cena je příliš vysoká, maximálně {MAX_FINAL_PRICE} Kč."})
|
|
else:
|
|
data["final_price"] = decimal_price
|
|
except (InvalidOperation, TypeError, ValueError):
|
|
raise serializers.ValidationError("Výsledná cena není platné číslo.")
|
|
else:
|
|
raise serializers.ValidationError("Výpočet ceny selhal.")
|
|
except Exception as e:
|
|
logger.error(f"PriceCalculationSerializer failed: {e}", exc_info=True)
|
|
market_slot = data.get("market_slot")
|
|
event = data.get("event")
|
|
reserved_from = data.get("reserved_from")
|
|
reserved_to = data.get("reserved_to")
|
|
used_extension = data.get("used_extension", 0)
|
|
price_per_m2 = data.get("price_per_m2")
|
|
if price_per_m2 is None:
|
|
if market_slot and hasattr(market_slot, "price_per_m2"):
|
|
price_per_m2 = market_slot.price_per_m2
|
|
elif event and hasattr(event, "price_per_m2"):
|
|
price_per_m2 = event.price_per_m2
|
|
else:
|
|
raise serializers.ValidationError("Cena za m² není dostupná.")
|
|
base_size = getattr(market_slot, "base_size", None)
|
|
if base_size is None:
|
|
raise serializers.ValidationError("Základní velikost (base_size) není dostupná.")
|
|
duration_days = (reserved_to - reserved_from).days
|
|
base_size_decimal = Decimal(str(base_size))
|
|
used_extension_decimal = Decimal(str(used_extension))
|
|
duration_days_decimal = Decimal(str(duration_days))
|
|
price_per_m2_decimal = Decimal(str(price_per_m2))
|
|
calculated_price = duration_days_decimal * (price_per_m2_decimal * (base_size_decimal + used_extension_decimal))
|
|
try:
|
|
decimal_price = Decimal(str(calculated_price)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
# Clamp value to max allowed and raise error if exceeded
|
|
if decimal_price > MAX_FINAL_PRICE:
|
|
logger.error(f"ReservationSerializer: final_price ({decimal_price}) exceeds max allowed ({MAX_FINAL_PRICE})")
|
|
data["final_price"] = MAX_FINAL_PRICE
|
|
raise serializers.ValidationError({"final_price": f"Cena je příliš vysoká, maximálně {MAX_FINAL_PRICE} Kč."})
|
|
else:
|
|
data["final_price"] = decimal_price
|
|
except (InvalidOperation, TypeError, ValueError):
|
|
raise serializers.ValidationError("Výsledná cena není platné číslo.")
|
|
else:
|
|
price_per_m2 = data.get("price_per_m2")
|
|
if price_per_m2 is None:
|
|
if market_slot and hasattr(market_slot, "price_per_m2"):
|
|
price_per_m2 = market_slot.price_per_m2
|
|
elif event and hasattr(event, "price_per_m2"):
|
|
price_per_m2 = event.price_per_m2
|
|
else:
|
|
raise serializers.ValidationError("Cena za m² není dostupná.")
|
|
resolution = event.square.cellsize if event and hasattr(event, "square") else 1
|
|
width = getattr(market_slot, "width", 1)
|
|
height = getattr(market_slot, "height", 1)
|
|
# If you want to include used_extension, add it to area
|
|
area_m2 = Decimal(width) * Decimal(height) * Decimal(resolution) * Decimal(resolution)
|
|
duration_days = (reserved_to - reserved_from).days
|
|
|
|
price_per_m2_decimal = Decimal(str(price_per_m2))
|
|
calculated_price = Decimal(duration_days) * area_m2 * price_per_m2_decimal
|
|
try:
|
|
decimal_price = Decimal(str(calculated_price)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
# Clamp value to max allowed and raise error if exceeded
|
|
if decimal_price > MAX_FINAL_PRICE:
|
|
logger.error(f"ReservationSerializer: final_price ({decimal_price}) exceeds max allowed ({MAX_FINAL_PRICE})")
|
|
data["final_price"] = MAX_FINAL_PRICE
|
|
raise serializers.ValidationError({"final_price": f"Cena je příliš vysoká, maximálně {MAX_FINAL_PRICE} Kč."})
|
|
else:
|
|
data["final_price"] = decimal_price
|
|
except (InvalidOperation, TypeError, ValueError):
|
|
raise serializers.ValidationError("Výsledná cena není platné číslo.")
|
|
else:
|
|
if self.instance: # update
|
|
if final_price != self.instance.final_price and (not user or user.role not in privileged_roles):
|
|
raise serializers.ValidationError({
|
|
"final_price": "Pouze administrátor nebo úředník může upravit finální cenu."
|
|
})
|
|
else: # create
|
|
if not user or user.role not in privileged_roles:
|
|
raise serializers.ValidationError({
|
|
"final_price": "Pouze administrátor nebo úředník může nastavit finální cenu."
|
|
})
|
|
if data.get("final_price") is not None:
|
|
try:
|
|
decimal_price = Decimal(str(data["final_price"])).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
# Clamp value to max allowed and raise error if exceeded
|
|
if decimal_price > MAX_FINAL_PRICE:
|
|
logger.error(f"ReservationSerializer: final_price ({decimal_price}) exceeds max allowed ({MAX_FINAL_PRICE})")
|
|
data["final_price"] = MAX_FINAL_PRICE
|
|
raise serializers.ValidationError({"final_price": f"Cena je příliš vysoká, maximálně {MAX_FINAL_PRICE} Kč."})
|
|
data["final_price"] = decimal_price
|
|
except (InvalidOperation, TypeError, ValueError):
|
|
raise serializers.ValidationError("Výsledná cena není platné číslo.")
|
|
if data.get("final_price") < 0:
|
|
raise serializers.ValidationError("Cena za m² nemůže být záporná.")
|
|
else:
|
|
# Remove final_price if not privileged
|
|
data.pop("final_price", None)
|
|
|
|
if reserved_from >= reserved_to:
|
|
raise serializers.ValidationError("Datum začátku rezervace musí být dříve než její konec.")
|
|
|
|
if reserved_from < event.start or reserved_to > event.end:
|
|
raise serializers.ValidationError("Rezervace musí být v rámci trvání akce.")
|
|
|
|
overlapping = None
|
|
if market_slot:
|
|
if market_slot.event != event:
|
|
raise serializers.ValidationError("Prodejní místo nepatří do dané akce.")
|
|
|
|
if used_extension > market_slot.available_extension:
|
|
raise serializers.ValidationError("Požadované rozšíření překračuje dostupné rozšíření.")
|
|
|
|
overlapping = Reservation.objects.exclude(id=self.instance.id if self.instance else None).filter(
|
|
event=event,
|
|
market_slot=market_slot,
|
|
reserved_from__lt=reserved_to,
|
|
reserved_to__gt=reserved_from,
|
|
status="reserved"
|
|
)
|
|
|
|
if overlapping is not None and overlapping.exists():
|
|
logger.debug(f"ReservationSerializer.validate: Found overlapping reservations for market_slot {market_slot.id} in event {event.id}")
|
|
raise serializers.ValidationError("Rezervace se překrývá s jinou rezervací na stejném místě.")
|
|
|
|
return data
|
|
|
|
class ReservationAvailabilitySerializer(serializers.Serializer):
|
|
event_id = serializers.IntegerField()
|
|
market_slot_id = serializers.IntegerField()
|
|
reserved_from = serializers.DateField()
|
|
reserved_to = serializers.DateField()
|
|
|
|
class Meta:
|
|
model = Reservation
|
|
fields = ["event", "market_slot", "reserved_from", "reserved_to"]
|
|
extra_kwargs = {
|
|
"event": {"help_text": "ID of the event"},
|
|
"market_slot": {"help_text": "ID of the market slot"},
|
|
"reserved_from": {"help_text": "Start date of the reservation"},
|
|
"reserved_to": {"help_text": "End date of the reservation"},
|
|
}
|
|
|
|
def validate(self, data):
|
|
event_id = data.get("event_id")
|
|
market_slot_id = data.get("market_slot_id")
|
|
reserved_from = data.get("reserved_from")
|
|
reserved_to = data.get("reserved_to")
|
|
|
|
if reserved_from >= reserved_to:
|
|
raise serializers.ValidationError("Konec rezervace musí být po začátku.")
|
|
|
|
# Zkontroluj existenci Eventu a Slotu
|
|
try:
|
|
event = Event.objects.get(id=event_id)
|
|
except Event.DoesNotExist:
|
|
raise serializers.ValidationError("Událost neexistuje.")
|
|
|
|
try:
|
|
market_slot = MarketSlot.objects.get(id=market_slot_id)
|
|
except MarketSlot.DoesNotExist:
|
|
raise serializers.ValidationError("Slot neexistuje.")
|
|
|
|
# Zkontroluj status slotu
|
|
if market_slot.status == "blocked":
|
|
raise serializers.ValidationError("Tento slot je zablokovaný správcem.")
|
|
|
|
# Zkontroluj, že datumy spadají do rozsahu události
|
|
if reserved_from < event.date_from or reserved_to > event.date_to:
|
|
raise serializers.ValidationError("Vybrané datumy nespadají do trvání akce.")
|
|
|
|
# Zkontroluj, jestli už neexistuje kolizní rezervace
|
|
conflict = Reservation.objects.filter(
|
|
event=event,
|
|
market_slot=market_slot,
|
|
reserved_from__lt=reserved_to,
|
|
reserved_to__gt=reserved_from,
|
|
status="reserved"
|
|
).exists()
|
|
|
|
if conflict:
|
|
raise serializers.ValidationError("Tento slot je v daném termínu již rezervován.")
|
|
|
|
return data
|
|
|
|
#--- Reservation end ----
|
|
|
|
|
|
class MarketSlotSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MarketSlot
|
|
fields = [
|
|
"id", "event", "number", "status",
|
|
"base_size", "available_extension",
|
|
"x", "y", "width", "height",
|
|
"price_per_m2"
|
|
]
|
|
|
|
read_only_fields = ["id", "number"]
|
|
extra_kwargs = {
|
|
"event": {"help_text": "ID akce (Event), ke které toto místo patří", "required": True},
|
|
"number": {"help_text": "Pořadové číslo prodejního místa u Akce, ke které toto místo patří", "required": False},
|
|
"status": {"help_text": "Stav prodejního místa", "required": False},
|
|
"base_size": {"help_text": "Základní velikost (m²)", "required": True},
|
|
"available_extension": {"help_text": "Možnost rozšíření (m²)", "required": False, "default": 0},
|
|
"x": {"help_text": "X souřadnice levého horního rohu", "required": True},
|
|
"y": {"help_text": "Y souřadnice levého horního rohu", "required": True},
|
|
"width": {"help_text": "Šířka Slotu", "required": True},
|
|
"height": {"help_text": "Výška Slotu", "required": True},
|
|
"price_per_m2": {"help_text": "Cena za m² tohoto místa", "required": False, "default": 0},
|
|
}
|
|
|
|
def validate_base_size(self, value):
|
|
if value <= 0:
|
|
raise serializers.ValidationError("Základní velikost musí být větší než nula.")
|
|
return value
|
|
|
|
def validate(self, data):
|
|
price_per_m2 = data.setdefault("price_per_m2", 0)
|
|
if price_per_m2 < 0:
|
|
raise serializers.ValidationError("Cena za m² nemůže být záporná.")
|
|
|
|
if data.setdefault("available_extension", 0) < 0:
|
|
raise serializers.ValidationError("Velikost možného rozšíření musí být větší než nula.")
|
|
|
|
if data.get("width", 0) <= 0 or data.get("height", 0) <= 0:
|
|
raise serializers.ValidationError("Šířka a výška místa musí být větší než nula.")
|
|
|
|
return data
|
|
|
|
|
|
class EventSerializer(serializers.ModelSerializer):
|
|
square = SquareShortSerializer(read_only=True)
|
|
square_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=Square.objects.all(), source="square", write_only=True
|
|
)
|
|
|
|
|
|
market_slots = MarketSlotSerializer(many=True, read_only=True, source="event_marketSlots")
|
|
event_products = EventProductSerializer(many=True, read_only=True)
|
|
|
|
start = serializers.DateField()
|
|
end = serializers.DateField()
|
|
|
|
class Meta:
|
|
model = Event
|
|
fields = [
|
|
"id", "name", "description", "start", "end", "price_per_m2", "image", "market_slots", "event_products",
|
|
"square", # nested read-only
|
|
"square_id" # required in POST/PUT
|
|
]
|
|
read_only_fields = ["id"]
|
|
extra_kwargs = {
|
|
"name": {"help_text": "Název události", "required": True},
|
|
"description": {"help_text": "Popis události", "required": False},
|
|
"start": {"help_text": "Datum a čas začátku události", "required": True},
|
|
"end": {"help_text": "Datum a čas konce události", "required": True},
|
|
"price_per_m2": {"help_text": "Cena za m² pro rezervaci", "required": True},
|
|
"image": {"help_text": "Obrázek nebo plán náměstí", "required": False, "allow_null": True},
|
|
|
|
"market_slots": {"help_text": "Seznam prodejních míst vytvořených v rámci této události", "required": False},
|
|
"event_products": {"help_text": "Seznam povolených zboží k prodeji v rámci této události", "required": False},
|
|
|
|
"square": {"help_text": "Náměstí, na kterém se akce koná (jen ke čtení)", "required": False},
|
|
"square_id": {"help_text": "ID Náměstí, na kterém se akce koná (jen ke zápis)", "required": True},
|
|
}
|
|
|
|
def validate(self, data):
|
|
start = data.get("start")
|
|
end = data.get("end")
|
|
square = data.get("square")
|
|
|
|
if not start or not end or not square:
|
|
raise serializers.ValidationError("Pole start, end a square musí být vyplněné.")
|
|
|
|
if start >= end:
|
|
raise serializers.ValidationError("Datum začátku musí být před datem konce.")
|
|
|
|
if data.get("price_per_m2", 0) <= 0:
|
|
raise serializers.ValidationError("Cena za m² plochy pro rezervaci musí být větší než 0.")
|
|
|
|
overlapping = Event.objects.exclude(id=self.instance.id if self.instance else None).filter(
|
|
square=square,
|
|
start__lt=end,
|
|
end__gt=start,
|
|
)
|
|
|
|
if overlapping.exists():
|
|
raise serializers.ValidationError("V tomto termínu už na daném náměstí probíhá jiná událost.")
|
|
|
|
return data
|
|
|
|
|
|
class SquareSerializer(serializers.ModelSerializer):
|
|
|
|
image = serializers.ImageField(required=False, allow_null=True) # Ensure DRF handles image upload
|
|
|
|
class Meta:
|
|
model = Square
|
|
fields = [
|
|
"id", "name", "description", "street", "city", "psc",
|
|
"width", "height", "grid_rows", "grid_cols", "cellsize",
|
|
"image"
|
|
]
|
|
read_only_fields = ["id"]
|
|
extra_kwargs = {
|
|
"name": {"help_text": "Název náměstí", "required": True},
|
|
"description": {"help_text": "Popis náměstí", "required": False},
|
|
"street": {"help_text": "Ulice, kde se náměstí nachází", "required": False},
|
|
"city": {"help_text": "Město, kde se náměstí nachází", "required": False},
|
|
"psc": {"help_text": "PSČ (5 číslic)", "required": False},
|
|
"width": {"help_text": "Šířka náměstí v metrech", "required": True},
|
|
"height": {"help_text": "Výška náměstí v metrech", "required": True},
|
|
"grid_rows": {"help_text": "Počet řádků gridu", "required": True},
|
|
"grid_cols": {"help_text": "Počet sloupců gridu", "required": True},
|
|
"cellsize": {"help_text": "Velikost buňky gridu v pixelech", "required": True},
|
|
"image": {"help_text": "Obrázek / mapa náměstí", "required": False},
|
|
}
|
|
|
|
#-----------------------------------------------------------------------
|
|
class ReservedDaysSerializer(serializers.Serializer):
|
|
market_slot_id = serializers.IntegerField()
|
|
reserved_days = serializers.ListField(child=serializers.DateField(), read_only=True)
|
|
|
|
def to_representation(self, instance):
|
|
# Accept instance as dict or int
|
|
if isinstance(instance, dict):
|
|
market_slot_id = instance.get("market_slot_id")
|
|
else:
|
|
market_slot_id = instance # assume int
|
|
|
|
try:
|
|
market_slot = MarketSlot.objects.get(id=market_slot_id)
|
|
except MarketSlot.DoesNotExist:
|
|
return {"market_slot_id": market_slot_id, "reserved_days": []}
|
|
|
|
# Get all reserved days for this slot, return each day individually
|
|
reservations = Reservation.objects.filter(
|
|
market_slot_id=market_slot_id,
|
|
status="reserved"
|
|
)
|
|
reserved_days = set()
|
|
for reservation in reservations:
|
|
current = reservation.reserved_from
|
|
end = reservation.reserved_to
|
|
# Convert to date if it's a datetime
|
|
if hasattr(current, "date"):
|
|
current = current.date()
|
|
if hasattr(end, "date"):
|
|
end = end.date()
|
|
# Include both start and end dates
|
|
while current <= end:
|
|
reserved_days.add(current)
|
|
current += timedelta(days=1)
|
|
|
|
# Return reserved days as a sorted list of individual dates
|
|
return {
|
|
"market_slot_id": market_slot_id,
|
|
"reserved_days": sorted(reserved_days)
|
|
}
|