From a7a22e79624f1847cfa1d388eb7370644efede09 Mon Sep 17 00:00:00 2001 From: mtyton Date: Tue, 15 Aug 2023 13:36:11 +0200 Subject: [PATCH] modifed parrams assignment --- artel/store/admin.py | 6 +- artel/store/forms.py | 4 +- artel/store/loader.py | 20 +++---- artel/store/models.py | 95 ++++++++++++++++---------------- artel/store/tests/factories.py | 34 ++++++------ artel/store/tests/test_loader.py | 6 +- artel/store/tests/test_models.py | 32 +++++------ artel/store/tests/test_views.py | 24 ++++---- 8 files changed, 110 insertions(+), 111 deletions(-) diff --git a/artel/store/admin.py b/artel/store/admin.py index 0a6aa7f..796fbd7 100644 --- a/artel/store/admin.py +++ b/artel/store/admin.py @@ -20,8 +20,8 @@ class ProductCategoryAdmin(ModelAdmin): list_display = ("name", ) -class ProductCategoryParamAdmin(ModelAdmin): - model = models.ProductCategoryParam +class ProductTemplateParamAdmin(ModelAdmin): + model = models.ProductTemplateParam list_display = ("key", "param_type") @@ -59,7 +59,7 @@ class StoreAdminGroup(ModelAdminGroup): items = ( ProductAuthorAdmin, ProductCategoryAdmin, - ProductCategoryParamAdmin, + ProductTemplateParamAdmin, ProductTemplateAdmin, ProductAdmin, DocumentTemplateAdmin, diff --git a/artel/store/forms.py b/artel/store/forms.py index 64ab14f..27cbe40 100644 --- a/artel/store/forms.py +++ b/artel/store/forms.py @@ -5,7 +5,7 @@ from django.db.models import Model from store.models import ( ProductTemplate, - ProductCategoryParamValue, + ProductTemplateParam, Product, PaymentMethod, DeliveryMethod @@ -74,7 +74,7 @@ class ProductTemplateConfigForm(forms.Form): category_params = template.category.category_params.all() for param in category_params: self.fields[param.key] = forms.ModelChoiceField( - queryset=ProductCategoryParamValue.objects.filter(param=param), + queryset=ProductTemplateParam.objects.filter(param=param), widget=ButtonToggleSelect(attrs={"class": "btn-group btn-group-toggle"}), ) diff --git a/artel/store/loader.py b/artel/store/loader.py index 8174711..214e42c 100644 --- a/artel/store/loader.py +++ b/artel/store/loader.py @@ -2,11 +2,11 @@ import logging import requests import pandas as pd -from django.core import files +from django.core.files.base import ContentFile from store.models import ( ProductTemplate, - ProductCategoryParamValue, + ProductTemplateParam, Product, ProductImage ) @@ -29,7 +29,7 @@ class TemplateLoader(BaseLoader): class ProductLoader(BaseLoader): - def _get_images(self, row) -> list[files.ContentFile]: + def _get_images(self, row) -> list[ContentFile]: urls = row["images"] images = [] for url in urls: @@ -37,28 +37,28 @@ class ProductLoader(BaseLoader): if response.status_code == 200: data = response.raw file_name = url.split("/")[-1] - image = files.ContentFile(data, name=file_name) + image = ContentFile(data, name=file_name) images.append(image) return images def _process_row(self, row): template = ProductTemplate.objects.get(code=row["template"]) - price = float(row["price"]) + price = float(row["price"].strip("zł").replace(",", ".")) name = row["name"] available = bool(row["available"]) params = [] for param in row["params"]: key, value = param - param = ProductCategoryParamValue.objects.get(param__key=key, value=value) + param = ProductTemplateParam.objects.get(param__key=key, value=value) params.append(param) product = Product.objects.get_or_create_by_params(template=template, params=params) product.price = price product.name = name product.available = available - - images = self._get_images(row) - for i, image in enumerate(images): - ProductImage.objects.create(product=product, image=image, is_main=bool(i==0)) + # NOTE - temporary solution + # images = self._get_images(row) + # for i, image in enumerate(images): + # ProductImage.objects.create(product=product, image=image, is_main=bool(i==0)) product.save() return product diff --git a/artel/store/models.py b/artel/store/models.py index 2919cf9..8bb104f 100644 --- a/artel/store/models.py +++ b/artel/store/models.py @@ -95,54 +95,12 @@ class ProductCategory(ClusterableModel): ] -class CategoryParamTypeChoices(models.TextChoices): - INT = "int" - STRING = "str" - FLOAT = "float" - - -class ProductCategoryParam(ClusterableModel): - category = ParentalKey(ProductCategory, on_delete=models.CASCADE, related_name="category_params") - key = models.CharField(max_length=200) - param_type = models.CharField(max_length=200, choices=CategoryParamTypeChoices.choices) - - def __str__(self): - return self.key - - panels = [ - FieldPanel("category"), - FieldPanel("key"), - FieldPanel("param_type"), - InlinePanel("param_values") - ] - - def get_available_values(self) -> Iterator[any]: - for elem in self.param_values.all(): - yield elem.get_value() - - -class ProductCategoryParamValue(ClusterableModel): - param = ParentalKey(ProductCategoryParam, on_delete=models.CASCADE, related_name="param_values") - value = models.CharField(max_length=255) - - def get_value(self): - try: - func = getattr(builtins, self.param.param_type) - return func(self.value) - except ValueError: - return - - def __str__(self): - return f"{self.param.key}: {self.value}" - - class ProductTemplate(ClusterableModel): category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE) author = models.ForeignKey(ProductAuthor, on_delete=models.CASCADE) title = models.CharField(max_length=255) code = models.CharField(max_length=255) description = models.TextField(blank=True) - # TODO - add mechanism for enabling params tags = TaggableManager() @@ -175,9 +133,50 @@ class ProductTemplateImage(BaseImageModel): is_main = models.BooleanField(default=False) +class TemplateParamValueChoices(models.TextChoices): + INT = "int" + STRING = "str" + FLOAT = "float" + + +class ProductTemplateParam(ClusterableModel): + template = ParentalKey(ProductTemplate, on_delete=models.CASCADE, related_name="template_params") + key = models.CharField(max_length=200) + param_type = models.CharField(max_length=200, choices=TemplateParamValueChoices.choices) + + def __str__(self): + return self.key + + panels = [ + FieldPanel("category"), + FieldPanel("key"), + FieldPanel("param_type"), + InlinePanel("param_values") + ] + + def get_available_values(self) -> Iterator[any]: + for elem in self.param_values.all(): + yield elem.get_value() + + +class ProductTemplateParamValue(ClusterableModel): + param = ParentalKey(ProductTemplateParam, on_delete=models.CASCADE, related_name="param_values") + value = models.CharField(max_length=255) + + def get_value(self): + try: + func = getattr(builtins, self.param.param_type) + return func(self.value) + except ValueError: + return + + def __str__(self): + return f"{self.param.key}: {self.value}" + + class ProductManager(models.Manager): - def get_or_create_by_params(self, params: list[ProductCategoryParamValue], template: ProductTemplate): + def get_or_create_by_params(self, params: list[ProductTemplateParam], template: ProductTemplate): products = self.filter(template=template) for param in params: @@ -208,8 +207,8 @@ class Product(ClusterableModel): name = models.CharField(max_length=255, blank=True) template = models.ForeignKey(ProductTemplate, on_delete=models.CASCADE, related_name="products") params = models.ManyToManyField( - ProductCategoryParamValue, blank=True, through="ProductParam", - limit_choices_to=models.Q(param__category=models.F("product__template__category")) + ProductTemplateParamValue, blank=True, through="ProductParam", + limit_choices_to=models.Q(param__template=models.F("product__template")) ) price = models.FloatField() available = models.BooleanField(default=True) @@ -260,7 +259,7 @@ class ProductImage(BaseImageModel): class ProductParam(models.Model): product = ParentalKey(Product, on_delete=models.CASCADE, related_name="product_params") - param_value = models.ForeignKey(ProductCategoryParamValue, on_delete=models.CASCADE) + param_value = models.ForeignKey(ProductTemplateParamValue, on_delete=models.CASCADE) def save(self, *args, **kwargs): self.full_clean() @@ -277,8 +276,8 @@ def validate_param(sender, **kwargs): errors = [] for pk in pk_set: try: - param = ProductCategoryParamValue.objects.get(pk=pk).param - except ProductCategoryParamValue.DoesNotExist as e: + param = ProductTemplateParamValue.objects.get(pk=pk).param + except ProductTemplateParamValue.DoesNotExist as e: logger.exception(f"Product param validation failed with exception: {str(e)}") count = product_instance.params.filter(productparam__param_value__param=param).count() if count >= 1: diff --git a/artel/store/tests/factories.py b/artel/store/tests/factories.py index 261da7a..d951619 100644 --- a/artel/store/tests/factories.py +++ b/artel/store/tests/factories.py @@ -31,23 +31,6 @@ class ProductCategoryFactory(DjangoModelFactory): name = Faker('name') -class ProductCategoryParamFactory(DjangoModelFactory): - class Meta: - model = 'store.ProductCategoryParam' - - key = Faker('name') - category = SubFactory(ProductCategoryFactory) - param_type = 'str' - - -class ProductCategoryParamValueFactory(DjangoModelFactory): - class Meta: - model = 'store.ProductCategoryParamValue' - - param = SubFactory(ProductCategoryParamFactory) - value = Faker('name') - - class ProductTemplateFactory(DjangoModelFactory): class Meta: model = 'store.ProductTemplate' @@ -59,6 +42,23 @@ class ProductTemplateFactory(DjangoModelFactory): category = SubFactory(ProductCategoryFactory) +class ProductTemplateParamFactory(DjangoModelFactory): + class Meta: + model = 'store.ProductTemplateParam' + + key = Faker('name') + template = SubFactory(ProductTemplateFactory) + param_type = 'str' + + +class ProductTemplateParamValueFactory(DjangoModelFactory): + class Meta: + model = 'store.ProductTemplateParamValue' + + param = SubFactory(ProductTemplateParamFactory) + value = Faker('name') + + class ProductFactory(DjangoModelFactory): class Meta: model = 'store.Product' diff --git a/artel/store/tests/test_loader.py b/artel/store/tests/test_loader.py index 9ee9c98..428b239 100644 --- a/artel/store/tests/test_loader.py +++ b/artel/store/tests/test_loader.py @@ -10,8 +10,8 @@ class TestProductLoader(TestCase): def setUp(self) -> None: self.category = factories.ProductCategoryFactory() self.template = factories.ProductTemplateFactory(category=self.category) - self.category_params = [factories.ProductCategoryParamFactory(category=self.category) for _ in range(3)] - self.category_param_values = [factories.ProductCategoryParamValueFactory(param=param) for param in self.category_params] + self.category_params = [factories.ProductTemplateParamFactory(category=self.category) for _ in range(3)] + self.category_param_values = [factories.ProductTemplateParamValueFactory(param=param) for param in self.category_params] def test_load_products_single_product_success(self): fake_df = pd.DataFrame({ @@ -93,5 +93,5 @@ class TestProductLoader(TestCase): loader.process() self.assertEqual(self.template.products.count(), 0) - mock_logger.exception.assert_called_with("ProductCategoryParamValue matching query does not exist.") + mock_logger.exception.assert_called_with("ProductTemplateyParamValue matching query does not exist.") \ No newline at end of file diff --git a/artel/store/tests/test_models.py b/artel/store/tests/test_models.py index e3ffe3b..6568242 100644 --- a/artel/store/tests/test_models.py +++ b/artel/store/tests/test_models.py @@ -26,21 +26,21 @@ class ProductCategoryParamTestCase(TestCase): self.assertEqual(available_values, []) def test_get_available_values_one_value_success(self): - factories.ProductCategoryParamValueFactory(param=self.param, value="23") + factories.ProductTemplateParamValueFactory(param=self.param, value="23") available_values = [v for v in self.param.get_available_values()] self.assertEqual(available_values, [23]) self.assertEqual(len(available_values), 1) def test_get_available_values_multiple_values_success(self): - factories.ProductCategoryParamValueFactory(param=self.param, value="23") - factories.ProductCategoryParamValueFactory(param=self.param, value="24") - factories.ProductCategoryParamValueFactory(param=self.param, value="25") + factories.ProductTemplateParamValueFactory(param=self.param, value="23") + factories.ProductTemplateParamValueFactory(param=self.param, value="24") + factories.ProductTemplateParamValueFactory(param=self.param, value="25") available_values = [v for v in self.param.get_available_values()] self.assertEqual(available_values, [23, 24, 25]) self.assertEqual(len(available_values), 3) -class ProductCategoryParamValueTestCase(TestCase): +class ProductTemplateParamValueTestCase(TestCase): def setUp(self): super().setUp() self.category = factories.ProductCategoryFactory() @@ -52,7 +52,7 @@ class ProductCategoryParamValueTestCase(TestCase): param_type="int", key="test_param" ) - param_value = factories.ProductCategoryParamValueFactory(param=param, value="23") + param_value = factories.ProductTemplateParamValueFactory(param=param, value="23") proper_value = param_value.get_value() self.assertEqual(proper_value, 23) @@ -62,7 +62,7 @@ class ProductCategoryParamValueTestCase(TestCase): param_type="int", key="test_param" ) - param_value = factories.ProductCategoryParamValueFactory(param=param, value="wrong_value") + param_value = factories.ProductTemplateParamValueFactory(param=param, value="wrong_value") proper_value = param_value.get_value() self.assertEqual(proper_value, None) @@ -76,7 +76,7 @@ class ProductTestCase(TestCase): param_type="int", key="test_param" ) - param_value = factories.ProductCategoryParamValueFactory(param=param, value="23") + param_value = factories.ProductTemplateParamValueFactory(param=param, value="23") with transaction.atomic(): product.params.add(param_value) product.save() @@ -90,8 +90,8 @@ class ProductTestCase(TestCase): param_type="int", key="test_param" ) - param_value = factories.ProductCategoryParamValueFactory(param=param, value="23") - sec_param_value = factories.ProductCategoryParamValueFactory(param=param, value="24") + param_value = factories.ProductTemplateParamValueFactory(param=param, value="23") + sec_param_value = factories.ProductTemplateParamValueFactory(param=param, value="24") with self.assertRaises(ValidationError): with transaction.atomic(): product.params.add(param_value) @@ -100,8 +100,8 @@ class ProductTestCase(TestCase): def test_get_or_create_by_params_success(self): product = factories.ProductFactory(available=True) - value1 = factories.ProductCategoryParamValueFactory() - value2 = factories.ProductCategoryParamValueFactory() + value1 = factories.ProductTemplateParamValueFactory() + value2 = factories.ProductTemplateParamValueFactory() product.params.add(value1) product.params.add(value2) product.save() @@ -114,8 +114,8 @@ class ProductTestCase(TestCase): def test_get_or_create_by_params_success_not_existing_product(self): product = factories.ProductFactory(available=True) - value1 = factories.ProductCategoryParamValueFactory() - value2 = factories.ProductCategoryParamValueFactory() + value1 = factories.ProductTemplateParamValueFactory() + value2 = factories.ProductTemplateParamValueFactory() product.params.add(value1) product.price = 13.0 product.save() @@ -130,8 +130,8 @@ class ProductTestCase(TestCase): def test_get_or_create_by_params_success_not_existing_product_no_other_products(self): template = factories.ProductTemplateFactory() - value1 = factories.ProductCategoryParamValueFactory() - value2 = factories.ProductCategoryParamValueFactory() + value1 = factories.ProductTemplateParamValueFactory() + value2 = factories.ProductTemplateParamValueFactory() prod = store_models.Product.objects.get_or_create_by_params( params=[value1, value2], template=template, diff --git a/artel/store/tests/test_views.py b/artel/store/tests/test_views.py index ca6acb1..5883fb7 100644 --- a/artel/store/tests/test_views.py +++ b/artel/store/tests/test_views.py @@ -2,15 +2,15 @@ from django.test import TestCase from django.shortcuts import reverse from store.models import ( - ProductCategoryParam, - ProductCategoryParamValue, - CategoryParamTypeChoices + ProductTemplateParam, + ProductTemplateParamValue, + TemplateParamValueChoices ) from store.tests.factories import ( ProductTemplateFactory, ProductCategoryFactory, ProductFactory, - ProductCategoryParamValueFactory + ProductTemplateParamValueFactory ) @@ -21,18 +21,18 @@ class ConfigureProductViewTestCase(TestCase): self.category = ProductCategoryFactory() self.product_template = ProductTemplateFactory(category=self.category) # create template params and values for those params - self.param1 = ProductCategoryParam.objects.create( + self.param1 = ProductTemplateParam.objects.create( key="Mocowanie", category=self.category, - param_type=CategoryParamTypeChoices.STRING + param_type=TemplateParamValueChoices.STRING ) - self.param1_value1 = ProductCategoryParamValueFactory(param=self.param1) - self.param1_value2 = ProductCategoryParamValueFactory(param=self.param1) - self.param2 = ProductCategoryParam.objects.create( + self.param1_value1 = ProductTemplateParamValueFactory(param=self.param1) + self.param1_value2 = ProductTemplateParamValueFactory(param=self.param1) + self.param2 = ProductTemplateParam.objects.create( key="Format", category=self.category, - param_type=CategoryParamTypeChoices.STRING + param_type=TemplateParamValueChoices.STRING ) - self.param2_value1 = ProductCategoryParamValueFactory(param=self.param2) - self.param2_value2 = ProductCategoryParamValueFactory(param=self.param2) + self.param2_value1 = ProductTemplateParamValueFactory(param=self.param2) + self.param2_value2 = ProductTemplateParamValueFactory(param=self.param2) # create product variant self.variant1 = ProductFactory( template=self.product_template