feat: added tags
This commit is contained in:
@@ -3,3 +3,6 @@ from django.db import models
|
|||||||
|
|
||||||
class FileUpload(models.Model):
|
class FileUpload(models.Model):
|
||||||
file = models.FileField(upload_to="uploads", blank=False, null=False)
|
file = models.FileField(upload_to="uploads", blank=False, null=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.file.url
|
||||||
|
|||||||
+28
-1
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from shop.models import Product, ProductPrice, OrderLine, Order, Tax, Customer
|
from shop.models import Product, ProductPrice, OrderLine, Order, Tax, Customer, Provider, Tag, ProductBatch
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
|
||||||
@@ -58,3 +58,30 @@ class OrderLineAdmin(ModelAdmin):
|
|||||||
@admin.register(Order)
|
@admin.register(Order)
|
||||||
class OrderAdmin(ModelAdmin):
|
class OrderAdmin(ModelAdmin):
|
||||||
list_display = ("id",)
|
list_display = ("id",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Provider)
|
||||||
|
class ProviderAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"vat_id",
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Tag)
|
||||||
|
class TagAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductBatch)
|
||||||
|
class ProductBatchAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"product",
|
||||||
|
"quantity",
|
||||||
|
"provider",
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from shop.api.v1.viewsets import (
|
|||||||
OrderLineViewSet,
|
OrderLineViewSet,
|
||||||
ProviderViewSet,
|
ProviderViewSet,
|
||||||
ProductBatchViewSet,
|
ProductBatchViewSet,
|
||||||
|
TagViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ app_name = "shop"
|
|||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
|
||||||
router.register("taxes", TaxViewSet)
|
router.register("taxes", TaxViewSet)
|
||||||
|
router.register("tags", TagViewSet)
|
||||||
router.register("customers", CustomerViewSet)
|
router.register("customers", CustomerViewSet)
|
||||||
router.register("providers", ProviderViewSet)
|
router.register("providers", ProviderViewSet)
|
||||||
router.register("products", ProductViewSet)
|
router.register("products", ProductViewSet)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from shop.models import (
|
|||||||
Order,
|
Order,
|
||||||
ProductBatch,
|
ProductBatch,
|
||||||
Provider,
|
Provider,
|
||||||
|
Tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +25,15 @@ class TaxSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductPriceSerializer(serializers.ModelSerializer):
|
class ProductPriceSerializer(serializers.ModelSerializer):
|
||||||
tax = TaxSerializer()
|
tax = TaxSerializer()
|
||||||
|
|
||||||
@@ -60,6 +70,23 @@ class ProductSerializer(serializers.ModelSerializer):
|
|||||||
"is_digital_asset",
|
"is_digital_asset",
|
||||||
"images",
|
"images",
|
||||||
"price",
|
"price",
|
||||||
|
"tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ListProductSerializer(serializers.ModelSerializer):
|
||||||
|
price = ProductPriceSerializer()
|
||||||
|
tags = TagSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"stock",
|
||||||
|
"is_digital_asset",
|
||||||
|
"price",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -98,6 +125,8 @@ class CreateProductSerializer(serializers.ModelSerializer):
|
|||||||
"url",
|
"url",
|
||||||
"price",
|
"price",
|
||||||
"tax",
|
"tax",
|
||||||
|
"tags",
|
||||||
|
"images",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -197,3 +226,5 @@ class OrderLineSerializer(serializers.ModelSerializer):
|
|||||||
"taxes",
|
"taxes",
|
||||||
"tax_value",
|
"tax_value",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ from shop.api.v1.serializers import (
|
|||||||
ProductBatchSerializer,
|
ProductBatchSerializer,
|
||||||
CreateProductSerializer,
|
CreateProductSerializer,
|
||||||
CreateProductPriceSerializer,
|
CreateProductPriceSerializer,
|
||||||
|
TagSerializer,
|
||||||
|
ListProductSerializer,
|
||||||
)
|
)
|
||||||
from shop.models import (
|
from shop.models import (
|
||||||
Product,
|
Product,
|
||||||
@@ -30,6 +32,7 @@ from shop.models import (
|
|||||||
OrderLine,
|
OrderLine,
|
||||||
Provider,
|
Provider,
|
||||||
ProductBatch,
|
ProductBatch,
|
||||||
|
Tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
from shop.api.v1.permissions import ProductPermissions, ProductPricePermissions
|
from shop.api.v1.permissions import ProductPermissions, ProductPricePermissions
|
||||||
@@ -46,6 +49,8 @@ class ProductViewSet(ModelViewSet):
|
|||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.action == "create":
|
if self.action == "create":
|
||||||
return CreateProductSerializer
|
return CreateProductSerializer
|
||||||
|
elif self.action == "list":
|
||||||
|
return ListProductSerializer
|
||||||
return self.serializer_class
|
return self.serializer_class
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
@@ -145,3 +150,8 @@ class OrderLineViewSet(ModelViewSet):
|
|||||||
.select_related("order")
|
.select_related("order")
|
||||||
.filter(order__uuid=self.kwargs.get("order_id"))
|
.filter(order__uuid=self.kwargs.get("order_id"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TagViewSet(ModelViewSet):
|
||||||
|
serializer_class = TagSerializer
|
||||||
|
queryset = Tag.objects.all()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-05-14 16:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("files", "0001_initial"),
|
||||||
|
("shop", "0004_productbatch_provider"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Tag",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=16)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="product",
|
||||||
|
name="images",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
to="files.fileupload", verbose_name="Imágenes"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="product",
|
||||||
|
name="tags",
|
||||||
|
field=models.ManyToManyField(to="shop.tag", verbose_name="Etiquetas"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,6 +6,17 @@ from django.utils import timezone
|
|||||||
from django.utils.text import gettext_lazy as _
|
from django.utils.text import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(models.Model):
|
||||||
|
name = models.CharField(max_length=16)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Etiqueta")
|
||||||
|
verbose_name_plural = _("Etiquetas")
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=96,
|
max_length=96,
|
||||||
@@ -30,6 +41,7 @@ class Product(models.Model):
|
|||||||
images = models.ManyToManyField(
|
images = models.ManyToManyField(
|
||||||
"files.FileUpload", blank=True, verbose_name=_("Imágenes")
|
"files.FileUpload", blank=True, verbose_name=_("Imágenes")
|
||||||
)
|
)
|
||||||
|
tags = models.ManyToManyField("shop.Tag", blank=True, verbose_name=_("Etiquetas"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -103,6 +115,10 @@ class ProductBatch(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.code} - {self.product.name} - {self.quantity}"
|
return f"{self.code} - {self.product.name} - {self.quantity}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Remesa de producto")
|
||||||
|
verbose_name_plural = _("Remesas de productos")
|
||||||
|
|
||||||
|
|
||||||
class Tax(models.Model):
|
class Tax(models.Model):
|
||||||
code = models.CharField(
|
code = models.CharField(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from rest_framework.test import APITestCase
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from config.tests.mixins import TestUserAuthenticationMixin
|
from config.tests.mixins import TestUserAuthenticationMixin
|
||||||
from shop.models import Product, ProductPrice, Tax, ProductBatch
|
from shop.models import Product, ProductPrice, Tax, ProductBatch, Tag
|
||||||
|
|
||||||
|
|
||||||
class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
||||||
@@ -17,6 +17,7 @@ class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
|||||||
code="IVA",
|
code="IVA",
|
||||||
value=21,
|
value=21,
|
||||||
)
|
)
|
||||||
|
self.tag = Tag.objects.create(name="Alimentos")
|
||||||
|
|
||||||
def create_products(self):
|
def create_products(self):
|
||||||
self.product = Product.objects.create(
|
self.product = Product.objects.create(
|
||||||
@@ -58,6 +59,7 @@ class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
|||||||
"price": "1.20",
|
"price": "1.20",
|
||||||
"stock": "100",
|
"stock": "100",
|
||||||
"tax": self.tax.pk,
|
"tax": self.tax.pk,
|
||||||
|
"tags": [self.tag.pk],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
@@ -78,6 +80,9 @@ class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
|||||||
"price": "1.20",
|
"price": "1.20",
|
||||||
"stock": "100",
|
"stock": "100",
|
||||||
"tax": self.tax.pk,
|
"tax": self.tax.pk,
|
||||||
|
"tags": [
|
||||||
|
self.tag.pk,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
@@ -105,6 +110,9 @@ class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
|||||||
"price": "1.20",
|
"price": "1.20",
|
||||||
"stock": "100",
|
"stock": "100",
|
||||||
"tax": self.tax.pk,
|
"tax": self.tax.pk,
|
||||||
|
"tags": [
|
||||||
|
self.tag.pk,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
@@ -127,3 +135,32 @@ class TestProductsAPI(APITestCase, TestUserAuthenticationMixin):
|
|||||||
|
|
||||||
response = self.client.delete(f"/api/v1/shop/products/{self.product.pk}/")
|
response = self.client.delete(f"/api/v1/shop/products/{self.product.pk}/")
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
|
||||||
|
def test_create_update_product_tags(self):
|
||||||
|
self.login()
|
||||||
|
tag = Tag.objects.create(name="Alimentación")
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/v1/shop/products/",
|
||||||
|
{
|
||||||
|
"name": "Papafritas",
|
||||||
|
"description": "Las mejores papafritas",
|
||||||
|
"is_digital_asset": False,
|
||||||
|
"url": "",
|
||||||
|
"price": "1.20",
|
||||||
|
"stock": "100",
|
||||||
|
"tax": self.tax.pk,
|
||||||
|
"tags": [
|
||||||
|
self.tag.pk,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
pk = response.data.get("id")
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/v1/shop/products/{pk}/")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
response = self.client.patch(f"/api/v1/shop/products/{pk}/", {"tags": [tag.pk]})
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
response = self.client.get(f"/api/v1/shop/products/{pk}/")
|
||||||
|
assert response.data.get("tags", [])[0] == tag.pk
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from config.tests.mixins import TestUserAuthenticationMixin
|
||||||
|
from shop.models import Tag
|
||||||
|
|
||||||
|
|
||||||
|
class TestTagsAPI(APITestCase, TestUserAuthenticationMixin):
|
||||||
|
model_name = "tag"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.create_user()
|
||||||
|
|
||||||
|
def create_tags(self):
|
||||||
|
Tag.objects.create(name="Deportes")
|
||||||
|
|
||||||
|
def test_fetch_taxes(self):
|
||||||
|
self.create_tags()
|
||||||
|
self.login()
|
||||||
|
response = self.client.get("/api/v1/shop/tags/")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert len(response.data.get("results")) == 1
|
||||||
|
|
||||||
|
def test_create_retrieve_tax(self):
|
||||||
|
self.login()
|
||||||
|
response = self.client.post("/api/v1/shop/tags/", {"name": "Videojuegos"})
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
pk = response.data.get("id")
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/v1/shop/tags/{pk}/")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
def test_create_update_tax(self):
|
||||||
|
self.login()
|
||||||
|
response = self.client.post("/api/v1/shop/tags/", {"name": "Videojuegos"})
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
pk = response.data.get("id")
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/v1/shop/tags/{pk}/")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/v1/shop/tags/{pk}/", {"name": "Juegos de mesa"}
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/v1/shop/tags/{pk}/")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
assert response.data.get("name") == "Juegos de mesa"
|
||||||
Reference in New Issue
Block a user