From 87dacfec7f99ce953eaaf86d2489f8d2e4269aad Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 5 Nov 2019 18:04:34 +0300 Subject: [PATCH] refactored product app, see changes --- .../migrations/0021_auto_20191101_1323.py | 52 --------- .../migrations/0022_auto_20191101_1323.py | 33 ------ apps/product/admin.py | 9 +- .../migrations/0003_auto_20191101_1323.py | 106 ------------------ apps/product/models.py | 33 +++++- apps/product/transfer_data.py | 39 +++++-- apps/transfer/management/commands/transfer.py | 3 + apps/transfer/models.py | 21 ++-- apps/transfer/serializers/product.py | 95 +++++++++++++++- project/settings/base.py | 11 -- project/settings/local.py | 16 +++ requirements/base.txt | 1 - requirements/development.txt | 3 +- 13 files changed, 188 insertions(+), 234 deletions(-) delete mode 100644 apps/location/migrations/0021_auto_20191101_1323.py delete mode 100644 apps/location/migrations/0022_auto_20191101_1323.py delete mode 100644 apps/product/migrations/0003_auto_20191101_1323.py diff --git a/apps/location/migrations/0021_auto_20191101_1323.py b/apps/location/migrations/0021_auto_20191101_1323.py deleted file mode 100644 index 5bc751f0..00000000 --- a/apps/location/migrations/0021_auto_20191101_1323.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 2.2.4 on 2019-11-01 13:23 - -import django.contrib.gis.db.models.fields -from django.db import migrations, models -import utils.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('location', '0020_merge_20191030_1714'), - ] - - operations = [ - migrations.CreateModel( - name='WineSubRegion', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ], - options={ - 'verbose_name': 'wine region', - 'verbose_name_plural': 'wine regions', - }, - ), - migrations.CreateModel( - name='WineVillage', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('description', utils.models.TJSONField(help_text='{"en-GB":"some text"}', verbose_name='description')), - ], - options={ - 'verbose_name': 'wine village', - 'verbose_name_plural': 'wine villages', - }, - ), - migrations.RemoveField( - model_name='wineappellation', - name='wine_region', - ), - migrations.AddField( - model_name='wineregion', - name='coordinates', - field=django.contrib.gis.db.models.fields.PointField(blank=True, default=None, null=True, srid=4326, verbose_name='Coordinates'), - ), - migrations.AlterField( - model_name='wineregion', - name='name', - field=models.CharField(max_length=255, verbose_name='name'), - ), - ] diff --git a/apps/location/migrations/0022_auto_20191101_1323.py b/apps/location/migrations/0022_auto_20191101_1323.py deleted file mode 100644 index 4d02b140..00000000 --- a/apps/location/migrations/0022_auto_20191101_1323.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.4 on 2019-11-01 13:23 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('product', '0003_auto_20191101_1323'), - ('location', '0021_auto_20191101_1323'), - ] - - operations = [ - migrations.DeleteModel( - name='WineAppellation', - ), - migrations.AddField( - model_name='winevillage', - name='wine_region', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.WineRegion', verbose_name='wine region'), - ), - migrations.AddField( - model_name='winevillage', - name='wine_sub_region', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.WineSubRegion', verbose_name='wine sub region'), - ), - migrations.AddField( - model_name='winesubregion', - name='country', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='country'), - ), - ] diff --git a/apps/product/admin.py b/apps/product/admin.py index 17003011..16f4f96e 100644 --- a/apps/product/admin.py +++ b/apps/product/admin.py @@ -1,12 +1,19 @@ """Product admin conf.""" from django.contrib import admin from utils.admin import BaseModelAdminMixin -from .models import Product, ProductType, ProductSubType, Unit, Characteristic +from .models import Product, ProductType, ProductSubType, Unit, \ + Characteristic, ProductCharacteristic + + +class ProductCharacteristics(admin.TabularInline): + model = ProductCharacteristic + extra = 0 @admin.register(Product) class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Admin page for model Product.""" + inlines = [ProductCharacteristics, ] @admin.register(ProductType) diff --git a/apps/product/migrations/0003_auto_20191101_1323.py b/apps/product/migrations/0003_auto_20191101_1323.py deleted file mode 100644 index fa07c28c..00000000 --- a/apps/product/migrations/0003_auto_20191101_1323.py +++ /dev/null @@ -1,106 +0,0 @@ -# Generated by Django 2.2.4 on 2019-11-01 13:23 - -import django.contrib.gis.db.models.fields -from django.db import migrations, models -import django.db.models.deletion -import utils.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('location', '0021_auto_20191101_1323'), - ('product', '0002_product_slug'), - ] - - operations = [ - migrations.CreateModel( - name='Characteristic', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', utils.models.TJSONField(blank=True, help_text='{"en-GB":"some text"}', null=True, verbose_name='name')), - ('value', models.CharField(blank=True, max_length=255, null=True, verbose_name='value')), - ('priority', models.IntegerField(default=None, null=True, unique=True)), - ], - options={ - 'verbose_name': 'characteristic', - 'verbose_name_plural': 'characteristics', - }, - bases=(utils.models.TranslatedFieldsMixin, models.Model), - ), - migrations.CreateModel( - name='Unit', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('value', models.CharField(max_length=255, verbose_name='value')), - ], - options={ - 'verbose_name': 'unit', - 'verbose_name_plural': 'units', - }, - ), - migrations.CreateModel( - name='WineStandard', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('standard_type', models.CharField(choices=[('Appellation', 'Appellation'), ('Classification', 'Classification'), ('WineQuality', 'Wine quality'), ('YardClassification', 'Yard classification')], max_length=30, verbose_name='standard type')), - ('coordinates', django.contrib.gis.db.models.fields.PointField(blank=True, default=None, null=True, srid=4326, verbose_name='Coordinates')), - ], - options={ - 'verbose_name': 'wine standard', - 'verbose_name_plural': 'wine standards', - }, - ), - migrations.RemoveField( - model_name='product', - name='wine_appellation', - ), - migrations.AddField( - model_name='product', - name='wine_village', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.WineVillage', verbose_name='wine appellation'), - ), - migrations.RemoveField( - model_name='product', - name='characteristics', - ), - migrations.AlterField( - model_name='product', - name='establishment', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment'), - ), - migrations.AlterField( - model_name='product', - name='name', - field=models.CharField(default=None, max_length=255, null=True, verbose_name='name'), - ), - migrations.AlterField( - model_name='productsubtype', - name='index_name', - field=models.CharField(choices=[('rum', 'Rum'), ('other', 'Other'), ('extra brut', 'extra brut'), ('brut', 'brut'), ('brut nature', 'brut nature'), ('demi-sec', 'demi-sec'), ('Extra Dry', 'Extra Dry'), ('dosage zero', 'dosage zero'), ('sec', 'sec'), ('doux', 'doux'), ('moelleux', 'moelleux')], db_index=True, max_length=50, unique=True, verbose_name='Index name'), - ), - migrations.CreateModel( - name='WineClassification', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('standard', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.WineStandard', verbose_name='standard')), - ], - options={ - 'verbose_name': 'wine classification', - 'verbose_name_plural': 'wine classifications', - }, - ), - migrations.AddField( - model_name='product', - name='wine_standard', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='product.WineStandard', verbose_name='wine standard'), - ), - migrations.AddField( - model_name='product', - name='characteristics', - field=models.ManyToManyField(to='product.Characteristic', verbose_name='characteristics'), - ), - ] diff --git a/apps/product/models.py b/apps/product/models.py index d6c35f0f..3f146731 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -139,6 +139,7 @@ class Product(TranslatedFieldsMixin, BaseAttributes): 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')) @@ -229,23 +230,38 @@ class Unit(models.Model): return self.name -class Characteristic(TranslatedFieldsMixin, models.Model): +class Characteristic(models.Model): """Characteristic model.""" - STR_FIELD_NAME = 'name' - - name = TJSONField(_('name'), null=True, blank=True, - help_text='{"en-GB":"some text"}') + 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.""" @@ -271,6 +287,7 @@ class WineStandard(models.Model): verbose_name=_('standard type')) coordinates = gis_models.PointField( _('Coordinates'), blank=True, null=True, default=None) + old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) objects = WineStandardQuerySet.as_manager() @@ -289,6 +306,12 @@ 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')) + possible_subtype = models.ForeignKey(ProductSubType, on_delete=models.PROTECT, + blank=True, null=True, default=None, + help_text=_('Legacy attribute - possible_type')) + old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) objects = WineClassificationQuerySet.as_manager() diff --git a/apps/product/transfer_data.py b/apps/product/transfer_data.py index e8b95cc6..17940af9 100644 --- a/apps/product/transfer_data.py +++ b/apps/product/transfer_data.py @@ -1,12 +1,12 @@ from pprint import pprint -from transfer.models import EstablishmentBacklinks, WineColor +from transfer import models as transfer_models from transfer.serializers.partner import PartnerSerializer -from transfer.serializers.product import WineColorSerializer +from transfer.serializers import product as product_serializers def transfer_partner(): - queryset = EstablishmentBacklinks.objects.filter(type="Partner") + queryset = transfer_models.EstablishmentBacklinks.objects.filter(type="Partner") serialized_data = PartnerSerializer(data=list(queryset.values()), many=True) if serialized_data.is_valid(): @@ -16,16 +16,41 @@ def transfer_partner(): def transfer_wine_color(): - queryset = WineColor.objects.all() - serialized_data = WineColorSerializer(data=list(queryset.values()), - many=True) + queryset = transfer_models.WineColor.objects.all() + serialized_data = product_serializers.WineColorSerializer( + data=list(queryset.values()), + many=True) if serialized_data.is_valid(): serialized_data.save() else: - pprint(f"CharacteristicSerializer errors: {serialized_data.errors}") + pprint(f"WineColorSerializer errors: {serialized_data.errors}") + + +def transfer_wine_standard(): + queryset = transfer_models.WineClassification.objects.filter(parent_id__isnull=True) + serialized_data = product_serializers.WineStandardSerializer( + data=list(queryset.values()), + many=True) + if serialized_data.is_valid(): + serialized_data.save() + else: + pprint(f"WineStandardSerializer errors: {serialized_data.errors}") + + +def transfer_wine_classifications(): + queryset = transfer_models.WineClassification.objects.filter(parent_id__isnull=False) + serialized_data = product_serializers.WineStandardClassificationSerializer( + data=list(queryset.values()), + many=True) + if serialized_data.is_valid(): + serialized_data.save() + else: + pprint(f"WineStandardClassificationSerializer errors: {serialized_data.errors}") data_types = { "partner": [transfer_partner], "wine_color": [transfer_wine_color], + "wine_standard": [transfer_wine_standard], + "wine_classification": [transfer_wine_classifications], } diff --git a/apps/transfer/management/commands/transfer.py b/apps/transfer/management/commands/transfer.py index 06782c9b..1b7af37e 100644 --- a/apps/transfer/management/commands/transfer.py +++ b/apps/transfer/management/commands/transfer.py @@ -27,6 +27,9 @@ class Command(BaseCommand): LONG_DATA_TYPES = [ 'update_country_flag', + 'wine_color', + 'wine_standard', + 'wine_classification', ] def handle(self, *args, **options): diff --git a/apps/transfer/models.py b/apps/transfer/models.py index cf8ac881..f2120ea7 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -909,6 +909,16 @@ class WineColor(MigrateMixin): db_table = 'wine_colors' +class WineType(MigrateMixin): + using = 'legacy' + + name = models.CharField(max_length=255) + + class Meta: + managed = False + db_table = 'wine_types' + + class WineClassification(MigrateMixin): using = 'legacy' @@ -927,17 +937,6 @@ class WineClassification(MigrateMixin): db_table = 'wine_classifications' -class WineTypes(MigrateMixin): - using = 'legacy' - - name = models.CharField(max_length=255, blank=True, null=True) - fra_encima_id = models.IntegerField(blank=True, null=True) - - class Meta: - managed = False - db_table = 'wine_types' - - # 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) diff --git a/apps/transfer/serializers/product.py b/apps/transfer/serializers/product.py index 190396fa..6b984a2d 100644 --- a/apps/transfer/serializers/product.py +++ b/apps/transfer/serializers/product.py @@ -1,17 +1,21 @@ +from django.contrib.gis.db.models.fields import Point from rest_framework import serializers -from product.models import Characteristic +from product import models +from transfer.models import WineColor, WineType, WineClassification class WineColorSerializer(serializers.ModelSerializer): - NAME = 'Wine color' + NAME = 'wine_color' + id = serializers.IntegerField() name = serializers.CharField(allow_null=True) order_number = serializers.IntegerField(allow_null=True) class Meta: - model = Characteristic + model = models.Characteristic fields = ( + 'id', 'name', 'value', 'priority', @@ -19,8 +23,87 @@ class WineColorSerializer(serializers.ModelSerializer): ) def validate(self, attrs): + attrs['old_id'] = attrs.pop('id') attrs['value'] = attrs['name'] - attrs['name'] = {'en-GB': self.NAME} - attrs['priority'] = attrs['order_number'] - attrs.pop('order_number') + attrs['name'] = self.NAME + attrs['priority'] = attrs.pop('order_number') return attrs + + +class WineStandardSerializer(serializers.ModelSerializer): + id = serializers.IntegerField() + name = serializers.CharField() + type = serializers.ChoiceField(choices=models.WineStandard.STANDARDS, + allow_null=True) + longitude = serializers.FloatField(allow_null=True) + latitude = serializers.FloatField(allow_null=True) + + class Meta: + model = models.WineStandard + fields = ( + 'id', + 'name', + 'type', + 'longitude', + 'latitude', + ) + + def validate(self, attrs): + latitude = attrs.pop('latitude', None) + longitude = attrs.pop('longitude', None) + + attrs['coordinates'] = self.get_point(latitude, longitude) + attrs['old_id'] = attrs.get('id') + attrs['standard_type'] = attrs.pop('type', None) + return attrs + + def get_point(self, latitude, longitude): + if latitude and longitude: + return Point(x=longitude, y=latitude, srid=4326) + + +class WineStandardClassificationSerializer(serializers.ModelSerializer): + + id = serializers.IntegerField() + name = serializers.CharField() + possible_type_id = serializers.IntegerField(allow_null=True) + possible_color_id = serializers.IntegerField() + parent_id = serializers.IntegerField() + + class Meta: + model = models.WineClassification + fields = ( + 'id', + 'name', + 'possible_type_id', + 'possible_color_id', + 'parent_id', + ) + + def validate(self, attrs): + possible_type_id = attrs.pop('possible_type_id', None) + possible_color_id = attrs.pop('possible_color_id', None) + parent_id = attrs.pop('parent_id', None) + attrs['old_id'] = attrs.pop('id', None) + attrs['possible_subtype'] = self.get_possible_type(possible_type_id) + attrs['possible_color'] = self.get_possible_color(possible_color_id) + attrs['standard'] = self.get_wine_standard(parent_id) + return attrs + + def get_possible_type(self, possible_type_id): + legacy_qs = 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) + if qs.exists(): + return qs.first() + + def get_wine_standard(self, parent_id): + qs = models.WineStandard.objects.filter(old_id=parent_id) + if qs.exists(): + return qs.first() diff --git a/project/settings/base.py b/project/settings/base.py index 686f246e..0e410537 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -74,7 +74,6 @@ PROJECT_APPS = [ 'comment.apps.CommentConfig', 'favorites.apps.FavoritesConfig', 'rating.apps.RatingConfig', - # 'transfer.apps.TransferConfig', 'tag.apps.TagConfig', 'product.apps.ProductConfig', ] @@ -157,16 +156,6 @@ DATABASES = { 'HOST': os.environ.get('DB_HOSTNAME'), 'PORT': os.environ.get('DB_PORT'), }, - # 'legacy': { - # 'ENGINE': 'django.db.backends.mysql', - # # 'HOST': '172.17.0.1', - # # 'HOST': '172.23.0.1', - # 'HOST': 'mysql_db', - # 'PORT': 3306, - # 'NAME': 'dev', - # 'USER': 'dev', - # 'PASSWORD': 'octosecret123' - # } } diff --git a/project/settings/local.py b/project/settings/local.py index e87b99f6..eba35ebf 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -34,6 +34,22 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) # SORL thumbnails THUMBNAIL_DEBUG = True +# ADDED TRANSFER APP +INSTALLED_APPS.append('transfer.apps.TransferConfig') + + +# ADDED LEGACY DB CONNECTOR +DATABASES.update({ + 'legacy': { + 'ENGINE': 'django.db.backends.mysql', + # 'HOST': '172.17.0.1', + # 'HOST': '172.23.0.1', + 'HOST': 'mysql_db', + 'PORT': 3306, + 'NAME': 'dev', + 'USER': 'dev', + 'PASSWORD': 'octosecret123'}}) + # LOGGING LOGGING = { diff --git a/requirements/base.txt b/requirements/base.txt index a6a007bd..7bd55d12 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -42,7 +42,6 @@ django-storages==1.7.2 sorl-thumbnail==12.5.0 -# mysqlclient==1.4.4 PyYAML==5.1.2 # temp solution diff --git a/requirements/development.txt b/requirements/development.txt index 21f7ead9..77c3d73c 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -1,3 +1,4 @@ -r base.txt ipdb -ipython \ No newline at end of file +ipython +mysqlclient==1.4.4 \ No newline at end of file