diff --git a/config/api/v1/urls.py b/config/api/v1/urls.py index 3dea484..c7db314 100644 --- a/config/api/v1/urls.py +++ b/config/api/v1/urls.py @@ -9,7 +9,7 @@ urlpatterns = [ "swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui" ), path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"), - path("shop/", include("shop.api.v1.routers")), - path("files/", include("files.api.v1.urls")), - path("auth/", include("users.api.v1.urls")), + path("shop/", include("shop.api.v1.routers", namespace="shop")), + path("files/", include("files.api.v1.urls", namespace="files")), + path("auth/", include("users.api.v1.urls", namespace="auth")), ] diff --git a/files/api/v1/urls.py b/files/api/v1/urls.py index c438164..849a82e 100644 --- a/files/api/v1/urls.py +++ b/files/api/v1/urls.py @@ -2,6 +2,10 @@ from rest_framework.routers import DefaultRouter from files.api.v1.views import FileUploadViewSet + +app_name = "files" + + router = DefaultRouter(trailing_slash=True) router.register("", FileUploadViewSet) diff --git a/pytest.ini b/pytest.ini index 7522288..eb11cdc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -DJANGO_SETTINGS_MODULE = config.settings.develop +DJANGO_SETTINGS_MODULE = config.settings.base addopts = --ignore=src diff --git a/shop/api/v1/routers.py b/shop/api/v1/routers.py index d962b9c..eee127f 100644 --- a/shop/api/v1/routers.py +++ b/shop/api/v1/routers.py @@ -11,13 +11,16 @@ from shop.api.v1.viewsets import ( ) +app_name = "shop" + + router = DefaultRouter() router.register("taxes", TaxViewSet) router.register("customers", CustomerViewSet) router.register("providers", ProviderViewSet) router.register("products", ProductViewSet) -router.register("product-prices", ProductPriceViewSet) +router.register("products/(?P[^/.]+)/prices", ProductPriceViewSet) router.register("product-batches", ProductBatchViewSet) router.register("orders", OrderViewSet) router.register("orders/(?P[^/.]+)/lines", OrderLineViewSet) diff --git a/shop/api/v1/serializers.py b/shop/api/v1/serializers.py index cf2b1a8..ab75fec 100644 --- a/shop/api/v1/serializers.py +++ b/shop/api/v1/serializers.py @@ -1,3 +1,4 @@ +from django.utils import timezone from rest_framework import serializers from files.api.v1.serializers import FileUploadSerializer @@ -17,6 +18,7 @@ class TaxSerializer(serializers.ModelSerializer): class Meta: model = Tax fields = ( + "id", "code", "value", ) @@ -35,15 +37,20 @@ class ProductPriceSerializer(serializers.ModelSerializer): ) +class CreateProductPriceSerializer(serializers.ModelSerializer): + class Meta: + model = ProductPrice + fields = ( + "id", + "price", + "tax", + ) + + class ProductSerializer(serializers.ModelSerializer): images = FileUploadSerializer(many=True, read_only=True) price = ProductPriceSerializer() - def get_price(self, product): - last_price = product.prices.last() - - return ProductPriceSerializer(last_price).data - class Meta: model = Product fields = ( @@ -56,10 +63,49 @@ class ProductSerializer(serializers.ModelSerializer): ) +class CreateProductSerializer(serializers.ModelSerializer): + price = serializers.DecimalField( + max_digits=13, decimal_places=4, required=False, write_only=True + ) + tax = serializers.PrimaryKeyRelatedField( + queryset=Tax.objects.all(), write_only=True + ) + + def create(self, validated_data): + instance = Product.objects.create( + name=validated_data.get("name"), + description=validated_data.get("description"), + stock=validated_data.get("stock"), + is_digital_asset=validated_data.get("is_digital_asset"), + url=validated_data.get("url"), + ) + ProductPrice.objects.create( + product=instance, + date=timezone.now(), + price=validated_data.get("price"), + tax=validated_data.get("tax"), + ) + return instance + + class Meta: + model = Product + fields = ( + "id", + "name", + "description", + "stock", + "is_digital_asset", + "url", + "price", + "tax", + ) + + class ProductBatchSerializer(serializers.ModelSerializer): class Meta: model = ProductBatch fields = ( + "id", "code", "product", "quantity", @@ -72,6 +118,7 @@ class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = ( + "id", "vat_id", "first_name", "last_name", @@ -88,6 +135,7 @@ class ProviderSerializer(serializers.ModelSerializer): class Meta: model = Provider fields = ( + "id", "vat_id", "name", "email", diff --git a/shop/api/v1/viewsets.py b/shop/api/v1/viewsets.py index de44b33..8cfdb7c 100644 --- a/shop/api/v1/viewsets.py +++ b/shop/api/v1/viewsets.py @@ -1,4 +1,9 @@ +from django.db.models.deletion import ProtectedError +from django.utils.text import gettext_lazy as _ + from rest_framework.viewsets import ModelViewSet +from rest_framework.exceptions import ValidationError +from rest_framework.permissions import IsAdminUser from shop.api.v1.serializers import ( ProductSerializer, @@ -9,6 +14,8 @@ from shop.api.v1.serializers import ( OrderLineSerializer, ProviderSerializer, ProductBatchSerializer, + CreateProductSerializer, + CreateProductPriceSerializer, ) from shop.models import ( Product, @@ -21,15 +28,47 @@ from shop.models import ( ProductBatch, ) +from shop.api.v1.permissions import ProductPermissions, ProductPricePermissions + class ProductViewSet(ModelViewSet): serializer_class = ProductSerializer - queryset = Product.objects.all() + queryset = Product.objects.prefetch_related("prices").all() + permission_classes = ( + IsAdminUser, + ProductPermissions, + ) + + def get_serializer_class(self): + if self.action == "create": + return CreateProductSerializer + return self.serializer_class + + def perform_destroy(self, instance): + try: + super().perform_destroy(instance) + except ProtectedError: + raise ValidationError(_("No se puede borrar el producto")) class ProductPriceViewSet(ModelViewSet): serializer_class = ProductPriceSerializer queryset = ProductPrice.objects.all() + permission_classes = ( + IsAdminUser, + ProductPricePermissions, + ) + + def get_queryset(self): + return super().get_queryset().filter(product_id=self.kwargs.get("product_id")) + + def get_serializer_class(self): + if self.action == "create": + return CreateProductPriceSerializer + return self.serializer_class + + def perform_create(self, serializer): + serializer.save(product_id=self.kwargs.get("product_id")) class ProductBatchViewSet(ModelViewSet): diff --git a/shop/models.py b/shop/models.py index ae1259e..b35ae0b 100644 --- a/shop/models.py +++ b/shop/models.py @@ -34,6 +34,10 @@ class Product(models.Model): def __str__(self): return self.name + @property + def price(self): + return self.prices.last() + class Meta: verbose_name = _("Producto") verbose_name_plural = _("Productos") diff --git a/users/api/v1/urls.py b/users/api/v1/urls.py index 020724e..9272041 100644 --- a/users/api/v1/urls.py +++ b/users/api/v1/urls.py @@ -7,6 +7,10 @@ from rest_framework_simplejwt.views import ( token_verify, ) + +app_name = "auth" + + urlpatterns = [ path("login/", token_obtain_pair, name="login"), path("refresh/", token_refresh, name="refresh_jwt"),