diff --git a/apps/product/admin.py b/apps/product/admin.py index 16f4f96e..b982392e 100644 --- a/apps/product/admin.py +++ b/apps/product/admin.py @@ -1,19 +1,12 @@ """Product admin conf.""" from django.contrib import admin from utils.admin import BaseModelAdminMixin -from .models import Product, ProductType, ProductSubType, Unit, \ - Characteristic, ProductCharacteristic - - -class ProductCharacteristics(admin.TabularInline): - model = ProductCharacteristic - extra = 0 +from .models import Product, ProductType, ProductSubType, Unit @admin.register(Product) class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Admin page for model Product.""" - inlines = [ProductCharacteristics, ] @admin.register(ProductType) @@ -29,8 +22,3 @@ class ProductSubTypeAdmin(admin.ModelAdmin): @admin.register(Unit) class UnitAdmin(admin.ModelAdmin): """Admin page for model Unit.""" - - -@admin.register(Characteristic) -class CharacteristicAdmin(admin.ModelAdmin): - """Admin page for model Characteristic.""" diff --git a/apps/product/models.py b/apps/product/models.py index bde29905..954f0693 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,10 +1,10 @@ """Product app models.""" -from django.db import models -from django.contrib.gis.db import models as gis_models from django.contrib.contenttypes import fields as generic +from django.contrib.gis.db import models as gis_models from django.core.exceptions import ValidationError -from django.contrib.postgres.fields import JSONField +from django.db import models from django.utils.translation import gettext_lazy as _ + from utils.models import (BaseAttributes, ProjectBaseMixin, TranslatedFieldsMixin, TJSONField) @@ -31,6 +31,9 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin): unique=True, db_index=True, verbose_name=_('Index name')) use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='product_types', + verbose_name=_('Tag')) class Meta: """Meta class.""" @@ -120,6 +123,27 @@ class ProductQuerySet(models.QuerySet): return self.filter(subtypes__index_name=product_subtype) +class ProductBrandQuerySet(models.QuerySet): + """QuerySet for model ProductBrand.""" + + +class ProductBrand(models.Model): + + name = models.CharField(max_length=255, unique=True, + verbose_name=_('product brand')) + + objects = ProductBrandQuerySet.as_manager() + + class Meta: + """Meta class.""" + verbose_name = _('Brand') + verbose_name_plural = _('Brands') + + def __str__(self): + """Overridden str dunder.""" + return self.name + + class Product(TranslatedFieldsMixin, BaseAttributes): """Product models.""" @@ -131,6 +155,16 @@ class Product(TranslatedFieldsMixin, BaseAttributes): (ONLINE, _('Online')), ) + PUBLISHED = 0 + OUT_OF_PRODUCTION = 1 + WAITING = 2 + + STATE_CHOICES = ( + (PUBLISHED, _('Published')), + (OUT_OF_PRODUCTION, _('Out_of_production')), + (WAITING, _('Waiting')), + ) + category = models.PositiveIntegerField(choices=CATEGORY_CHOICES, default=COMMON) name = models.CharField(max_length=255, @@ -138,9 +172,6 @@ class Product(TranslatedFieldsMixin, BaseAttributes): verbose_name=_('name')) description = TJSONField(_('Description'), null=True, blank=True, default=None, help_text='{"en-GB":"some text"}') - characteristics = models.ManyToManyField('Characteristic', - through='ProductCharacteristic', - verbose_name=_('characteristics')) country = models.ManyToManyField('location.Country', verbose_name=_('Country')) available = models.BooleanField(_('Available'), default=True) @@ -170,6 +201,14 @@ class Product(TranslatedFieldsMixin, BaseAttributes): favorites = generic.GenericRelation(to='favorites.Favorites') old_id = models.PositiveIntegerField(_('old id'), default=None, blank=True, null=True) + state = models.PositiveIntegerField(choices=STATE_CHOICES, + default=WAITING, + verbose_name=_('state'), + help_text=_('attribute from legacy db')) + brand = models.ForeignKey(ProductBrand, on_delete=models.PROTECT, null=True, + verbose_name=_('brand')) + tags = models.ManyToManyField('tag.Tag', related_name='products', + verbose_name=_('Tag')) objects = ProductManager.from_queryset(ProductQuerySet)() @@ -232,39 +271,6 @@ class Unit(models.Model): return self.name -class Characteristic(models.Model): - """Characteristic model.""" - - name = models.CharField(_('name'), max_length=255) - value = models.CharField(max_length=255, null=True, blank=True, - verbose_name=_('value')) - # unit = models.ForeignKey('Unit', on_delete=models.PROTECT) - priority = models.IntegerField(unique=True, null=True, default=None) - old_id = models.PositiveIntegerField(blank=True, null=True, default=None) - - class Meta: - """Meta model.""" - verbose_name = _('characteristic') - verbose_name_plural = _('characteristics') - - def __str__(self): - return self.name - - -class ProductCharacteristic(models.Model): - - product = models.ForeignKey('Product', on_delete=models.CASCADE) - characteristic = models.ForeignKey('Characteristic', on_delete=models.CASCADE) - - class Meta: - """Meta class.""" - verbose_name = _('product characteristic') - verbose_name_plural = _('product characteristics') - - def __str__(self): - return f'{self.characteristic.name} - {self.characteristic.value}' - - class WineStandardQuerySet(models.QuerySet): """Wine appellation queryset.""" @@ -308,8 +314,8 @@ class WineClassification(models.Model): name = models.CharField(_('name'), max_length=255) standard = models.ForeignKey(WineStandard, on_delete=models.PROTECT, verbose_name=_('standard')) - possible_color = models.ForeignKey(Characteristic, on_delete=models.PROTECT, - help_text=_('Legacy attribute')) + tags = models.ManyToManyField('tag.Tag', related_name='wine_classifications', + verbose_name=_('Tag')) possible_subtype = models.ForeignKey(ProductSubType, on_delete=models.PROTECT, blank=True, null=True, default=None, help_text=_('Legacy attribute - possible_type')) diff --git a/apps/product/transfer_data.py b/apps/product/transfer_data.py index 17940af9..98c4d83c 100644 --- a/apps/product/transfer_data.py +++ b/apps/product/transfer_data.py @@ -1,5 +1,7 @@ from pprint import pprint +from django.conf import settings +from tag.models import TagCategory from transfer import models as transfer_models from transfer.serializers.partner import PartnerSerializer from transfer.serializers import product as product_serializers @@ -16,6 +18,16 @@ def transfer_partner(): def transfer_wine_color(): + wine_color_index_name = 'wine_color' + + TagCategory.objects.get_or_create( + index_name=wine_color_index_name, + defaults={ + 'label': {settings.FALLBACK_LOCALE: wine_color_index_name}, + 'value_type': TagCategory.STRING, + 'index_name': wine_color_index_name, + 'public': True + }) queryset = transfer_models.WineColor.objects.all() serialized_data = product_serializers.WineColorSerializer( data=list(queryset.values()), @@ -26,6 +38,37 @@ def transfer_wine_color(): pprint(f"WineColorSerializer errors: {serialized_data.errors}") +def transfer_wine_bottles_produced(): + bottles_produced_index_name = 'bottles_produced' + + TagCategory.objects.get_or_create( + index_name=bottles_produced_index_name, + defaults={ + 'label': {settings.FALLBACK_LOCALE: bottles_produced_index_name}, + 'value_type': TagCategory.STRING, + 'index_name': bottles_produced_index_name, + 'public': True + }) + raw_queryset = transfer_models.Products.objects.raw( + """ + SELECT + DISTINCT bottles_produced, + 1 as id + FROM products + WHERE bottles_produced IS NOT NULL + AND bottles_produced !=''; + """ + ) + queryset = [vars(query) for query in raw_queryset] + serialized_data = product_serializers.BottlesProducedSerializer( + data=queryset, + many=True) + if serialized_data.is_valid(): + serialized_data.save() + else: + pprint(f"transfer_wine_bottles_produced errors: {serialized_data.errors}") + + def transfer_wine_standard(): queryset = transfer_models.WineClassification.objects.filter(parent_id__isnull=True) serialized_data = product_serializers.WineStandardSerializer( @@ -34,7 +77,7 @@ def transfer_wine_standard(): if serialized_data.is_valid(): serialized_data.save() else: - pprint(f"WineStandardSerializer errors: {serialized_data.errors}") + pprint(f"transfer_wine_standard errors: {serialized_data.errors}") def transfer_wine_classifications(): @@ -45,12 +88,50 @@ def transfer_wine_classifications(): if serialized_data.is_valid(): serialized_data.save() else: - pprint(f"WineStandardClassificationSerializer errors: {serialized_data.errors}") + pprint(f"transfer_wine_classifications errors: {serialized_data.errors}") + + +def transfer_product_brand(): + raw_queryset = transfer_models.Products.objects.raw( + """ + SELECT + DISTINCT brand, + 1 as id + FROM products + WHERE brand IS NOT NULL + AND brand !=''; + """ + ) + queryset = [vars(query) for query in raw_queryset] + serialized_data = product_serializers.ProductBrandSerializer( + data=queryset, + many=True) + if serialized_data.is_valid(): + serialized_data.save() + else: + pprint(f"transfer_product_brand errors: {serialized_data.errors}") + + +def transfer_product(): + queryset = transfer_models.Products.objects.all() + serialized_data = product_serializers.ProductSerializer( + data=list(queryset.values()), + many=True) + if serialized_data.is_valid(): + serialized_data.save() + else: + pprint(f"transfer_product errors: {serialized_data.errors}") data_types = { "partner": [transfer_partner], - "wine_color": [transfer_wine_color], - "wine_standard": [transfer_wine_standard], - "wine_classification": [transfer_wine_classifications], + "wine_characteristics": [ + transfer_wine_color, + transfer_wine_bottles_produced, + transfer_wine_standard, + transfer_wine_classifications], + "product": [ + transfer_product_brand, + transfer_product + ], } diff --git a/apps/tag/models.py b/apps/tag/models.py index 7aa0a5ec..064def73 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -34,8 +34,9 @@ class Tag(TranslatedFieldsMixin, models.Model): category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) - chosen_tag_settings = models.ManyToManyField(Country, through='ChosenTagSettings') + priority = models.PositiveIntegerField(null=True, default=0) + old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) objects = TagQuerySet.as_manager() diff --git a/apps/transfer/management/commands/transfer.py b/apps/transfer/management/commands/transfer.py index 1b7af37e..a3726dd7 100644 --- a/apps/transfer/management/commands/transfer.py +++ b/apps/transfer/management/commands/transfer.py @@ -27,9 +27,8 @@ class Command(BaseCommand): LONG_DATA_TYPES = [ 'update_country_flag', - 'wine_color', - 'wine_standard', - 'wine_classification', + 'wine_characteristics', + 'product', ] def handle(self, *args, **options): diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 850bd201..e4a31dad 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -937,36 +937,36 @@ class WineClassification(MigrateMixin): db_table = 'wine_classifications' -# class Products(models.Model): -# establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True) -# brand = models.CharField(max_length=255, blank=True, null=True) -# name = models.CharField(max_length=255, blank=True, null=True) -# vintage = models.CharField(max_length=255, blank=True, null=True) -# type = models.CharField(max_length=255, blank=True, null=True) -# unique_key = models.CharField(max_length=255, blank=True, null=True) -# price = models.FloatField(blank=True, null=True) -# average_price_in_shops = models.FloatField(blank=True, null=True) -# fra_encima_id = models.IntegerField(blank=True, null=True) -# wine_sub_region_id = models.IntegerField(blank=True, null=True) -# classification = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True) -# wine_region = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True) -# wine_type = models.ForeignKey('WineTypes', models.DO_NOTHING, blank=True, null=True) -# wine_color = models.ForeignKey('WineColors', models.DO_NOTHING, blank=True, null=True) -# appellation = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True) -# created_at = models.DateTimeField() -# updated_at = models.DateTimeField() -# state = models.CharField(max_length=255, blank=True, null=True) -# wine_style = models.ForeignKey('WineStyles', models.DO_NOTHING, blank=True, null=True) -# village = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True) -# vineyard = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True) -# yard_classification = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True) -# wine_quality = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True) -# bottles_produced = models.CharField(max_length=3000, blank=True, null=True) -# deu_import_id = models.IntegerField(blank=True, null=True) -# -# class Meta: -# managed = False -# db_table = 'products' +class Products(MigrateMixin): + using = 'legacy' + + establishment = models.ForeignKey('Establishments', models.DO_NOTHING, null=True) + brand = models.CharField(max_length=255, null=True) + name = models.CharField(max_length=255, null=True) + vintage = models.CharField(max_length=255, null=True) + type = models.CharField(max_length=255, null=True) + price = models.FloatField(null=True) + average_price_in_shops = models.FloatField(null=True) + wine_sub_region_id = models.IntegerField(null=True) + classification = models.ForeignKey('WineClassification', models.DO_NOTHING, null=True, + related_name='product_classification') + wine_region = models.ForeignKey('WineLocations', models.DO_NOTHING, null=True) + wine_type = models.ForeignKey('WineType', models.DO_NOTHING, null=True) + wine_color = models.ForeignKey('WineColor', models.DO_NOTHING, null=True) + appellation = models.ForeignKey('WineClassification', models.DO_NOTHING, null=True) + state = models.CharField(max_length=255) + village = models.ForeignKey('WineLocations', models.DO_NOTHING, null=True, + related_name='product_village') + vineyard = models.ForeignKey('WineLocations', models.DO_NOTHING, null=True, + related_name='product_vineyard') + wine_quality = models.ForeignKey('WineClassification', models.DO_NOTHING, null=True, + related_name='product_wine_quality') + bottles_produced = models.CharField(max_length=3000, null=True) + + class Meta: + managed = False + db_table = 'products' + class HomePages(models.Model): using = 'legacy' diff --git a/apps/transfer/serializers/product.py b/apps/transfer/serializers/product.py index daa41588..880e3906 100644 --- a/apps/transfer/serializers/product.py +++ b/apps/transfer/serializers/product.py @@ -1,36 +1,71 @@ from rest_framework import serializers +from django.utils.text import slugify from product import models -from transfer.models import WineClassification +from location import models as location_models +from tag import models as tag_models +from establishment import models as establishment_models +from transfer import models as transfer_models from utils.methods import get_point_from_coordinates from transfer.mixins import TransferSerializerMixin +from django.conf import settings class WineColorSerializer(TransferSerializerMixin): - - NAME = 'wine_color' + TAG_CATEGORY = 'wine_color' id = serializers.IntegerField() name = serializers.CharField(allow_null=True) order_number = serializers.IntegerField(allow_null=True) class Meta: - model = models.Characteristic + model = tag_models.Tag fields = ( 'id', 'name', - 'value', - 'priority', 'order_number', ) def validate(self, attrs): - attrs['old_id'] = attrs.pop('id') - attrs['value'] = attrs['name'] - attrs['name'] = self.NAME + value = attrs.pop('name') + attrs['old_id'] = attrs.pop('id', None) + attrs['label'] = {settings.FALLBACK_LOCALE: value} + attrs['value'] = slugify(value) attrs['priority'] = attrs.pop('order_number') + attrs['category'] = self.tag_category return attrs + @property + def tag_category(self): + qs = tag_models.TagCategory.objects.filter(index_name=self.TAG_CATEGORY) + if qs.exists(): + return qs.first() + + +class BottlesProducedSerializer(TransferSerializerMixin): + TAG_CATEGORY = 'bottles_produced' + + bottles_produced = serializers.IntegerField() + + class Meta: + model = tag_models.Tag + fields = ( + 'bottles_produced', + ) + + def validate(self, attrs): + value = attrs.pop('bottles_produced') + attrs['label'] = {settings.FALLBACK_LOCALE: value} + attrs['value'] = slugify(value) + attrs['category'] = self.tag_category + return attrs + + @property + def tag_category(self): + qs = tag_models.TagCategory.objects.filter(index_name=self.TAG_CATEGORY) + if qs.exists(): + return qs.first() + class WineStandardSerializer(TransferSerializerMixin): id = serializers.IntegerField() @@ -60,7 +95,7 @@ class WineStandardSerializer(TransferSerializerMixin): return attrs -class WineStandardClassificationSerializer(TransferSerializerMixin): +class WineStandardClassificationSerializer(serializers.ModelSerializer): id = serializers.IntegerField() name = serializers.CharField() @@ -84,20 +119,28 @@ class WineStandardClassificationSerializer(TransferSerializerMixin): parent_id = attrs.pop('parent_id', None) attrs['old_id'] = attrs.pop('id') attrs['possible_subtype'] = self.get_possible_type(possible_type_id) - attrs['possible_color'] = self.get_possible_color(possible_color_id) + attrs['possible_color_tag'] = self.get_possible_color_tag(possible_color_id) attrs['standard'] = self.get_wine_standard(parent_id) return attrs + def create(self, validated_data): + possible_color_tag = validated_data.pop('possible_color_tag', None) + obj = super().create(validated_data) + if possible_color_tag: + obj.tags.add(possible_color_tag) + return obj + def get_possible_type(self, possible_type_id): - legacy_qs = WineClassification.objects.filter(id=possible_type_id) + legacy_qs = transfer_models.WineClassification.objects.filter(id=possible_type_id) if legacy_qs.exists(): qs = models.ProductSubType.objects.filter(index_name=legacy_qs.first()) if qs.exists(): return qs.first() - def get_possible_color(self, possible_color_id): - qs = models.Characteristic.objects.filter(name=WineColorSerializer.NAME, - old_id=possible_color_id) + def get_possible_color_tag(self, possible_color_id): + qs = tag_models.Tag.objects.filter( + old_id=possible_color_id, + category__index_name=slugify(WineColorSerializer.TAG_CATEGORY)) if qs.exists(): return qs.first() @@ -105,3 +148,111 @@ class WineStandardClassificationSerializer(TransferSerializerMixin): qs = models.WineStandard.objects.filter(old_id=parent_id) if qs.exists(): return qs.first() + + +class ProductBrandSerializer(TransferSerializerMixin): + + brand = serializers.CharField() + + class Meta: + model = models.ProductBrand + fields = ( + 'brand', + ) + + def validate(self, attrs): + attrs['name'] = attrs.pop('brand') + return attrs + + +class ProductSerializer(TransferSerializerMixin): + + establishment_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.Establishments.objects.all(), + allow_null=True) + classification_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineClassification.objects.all(), + allow_null=True) + wine_region_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineLocations.objects.all(), + allow_null=True) + wine_type_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineType.objects.all(), + allow_null=True) + wine_color_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineColor.objects.all(), + allow_null=True) + appellation_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineClassification.objects.all(), + allow_null=True) + village_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineLocations.objects.all(), + allow_null=True) + vineyard_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineLocations.objects.all(), + allow_null=True) + wine_quality_id = serializers.PrimaryKeyRelatedField( + queryset=transfer_models.WineClassification.objects.all(), + allow_null=True) + + brand = serializers.CharField(allow_null=True) + name = serializers.CharField(allow_null=True) + vintage = serializers.CharField(allow_null=True) + type = serializers.CharField(allow_null=True) + price = serializers.CharField(allow_null=True) + average_price_in_shops = serializers.CharField(allow_null=True) + wine_sub_region_id = serializers.CharField(allow_null=True) + state = serializers.CharField() + bottles_produced = serializers.CharField(allow_null=True) + + class Meta: + model = models.Product + fields = ( + 'establishment_id', + 'classification_id', + 'wine_region_id', + 'wine_type_id', + 'wine_color_id', + 'appellation_id', + 'village_id', + 'vineyard_id', + 'wine_quality_id', + 'brand', + 'name', + 'vintage', + 'type', + 'price', + 'average_price_in_shops', + 'wine_sub_region_id', + 'state', + 'bottles_produced', + ) + + def validate(self, attrs): + establishment_old_id = attrs.pop('establishment_id', None) + state = attrs.pop('state', None) + brand = attrs.pop('brand', None) + + attrs['establishment'] = self.get_establishment(establishment_old_id) + attrs['state'] = self.get_state(state) + attrs['brand'] = self.get_brand(brand) + import ipdb; ipdb.set_trace() + return attrs + + def get_establishment(self, establishment_old_id): + qs = establishment_models.Establishment.objects.filter( + old_id=establishment_old_id) + if qs.exists(): + return qs.first() + + def get_state(self, state): + if state == 'published': + return models.Product.PUBLISHED + elif state == 'out_of_production': + return models.Product.OUT_OF_PRODUCTION + return models.Product.WAITING + + def get_brand(self, brand): + qs = models.ProductBrand.objects.filter(brand__icontains=brand) + if qs.exists(): + return qs.first()