diff --git a/.gitignore b/.gitignore index b809595..99e9fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -143,3 +143,4 @@ artel/static/ # media artel/media/* +artel/store/data/* 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..2f45ad9 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, + ProductTemplateParamValue, Product, PaymentMethod, DeliveryMethod @@ -71,11 +71,16 @@ class ButtonToggleSelect(forms.RadioSelect): class ProductTemplateConfigForm(forms.Form): def _create_dynamic_fields(self, template: ProductTemplate): - category_params = template.category.category_params.all() - for param in category_params: + template_params = template.template_params.all() + for param in template_params: + queryset = ProductTemplateParamValue.objects.filter(param=param) + if queryset.count() >= 4: + widget = forms.Select(attrs={"class": "form-select"}) + else: + widget = ButtonToggleSelect(attrs={"class": "btn-group btn-group-toggle"}) self.fields[param.key] = forms.ModelChoiceField( - queryset=ProductCategoryParamValue.objects.filter(param=param), - widget=ButtonToggleSelect(attrs={"class": "btn-group btn-group-toggle"}), + queryset=queryset, + widget=widget, ) def __init__( diff --git a/artel/store/loader.py b/artel/store/loader.py index 8174711..0aacc9f 100644 --- a/artel/store/loader.py +++ b/artel/store/loader.py @@ -1,12 +1,14 @@ import logging +import time import requests import pandas as pd -from django.core import files - +from django.core.files.base import ContentFile +from django.conf import settings from store.models import ( ProductTemplate, - ProductCategoryParamValue, + ProductTemplateParam, + ProductTemplateParamValue, Product, ProductImage ) @@ -29,36 +31,46 @@ class TemplateLoader(BaseLoader): class ProductLoader(BaseLoader): - def _get_images(self, row) -> list[files.ContentFile]: - urls = row["images"] + def _clear(self): + Product.objects.all().delete() + + def __init__(self, path, param_names, clear=False): + super().__init__(path) + self.param_names = param_names + if clear: + self._clear() + + def _get_images(self, row) -> list[ContentFile]: + url = row["images"] images = [] - for url in urls: - response = requests.get(url, stream=True) - if response.status_code == 200: - data = response.raw - file_name = url.split("/")[-1] - image = files.ContentFile(data, name=file_name) + response = requests.get( + url+"/preview", stream=True + ) + if response.status_code == 200: + data = response.content + image = ContentFile(data, name=row["template"]) 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) - params.append(param) + for key in self.param_names: + value = row[key] + param, _ = ProductTemplateParam.objects.get_or_create(key=key, template=template) + param_value, _ = ProductTemplateParamValue.objects.get_or_create(param=param, value=value) + params.append(param_value) 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 @@ -66,6 +78,7 @@ class ProductLoader(BaseLoader): data = self.load_data() products = [] for _, row in data.iterrows(): + time.sleep(5) try: product = self._process_row(row) except Exception as e: diff --git a/artel/store/management/commands/load_products.py b/artel/store/management/commands/load_products.py index a277ad0..84197e0 100644 --- a/artel/store/management/commands/load_products.py +++ b/artel/store/management/commands/load_products.py @@ -9,5 +9,5 @@ class Command(BaseCommand): help = "Load products from csv file" def handle(self, *args, **options): - loader = ProductLoader(settings.PRODUCTS_CSV_PATH) + loader = ProductLoader(settings.PRODUCTS_CSV_PATH, ["mocowanie", "format", "wykonanie"], True) loader.process() diff --git a/artel/store/migrations/0013_producttemplateparam_alter_product_params_and_more.py b/artel/store/migrations/0013_producttemplateparam_alter_product_params_and_more.py new file mode 100644 index 0000000..add7879 --- /dev/null +++ b/artel/store/migrations/0013_producttemplateparam_alter_product_params_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 4.1.10 on 2023-08-15 10:44 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("store", "0012_deliverymethod_order_uuid_product_uuid_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="ProductTemplateParam", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("key", models.CharField(max_length=200)), + ( + "param_type", + models.CharField(choices=[("int", "Int"), ("str", "String"), ("float", "Float")], max_length=200), + ), + ( + "template", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="template_params", + to="store.producttemplate", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AlterField( + model_name="product", + name="params", + field=models.ManyToManyField( + blank=True, + limit_choices_to=models.Q(("param__template", models.F("product__template"))), + through="store.ProductParam", + to="store.productcategoryparamvalue", + ), + ), + migrations.RenameModel( + old_name="ProductCategoryParamValue", + new_name="ProductTemplateParamValue", + ), + migrations.DeleteModel( + name="ProductCategoryParam", + ), + migrations.AlterField( + model_name="producttemplateparamvalue", + name="param", + field=modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="param_values", + to="store.producttemplateparam", + ), + ), + ] diff --git a/artel/store/models.py b/artel/store/models.py index 2919cf9..5e32d13 100644 --- a/artel/store/models.py +++ b/artel/store/models.py @@ -90,59 +90,16 @@ class ProductCategory(ClusterableModel): return self.name panels = [ - FieldPanel("name"), - InlinePanel("category_params") + FieldPanel("name") ] -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() @@ -164,6 +121,7 @@ class ProductTemplate(ClusterableModel): FieldPanel('description'), InlinePanel("template_images", label="Template Images"), FieldPanel("tags"), + InlinePanel("template_params") ] @@ -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("template"), + 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/templates/store/forms/button_toggle_select.html b/artel/store/templates/store/forms/button_toggle_select.html index 2d5e4b6..b145705 100644 --- a/artel/store/templates/store/forms/button_toggle_select.html +++ b/artel/store/templates/store/forms/button_toggle_select.html @@ -10,19 +10,3 @@ {% endfor %} {% endwith %} - diff --git a/artel/store/tests/factories.py b/artel/store/tests/factories.py index 261da7a..31f961d 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' @@ -74,7 +74,7 @@ class ProductParamFactory(DjangoModelFactory): model = 'store.ProductParam' product = SubFactory(ProductFactory) - param = SubFactory(ProductCategoryParamFactory) + param = SubFactory(ProductTemplateParamValueFactory) class PaymentMethodFactory(DjangoModelFactory): diff --git a/artel/store/tests/test_loader.py b/artel/store/tests/test_loader.py index 9ee9c98..09b039e 100644 --- a/artel/store/tests/test_loader.py +++ b/artel/store/tests/test_loader.py @@ -10,23 +10,21 @@ 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.template_params = [factories.ProductTemplateParamFactory(template=self.template) for _ in range(3)] + self.templat_params_values = [factories.ProductTemplateParamValueFactory(param=param) for param in self.template_params] def test_load_products_single_product_success(self): fake_df = pd.DataFrame({ "template": [self.template.code], - "price": [10.0], + "price": str(10.0), "name": ["Test product"], "available": [True], - "params": [[ - (self.category_params[0].key, self.category_param_values[0].value), - (self.category_params[1].key, self.category_param_values[1].value), - (self.category_params[2].key, self.category_param_values[2].value), - ]] + self.template_params[0].key: self.templat_params_values[0].value, + self.template_params[1].key: self.templat_params_values[1].value, + self.template_params[2].key: self.templat_params_values[2].value }) with patch("store.loader.BaseLoader.load_data", return_value=fake_df): - loader = ProductLoader("fake_path") + loader = ProductLoader("fake_path", [p.key for p in self.template_params]) loader.process() self.assertEqual(self.template.products.count(), 1) @@ -42,14 +40,12 @@ class TestProductLoader(TestCase): "price": ["FASDSADQAW"], "name": ["Test product"], "available": [True], - "params": [[ - (self.category_params[0].key, self.category_param_values[0].value), - (self.category_params[1].key, self.category_param_values[1].value), - (self.category_params[2].key, self.category_param_values[2].value), - ]] + self.template_params[0].key: self.templat_params_values[0].value, + self.template_params[1].key: self.templat_params_values[1].value, + self.template_params[2].key: self.templat_params_values[2].value }) with patch("store.loader.BaseLoader.load_data", return_value=fake_df): - loader = ProductLoader("fake_path") + loader = ProductLoader("fake_path", [p.key for p in self.template_params]) loader.process() self.assertEqual(self.template.products.count(), 0) @@ -59,39 +55,17 @@ class TestProductLoader(TestCase): def test_load_no_existing_template_code_failure(self, mock_logger): fake_df = pd.DataFrame({ "template": ["NOTEEXISTINGTEMPLATE"], - "price": [10.0], + "price": str(10.0), "name": ["Test product"], "available": [True], - "params": [[ - (self.category_params[0].key, self.category_param_values[0].value), - (self.category_params[1].key, self.category_param_values[1].value), - (self.category_params[2].key, self.category_param_values[2].value), - ]] + self.template_params[0].key: self.templat_params_values[0].value, + self.template_params[1].key: self.templat_params_values[1].value, + self.template_params[2].key: self.templat_params_values[2].value }) with patch("store.loader.BaseLoader.load_data", return_value=fake_df): - loader = ProductLoader("fake_path") + loader = ProductLoader("fake_path", [p.key for p in self.template_params]) loader.process() self.assertEqual(self.template.products.count(), 0) mock_logger.exception.assert_called_with("ProductTemplate matching query does not exist.") - - @patch("store.loader.logger") - def test_not_existing_params_key_value_pairs_failure(self, mock_logger): - fake_df = pd.DataFrame({ - "template": [self.template.code], - "price": [10.0], - "name": ["Test product"], - "available": [True], - "params": [[ - (self.category_params[0].key, self.category_param_values[2].value), - (self.category_params[1].key, self.category_param_values[0].value), - (self.category_params[2].key, self.category_param_values[1].value), - ]] - }) - with patch("store.loader.BaseLoader.load_data", return_value=fake_df): - loader = ProductLoader("fake_path") - loader.process() - - self.assertEqual(self.template.products.count(), 0) - mock_logger.exception.assert_called_with("ProductCategoryParamValue 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..10b81c3 100644 --- a/artel/store/tests/test_models.py +++ b/artel/store/tests/test_models.py @@ -15,8 +15,9 @@ class ProductCategoryParamTestCase(TestCase): def setUp(self): super().setUp() self.category = factories.ProductCategoryFactory() - self.param = factories.ProductCategoryParamFactory( - category=self.category, + self.template = factories.ProductTemplateFactory(category=self.category) + self.param = factories.ProductTemplateParamFactory( + template=self.template, param_type="int", key="test_param" ) @@ -26,72 +27,73 @@ 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() + self.template = factories.ProductTemplateFactory(category=self.category) def test_get_value_success(self): - param = factories.ProductCategoryParamFactory( - category=self.category, + param = factories.ProductTemplateParamFactory( + template=self.template, 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) def test_get_value_failure_wrong_value(self): - param = factories.ProductCategoryParamFactory( - category=self.category, + param = factories.ProductTemplateParamFactory( + template=self.template, 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) class ProductTestCase(TestCase): - def test_category_params_one_value_success(self): + def test_template_params_one_value_success(self): product = factories.ProductFactory() - param = factories.ProductCategoryParamFactory( - category=product.template.category, + param = factories.ProductTemplateParamFactory( + template=product.template, 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() self.assertEqual(product.params.count(), 1) self.assertEqual(product.params.first().get_value(), 23) - def test_category_params_multiple_values_failure(self): + def test_template_params_multiple_values_failure(self): product = factories.ProductFactory() - param = factories.ProductCategoryParamFactory( - category=product.template.category, + param = factories.ProductTemplateParamFactory( + template=product.template, 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 +102,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 +116,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 +132,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..9f5edfb 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( - key="Mocowanie", category=self.category, - param_type=CategoryParamTypeChoices.STRING + self.param1 = ProductTemplateParam.objects.create( + key="Mocowanie", template=self.product_template, + param_type=TemplateParamValueChoices.STRING ) - self.param1_value1 = ProductCategoryParamValueFactory(param=self.param1) - self.param1_value2 = ProductCategoryParamValueFactory(param=self.param1) - self.param2 = ProductCategoryParam.objects.create( - key="Format", category=self.category, - param_type=CategoryParamTypeChoices.STRING + self.param1_value1 = ProductTemplateParamValueFactory(param=self.param1) + self.param1_value2 = ProductTemplateParamValueFactory(param=self.param1) + self.param2 = ProductTemplateParam.objects.create( + key="Format", template=self.product_template, + 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