feat: added creation endpoints

This commit is contained in:
2024-03-25 00:23:03 +01:00
parent 6b04fe7e23
commit 43338ceeec
16 changed files with 287 additions and 97 deletions
+1
View File
@@ -9,4 +9,5 @@ urlpatterns = [
"swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui" "swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"
), ),
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"), path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
path("shop/", include("shop.api.v1.routers")),
] ]
+10 -1
View File
@@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from shop.models import Product, ProductPrice, OrderLine, Order, Tax from shop.models import Product, ProductPrice, OrderLine, Order, Tax, Customer
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
@@ -35,6 +35,15 @@ class TaxAdmin(ModelAdmin):
) )
@admin.register(Customer)
class CustomerAdmin(ModelAdmin):
list_display = (
"vat_id",
"first_name",
"last_name",
)
@admin.register(OrderLine) @admin.register(OrderLine)
class OrderLineAdmin(ModelAdmin): class OrderLineAdmin(ModelAdmin):
autocomplete_fields = ("product",) autocomplete_fields = ("product",)
View File
View File
+22
View File
@@ -0,0 +1,22 @@
from rest_framework.routers import DefaultRouter
from shop.api.v1.viewsets import (
TaxViewSet,
CustomerViewSet,
ProductViewSet,
ProductPriceViewSet,
OrderViewSet,
OrderLineViewSet,
)
router = DefaultRouter()
router.register("taxes", TaxViewSet)
router.register("customers", CustomerViewSet)
router.register("product", ProductViewSet)
router.register("product-prices", ProductPriceViewSet)
router.register("orders", OrderViewSet)
router.register("orders/(?P<order_id>[^/.]+)/lines", OrderLineViewSet)
urlpatterns = router.urls
+99
View File
@@ -0,0 +1,99 @@
from rest_framework import serializers
from shop.models import Product, ProductPrice, Customer, Tax, OrderLine, Order
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"name",
"description",
"stock",
"unit",
)
class ProductPriceSerializer(serializers.ModelSerializer):
class Meta:
model = ProductPrice
fields = (
"price",
"date",
"product",
)
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = (
"vat_id",
"first_name",
"last_name",
"email",
"address",
"city",
"state",
"country",
"zip",
)
class TaxSerializer(serializers.ModelSerializer):
class Meta:
model = Tax
fields = (
"code",
"value",
)
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = (
"uuid",
"creation_date",
"last_modification_date",
"base_total",
"total",
"customer",
"billing_address",
"billing_city",
"billing_state",
"billing_country",
"billing_zip",
"shipping_address",
"shipping_city",
"shipping_state",
"shipping_country",
"shipping_zip",
"contact_phone",
)
read_only_fields = (
"uuid",
"base_total",
"total",
)
class OrderLineSerializer(serializers.ModelSerializer):
class Meta:
model = OrderLine
fields = (
"product",
"quantity",
"order",
"price",
"base_total",
"total",
"taxes",
"tax_value",
)
read_only_fields = (
"price",
"base_total",
"total",
"taxes",
"tax_value",
)
+45
View File
@@ -0,0 +1,45 @@
from rest_framework.viewsets import ModelViewSet
from shop.api.v1.serializers import (
ProductSerializer,
ProductPriceSerializer,
CustomerSerializer,
TaxSerializer,
OrderSerializer,
OrderLineSerializer,
)
from shop.models import Product, ProductPrice, Customer, Tax, Order, OrderLine
class ProductViewSet(ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
class ProductPriceViewSet(ModelViewSet):
serializer_class = ProductPriceSerializer
queryset = ProductPrice.objects.all()
class CustomerViewSet(ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
class TaxViewSet(ModelViewSet):
serializer_class = TaxSerializer
queryset = Tax.objects.all()
class OrderViewSet(ModelViewSet):
serializer_class = OrderSerializer
queryset = Order.objects.all()
lookup_field = "uuid"
class OrderLineViewSet(ModelViewSet):
serializer_class = OrderLineSerializer
queryset = OrderLine.objects.all()
def get_queryset(self):
return super().get_queryset().filter(order_id=self.kwargs.get("order_id"))
+73 -67
View File
@@ -1,7 +1,8 @@
# Generated by Django 5.0.3 on 2024-03-24 18:23 # Generated by Django 5.0.3 on 2024-03-24 22:50
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import uuid
from decimal import Decimal from decimal import Decimal
from django.db import migrations, models from django.db import migrations, models
@@ -50,64 +51,6 @@ class Migration(migrations.Migration):
"verbose_name_plural": "Clientes", "verbose_name_plural": "Clientes",
}, },
), ),
migrations.CreateModel(
name="OrderLine",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"quantity",
models.DecimalField(
decimal_places=4,
default=Decimal("1"),
max_digits=13,
verbose_name="Cantidad",
),
),
(
"price",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Precio"
),
),
(
"base_total",
models.DecimalField(
decimal_places=4,
default=Decimal("0"),
max_digits=13,
verbose_name="Total sin impuestos",
),
),
(
"tax_value",
models.PositiveIntegerField(verbose_name="Valor de impuestos"),
),
(
"taxes",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Impuestos"
),
),
(
"total",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Total"
),
),
],
options={
"verbose_name": "Línea de pedido",
"verbose_name_plural": "Líneas de pedido",
},
),
migrations.CreateModel( migrations.CreateModel(
name="Product", name="Product",
fields=[ fields=[
@@ -196,6 +139,7 @@ class Migration(migrations.Migration):
verbose_name="ID", verbose_name="ID",
), ),
), ),
("uuid", models.UUIDField(default=uuid.uuid4, verbose_name="UUID")),
( (
"creation_date", "creation_date",
models.DateTimeField( models.DateTimeField(
@@ -288,25 +232,87 @@ class Migration(migrations.Migration):
verbose_name="Cliente", verbose_name="Cliente",
), ),
), ),
(
"lines",
models.ManyToManyField(to="shop.orderline", verbose_name="Líneas"),
),
], ],
options={ options={
"verbose_name": "Pedido", "verbose_name": "Pedido",
"verbose_name_plural": "Pedidos", "verbose_name_plural": "Pedidos",
}, },
), ),
migrations.AddField( migrations.CreateModel(
model_name="orderline", name="OrderLine",
name="product", fields=[
field=models.ForeignKey( (
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"quantity",
models.DecimalField(
decimal_places=4,
default=Decimal("1"),
max_digits=13,
verbose_name="Cantidad",
),
),
(
"price",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Precio"
),
),
(
"base_total",
models.DecimalField(
decimal_places=4,
default=Decimal("0"),
max_digits=13,
verbose_name="Total sin impuestos",
),
),
(
"tax_value",
models.PositiveIntegerField(verbose_name="Valor de impuestos"),
),
(
"taxes",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Impuestos"
),
),
(
"total",
models.DecimalField(
decimal_places=4, max_digits=13, verbose_name="Total"
),
),
(
"order",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lines",
to="shop.order",
verbose_name="Pedido",
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
to="shop.product", to="shop.product",
verbose_name="Producto", verbose_name="Producto",
), ),
), ),
],
options={
"verbose_name": "Línea de pedido",
"verbose_name_plural": "Líneas de pedido",
},
),
migrations.CreateModel( migrations.CreateModel(
name="ProductPrice", name="ProductPrice",
fields=[ fields=[
+8 -1
View File
@@ -1,4 +1,5 @@
from decimal import Decimal from decimal import Decimal
from uuid import uuid4
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@@ -108,6 +109,12 @@ class Customer(models.Model):
class OrderLine(models.Model): class OrderLine(models.Model):
order = models.ForeignKey(
"shop.Order",
on_delete=models.CASCADE,
related_name="lines",
verbose_name=_("Pedido"),
)
product = models.ForeignKey( product = models.ForeignKey(
"shop.Product", "shop.Product",
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -163,7 +170,7 @@ class OrderLine(models.Model):
class Order(models.Model): class Order(models.Model):
lines = models.ManyToManyField("shop.OrderLine", verbose_name=_("Líneas")) uuid = models.UUIDField(default=uuid4, verbose_name=_("UUID"), db_index=True)
creation_date = models.DateTimeField( creation_date = models.DateTimeField(
auto_now_add=True, verbose_name=_("Fecha de creación") auto_now_add=True, verbose_name=_("Fecha de creación")
) )
+21 -18
View File
@@ -1,6 +1,6 @@
from decimal import Decimal from decimal import Decimal
from rest_framework.test import APITestCase as TestCase from rest_framework.test import APITestCase as TestCase
from shop.models import Product, ProductPrice, OrderLine, Tax, Customer from shop.models import Product, ProductPrice, Tax, Customer
from shop.utils import create_order_line_for_product, create_order from shop.utils import create_order_line_for_product, create_order
@@ -45,25 +45,8 @@ class ShopModelsTest(TestCase):
country="Tatooine", country="Tatooine",
) )
l1 = create_order_line_for_product(
self.potatoes,
quantity=Decimal("1.5"),
tax=self.tax,
)
l2 = create_order_line_for_product(
self.gasoline,
quantity=Decimal("40"),
tax=self.tax,
)
l3 = create_order_line_for_product(
self.usb_c,
quantity=Decimal("1.00"),
tax=self.tax,
)
order = create_order( order = create_order(
customer=self.customer, customer=self.customer,
lines=OrderLine.objects.all(),
billing_address=self.customer.address, billing_address=self.customer.address,
billing_city=self.customer.city, billing_city=self.customer.city,
billing_state=self.customer.state, billing_state=self.customer.state,
@@ -71,5 +54,25 @@ class ShopModelsTest(TestCase):
billing_country=self.customer.country, billing_country=self.customer.country,
) )
l1 = create_order_line_for_product(
self.potatoes,
quantity=Decimal("1.5"),
tax=self.tax,
order=order,
)
l2 = create_order_line_for_product(
self.gasoline,
quantity=Decimal("40"),
tax=self.tax,
order=order,
)
l3 = create_order_line_for_product(
self.usb_c,
quantity=Decimal("1.00"),
tax=self.tax,
order=order,
)
order.calculate_total_from_lines()
assert order.total == l1.total + l2.total + l3.total assert order.total == l1.total + l2.total + l3.total
assert order.base_total == l1.base_total + l2.base_total + l3.base_total assert order.base_total == l1.base_total + l2.base_total + l3.base_total
+4 -6
View File
@@ -5,12 +5,15 @@ from shop.models import OrderLine, Tax, Product, Order, Customer
from django.db.models import QuerySet from django.db.models import QuerySet
def create_order_line_for_product(product: Product, quantity: Decimal, tax: Tax): def create_order_line_for_product(
product: Product, quantity: Decimal, tax: Tax, order: Order
):
price = product.prices.last().price price = product.prices.last().price
base_total = round(price * quantity, 2) base_total = round(price * quantity, 2)
taxes = round(base_total * (tax.value / Decimal("100")), 2) taxes = round(base_total * (tax.value / Decimal("100")), 2)
return OrderLine.objects.create( return OrderLine.objects.create(
order=order,
product=product, product=product,
quantity=quantity, quantity=quantity,
price=price, price=price,
@@ -23,7 +26,6 @@ def create_order_line_for_product(product: Product, quantity: Decimal, tax: Tax)
def create_order( def create_order(
customer: Customer, customer: Customer,
lines: Iterable,
billing_address: str, billing_address: str,
billing_city: str, billing_city: str,
billing_state: str, billing_state: str,
@@ -61,8 +63,4 @@ def create_order(
shipping_zip=shipping_zip, shipping_zip=shipping_zip,
) )
for line in lines:
order.lines.add(line)
order.calculate_total_from_lines()
return order return order
View File
View File
View File
View File
View File