Refactor order creation and add configuration endpoints

Refactored order creation logic to use new serializers and transaction handling, improving validation and modularity. Introduced admin and public endpoints for shop configuration with sensitive fields protected. Enhanced Zásilkovna (Packeta) integration, including packet widget template, new API fields, and improved error handling. Added django-silk for profiling, updated requirements and settings, and improved frontend Orval config for API client generation.
This commit is contained in:
David Bruno Vontor
2025-12-08 18:19:20 +01:00
parent 5b066e2770
commit 946f86db7e
18 changed files with 606 additions and 309 deletions

View File

@@ -1,8 +1,11 @@
from rest_framework import viewsets, mixins, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.template import loader
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse, OpenApiParameter, OpenApiTypes
from backend.configuration.models import ShopConfiguration
from .models import ZasilkovnaShipment, ZasilkovnaPacket
from .serializers import (
@@ -11,32 +14,37 @@ from .serializers import (
TrackingURLSerializer,
)
# -- SHIPMENT --
@extend_schema_view(
list=extend_schema(
tags=["Zásilkovna"],
summary="List shipments",
tags=["Packeta-Shipment"],
summary="Hromadný shipment",
description="Returns a paginated list of Packeta (Zásilkovna) shipments.",
responses={200: ZasilkovnaShipmentSerializer},
responses=ZasilkovnaShipmentSerializer,
),
retrieve=extend_schema(
tags=["Zásilkovna"],
summary="Retrieve a shipment",
tags=["Packeta-Shipment"],
summary="Detail hromadné zásilky",
description="Returns detail for a single shipment.",
responses={200: ZasilkovnaShipmentSerializer},
responses=ZasilkovnaShipmentSerializer,
),
)
class ZasilkovnaShipmentViewSet(viewsets.ReadOnlyModelViewSet):
class ZasilkovnaShipmentViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet,):
queryset = ZasilkovnaShipment.objects.all().order_by("-created_at")
serializer_class = ZasilkovnaShipmentSerializer
# -- PACKET --
@extend_schema_view(
retrieve=extend_schema(
tags=["Zásilkovna"],
summary="Retrieve a packet",
description="Returns detail for a single packet.",
tags=["Packet"],
summary="Packet",
description="#TODO: Popis endpointu",
responses={200: ZasilkovnaPacketSerializer},
)
)
@@ -45,12 +53,14 @@ class ZasilkovnaPacketViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet
serializer_class = ZasilkovnaPacketSerializer
@extend_schema(
tags=["Zásilkovna"],
tags=["Packet"],
summary="Get public tracking URL",
description=(
"Returns the public Zásilkovna tracking URL derived from the packet's barcode."
),
responses={200: OpenApiResponse(response=TrackingURLSerializer)},
description="Returns the public Zásilkovna tracking URL derived from the packet's barcode.",
responses=OpenApiResponse(response=TrackingURLSerializer),
parameters=[
OpenApiParameter(name="pk", location=OpenApiParameter.PATH, description="Packet ID", required=True, type=int),
],
request=None,
)
@action(detail=True, methods=["get"], url_path="tracking-url")
def tracking_url(self, request, pk=None):
@@ -60,21 +70,73 @@ class ZasilkovnaPacketViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet
"tracking_url": packet.get_tracking_url(),
}
return Response(data)
#HOTOVO
@extend_schema(
tags=["Zásilkovna"],
tags=["Packet"],
summary="Order shipping",
description=(
"Objedná přepravu přes API Packety,"
"podle existujicího objektu, kde je od uživatele uložený id od místa poslání."
),
request=None,
responses=OpenApiResponse(response=ZasilkovnaPacketSerializer),
parameters=[
OpenApiParameter(name="pk", location=OpenApiParameter.PATH, description="Packet ID", required=True, type=int),
],
)
@action(detail=True, methods=["patch"], url_path="order-shipping")
def order_shipping(self, request, pk=None):
packet: ZasilkovnaPacket = self.get_object()
packet.order_shipping()
serializer = self.get_serializer(packet)
return Response(serializer.data, status=status.HTTP_200_OK)
#HOTOVO
@extend_schema(
tags=["Packet"],
summary="Cancel packet",
description=(
"Cancels the packet through the Packeta API and updates its state to CANCELED. "
"No request body is required."
),
request=None,
responses={200: OpenApiResponse(response=ZasilkovnaPacketSerializer)},
responses=OpenApiResponse(response=ZasilkovnaPacketSerializer),
parameters=[
OpenApiParameter(name="pk", location=OpenApiParameter.PATH, description="Packet ID", required=True, type=int),
],
)
@action(detail=True, methods=["patch"], url_path="cancel")
def cancel(self, request, pk=None):
packet: ZasilkovnaPacket = self.get_object()
packet.cancel_packet()
serializer = self.get_serializer(packet)
return Response(serializer.data, status=status.HTTP_200_OK)
packet: ZasilkovnaPacket = self.get_object()
packet.cancel_packet()
serializer = self.get_serializer(packet)
return Response(serializer.data, status=status.HTTP_200_OK)
#TODO: dodělat/domluvit se
@extend_schema(
tags=["Packet"],
summary="Get widget for user, to select pickup point.",
description=(
"Returns HTML widget for user to select pickup point. "
"No request body is required."
),
request=None,
responses={200: OpenApiResponse(response=TrackingURLSerializer)},
)
@action(detail=True, methods=["get"], url_path="pickup-point-widget")
def pickup_point_widget(self, request):
#https://configurator.widget.packeta.com/cs
widget_html = loader.render_to_string(
"zasilkovna/pickup_point_widget.html",
{
"api_key": ShopConfiguration.get_solo().zasilkovna_widget_api_key,
}
)
return Response({"widget_html": widget_html})