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:
@@ -228,7 +228,14 @@ class Carrier(models.Model):
|
||||
|
||||
returning = models.BooleanField(default=False, help_text="Zda je tato zásilka na vrácení")
|
||||
|
||||
shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk is None:
|
||||
|
||||
if self.shipping_price is None:
|
||||
self.shipping_price = self.get_price()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_price(self):
|
||||
@@ -289,28 +296,8 @@ class Payment(models.Model):
|
||||
StripeModel, on_delete=models.CASCADE, null=True, blank=True, related_name="payment"
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.order:
|
||||
order = Order.objects.get(payment=self)
|
||||
|
||||
#validace platebních metod
|
||||
if self.payment_method == self.PAYMENT.SHOP and Carrier.objects.get(orders=order).shipping_method != Carrier.SHIPPING.STORE:
|
||||
raise ValueError("Platba v obchodě je možná pouze pro osobní odběr.")
|
||||
|
||||
#validace dobírky (jestli není použitá pro osobní odběr)
|
||||
elif self.payment_method == self.PAYMENT.CASH_ON_DELIVERY and Carrier.objects.get(orders=order).shipping_method == Carrier.SHIPPING.STORE:
|
||||
raise ValueError("Dobírka není možná pro osobní odběr.")
|
||||
|
||||
#vytvoření platebních metod pokud nový objekt
|
||||
if not self.pk:
|
||||
if self.payment_method == self.PAYMENT.STRIPE:
|
||||
self.stripe = StripePayment.objects.create(amount=order.total_price)
|
||||
else:
|
||||
self.stripe = None
|
||||
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
# ------------------ SLEVOVÉ KÓDY ------------------
|
||||
@@ -563,7 +550,7 @@ class Refund(models.Model):
|
||||
"return_reason": return_reason,
|
||||
}
|
||||
|
||||
#TODO: přesunou zásilkovna field tady taky (zkontrolovat jestli jsou views napojené a použít metodu send z carrier)
|
||||
|
||||
carrier = models.OneToOneField(
|
||||
"Carrier",
|
||||
on_delete=models.CASCADE,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from backend.thirdparty.stripe.client import StripeClient
|
||||
|
||||
from .models import Refund, Order, Invoice
|
||||
|
||||
|
||||
@@ -69,6 +71,9 @@ from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from decimal import Decimal
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
from .models import (
|
||||
Category,
|
||||
@@ -82,155 +87,242 @@ from .models import (
|
||||
Payment,
|
||||
)
|
||||
|
||||
from thirdparty.stripe.models import StripeModel, StripePayment
|
||||
|
||||
from thirdparty.zasilkovna.serializers import ZasilkovnaPacketSerializer
|
||||
from thirdparty.zasilkovna.models import ZasilkovnaPacket
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# ----------------- CREATING ORDER SERIALIZER -----------------
|
||||
|
||||
class UserBriefSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "first_name", "last_name", "email"]
|
||||
#correct
|
||||
# -- CARRIER --
|
||||
class OrderCarrierSerializer(serializers.ModelSerializer):
|
||||
# vstup: jen ID adresy z widgetu (write-only)
|
||||
packeta_address_id = serializers.IntegerField(required=False, write_only=True)
|
||||
|
||||
# výstup: serializovaný packet
|
||||
zasilkovna = ZasilkovnaPacketSerializer(many=True, read_only=True)
|
||||
|
||||
class ProductBriefSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ["id", "name", "price"]
|
||||
|
||||
|
||||
class OrderItemReadSerializer(serializers.ModelSerializer):
|
||||
product = ProductBriefSerializer(read_only=True)
|
||||
total_price = serializers.SerializerMethodField()
|
||||
|
||||
def get_total_price(self, obj):
|
||||
return obj.get_total_price(list(obj.order.discount.all()) if getattr(obj, "order", None) else None)
|
||||
|
||||
class Meta:
|
||||
model = OrderItem
|
||||
fields = ["id", "product", "quantity", "total_price"]
|
||||
read_only_fields = ["id", "total_price"]
|
||||
|
||||
|
||||
class CarrierReadSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Carrier
|
||||
fields = ["id", "shipping_method", "weight", "returning"]
|
||||
read_only_fields = ["id"]
|
||||
fields = ["shipping_method", "state", "zasilkovna", "shipping_price", "packeta_address_id"]
|
||||
read_only_fields = ["state", "shipping_price", "zasilkovna"]
|
||||
|
||||
def create(self, validated_data):
|
||||
packeta_address_id = validated_data.pop("packeta_address_id", None)
|
||||
|
||||
carrier = Carrier.objects.create(**validated_data)
|
||||
|
||||
if packeta_address_id is not None:
|
||||
# vytvoříme nový packet s danou addressId
|
||||
packet = ZasilkovnaPacket.objects.create(addressId=packeta_address_id)
|
||||
carrier.zasilkovna.add(packet)
|
||||
|
||||
return carrier
|
||||
|
||||
|
||||
class PaymentReadSerializer(serializers.Serializer):
|
||||
payment_method = serializers.CharField(read_only=True)
|
||||
stripe_id = serializers.IntegerField(source="stripe.id", read_only=True, allow_null=True)
|
||||
#correct
|
||||
# -- ORDER ITEMs --
|
||||
class OrderItemCreateSerializer(serializers.Serializer):
|
||||
product_id = serializers.IntegerField()
|
||||
quantity = serializers.IntegerField(min_value=1, default=1)
|
||||
|
||||
def validate(self, attrs):
|
||||
product_id = attrs.get("product_id")
|
||||
try:
|
||||
product = Product.objects.get(pk=product_id)
|
||||
except Product.DoesNotExist:
|
||||
raise serializers.ValidationError({"product_id": "Product not found."})
|
||||
|
||||
attrs["product"] = product
|
||||
return attrs
|
||||
|
||||
|
||||
class OrderReadSerializer(serializers.ModelSerializer):
|
||||
items = OrderItemReadSerializer(many=True, read_only=True)
|
||||
carrier = CarrierReadSerializer(read_only=True)
|
||||
payment = PaymentReadSerializer(source="payment", read_only=True)
|
||||
user = serializers.SerializerMethodField()
|
||||
|
||||
def get_user(self, obj):
|
||||
request = self.context.get("request") if hasattr(self, "context") else None
|
||||
if request and getattr(request, "user", None) and request.user.is_authenticated and obj.user:
|
||||
return UserBriefSerializer(obj.user).data
|
||||
return None
|
||||
# -- PAYMENT --
|
||||
class PaymentSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
model = Payment
|
||||
fields = [
|
||||
"id",
|
||||
"status",
|
||||
"total_price",
|
||||
"currency",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"city",
|
||||
"postal_code",
|
||||
"country",
|
||||
"note",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"carrier",
|
||||
"payment",
|
||||
"user",
|
||||
"items",
|
||||
"payment_method",
|
||||
"stripe",
|
||||
"stripe_session_id",
|
||||
"stripe_payment_intent",
|
||||
"stripe_session_url",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"status",
|
||||
"total_price",
|
||||
"currency",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"stripe",
|
||||
"stripe_session_id",
|
||||
"stripe_payment_intent",
|
||||
"stripe_session_url",
|
||||
]
|
||||
|
||||
def create(self, validated_data):
|
||||
order = self.context.get("order") # musíš ho předat při inicializaci serializeru
|
||||
carrier = self.context.get("carrier")
|
||||
|
||||
class OrderMiniSerializer(serializers.ModelSerializer):
|
||||
amount = serializers.DecimalField(max_digits=10, decimal_places=2, source="total_price", read_only=True)
|
||||
shipping_method = serializers.CharField(source="carrier.shipping_method", read_only=True)
|
||||
with transaction.atomic():
|
||||
payment = Payment.objects.create(
|
||||
order=order,
|
||||
carrier=carrier,
|
||||
**validated_data
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ["id", "amount", "status", "email", "shipping_method"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
# ----------------- CREATE PAYLOAD SERIALIZERS (PUBLIC) -----------------
|
||||
|
||||
class OrderItemCreateSerializer(serializers.Serializer):
|
||||
product_id = serializers.IntegerField(label="Product ID")
|
||||
quantity = serializers.IntegerField(min_value=1, label="Quantity")
|
||||
|
||||
|
||||
class CarrierCreateSerializer(serializers.Serializer):
|
||||
shipping_method = serializers.ChoiceField(
|
||||
choices=Carrier.SHIPPING.choices,
|
||||
label="Shipping Method",
|
||||
help_text="Choose 'store' (pickup) or 'packeta'",
|
||||
)
|
||||
weight = serializers.DecimalField(
|
||||
required=False,
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
label="Weight (kg)",
|
||||
)
|
||||
|
||||
|
||||
class PaymentCreateSerializer(serializers.Serializer):
|
||||
payment_method = serializers.ChoiceField(
|
||||
choices=Payment.PAYMENT.choices,
|
||||
label="Payment Method",
|
||||
help_text="Choose 'shop', 'stripe' or 'cash_on_delivery'",
|
||||
)
|
||||
# pokud je Stripe, vytvoříme checkout session
|
||||
if payment.payment_method == Payment.PAYMENT.SHOP and carrier.shipping_method != Carrier.SHIPPING.STORE:
|
||||
raise serializers.ValidationError("Platba v obchodě je možná pouze pro osobní odběr.")
|
||||
|
||||
elif payment.payment_method == Payment.PAYMENT.CASH_ON_DELIVERY and carrier.shipping_method == Carrier.SHIPPING.STORE:
|
||||
raise ValidationError("Dobírka není možná pro osobní odběr.")
|
||||
|
||||
|
||||
if payment.payment_method == Payment.PAYMENT.STRIPE:
|
||||
session = StripeClient.create_checkout_session(order)
|
||||
|
||||
payment.stripe_session_id = session.id
|
||||
payment.stripe_payment_intent = session.payment_intent
|
||||
payment.stripe_session_url = session.url
|
||||
|
||||
payment.save(update_fields=[
|
||||
"stripe_session_id",
|
||||
"stripe_payment_intent",
|
||||
"stripe_session_url",
|
||||
])
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
# -- ORDER CREATE SERIALIZER --
|
||||
class OrderCreateSerializer(serializers.Serializer):
|
||||
# Customer/billing
|
||||
first_name = serializers.CharField(max_length=100, label="First Name")
|
||||
last_name = serializers.CharField(max_length=100, label="Last Name")
|
||||
email = serializers.EmailField(label="Email")
|
||||
phone = serializers.CharField(max_length=20, required=False, allow_blank=True, label="Phone")
|
||||
address = serializers.CharField(max_length=255, label="Address")
|
||||
city = serializers.CharField(max_length=100, label="City")
|
||||
postal_code = serializers.CharField(max_length=20, label="Postal Code")
|
||||
country = serializers.CharField(max_length=100, required=False, default="Czech Republic", label="Country")
|
||||
note = serializers.CharField(required=False, allow_blank=True, label="Note")
|
||||
# Customer/billing data (optional when authenticated)
|
||||
first_name = serializers.CharField(required=False)
|
||||
last_name = serializers.CharField(required=False)
|
||||
email = serializers.EmailField(required=False)
|
||||
phone = serializers.CharField(required=False, allow_blank=True)
|
||||
address = serializers.CharField(required=False)
|
||||
city = serializers.CharField(required=False)
|
||||
postal_code = serializers.CharField(required=False)
|
||||
country = serializers.CharField(required=False, default="Czech Republic")
|
||||
note = serializers.CharField(required=False, allow_blank=True)
|
||||
|
||||
# Nested
|
||||
# Nested structures
|
||||
#produkty
|
||||
items = OrderItemCreateSerializer(many=True)
|
||||
carrier = CarrierCreateSerializer()
|
||||
payment = PaymentCreateSerializer()
|
||||
|
||||
#doprava/vyzvednutí + zasilkovna input (serializer)
|
||||
carrier = OrderCarrierSerializer()
|
||||
|
||||
payment = PaymentSerializer()
|
||||
|
||||
#slevové kódy
|
||||
discount_codes = serializers.ListField(
|
||||
child=serializers.CharField(), required=False, allow_empty=True, label="Discount Codes"
|
||||
child=serializers.CharField(), required=False, allow_empty=True
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
request = self.context.get("request")
|
||||
|
||||
#kontrola jestli je uzivatel valid/prihlasen
|
||||
is_auth = bool(getattr(getattr(request, "user", None), "is_authenticated", False))
|
||||
|
||||
# pokud není, tak se musí vyplnit povinné údaje
|
||||
required_fields = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"address",
|
||||
"city",
|
||||
"postal_code",
|
||||
]
|
||||
|
||||
if not is_auth:
|
||||
missing_fields = []
|
||||
|
||||
# přidame fieldy, které nejsou vyplněné
|
||||
for field in required_fields:
|
||||
if attrs.get(field) not in required_fields:
|
||||
missing_fields.append(field)
|
||||
|
||||
if missing_fields:
|
||||
raise serializers.ValidationError({"billing": f"Missing fields: {', '.join(missing_fields)}"})
|
||||
|
||||
# pokud chybí itemy:
|
||||
if not attrs.get("items"):
|
||||
raise serializers.ValidationError({"items": "At least one item is required."})
|
||||
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
items_data = validated_data.pop("items", [])
|
||||
carrier_data = validated_data.pop("carrier")
|
||||
payment_data = validated_data.pop("payment")
|
||||
codes = validated_data.pop("discount_codes", [])
|
||||
|
||||
request = self.context.get("request")
|
||||
user = getattr(request, "user", None)
|
||||
is_auth = bool(getattr(user, "is_authenticated", False))
|
||||
|
||||
with transaction.atomic():
|
||||
# Create Order (user data imported on save if user is set)
|
||||
order = Order(
|
||||
user=user if is_auth else None,
|
||||
first_name=validated_data.get("first_name", ""),
|
||||
last_name=validated_data.get("last_name", ""),
|
||||
email=validated_data.get("email", ""),
|
||||
phone=validated_data.get("phone", ""),
|
||||
address=validated_data.get("address", ""),
|
||||
city=validated_data.get("city", ""),
|
||||
postal_code=validated_data.get("postal_code", ""),
|
||||
country=validated_data.get("country", "Czech Republic"),
|
||||
note=validated_data.get("note", ""),
|
||||
)
|
||||
|
||||
# Order.save se postara o to jestli má doplnit data z usera
|
||||
order.save()
|
||||
|
||||
# Vytvoření Carrier skrz serializer
|
||||
carrier = OrderCarrierSerializer(data=carrier_data)
|
||||
carrier.is_valid(raise_exception=True)
|
||||
carrier = carrier.save()
|
||||
order.carrier = carrier
|
||||
order.save(update_fields=["carrier", "updated_at"]) # will recalc total later
|
||||
|
||||
|
||||
# Vytvořit Order Items individualně, aby se spustila kontrola položek na skladu
|
||||
for item in items_data:
|
||||
product = item["product"] # OrderItemCreateSerializer.validate
|
||||
quantity = int(item.get("quantity", 1))
|
||||
OrderItem.objects.create(order=order, product=product, quantity=quantity)
|
||||
|
||||
|
||||
# -- Slevové kódy --
|
||||
if codes:
|
||||
discounts = list(DiscountCode.objects.filter(code__in=codes))
|
||||
if discounts:
|
||||
order.discount.add(*discounts)
|
||||
|
||||
|
||||
|
||||
# -- Payment --
|
||||
payment_serializer = PaymentSerializer(
|
||||
data=payment_data,
|
||||
context={"order": order, "carrier": carrier}
|
||||
)
|
||||
payment_serializer.is_valid(raise_exception=True)
|
||||
payment = payment_serializer.save()
|
||||
|
||||
# přiřadíme k orderu
|
||||
order.payment = payment
|
||||
order.save(update_fields=["payment"])
|
||||
|
||||
return order
|
||||
|
||||
|
||||
|
||||
# ----------------- ADMIN/READ MODELS -----------------
|
||||
|
||||
@@ -262,55 +354,102 @@ class ProductImageSerializer(serializers.ModelSerializer):
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"code",
|
||||
"category",
|
||||
"price",
|
||||
"url",
|
||||
"stock",
|
||||
"is_active",
|
||||
"limited_to",
|
||||
"default_carrier",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
fields = "__all__"
|
||||
read_only_fields = ["created_at", "updated_at"]
|
||||
|
||||
|
||||
class DiscountCodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DiscountCode
|
||||
fields = [
|
||||
"id",
|
||||
"code",
|
||||
"description",
|
||||
"percent",
|
||||
"amount",
|
||||
"valid_from",
|
||||
"valid_to",
|
||||
"active",
|
||||
"usage_limit",
|
||||
"used_count",
|
||||
"specific_products",
|
||||
"specific_categories",
|
||||
]
|
||||
fields = "__all__"
|
||||
read_only_fields = ["used_count"]
|
||||
|
||||
|
||||
class RefundSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Refund
|
||||
fields = [
|
||||
"id",
|
||||
"order",
|
||||
"reason_choice",
|
||||
"reason_text",
|
||||
"verified",
|
||||
"created_at",
|
||||
]
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id", "verified", "created_at"]
|
||||
|
||||
|
||||
# ----------------- READ SERIALIZERS USED BY VIEWS -----------------
|
||||
|
||||
class ZasilkovnaPacketReadSerializer(ZasilkovnaPacketSerializer):
|
||||
class Meta(ZasilkovnaPacketSerializer.Meta):
|
||||
fields = getattr(ZasilkovnaPacketSerializer.Meta, "fields", None)
|
||||
|
||||
|
||||
class CarrierReadSerializer(serializers.ModelSerializer):
|
||||
zasilkovna = ZasilkovnaPacketReadSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Carrier
|
||||
fields = ["shipping_method", "state", "zasilkovna", "shipping_price"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class ProductMiniSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ["id", "name", "price"]
|
||||
|
||||
|
||||
class OrderItemReadSerializer(serializers.ModelSerializer):
|
||||
product = ProductMiniSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = OrderItem
|
||||
fields = ["id", "product", "quantity"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class PaymentReadSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Payment
|
||||
fields = ["payment_method"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class OrderMiniSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ["id", "status", "total_price", "created_at"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class OrderReadSerializer(serializers.ModelSerializer):
|
||||
items = OrderItemReadSerializer(many=True, read_only=True)
|
||||
carrier = CarrierReadSerializer(read_only=True)
|
||||
payment = PaymentReadSerializer(read_only=True)
|
||||
discount_codes = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = [
|
||||
"id",
|
||||
"status",
|
||||
"total_price",
|
||||
"currency",
|
||||
"user",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"city",
|
||||
"postal_code",
|
||||
"country",
|
||||
"note",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"items",
|
||||
"carrier",
|
||||
"payment",
|
||||
"discount_codes",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_discount_codes(self, obj: Order):
|
||||
return list(obj.discount.values_list("code", flat=True))
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ from .serializers import (
|
||||
RefundSerializer,
|
||||
)
|
||||
|
||||
|
||||
#FIXME: uravit view na nový order serializer
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Orders"], summary="List Orders (public)"),
|
||||
retrieve=extend_schema(tags=["Orders"], summary="Retrieve Order (public)"),
|
||||
@@ -63,7 +63,7 @@ class OrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Ge
|
||||
return OrderReadSerializer
|
||||
|
||||
@extend_schema(
|
||||
tags=["Orders"],
|
||||
tags=["Order"],
|
||||
summary="Create Order (public)",
|
||||
request=OrderCreateSerializer,
|
||||
responses={201: OrderReadSerializer},
|
||||
@@ -92,87 +92,10 @@ class OrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Ge
|
||||
],
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = OrderCreateSerializer(data=request.data)
|
||||
serializer = OrderCreateSerializer(data=request.data, context={"request": request})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
user = request.user
|
||||
|
||||
#VELMI DŮLEŽITELÉ: vše vytvořit v transakci, aby se nepřidávaly neúplné objednávky
|
||||
with transaction.atomic():
|
||||
|
||||
# Create base order (customer details only for now)
|
||||
order = Order.objects.create(
|
||||
user=user if getattr(user, "is_authenticated", False) else None,
|
||||
first_name=data["first_name"],
|
||||
last_name=data["last_name"],
|
||||
email=data["email"],
|
||||
phone=data.get("phone", ""),
|
||||
address=data["address"],
|
||||
city=data["city"],
|
||||
postal_code=data["postal_code"],
|
||||
country=data.get("country", "Czech Republic"),
|
||||
note=data.get("note", ""),
|
||||
)
|
||||
|
||||
# Carrier
|
||||
carrier_payload = data["carrier"]
|
||||
carrier = Carrier.objects.create(
|
||||
shipping_method=carrier_payload["shipping_method"],
|
||||
weight=carrier_payload.get("weight"),
|
||||
)
|
||||
order.carrier = carrier
|
||||
order.save(update_fields=["carrier", "updated_at"]) # recalc later after items
|
||||
|
||||
# Items
|
||||
items_payload = data["items"]
|
||||
order_items = []
|
||||
for item in items_payload:
|
||||
product = Product.objects.get(pk=item["product_id"]) # raises 404 if missing
|
||||
qty = int(item["quantity"])
|
||||
order_items.append(OrderItem(order=order, product=product, quantity=qty))
|
||||
OrderItem.objects.bulk_create(order_items)
|
||||
|
||||
# Discount codes (optional)
|
||||
codes = data.get("discount_codes") or []
|
||||
if codes:
|
||||
discounts = list(DiscountCode.objects.filter(code__in=codes))
|
||||
order.discount.add(*discounts)
|
||||
|
||||
# Recalculate now that items/discounts/carrier are linked
|
||||
order.save()
|
||||
|
||||
# Payment and validation
|
||||
pay_payload = data["payment"]
|
||||
payment_method = pay_payload["payment_method"]
|
||||
|
||||
# Validate combos (mirror of Payment.save but here we have order)
|
||||
if payment_method == Payment.PAYMENT.SHOP and order.carrier.shipping_method != Carrier.SHIPPING.STORE:
|
||||
return Response(
|
||||
{"payment": "Platba v obchodě je možná pouze pro osobní odběr."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if payment_method == Payment.PAYMENT.CASH_ON_DELIVERY and order.carrier.shipping_method == Carrier.SHIPPING.STORE:
|
||||
return Response(
|
||||
{"payment": "Dobírka není možná pro osobní odběr."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Create payment WITHOUT triggering Payment.save (which expects reverse link first)
|
||||
payment = Payment(payment_method=payment_method)
|
||||
# Bypass custom save by bulk_create
|
||||
Payment.objects.bulk_create([payment])
|
||||
order.payment = payment
|
||||
order.save(update_fields=["payment", "updated_at"])
|
||||
|
||||
# If Stripe, create StripePayment now and attach
|
||||
if payment_method == Payment.PAYMENT.STRIPE:
|
||||
from thirdparty.stripe.models import StripePayment
|
||||
|
||||
stripe_obj = StripePayment.objects.create(amount=order.total_price)
|
||||
payment.stripe = stripe_obj
|
||||
payment.save(update_fields=["stripe"])
|
||||
|
||||
out = self.get_serializer(order)
|
||||
order = serializer.save()
|
||||
out = OrderReadSerializer(order)
|
||||
return Response(out.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
# -- List mini orders -- (public) --
|
||||
|
||||
@@ -22,7 +22,11 @@ class ShopConfiguration(models.Model):
|
||||
|
||||
#zasilkovna settings
|
||||
zasilkovna_shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=50)
|
||||
zasilkovna_address_id = models.CharField(max_length=100, blank=True, null=True, help_text="ID výdejního místa Zásilkovny pro odesílání zásilek")
|
||||
#FIXME: není implementováno ↓↓↓
|
||||
zasilkovna_api_key = models.CharField(max_length=255, blank=True, null=True, help_text="API klíč pro přístup k Zásilkovna API (zatím není využito)")
|
||||
#FIXME: není implementováno ↓↓↓
|
||||
zasilkovna_api_password = models.CharField(max_length=255, blank=True, null=True, help_text="API heslo pro přístup k Zásilkovna API (zatím není využito)")
|
||||
#FIXME: není implementováno ↓↓↓
|
||||
free_shipping_over = models.DecimalField(max_digits=10, decimal_places=2, default=2000)
|
||||
|
||||
#coupon settings
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ShopConfiguration
|
||||
|
||||
|
||||
class ShopConfigurationAdminSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ShopConfiguration
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"logo",
|
||||
"favicon",
|
||||
"contact_email",
|
||||
"contact_phone",
|
||||
"contact_address",
|
||||
"opening_hours",
|
||||
"facebook_url",
|
||||
"instagram_url",
|
||||
"youtube_url",
|
||||
"tiktok_url",
|
||||
"whatsapp_number",
|
||||
"zasilkovna_shipping_price",
|
||||
"zasilkovna_api_key",
|
||||
"zasilkovna_api_password",
|
||||
"free_shipping_over",
|
||||
"multiplying_coupons",
|
||||
"addition_of_coupons_amount",
|
||||
"currency",
|
||||
]
|
||||
|
||||
|
||||
class ShopConfigurationPublicSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ShopConfiguration
|
||||
# Expose only non-sensitive fields
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"logo",
|
||||
"favicon",
|
||||
"contact_email",
|
||||
"contact_phone",
|
||||
"contact_address",
|
||||
"opening_hours",
|
||||
"facebook_url",
|
||||
"instagram_url",
|
||||
"youtube_url",
|
||||
"tiktok_url",
|
||||
# Exclude API keys/passwords
|
||||
"zasilkovna_shipping_price",
|
||||
"free_shipping_over",
|
||||
"currency",
|
||||
]
|
||||
|
||||
|
||||
8
backend/configuration/urls.py
Normal file
8
backend/configuration/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import ShopConfigurationAdminViewSet, ShopConfigurationPublicViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"admin/shop-configuration", ShopConfigurationAdminViewSet, basename="shop-config-admin")
|
||||
router.register(r"public/shop-configuration", ShopConfigurationPublicViewSet, basename="shop-config-public")
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -1,6 +1,25 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets, mixins
|
||||
from rest_framework.permissions import IsAdminUser, AllowAny
|
||||
from .models import ShopConfiguration
|
||||
from .serializers import (
|
||||
ShopConfigurationAdminSerializer,
|
||||
ShopConfigurationPublicSerializer,
|
||||
)
|
||||
|
||||
# Create your views here.
|
||||
|
||||
#TODO: dej public tag pro view
|
||||
# rozdělit fieldy podle práv aby se třeba neexposelo citlivé údaje
|
||||
class _SingletonQuerysetMixin:
|
||||
def get_queryset(self):
|
||||
return ShopConfiguration.objects.filter(pk=1)
|
||||
|
||||
def get_object(self):
|
||||
return ShopConfiguration.get_solo()
|
||||
|
||||
|
||||
class ShopConfigurationAdminViewSet(_SingletonQuerysetMixin, viewsets.ModelViewSet):
|
||||
permission_classes = [IsAdminUser]
|
||||
serializer_class = ShopConfigurationAdminSerializer
|
||||
|
||||
|
||||
class ShopConfigurationPublicViewSet(_SingletonQuerysetMixin, viewsets.ReadOnlyModelViewSet):
|
||||
permission_classes = [AllowAny]
|
||||
serializer_class = ShopConfigurationPublicSerializer
|
||||
@@ -71,6 +71,8 @@ django-cors-headers #csfr
|
||||
celery #slouží k vytvaření asynchoních úkolu (třeba každou hodinu vyčistit cache atd.)
|
||||
django-celery-beat #slouží k plánování úkolů pro Celery
|
||||
|
||||
django-silk
|
||||
django-silk[formatting]
|
||||
|
||||
# -- EDITING photos, gifs, videos --
|
||||
|
||||
|
||||
7
backend/thirdparty/zasilkovna/client.py
vendored
7
backend/thirdparty/zasilkovna/client.py
vendored
@@ -15,11 +15,14 @@ zeepZasClient = Client(wsdl=WSDL_URL)
|
||||
|
||||
|
||||
class PacketaAPI:
|
||||
#TODO: zeptat se jestli nepřidat další checkovací parametry ohledně zásilkovny např: blokování podle nastavení webu
|
||||
#TODO: zeptat se jestli nepřidat další checkovací parametry ohledně zásilkovny např: blokování podle configurace webu
|
||||
# popřemýšlet, jestli api klíče nenastavit přes configurator webu
|
||||
def __getattribute__(self):
|
||||
if PACKETA_API_PASSWORD is None:
|
||||
if PACKETA_API_PASSWORD in [None, ""]:
|
||||
raise Exception("Packeta API password is not set in environment variables.")
|
||||
|
||||
elif zeepZasClient is None:
|
||||
raise Exception("Packeta SOAP client is not initialized.")
|
||||
|
||||
# ---------- CREATE PACKET METHODS ----------
|
||||
|
||||
|
||||
40
backend/thirdparty/zasilkovna/models.py
vendored
40
backend/thirdparty/zasilkovna/models.py
vendored
@@ -34,23 +34,23 @@ class ZasilkovnaPacket(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class STATE(models.TextChoices):
|
||||
PENDING = "PENDING", "Podáno"
|
||||
SENDED = "SENDED", "Odesláno"
|
||||
ARRIVED = "ARRIVED", "Doručeno"
|
||||
CANCELED = "CANCELED", "Zrušeno"
|
||||
WAITING_FOR_ORDER = "WAITING_FOR_ORDERING_SHIPMENT", "cz#Čeká na objednání zásilkovny"
|
||||
PENDING = "PENDING", "cz#Podáno"
|
||||
SENDED = "SENDED", "cz#Odesláno"
|
||||
ARRIVED = "ARRIVED", "cz#Doručeno"
|
||||
CANCELED = "CANCELED", "cz#Zrušeno"
|
||||
|
||||
RETURNING = "RETURNING", "Posláno zpátky"
|
||||
RETURNED = "RETURNED", "Vráceno"
|
||||
RETURNING = "RETURNING", "cz#Posláno zpátky"
|
||||
RETURNED = "RETURNED", "cz#Vráceno"
|
||||
state = models.CharField(max_length=20, choices=STATE.choices, default=STATE.PENDING)
|
||||
|
||||
# ------- API -------
|
||||
#TODO: změnit na nastavení adresy eshopu/obchodu z modelu konfigurace
|
||||
# https://client.packeta.com/cs/senders (admin rozhraní)
|
||||
|
||||
addressId = models.IntegerField(help_text="ID adresy, v Widgetu zásilkovny který si vybere uživatel.")
|
||||
addressId = models.IntegerField(null=True, blank=True, help_text="ID adresy/pointu, ve Widgetu zásilkovny který si vybere uživatel.")
|
||||
|
||||
packet_id = models.IntegerField(help_text="Číslo zásilky v Packetě (api)")
|
||||
barcode = models.CharField(max_length=64, help_text="Čárový kód zásilky v Packetě")
|
||||
packet_id = models.IntegerField(null=True, blank=True, help_text="Číslo zásilky v Packetě (vraceno od API od Packety)")
|
||||
barcode = models.CharField(null=True, blank=True, max_length=64, help_text="Čárový kód zásilky od Packety")
|
||||
|
||||
weight = models.IntegerField(
|
||||
default=0,
|
||||
@@ -61,6 +61,7 @@ class ZasilkovnaPacket(models.Model):
|
||||
return_routing = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Seznam 2 routing stringů pro vrácení zásilky"
|
||||
)
|
||||
|
||||
@@ -73,7 +74,13 @@ class ZasilkovnaPacket(models.Model):
|
||||
size_of_pdf = models.CharField(max_length=20, choices=PDF_SIZE.choices, default=PDF_SIZE.A6_ON_A6)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# workaroud to avoid circular import
|
||||
return super().save(args, **kwargs)
|
||||
|
||||
|
||||
def order_shippment(self):
|
||||
if self.addressId is None:
|
||||
raise ValidationError("AddressId must be set to order shipping.")
|
||||
|
||||
Carrier = apps.get_model('commerce', 'Carrier')
|
||||
Order = apps.get_model('commerce', 'Order')
|
||||
|
||||
@@ -84,26 +91,27 @@ class ZasilkovnaPacket(models.Model):
|
||||
|
||||
if not self.packet_id:
|
||||
response = packeta_client.create_packet(
|
||||
address_id=self.addressId,
|
||||
addressId=self.addressId, # ID z widgetu
|
||||
weight=self.weight,
|
||||
number=order.id,
|
||||
name=order.first_name,
|
||||
surname=order.last_name,
|
||||
company=order.company,
|
||||
email=order.email,
|
||||
addressId=ShopConfiguration.get_solo().zasilkovna_address_id,
|
||||
|
||||
cod=order.total_price if cash_on_delivery else 0, # dobírka
|
||||
value=order.total_price,
|
||||
|
||||
currency=ShopConfiguration.get_solo().currency,
|
||||
currency=ShopConfiguration.get_solo().currency, #CZK
|
||||
eshop= ShopConfiguration.get_solo().name,
|
||||
)
|
||||
self.packet_id = response['packet_id']
|
||||
self.barcode = response['barcode']
|
||||
else:
|
||||
raise ValidationError("Přeprava už byla objednana!!!.")
|
||||
|
||||
return super().save(args, **kwargs)
|
||||
|
||||
return self.save()
|
||||
|
||||
def cancel_packet(self):
|
||||
"""Cancel this packet via the Packeta API."""
|
||||
packeta_client.cancel_packet(self.packet_id)
|
||||
|
||||
24
backend/thirdparty/zasilkovna/serializers.py
vendored
24
backend/thirdparty/zasilkovna/serializers.py
vendored
@@ -15,16 +15,27 @@ class ZasilkovnaPacketSerializer(serializers.ModelSerializer):
|
||||
"weight",
|
||||
"return_routing",
|
||||
]
|
||||
read_only_fields = fields
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"barcode",
|
||||
"state",
|
||||
"weight",
|
||||
"return_routing",
|
||||
]
|
||||
|
||||
|
||||
#Just for tracking URL of packet
|
||||
class TrackingURLSerializer(serializers.Serializer):
|
||||
barcode = serializers.CharField(read_only=True)
|
||||
tracking_url = serializers.URLField(read_only=True)
|
||||
|
||||
|
||||
|
||||
# -- SHIPMENT --
|
||||
|
||||
class ZasilkovnaShipmentSerializer(serializers.ModelSerializer):
|
||||
packets = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
packets = serializers.PrimaryKeyRelatedField(many=True)
|
||||
|
||||
class Meta:
|
||||
model = ZasilkovnaShipment
|
||||
@@ -33,6 +44,11 @@ class ZasilkovnaShipmentSerializer(serializers.ModelSerializer):
|
||||
"created_at",
|
||||
"shipment_id",
|
||||
"barcode",
|
||||
"packets",
|
||||
"packets",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"shipment_id",
|
||||
"barcode",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
47
backend/thirdparty/zasilkovna/templates/zasilkovna/pickup_point_widget.html
vendored
Normal file
47
backend/thirdparty/zasilkovna/templates/zasilkovna/pickup_point_widget.html
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
{% load static %}
|
||||
<script src="https://widget.packeta.com/v6/www/js/library.js"></script>
|
||||
|
||||
<input type="hidden" id="packetaApiKey" value="{{ packeta_Api_Key }}">
|
||||
|
||||
<script>
|
||||
const packetaApiKey = document.querySelector('#packetaApiKey').value;
|
||||
|
||||
const packetaOptions = {
|
||||
country: "cz,sk",
|
||||
language: "cs",
|
||||
valueFormat: "\"Packeta\",id,carrierId,carrierPickupPointId,name,city,street",
|
||||
view: "modal",
|
||||
vendors: [
|
||||
{
|
||||
country: "cz",
|
||||
group: "zbox",
|
||||
price: 45,
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
country: "cz",
|
||||
price: 45,
|
||||
selected: true
|
||||
}
|
||||
],
|
||||
defaultCurrency: "CZK",
|
||||
defaultPrice: "45"
|
||||
};
|
||||
|
||||
|
||||
|
||||
function showSelectedPickupPoint(point) {
|
||||
const saveElement = document.querySelector(".packeta-selector-value");
|
||||
// Add here an action on pickup point selection
|
||||
saveElement.innerText = '';
|
||||
if (point) {
|
||||
console.log("Selected point", point);
|
||||
saveElement.innerText = "point: " + JSON.stringify(point, null, 4); // DŮLEŽITÉ PRO DALŠÍ ZPRACOVÁNÍ (jenom potřebuji ID)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<button class="packeta-selector-open"
|
||||
onclick="Packeta.Widget.pick(packetaApiKey, showSelectedPickupPoint, packetaOptions)">Select pick-up point</button>
|
||||
<div class="packeta-selector-value"></div>
|
||||
108
backend/thirdparty/zasilkovna/views.py
vendored
108
backend/thirdparty/zasilkovna/views.py
vendored
@@ -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})
|
||||
@@ -106,6 +106,7 @@ LOGGING = {
|
||||
},
|
||||
}
|
||||
|
||||
# -- PŘÍKLAD POUŽITÍ LOGS --
|
||||
"""
|
||||
import logging
|
||||
|
||||
@@ -120,6 +121,9 @@ logger.error("Chyba – něco se pokazilo, ale aplikace jede dál")
|
||||
logger.critical("Kritická chyba – selhání systému, třeba pád služby")
|
||||
"""
|
||||
|
||||
# -- SILK --
|
||||
SILKY_PYTHON_PROFILER = True
|
||||
|
||||
#---------------------------------- END LOGS ---------------------------------------
|
||||
|
||||
#-------------------------------------SECURITY 🔐------------------------------------
|
||||
@@ -374,6 +378,8 @@ INSTALLED_APPS = [
|
||||
#Nastavení stránky
|
||||
#'constance',
|
||||
#'constance.backends.database',
|
||||
|
||||
'silk',
|
||||
|
||||
'django.contrib.sitemaps',
|
||||
|
||||
@@ -403,6 +409,8 @@ MIDDLEWARE = [
|
||||
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
|
||||
'silk.middleware.SilkyMiddleware',
|
||||
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
|
||||
@@ -29,12 +29,14 @@ urlpatterns = [
|
||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
|
||||
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
||||
path('silk/', include('silk.urls', namespace='silk')),
|
||||
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
path("api/choices/", choices, name="choices"),
|
||||
path('api/account/', include('account.urls')),
|
||||
path('api/commerce/', include('commerce.urls')),
|
||||
path('api/configuration/', include('configuration.urls')),
|
||||
#path('api/advertisments/', include('advertisements.urls')),
|
||||
|
||||
path('api/stripe/', include('thirdparty.stripe.urls')),
|
||||
|
||||
Reference in New Issue
Block a user