Merge remote-tracking branch 'origin/develop' into feature/migrate-inquiries

# Conflicts:
#	apps/transfer/management/commands/transfer.py
#	apps/transfer/models.py
This commit is contained in:
alex 2019-11-11 17:02:24 +03:00
commit 7106249d1e
46 changed files with 2011 additions and 370 deletions

View File

@ -1,6 +1,7 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connections from django.db import connections
from django.db.models import Q from django.db.models import Q, F, Value
from django.db.models.functions import ConcatPair
from establishment.management.commands.add_position import namedtuplefetchall from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User from account.models import User
@ -11,33 +12,29 @@ class Command(BaseCommand):
def account_sql(self): def account_sql(self):
with connections['legacy'].cursor() as cursor: with connections['legacy'].cursor() as cursor:
cursor.execute(''' cursor.execute('''
select a.email, a.id as account_id, a.encrypted_password select a.email, a.id as account_id, a.encrypted_password,
case when a.confirmed_at is not null then true else false end as confirmed_at,
case when a.confirmed_at is null then true else false end as unconfirmed_email,
nickname
from accounts as a from accounts as a
where a.email is not null where a.email is not null and a.nickname!='admin'
and a.email not in ('cyril@tomatic.net',
'cyril2@tomatic.net',
'd.sadykova@id-east.ru',
'd.sadykova@octopod.ru',
'n.yurchenko@id-east.ru'
)
and a.confirmed_at is not null
''') ''')
return namedtuplefetchall(cursor) return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
objects = [] objects = []
for a in self.account_sql(): for a in self.account_sql():
count = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id)).count() users = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id))
if count == 0: if not users.exists():
objects.append(User(email=a.email, objects.append(User(email=a.email,
unconfirmed_email=False, unconfirmed_email=a.unconfirmed_email,
email_confirmed=True, email_confirmed=a.confirmed_at,
old_id=a.account_id, old_id=a.account_id,
password='bcrypt$'+a.encrypted_password password=a.encrypted_password,
username=a.nickname
)) ))
else:
user = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id))
user.update(password='bcrypt$'+a.encrypted_password)
User.objects.bulk_create(objects) User.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Created accounts objects.')) user = User.objects.filter(old_id__isnull=False)
user.update(password=ConcatPair(Value('bcrypt$'), F('password')))
self.stdout.write(self.style.WARNING(f'Created accounts objects.'))

View File

@ -20,14 +20,7 @@ class Command(BaseCommand):
( (
select a.email, a.id as account_id select a.email, a.id as account_id
from accounts as a from accounts as a
where a.email is not null where a.email is not null
and a.email not in ('cyril@tomatic.net',
'cyril2@tomatic.net',
'd.sadykova@id-east.ru',
'd.sadykova@octopod.ru',
'n.yurchenko@id-east.ru'
)
and a.confirmed_at is not null
) t ) t
join identities i on i.account_id = t.account_id join identities i on i.account_id = t.account_id
''') ''')
@ -39,13 +32,12 @@ class Command(BaseCommand):
user = User.objects.filter(old_id=s.account_id) user = User.objects.filter(old_id=s.account_id)
if user.count() > 0: if user.count() > 0:
social = UserSocialAuth.objects.filter(user=user.first(), social = UserSocialAuth.objects.filter(user=user.first(),
provider=s.provider, provider=s.provider,
uid=s.uid) uid=s.uid)
if social.count() == 0: if social.count() == 0:
objects.append(UserSocialAuth(user=user.first(), provider=s.provider, objects.append(UserSocialAuth(user=user.first(), provider=s.provider,
uid=s.uid) uid=s.uid)
) )
print('INSERT')
UserSocialAuth.objects.bulk_create(objects) UserSocialAuth.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Created social objects.')) self.stdout.write(self.style.WARNING(f'Created social objects.'))

View File

@ -7,11 +7,11 @@ from transfer.serializers.account import UserSerializer
from transfer.serializers.user_social_auth import UserSocialAuthSerializer from transfer.serializers.user_social_auth import UserSocialAuthSerializer
STOP_LIST = ( STOP_LIST = (
'cyril@tomatic.net', # 'cyril@tomatic.net',
'cyril2@tomatic.net', # 'cyril2@tomatic.net',
'd.sadykova@id-east.ru', # 'd.sadykova@id-east.ru',
'd.sadykova@octopod.ru', # 'd.sadykova@octopod.ru',
'n.yurchenko@id-east.ru', # 'n.yurchenko@id-east.ru',
) )
@ -20,7 +20,7 @@ def transfer_user():
# queryset = queryset.annotate(nickname=F('account__nickname')) # queryset = queryset.annotate(nickname=F('account__nickname'))
# queryset = queryset.annotate(email=F('account__email')) # queryset = queryset.annotate(email=F('account__email'))
queryset = Accounts.objects.filter(confirmed_at__isnull=False).exclude(email__in=STOP_LIST) queryset = Accounts.objects.exclude(email__in=STOP_LIST)
serialized_data = UserSerializer(data=list(queryset.values()), many=True) serialized_data = UserSerializer(data=list(queryset.values()), many=True)
@ -33,7 +33,7 @@ def transfer_user():
def transfer_identities(): def transfer_identities():
queryset = Identities.objects.exclude( queryset = Identities.objects.exclude(
Q(account_id__isnull=True) | Q(account_id__isnull=True) |
Q(account__confirmed_at__isnull=True) | # Q(account__confirmed_at__isnull=True) |
Q(account__email__in=STOP_LIST) Q(account__email__in=STOP_LIST)
).values_list( ).values_list(
'account_id', 'account_id',

View File

@ -0,0 +1,9 @@
from rest_framework import serializers
from collection import models
class CollectionSerializer(serializers.ModelSerializer):
"""Collection serializer."""
class Meta:
model = models.Collection.objects.all()
fields = '__all__'

View File

@ -0,0 +1,11 @@
"""Collection common urlpaths."""
from django.urls import path
from collection.views import back as views
app_name = 'collection'
urlpatterns = [
path('', views.CollectionListCreateView.as_view(), name='list-create'),
]

View File

@ -0,0 +1,19 @@
from rest_framework import generics, permissions
from collection import models
from collection.serializers import common, back
class CollectionListCreateView(generics.ListCreateAPIView):
"""Collection list-create view."""
queryset = models.Collection.objects.all()
serializer_class = back.CollectionSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )
class CollectionRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Collection list-create view."""
queryset = models.Collection.objects.all()
serializer_class = back.CollectionSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )

View File

@ -16,7 +16,6 @@ def transfer_establishment():
old_establishments = Establishments.objects.exclude( old_establishments = Establishments.objects.exclude(
id__in=list(Establishment.objects.all().values_list('old_id', flat=True)) id__in=list(Establishment.objects.all().values_list('old_id', flat=True))
).exclude( ).exclude(
Q(type='Wineyard') |
Q(location__timezone__isnull=True), Q(location__timezone__isnull=True),
).prefetch_related( ).prefetch_related(
'establishmentinfos_set', 'establishmentinfos_set',

View File

@ -19,22 +19,6 @@ class CityAdmin(admin.ModelAdmin):
"""City admin.""" """City admin."""
class WineAppellationInline(admin.TabularInline):
model = models.WineAppellation
extra = 0
@admin.register(models.WineRegion)
class WineRegionAdmin(admin.ModelAdmin):
"""WineRegion admin."""
inlines = [WineAppellationInline, ]
@admin.register(models.WineAppellation)
class WineAppellationAdmin(admin.ModelAdmin):
"""WineAppellation admin."""
@admin.register(models.Address) @admin.register(models.Address)
class AddressAdmin(admin.OSMGeoAdmin): class AddressAdmin(admin.OSMGeoAdmin):
"""Address admin.""" """Address admin."""

View File

@ -21,5 +21,5 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RunPython(load_data_from_sql, revert_data), # migrations.RunPython(load_data_from_sql, revert_data),
] ]

View File

@ -0,0 +1,69 @@
# Generated by Django 2.2.4 on 2019-11-11 07:31
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', '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')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
],
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')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
],
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.AddField(
model_name='wineregion',
name='description',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description'),
),
migrations.AddField(
model_name='wineregion',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
migrations.AlterField(
model_name='wineregion',
name='country',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='country'),
),
migrations.AlterField(
model_name='wineregion',
name='name',
field=models.CharField(max_length=255, verbose_name='name'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.2.4 on 2019-11-11 07:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('location', '0021_auto_20191111_0731'),
('product', '0009_auto_20191111_0731'),
]
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='winesubregion',
name='wine_region',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.WineRegion', verbose_name='wine sub region'),
),
]

View File

@ -160,14 +160,19 @@ class WineRegionQuerySet(models.QuerySet):
"""Wine region queryset.""" """Wine region queryset."""
class WineRegion(TranslatedFieldsMixin, models.Model): class WineRegion(models.Model):
"""Wine region model.""" """Wine region model."""
STR_FIELD_NAME = 'name' name = models.CharField(_('name'), max_length=255)
name = TJSONField(verbose_name=_('Name'),
help_text='{"en-GB":"some text"}')
country = models.ForeignKey(Country, on_delete=models.PROTECT, country = models.ForeignKey(Country, on_delete=models.PROTECT,
blank=True, null=True, default=None,
verbose_name=_('country')) verbose_name=_('country'))
coordinates = models.PointField(
_('Coordinates'), blank=True, null=True, default=None)
old_id = models.PositiveIntegerField(_('old id'), default=None,
blank=True, null=True)
description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'),
help_text='{"en-GB":"some text"}')
objects = WineRegionQuerySet.as_manager() objects = WineRegionQuerySet.as_manager()
@ -177,26 +182,47 @@ class WineRegion(TranslatedFieldsMixin, models.Model):
verbose_name = _('wine region') verbose_name = _('wine region')
class WineAppellationQuerySet(models.QuerySet): class WineSubRegionQuerySet(models.QuerySet):
"""Wine appellation queryset.""" """Wine sub region QuerySet."""
class WineAppellation(TranslatedFieldsMixin, models.Model): class WineSubRegion(models.Model):
"""Wine appellation model.""" """Wine sub region model."""
STR_FIELD_NAME = 'name' name = models.CharField(_('name'), max_length=255)
name = TJSONField(verbose_name=_('Name'),
help_text='{"en-GB":"some text"}')
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT, wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
related_name='appellations', verbose_name=_('wine sub region'))
verbose_name=_('wine region')) old_id = models.PositiveIntegerField(_('old id'), default=None,
blank=True, null=True)
objects = WineAppellationQuerySet.as_manager() objects = WineSubRegionQuerySet.as_manager()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
verbose_name_plural = _('wine appellations') verbose_name_plural = _('wine regions')
verbose_name = _('wine appellation') verbose_name = _('wine region')
class WineVillageQuerySet(models.QuerySet):
"""Wine village QuerySet."""
class WineVillage(models.Model):
"""
Wine village.
Description: Imported from legacy DB.
"""
name = models.CharField(_('name'), max_length=255)
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
verbose_name=_('wine region'))
old_id = models.PositiveIntegerField(_('old id'), default=None,
blank=True, null=True)
objects = WineVillageQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('wine village')
verbose_name_plural = _('wine villages')
# todo: Make recalculate price levels # todo: Make recalculate price levels

View File

@ -152,19 +152,6 @@ class AddressDetailSerializer(AddressBaseSerializer):
) )
class WineAppellationBaseSerializer(serializers.ModelSerializer):
"""Wine appellations."""
name_translated = TranslatedField()
class Meta:
"""Meta class."""
model = models.WineAppellation
fields = [
'id',
'name_translated',
]
class WineRegionBaseSerializer(serializers.ModelSerializer): class WineRegionBaseSerializer(serializers.ModelSerializer):
"""Wine region serializer.""" """Wine region serializer."""
name_translated = TranslatedField() name_translated = TranslatedField()

View File

@ -1,14 +1,13 @@
from transfer.serializers.location import CountrySerializer, RegionSerializer, \ from transfer.serializers import location as location_serializers
CitySerializer, AddressSerializer, \ from transfer import models as transfer_models
Country from location.models import Country
from transfer.models import Cities, Locations
from pprint import pprint from pprint import pprint
from requests import get from requests import get
def transfer_countries(): def transfer_countries():
queryset = Cities.objects.raw(""" queryset = transfer_models.Cities.objects.raw("""
SELECT cities.id, cities.country_code_2 SELECT cities.id, cities.country_code_2
FROM cities FROM cities
WHERE country_code_2 IS NOT NULL AND WHERE country_code_2 IS NOT NULL AND
@ -18,7 +17,7 @@ def transfer_countries():
queryset = [vars(query) for query in queryset] queryset = [vars(query) for query in queryset]
serialized_data = CountrySerializer(data=queryset, many=True) serialized_data = location_serializers.CountrySerializer(data=queryset, many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -26,7 +25,7 @@ def transfer_countries():
def transfer_regions(): def transfer_regions():
regions_without_subregion_queryset = Cities.objects.raw(""" regions_without_subregion_queryset = transfer_models.Cities.objects.raw("""
SELECT cities.id, SELECT cities.id,
cities.region_code, cities.region_code,
cities.country_code_2, cities.country_code_2,
@ -47,13 +46,14 @@ def transfer_regions():
regions_without_subregion_queryset = [vars(query) for query in regions_without_subregion_queryset] regions_without_subregion_queryset = [vars(query) for query in regions_without_subregion_queryset]
serialized_without_subregion = RegionSerializer(data=regions_without_subregion_queryset, many=True) serialized_without_subregion = location_serializers.RegionSerializer(data=regions_without_subregion_queryset,
many=True)
if serialized_without_subregion.is_valid(): if serialized_without_subregion.is_valid():
serialized_without_subregion.save() serialized_without_subregion.save()
else: else:
pprint(f"Parent regions serializer errors: {serialized_without_subregion.errors}") pprint(f"Parent regions serializer errors: {serialized_without_subregion.errors}")
regions_with_subregion_queryset = Cities.objects.raw(""" regions_with_subregion_queryset = transfer_models.Cities.objects.raw("""
SELECT SELECT
cities.id, cities.id,
cities.region_code, cities.region_code,
@ -84,7 +84,8 @@ def transfer_regions():
regions_with_subregion_queryset = [vars(query) for query in regions_with_subregion_queryset] regions_with_subregion_queryset = [vars(query) for query in regions_with_subregion_queryset]
serialized_with_subregion = RegionSerializer(data=regions_with_subregion_queryset, many=True) serialized_with_subregion = location_serializers.RegionSerializer(data=regions_with_subregion_queryset,
many=True)
if serialized_with_subregion.is_valid(): if serialized_with_subregion.is_valid():
serialized_with_subregion.save() serialized_with_subregion.save()
else: else:
@ -92,7 +93,7 @@ def transfer_regions():
def transfer_cities(): def transfer_cities():
queryset = Cities.objects.raw("""SELECT cities.id, cities.name, cities.country_code_2, cities.zip_code, queryset = transfer_models.Cities.objects.raw("""SELECT cities.id, cities.name, cities.country_code_2, cities.zip_code,
cities.is_island, cities.region_code, cities.subregion_code cities.is_island, cities.region_code, cities.subregion_code
FROM cities WHERE FROM cities WHERE
region_code IS NOT NULL AND region_code IS NOT NULL AND
@ -103,7 +104,7 @@ def transfer_cities():
queryset = [vars(query) for query in queryset] queryset = [vars(query) for query in queryset]
serialized_data = CitySerializer(data=queryset, many=True) serialized_data = location_serializers.CitySerializer(data=queryset, many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
@ -111,7 +112,7 @@ def transfer_cities():
def transfer_addresses(): def transfer_addresses():
queryset = Locations.objects.raw("""SELECT locations.id, locations.zip_code, locations.longitude, queryset = transfer_models.Locations.objects.raw("""SELECT locations.id, locations.zip_code, locations.longitude,
locations.latitude, locations.address, locations.city_id locations.latitude, locations.address, locations.city_id
FROM locations WHERE FROM locations WHERE
locations.address != "" AND locations.address != "" AND
@ -126,13 +127,46 @@ def transfer_addresses():
queryset = [vars(query) for query in queryset] queryset = [vars(query) for query in queryset]
serialized_data = AddressSerializer(data=queryset, many=True) serialized_data = location_serializers.AddressSerializer(data=queryset, many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()
else: else:
pprint(f"Address serializer errors: {serialized_data.errors}") pprint(f"Address serializer errors: {serialized_data.errors}")
def transfer_wine_region():
queryset = transfer_models.WineLocations.objects.filter(type='WineRegion')
serialized_data = location_serializers.WineRegion(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"WineStandardClassificationSerializer errors: {serialized_data.errors}")
def transfer_wine_sub_region():
queryset = transfer_models.WineLocations.objects.filter(type='WineSubRegion')
serialized_data = location_serializers.WineSubRegion(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"WineStandardClassificationSerializer errors: {serialized_data.errors}")
def transfer_wine_village():
queryset = transfer_models.WineLocations.objects.filter(type='Village')
serialized_data = location_serializers.WineVillage(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"WineStandardClassificationSerializer errors: {serialized_data.errors}")
def update_flags(): def update_flags():
queryset = Country.objects.only("id", "code", "svg_image").filter(old_id__isnull=False) queryset = Country.objects.only("id", "code", "svg_image").filter(old_id__isnull=False)
link_to_request = "https://s3.eu-central-1.amazonaws.com/gm-test.com/media" link_to_request = "https://s3.eu-central-1.amazonaws.com/gm-test.com/media"
@ -150,9 +184,12 @@ data_types = {
transfer_countries, transfer_countries,
transfer_regions, transfer_regions,
transfer_cities, transfer_cities,
transfer_addresses transfer_addresses,
transfer_wine_region,
transfer_wine_sub_region,
transfer_wine_village,
], ],
"update_country_flag": [ "update_country_flag": [
update_flags update_flags
] ],
} }

View File

@ -1,12 +1,17 @@
"""Product admin conf.""" """Product admin conf."""
from django.contrib import admin from django.contrib import admin
from utils.admin import BaseModelAdminMixin from utils.admin import BaseModelAdminMixin
from .models import Product, ProductType, ProductSubType from .models import Product, ProductType, ProductSubType, Unit
@admin.register(Product) @admin.register(Product)
class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin): class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin page for model Product.""" """Admin page for model Product."""
search_fields = ('name', )
list_filter = ('available', 'product_type')
list_display = ('id', '__str__', 'get_category_display', 'product_type')
raw_id_fields = ('subtypes', 'classifications', 'standards',
'tags', 'gallery')
@admin.register(ProductType) @admin.register(ProductType)
@ -17,3 +22,8 @@ class ProductTypeAdmin(admin.ModelAdmin):
@admin.register(ProductSubType) @admin.register(ProductSubType)
class ProductSubTypeAdmin(admin.ModelAdmin): class ProductSubTypeAdmin(admin.ModelAdmin):
"""Admin page for model ProductSubType.""" """Admin page for model ProductSubType."""
@admin.register(Unit)
class UnitAdmin(admin.ModelAdmin):
"""Admin page for model Unit."""

View File

@ -1,54 +0,0 @@
from django.core.management.base import BaseCommand
from product.models import ProductType, ProductSubType
def add_type():
product_type = ProductType.objects.create(
name={'"en-GB"': "Wine"},
index_name=ProductType.WINE
)
return product_type.save()
def add_subtype(id_type):
subtypes = ProductSubType.objects.bulk_create([
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.EXTRA_BRUT},
index_name=ProductSubType.EXTRA_BRUT),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.BRUT},
index_name=ProductSubType.BRUT),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.BRUT_NATURE},
index_name=ProductSubType.BRUT_NATURE),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.DEMI_SEC},
index_name=ProductSubType.DEMI_SEC),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.EXTRA_DRY},
index_name=ProductSubType.EXTRA_DRY),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.DOSAGE_ZERO},
index_name=ProductSubType.DOSAGE_ZERO),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.SEC},
index_name=ProductSubType.SEC),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.SEC},
index_name=ProductSubType.SEC),
ProductSubType(product_type=id_type,
name={"en-GB", ProductSubType.MOELLEUX},
index_name=ProductSubType.MOELLEUX),
])
class Command(BaseCommand):
help = 'Add product data'
def handle(self, *args, **kwarg):
product_type = add_type()
add_subtype(product_type.id)

View File

@ -0,0 +1,34 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from product.models import ProductType, ProductSubType
class Command(BaseCommand):
help = 'Add product sub type data'
def handle(self, *args, **kwarg):
error_counter = 0
create_counter = 0
product_subtypes = {
'plate': {
'product_type_index_name': 'souvenir',
'name': 'plate',
'index_name': 'plate',
}
}
for product_subtype in product_subtypes.values():
product_type_qs = ProductType.objects.filter(
index_name=product_subtype.get('product_type_index_name'))
if product_type_qs:
product_type = product_type_qs.first()
subtype, status = ProductSubType.objects.get_or_create(**{
'name': {settings.FALLBACK_LOCALE: product_subtype.get('name')},
'index_name': product_subtype.get('index_name'),
'product_type': product_type
})
create_counter += 1 if status else 0
else:
error_counter += 1
self.stdout.write(self.style.WARNING(f'Errors occurred: {error_counter}\nCreated: {create_counter}'))

View File

@ -0,0 +1,20 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from product.models import ProductType
class Command(BaseCommand):
help = 'Add product type data'
def handle(self, *args, **kwarg):
product_types = ['wine', 'souvenir']
create_counter = 0
for product_type in product_types:
subtype, created = ProductType.objects.get_or_create(**{
'name': {settings.FALLBACK_LOCALE: product_type},
'index_name': product_type
})
if created:
created += 1
self.stdout.write(self.style.WARNING(f'Created: {create_counter}'))

View File

@ -0,0 +1,179 @@
# Generated by Django 2.2.4 on 2019-11-11 07:31
import django.contrib.gis.db.models.fields
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('gallery', '0006_merge_20191027_1758'),
('location', '0021_auto_20191111_0731'),
('tag', '0009_auto_20191111_0731'),
('product', '0008_auto_20191031_1410'),
]
operations = [
migrations.CreateModel(
name='ProductStandard',
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.PositiveSmallIntegerField(choices=[(0, 'Appellation'), (1, 'Wine quality'), (2, 'Yard classification')], verbose_name='standard type')),
('coordinates', django.contrib.gis.db.models.fields.PointField(blank=True, default=None, null=True, srid=4326, verbose_name='Coordinates')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
],
options={
'verbose_name': 'wine standard',
'verbose_name_plural': 'wine standards',
},
),
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.RemoveField(
model_name='product',
name='characteristics',
),
migrations.RemoveField(
model_name='product',
name='wine_appellation',
),
migrations.AddField(
model_name='product',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
migrations.AddField(
model_name='product',
name='old_unique_key',
field=models.CharField(blank=True, default=None, help_text='attribute from legacy db', max_length=255, null=True, unique=True),
),
migrations.AddField(
model_name='product',
name='state',
field=models.PositiveIntegerField(choices=[(0, 'Published'), (1, 'Out_of_production'), (2, 'Waiting')], default=2, help_text='attribute from legacy db', verbose_name='state'),
),
migrations.AddField(
model_name='product',
name='tags',
field=models.ManyToManyField(related_name='products', to='tag.Tag', verbose_name='Tag'),
),
migrations.AddField(
model_name='product',
name='vintage',
field=models.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1700), django.core.validators.MaxValueValidator(2100)], verbose_name='vintage year'),
),
migrations.AddField(
model_name='product',
name='wine_sub_region',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='wines', to='location.WineSubRegion', verbose_name='wine sub region'),
),
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.AddField(
model_name='producttype',
name='tag_categories',
field=models.ManyToManyField(related_name='product_types', to='tag.TagCategory', verbose_name='Tag'),
),
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='product',
name='product_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='product.ProductType', verbose_name='Type'),
),
migrations.AlterField(
model_name='product',
name='wine_region',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='wines', to='location.WineRegion', verbose_name='wine region'),
),
migrations.AlterField(
model_name='productsubtype',
name='index_name',
field=models.CharField(choices=[('rum', 'Rum'), ('plate', 'Plate'), ('other', 'Other')], db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
migrations.AlterField(
model_name='producttype',
name='index_name',
field=models.CharField(choices=[('food', 'Food'), ('wine', 'Wine'), ('liquor', 'Liquor'), ('souvenir', 'Souvenir'), ('book', 'Book')], db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
migrations.CreateModel(
name='ProductGallery',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_main', models.BooleanField(default=False, verbose_name='Is the main image')),
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_gallery', to='gallery.Image', verbose_name='gallery')),
('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_gallery', to='product.Product', verbose_name='product')),
],
options={
'verbose_name': 'product gallery',
'verbose_name_plural': 'product galleries',
'unique_together': {('product', 'image'), ('product', 'is_main')},
},
),
migrations.CreateModel(
name='ProductClassificationType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='classification type')),
('product_sub_type', models.ForeignKey(blank=True, default=None, help_text='Legacy attribute - possible_type (product type).Product type in our case is product subtype.', null=True, on_delete=django.db.models.deletion.PROTECT, to='product.ProductSubType', verbose_name='product subtype')),
('product_type', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='product.ProductType', verbose_name='product type')),
],
options={
'verbose_name': 'wine classification type',
'verbose_name_plural': 'wine classification types',
},
),
migrations.CreateModel(
name='ProductClassification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('classification_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.ProductClassificationType', verbose_name='classification type')),
('standard', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='product.ProductStandard', verbose_name='standard')),
('tags', models.ManyToManyField(related_name='product_classifications', to='tag.Tag', verbose_name='Tag')),
],
options={
'verbose_name': 'product classification',
'verbose_name_plural': 'product classifications',
},
),
migrations.AddField(
model_name='product',
name='classifications',
field=models.ManyToManyField(blank=True, to='product.ProductClassification', verbose_name='classifications'),
),
migrations.AddField(
model_name='product',
name='gallery',
field=models.ManyToManyField(through='product.ProductGallery', to='gallery.Image'),
),
migrations.AddField(
model_name='product',
name='standards',
field=models.ManyToManyField(blank=True, help_text='attribute from legacy db', to='product.ProductStandard', verbose_name='standards'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.7 on 2019-11-11 12:27
from django.db import migrations, models
import utils.models
class Migration(migrations.Migration):
dependencies = [
('product', '0009_auto_20191111_0731'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='country',
),
migrations.AlterField(
model_name='product',
name='available',
field=models.BooleanField(default=True, verbose_name='available'),
),
migrations.AlterField(
model_name='product',
name='description',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description'),
),
migrations.AlterField(
model_name='product',
name='slug',
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Slug'),
),
]

View File

@ -1,9 +1,11 @@
"""Product app models.""" """Product app models."""
from django.db import models
from django.contrib.contenttypes import fields as generic 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.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 django.utils.translation import gettext_lazy as _
from django.core.validators import MaxValueValidator, MinValueValidator
from utils.models import (BaseAttributes, ProjectBaseMixin, from utils.models import (BaseAttributes, ProjectBaseMixin,
TranslatedFieldsMixin, TJSONField) TranslatedFieldsMixin, TJSONField)
@ -17,11 +19,15 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
FOOD = 'food' FOOD = 'food'
WINE = 'wine' WINE = 'wine'
LIQUOR = 'liquor' LIQUOR = 'liquor'
SOUVENIR = 'souvenir'
BOOK = 'book'
INDEX_NAME_TYPES = ( INDEX_NAME_TYPES = (
(FOOD, _('Food')), (FOOD, _('Food')),
(WINE, _('Wine')), (WINE, _('Wine')),
(LIQUOR, _('Liquor')), (LIQUOR, _('Liquor')),
(SOUVENIR, _('Souvenir')),
(BOOK, _('Book')),
) )
name = TJSONField(blank=True, null=True, default=None, name = TJSONField(blank=True, null=True, default=None,
@ -30,6 +36,9 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
unique=True, db_index=True, unique=True, db_index=True,
verbose_name=_('Index name')) verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True) use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
tag_categories = models.ManyToManyField('tag.TagCategory',
related_name='product_types',
verbose_name=_('Tag'))
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -45,29 +54,13 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
# INDEX NAME CHOICES # INDEX NAME CHOICES
RUM = 'rum' RUM = 'rum'
PLATE = 'plate'
OTHER = 'other' 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'
INDEX_NAME_TYPES = ( INDEX_NAME_TYPES = (
(RUM, _('Rum')), (RUM, _('Rum')),
(PLATE, _('Plate')),
(OTHER, _('Other')), (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'))
) )
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE, product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
@ -99,7 +92,7 @@ class ProductQuerySet(models.QuerySet):
def with_base_related(self): def with_base_related(self):
return self.select_related('product_type', 'establishment') \ return self.select_related('product_type', 'establishment') \
.prefetch_related('product_type__subtypes', 'country') .prefetch_related('product_type__subtypes')
def common(self): def common(self):
return self.filter(category=self.model.COMMON) return self.filter(category=self.model.COMMON)
@ -122,7 +115,8 @@ class ProductQuerySet(models.QuerySet):
class Product(TranslatedFieldsMixin, BaseAttributes): class Product(TranslatedFieldsMixin, BaseAttributes):
"""Product models.""" """Product models."""
STR_FIELD_NAME = 'name' EARLIEST_VINTAGE_YEAR = 1700
LATEST_VINTAGE_YEAR = 2100
COMMON = 0 COMMON = 0
ONLINE = 1 ONLINE = 1
@ -132,38 +126,74 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
(ONLINE, _('Online')), (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, category = models.PositiveIntegerField(choices=CATEGORY_CHOICES,
default=COMMON) default=COMMON)
name = TJSONField(_('Name'), null=True, blank=True, default=None, name = models.CharField(max_length=255,
help_text='{"en-GB":"some text"}') default=None, null=True,
description = TJSONField(_('Description'), null=True, blank=True, verbose_name=_('name'))
description = TJSONField(_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}') default=None, help_text='{"en-GB":"some text"}')
#TODO set null=False available = models.BooleanField(_('available'), default=True)
characteristics = JSONField(_('Characteristics'), null=True)
country = models.ManyToManyField('location.Country',
verbose_name=_('Country'))
available = models.BooleanField(_('Available'), default=True)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
null=True,
related_name='products', verbose_name=_('Type')) related_name='products', verbose_name=_('Type'))
subtypes = models.ManyToManyField(ProductSubType, blank=True, subtypes = models.ManyToManyField(ProductSubType, blank=True,
related_name='products', related_name='products',
verbose_name=_('Subtypes')) verbose_name=_('Subtypes'))
establishment = models.ForeignKey('establishment.Establishment', establishment = models.ForeignKey('establishment.Establishment', on_delete=models.PROTECT,
on_delete=models.PROTECT, blank=True, null=True,
related_name='products', related_name='products',
verbose_name=_('establishment')) verbose_name=_('establishment'))
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None, public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('public mark'),) verbose_name=_('public mark'),)
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT, wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
related_name='wines', related_name='wines',
blank=True, null=True, blank=True, null=True, default=None,
verbose_name=_('wine region')) verbose_name=_('wine region'))
wine_appellation = models.ForeignKey('location.WineAppellation', on_delete=models.PROTECT, wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.PROTECT,
blank=True, null=True, related_name='wines',
verbose_name=_('wine appellation')) blank=True, null=True, default=None,
verbose_name=_('wine sub region'))
classifications = models.ManyToManyField('ProductClassification',
blank=True,
verbose_name=_('classifications'))
standards = models.ManyToManyField('ProductStandard',
blank=True,
verbose_name=_('standards'),
help_text=_('attribute from legacy db'))
wine_village = models.ForeignKey('location.WineVillage', on_delete=models.PROTECT,
blank=True, null=True,
verbose_name=_('wine appellation'))
slug = models.SlugField(unique=True, max_length=255, null=True, slug = models.SlugField(unique=True, max_length=255, null=True,
verbose_name=_('Establishment slug')) verbose_name=_('Slug'))
favorites = generic.GenericRelation(to='favorites.Favorites') 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'))
tags = models.ManyToManyField('tag.Tag', related_name='products',
verbose_name=_('Tag'))
old_unique_key = models.CharField(max_length=255, unique=True,
blank=True, null=True, default=None,
help_text=_('attribute from legacy db'))
vintage = models.IntegerField(verbose_name=_('vintage year'),
null=True, blank=True, default=None,
validators=[MinValueValidator(EARLIEST_VINTAGE_YEAR),
MaxValueValidator(LATEST_VINTAGE_YEAR)])
gallery = models.ManyToManyField('gallery.Image', through='ProductGallery')
reviews = generic.GenericRelation(to='review.Review')
objects = ProductManager.from_queryset(ProductQuerySet)() objects = ProductManager.from_queryset(ProductQuerySet)()
@ -173,15 +203,19 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
verbose_name = _('Product') verbose_name = _('Product')
verbose_name_plural = _('Products') verbose_name_plural = _('Products')
def __str__(self):
"""Override str dunder method."""
return f'{self.name}'
def clean_fields(self, exclude=None): def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude) super().clean_fields(exclude=exclude)
if self.product_type.index_name == ProductType.WINE and not self.wine_region: if self.product_type.index_name == ProductType.WINE and not self.wine_region:
raise ValidationError(_('wine_region field must be specified.')) raise ValidationError(_('wine_region field must be specified.'))
if not self.product_type.index_name == ProductType.WINE and self.wine_region: if not self.product_type.index_name == ProductType.WINE and self.wine_region:
raise ValidationError(_('wine_region field must not be specified.')) raise ValidationError(_('wine_region field must not be specified.'))
if (self.wine_region and self.wine_appellation) and \ # if (self.wine_region and self.wine_appellation) and \
self.wine_appellation not in self.wine_region.appellations.all(): # self.wine_appellation not in self.wine_region.appellations.all():
raise ValidationError(_('Wine appellation not exists in wine region.')) # raise ValidationError(_('Wine appellation not exists in wine region.'))
class OnlineProductManager(ProductManager): class OnlineProductManager(ProductManager):
@ -203,3 +237,129 @@ class OnlineProduct(Product):
proxy = True proxy = True
verbose_name = _('Online product') verbose_name = _('Online product')
verbose_name_plural = _('Online products') verbose_name_plural = _('Online products')
class Unit(models.Model):
"""Product unit model."""
name = models.CharField(max_length=255,
verbose_name=_('name'))
value = models.CharField(max_length=255,
verbose_name=_('value'))
class Meta:
"""Meta class."""
verbose_name = _('unit')
verbose_name_plural = _('units')
def __str__(self):
"""Overridden dunder method."""
return self.name
class ProductStandardQuerySet(models.QuerySet):
"""Product standard queryset."""
class ProductStandard(models.Model):
"""Product standard model."""
APPELLATION = 0
WINEQUALITY = 1
YARDCLASSIFICATION = 2
STANDARDS = (
(APPELLATION, _('Appellation')),
(WINEQUALITY, _('Wine quality')),
(YARDCLASSIFICATION, _('Yard classification')),
)
name = models.CharField(_('name'), max_length=255)
standard_type = models.PositiveSmallIntegerField(choices=STANDARDS,
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 = ProductStandardQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name_plural = _('wine standards')
verbose_name = _('wine standard')
class ProductGalleryQuerySet(models.QuerySet):
"""QuerySet for model Product"""
def main_image(self):
"""Return objects with flag is_main is True"""
return self.filter(is_main=True)
class ProductGallery(models.Model):
product = models.ForeignKey(Product, null=True,
related_name='product_gallery',
on_delete=models.CASCADE,
verbose_name=_('product'))
image = models.ForeignKey('gallery.Image', null=True,
related_name='product_gallery',
on_delete=models.CASCADE,
verbose_name=_('gallery'))
is_main = models.BooleanField(default=False,
verbose_name=_('Is the main image'))
objects = ProductGalleryQuerySet.as_manager()
class Meta:
"""ProductGallery meta class."""
verbose_name = _('product gallery')
verbose_name_plural = _('product galleries')
unique_together = (('product', 'is_main'), ('product', 'image'))
class ProductClassificationType(models.Model):
"""Product classification type."""
name = models.CharField(max_length=255, unique=True,
verbose_name=_('classification type'))
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
null=True, default=None,
verbose_name=_('product type'))
product_sub_type = models.ForeignKey(ProductSubType, on_delete=models.PROTECT,
blank=True, null=True, default=None,
verbose_name=_('product subtype'),
help_text=_('Legacy attribute - possible_type (product type).'
'Product type in our case is product subtype.'))
class Meta:
"""Meta class."""
verbose_name = _('wine classification type')
verbose_name_plural = _('wine classification types')
def __str__(self):
"""Override str dunder."""
return self.name
class ProductClassificationQuerySet(models.QuerySet):
"""Product classification QuerySet."""
class ProductClassification(models.Model):
"""Product classification model."""
classification_type = models.ForeignKey(ProductClassificationType, on_delete=models.PROTECT,
verbose_name=_('classification type'))
standard = models.ForeignKey(ProductStandard, on_delete=models.PROTECT,
null=True, blank=True, default=None,
verbose_name=_('standard'))
tags = models.ManyToManyField('tag.Tag', related_name='product_classifications',
verbose_name=_('Tag'))
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
objects = ProductClassificationQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('product classification')
verbose_name_plural = _('product classifications')

View File

@ -1,3 +1,4 @@
from .common import * from .common import *
from .web import * from .web import *
from .mobile import * from .mobile import *
from .back import *

View File

@ -0,0 +1,44 @@
"""Product app back-office serializers."""
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from product import models
from gallery.models import Image
class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
"""Serializer class for model ProductGallery."""
class Meta:
"""Meta class"""
model = models.ProductGallery
fields = [
'id',
'is_main',
]
def get_request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def validate(self, attrs):
"""Override validate method."""
product_pk = self.get_request_kwargs().get('pk')
image_id = self.get_request_kwargs().get('image_id')
product_qs = models.Product.objects.filter(pk=product_pk)
image_qs = Image.objects.filter(id=image_id)
if not product_qs.exists():
raise serializers.ValidationError({'detail': _('Product not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
product = product_qs.first()
image = image_qs.first()
attrs['product'] = product
attrs['image'] = image
return attrs

View File

@ -4,8 +4,8 @@ from utils.serializers import TranslatedField, FavoritesCreateSerializer
from product.models import Product, ProductSubType, ProductType from product.models import Product, ProductSubType, ProductType
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from location.serializers import (WineRegionBaseSerializer, WineAppellationBaseSerializer, from location.serializers import WineRegionBaseSerializer, CountrySimpleSerializer
CountrySimpleSerializer) from gallery.models import Image
class ProductSubTypeBaseSerializer(serializers.ModelSerializer): class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
@ -44,7 +44,6 @@ class ProductBaseSerializer(serializers.ModelSerializer):
product_type = ProductTypeBaseSerializer() product_type = ProductTypeBaseSerializer()
subtypes = ProductSubTypeBaseSerializer(many=True) subtypes = ProductSubTypeBaseSerializer(many=True)
wine_region = WineRegionBaseSerializer(allow_null=True) wine_region = WineRegionBaseSerializer(allow_null=True)
wine_appellation = WineAppellationBaseSerializer(allow_null=True)
available_countries = CountrySimpleSerializer(source='country', many=True) available_countries = CountrySimpleSerializer(source='country', many=True)
class Meta: class Meta:
@ -61,7 +60,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
'subtypes', 'subtypes',
'public_mark', 'public_mark',
'wine_region', 'wine_region',
'wine_appellation', 'standards',
'available_countries', 'available_countries',
] ]
@ -93,4 +92,76 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
'user': self.user, 'user': self.user,
'content_object': validated_data.pop('product') 'content_object': validated_data.pop('product')
}) })
return super().create(validated_data) return super().create(validated_data)
# class CropImageSerializer(serializers.Serializer):
# """Serializer for crop images for News object."""
#
# preview_url = serializers.SerializerMethodField()
# promo_horizontal_web_url = serializers.SerializerMethodField()
# promo_horizontal_mobile_url = serializers.SerializerMethodField()
# tile_horizontal_web_url = serializers.SerializerMethodField()
# tile_horizontal_mobile_url = serializers.SerializerMethodField()
# tile_vertical_web_url = serializers.SerializerMethodField()
# highlight_vertical_web_url = serializers.SerializerMethodField()
# editor_web_url = serializers.SerializerMethodField()
# editor_mobile_url = serializers.SerializerMethodField()
#
# def get_preview_url(self, obj):
# """Get crop preview."""
# return obj.instance.get_image_url('news_preview')
#
# def get_promo_horizontal_web_url(self, obj):
# """Get crop promo_horizontal_web."""
# return obj.instance.get_image_url('news_promo_horizontal_web')
#
# def get_promo_horizontal_mobile_url(self, obj):
# """Get crop promo_horizontal_mobile."""
# return obj.instance.get_image_url('news_promo_horizontal_mobile')
#
# def get_tile_horizontal_web_url(self, obj):
# """Get crop tile_horizontal_web."""
# return obj.instance.get_image_url('news_tile_horizontal_web')
#
# def get_tile_horizontal_mobile_url(self, obj):
# """Get crop tile_horizontal_mobile."""
# return obj.instance.get_image_url('news_tile_horizontal_mobile')
#
# def get_tile_vertical_web_url(self, obj):
# """Get crop tile_vertical_web."""
# return obj.instance.get_image_url('news_tile_vertical_web')
#
# def get_highlight_vertical_web_url(self, obj):
# """Get crop highlight_vertical_web."""
# return obj.instance.get_image_url('news_highlight_vertical_web')
#
# def get_editor_web_url(self, obj):
# """Get crop editor_web."""
# return obj.instance.get_image_url('news_editor_web')
#
# def get_editor_mobile_url(self, obj):
# """Get crop editor_mobile."""
# return obj.instance.get_image_url('news_editor_mobile')
class ProductImageSerializer(serializers.ModelSerializer):
"""Serializer for returning crop images of product image."""
orientation_display = serializers.CharField(source='get_orientation_display',
read_only=True)
original_url = serializers.URLField(source='image.url')
# auto_crop_images = CropImageSerializer(source='image', allow_null=True)
class Meta:
model = Image
fields = [
'id',
'title',
'orientation_display',
'original_url',
# 'auto_crop_images',
]
extra_kwargs = {
'orientation': {'write_only': True}
}

View File

@ -0,0 +1,157 @@
from pprint import pprint
from transfer import models as transfer_models
from transfer.serializers import product as product_serializers
from transfer.serializers.partner import PartnerSerializer
def transfer_partner():
queryset = transfer_models.EstablishmentBacklinks.objects.filter(type="Partner")
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"News serializer errors: {serialized_data.errors}")
def transfer_wine_color():
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"transfer_wine_color errors: {serialized_data.errors}")
def transfer_wine_sugar_content():
queryset = transfer_models.WineType.objects.all()
serialized_data = product_serializers.WineTypeSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer_wine_sugar_content errors: {serialized_data.errors}")
def transfer_wine_bottles_produced():
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.WineBottlesProducedSerializer(
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_classification_type():
raw_queryset = transfer_models.ProductClassification.objects.raw(
"""
SELECT
DISTINCT name,
1 as id
FROM wine_classifications;
"""
)
queryset = [vars(query) for query in raw_queryset]
serialized_data = product_serializers.WineClassificationTypeSerializer(
data=queryset,
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer_classification errors: {serialized_data.errors}")
def transfer_wine_standard():
queryset = transfer_models.ProductClassification.objects.filter(parent_id__isnull=True) \
.exclude(type='Classification')
serialized_data = product_serializers.ProductStandardSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer_wine_standard errors: {serialized_data.errors}")
def transfer_wine_classifications():
queryset = transfer_models.ProductClassification.objects.filter(type='Classification')
serialized_data = product_serializers.ProductClassificationSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer_wine_classifications 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:
errors = []
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_product errors: {errors}")
def transfer_plate():
queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
errors = []
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_plates errors: {errors}")
def transfer_plate_image():
queryset = transfer_models.Merchandise.objects.all()
serialized_data = product_serializers.PlateImageSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
errors = []
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_plates_images errors: {errors}")
data_types = {
"partner": [transfer_partner],
"wine_characteristics": [
transfer_wine_sugar_content,
transfer_wine_color,
transfer_wine_bottles_produced,
transfer_wine_classification_type,
transfer_wine_standard,
transfer_wine_classifications,
],
"product": [
transfer_product,
transfer_plate,
transfer_plate_image,
],
}

View File

@ -0,0 +1,13 @@
"""Product backoffice url patterns."""
from django.urls import path
from product.urls.common import urlpatterns as common_urlpatterns
from product import views
urlpatterns = [
path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(),
name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'),
]
urlpatterns.extend(common_urlpatterns)

View File

@ -7,6 +7,7 @@ app_name = 'product'
urlpatterns = [ urlpatterns = [
path('', views.ProductListView.as_view(), name='list'), path('', views.ProductListView.as_view(), name='list'),
path('slug/<slug:slug>', views.ProductDetailView.as_view(), name='detail'),
path('slug/<slug:slug>/favorites/', views.CreateFavoriteProductView.as_view(), path('slug/<slug:slug>/favorites/', views.CreateFavoriteProductView.as_view(),
name='create-destroy-favorites') name='create-destroy-favorites')
] ]

View File

@ -0,0 +1,75 @@
"""Product app back-office views."""
from django.conf import settings
from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404
from rest_framework import generics, status, permissions
from rest_framework.response import Response
from gallery.tasks import delete_image
from product import serializers, models
class ProductBackOfficeMixinView:
"""Product back-office mixin view."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Product.objects.with_base_related() \
.order_by('-created', )
class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
generics.CreateAPIView,
generics.DestroyAPIView):
"""Resource for a create gallery for product for back-office users."""
serializer_class = serializers.ProductBackOfficeGallerySerializer
def get_object(self):
"""
Returns the object the view is displaying.
"""
product_qs = self.filter_queryset(self.get_queryset())
product = get_object_or_404(product_qs, pk=self.kwargs['pk'])
gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs['image_id'])
# May raise a permission denied
self.check_object_permissions(self.request, gallery)
return gallery
def create(self, request, *args, **kwargs):
"""Overridden create method"""
super().create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""Override destroy method."""
gallery_obj = self.get_object()
if settings.USE_CELERY:
on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id,
completely=False))
else:
on_commit(lambda: delete_image(image_id=gallery_obj.image.id,
completely=False))
# Delete an instances of ProductGallery model
gallery_obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView):
"""Resource for returning gallery for product for back-office users."""
serializer_class = serializers.ProductImageSerializer
def get_object(self):
"""Override get_object method."""
qs = super(ProductBackOfficeGalleryListView, self).get_queryset()
product = get_object_or_404(qs, pk=self.kwargs['pk'])
# May raise a permission denied
self.check_object_permissions(self.request, product)
return product
def get_queryset(self):
"""Override get_queryset method."""
return self.get_object().gallery.all()

View File

@ -8,6 +8,7 @@ from product import filters
class ProductBaseView(generics.GenericAPIView): class ProductBaseView(generics.GenericAPIView):
"""Product base view""" """Product base view"""
permission_classes = (permissions.AllowAny, )
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method.""" """Override get_queryset method."""
@ -16,11 +17,16 @@ class ProductBaseView(generics.GenericAPIView):
class ProductListView(ProductBaseView, generics.ListAPIView): class ProductListView(ProductBaseView, generics.ListAPIView):
"""List view for model Product.""" """List view for model Product."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.ProductBaseSerializer serializer_class = serializers.ProductBaseSerializer
filter_class = filters.ProductFilterSet filter_class = filters.ProductFilterSet
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
"""Detail view fro model Product."""
lookup_field = 'slug'
serializer_class = serializers.ProductBaseSerializer
class CreateFavoriteProductView(generics.CreateAPIView, class CreateFavoriteProductView(generics.CreateAPIView,
generics.DestroyAPIView): generics.DestroyAPIView):
"""View for create/destroy product in favorites.""" """View for create/destroy product in favorites."""

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-11-11 07:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tag', '0008_auto_20191101_1244'),
]
operations = [
migrations.AddField(
model_name='tag',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
migrations.AddField(
model_name='tag',
name='priority',
field=models.PositiveIntegerField(default=0, null=True),
),
]

View File

@ -34,8 +34,9 @@ class Tag(TranslatedFieldsMixin, models.Model):
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
null=True, related_name='tags', null=True, related_name='tags',
verbose_name=_('Category')) verbose_name=_('Category'))
chosen_tag_settings = models.ManyToManyField(Country, through='ChosenTagSettings') 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() objects = TagQuerySet.as_manager()

View File

@ -30,6 +30,9 @@ class Command(BaseCommand):
'update_country_flag', 'update_country_flag',
'comment', 'comment',
'inquiries', # №6 - перенос запросов оценок 'inquiries', # №6 - перенос запросов оценок
'wine_characteristics',
'product',
'comment',
] ]
def handle(self, *args, **options): def handle(self, *args, **options):

View File

@ -1,5 +1,10 @@
from django.db import models from django.db import models
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from rest_framework import serializers
from tag import models as tag_models
from django.conf import settings
from product.models import ProductType, ProductSubType
from django.utils.text import slugify
class SecondDbManager(models.Manager): class SecondDbManager(models.Manager):
@ -24,3 +29,68 @@ class MigrateMixin(models.Model):
class Meta: class Meta:
abstract = True abstract = True
class TransferSerializerMixin(serializers.ModelSerializer):
"""Mixin for transferring legacy db models."""
def create(self, validated_data):
qs = self.Meta.model.objects.filter(**validated_data)
if not qs.exists():
return super().create(validated_data)
@property
def tag_category(self):
if self.CATEGORY_LABEL and self.CATEGORY_INDEX_NAME:
tag_category, _ = tag_models.TagCategory.objects.get_or_create(
index_name=self.CATEGORY_INDEX_NAME,
defaults={
'label': {settings.FALLBACK_LOCALE: self.CATEGORY_LABEL},
'value_type': tag_models.TagCategory.STRING,
'index_name': self.CATEGORY_INDEX_NAME,
'public': True
})
return tag_category
def get_vintage_year(self, vintage):
earliest_year = self.Meta.model.EARLIEST_VINTAGE_YEAR
latest_year = self.Meta.model.LATEST_VINTAGE_YEAR
if vintage:
if vintage.isdigit():
if len(vintage) == 2:
if vintage == '16':
return 2016
elif len(vintage) == 4:
if earliest_year < int(vintage) < latest_year:
return int(vintage)
elif vintage == '1584':
return 1984
elif vintage == '1017':
return 2017
elif len(vintage) == 5:
if vintage == '20115':
return 2015
elif vintage == '20174':
return 2017
elif vintage.endswith('er'):
return self.get_vintage_year(vintage[:-2])
def get_product_type(self, index_name):
if index_name:
qs = ProductType.objects.filter(
index_name__icontains=index_name)
if qs.exists():
return qs.first()
def get_product_sub_type(self, product_type, product_sub_type):
if not isinstance(product_type, ProductType):
product_type = self.get_product_type(product_type)
if product_type and product_sub_type:
qs = ProductSubType.objects.filter(
product_type=product_type,
index_name__icontains=product_sub_type)
if qs.exists():
return qs.first()
def get_slug(self, name, old_id):
return slugify(f'{name}-{old_id}')

View File

@ -899,47 +899,76 @@ class KeyValueMetadatumKeyValueMetadatumEstablishments(MigrateMixin):
db_table = 'key_value_metadatum_key_value_metadatum_establishments' db_table = 'key_value_metadatum_key_value_metadatum_establishments'
# class Products(models.Model): class WineColor(MigrateMixin):
# establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True) using = 'legacy'
# brand = models.CharField(max_length=255, blank=True, null=True)
# name = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255)
# vintage = models.CharField(max_length=255, blank=True, null=True) order_number = models.IntegerField(null=True, blank=True)
# type = models.CharField(max_length=255, blank=True, null=True)
# unique_key = models.CharField(max_length=255, blank=True, null=True) class Meta:
# price = models.FloatField(blank=True, null=True) managed = False
# average_price_in_shops = models.FloatField(blank=True, null=True) db_table = 'wine_colors'
# 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) class WineType(MigrateMixin):
# wine_region = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True) using = 'legacy'
# wine_type = models.ForeignKey('WineTypes', models.DO_NOTHING, blank=True, null=True)
# wine_color = models.ForeignKey('WineColors', models.DO_NOTHING, blank=True, null=True) name = models.CharField(max_length=255)
# appellation = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
# created_at = models.DateTimeField() class Meta:
# updated_at = models.DateTimeField() managed = False
# state = models.CharField(max_length=255, blank=True, null=True) db_table = 'wine_types'
# 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) class ProductClassification(MigrateMixin):
# yard_classification = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True) using = 'legacy'
# wine_quality = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
# bottles_produced = models.CharField(max_length=3000, blank=True, null=True) name = models.CharField(max_length=255)
# deu_import_id = models.IntegerField(blank=True, null=True) desc = models.TextField()
# latitude = models.FloatField(blank=True, null=True)
# class Meta: longitude = models.FloatField(blank=True, null=True)
# managed = False type = models.CharField(max_length=255)
# db_table = 'products' parent_id = models.IntegerField()
# possible_type_id = models.IntegerField(null=True, blank=True)
# possible_color_id = models.IntegerField(null=True, blank=True)
# class WineTypes(models.Model): fra_encima_id = models.IntegerField(null=True, blank=True)
# name = models.CharField(max_length=255, blank=True, null=True)
# fra_encima_id = models.IntegerField(blank=True, null=True) class Meta:
# created_at = models.DateTimeField() managed = False
# updated_at = models.DateTimeField() db_table = 'wine_classifications'
#
# class Meta:
# managed = False class Products(MigrateMixin):
# db_table = 'wine_types' 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('ProductClassification', 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('ProductClassification', 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('ProductClassification', models.DO_NOTHING, null=True,
related_name='product_wine_quality')
bottles_produced = models.CharField(max_length=3000, null=True)
unique_key = models.CharField(max_length=255, null=True)
class Meta:
managed = False
db_table = 'products'
class HomePages(models.Model): class HomePages(models.Model):
using = 'legacy' using = 'legacy'
@ -1004,6 +1033,35 @@ class Identities(MigrateMixin):
db_table = 'identities' db_table = 'identities'
class WineLocations(MigrateMixin):
using = 'legacy'
name = models.CharField(max_length=255)
desc = models.TextField(null=True)
latitude = models.FloatField(null=True)
longitude = models.FloatField(null=True)
type = models.CharField(max_length=255)
parent_id = models.IntegerField()
class Meta:
managed = False
db_table = 'wine_locations'
class Merchandise(MigrateMixin):
using = 'legacy'
name = models.CharField(max_length=255)
vintage = models.CharField(max_length=255)
highlighted = models.CharField(max_length=255)
site = models.ForeignKey('Sites', models.DO_NOTHING)
attachment_suffix_url = models.CharField(max_length=255)
class Meta:
managed = False
db_table = 'merchandises'
class Inquiries(MigrateMixin): class Inquiries(MigrateMixin):
using = 'legacy' using = 'legacy'

View File

@ -5,7 +5,7 @@ from account.models import User
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
nickname = serializers.CharField() nickname = serializers.CharField()
email = serializers.CharField() email = serializers.CharField()
confirmed_at = serializers.DateTimeField() confirmed_at = serializers.DateTimeField(allow_null=True)
id = serializers.CharField() id = serializers.CharField()
class Meta: class Meta:
@ -30,8 +30,11 @@ class UserSerializer(serializers.ModelSerializer):
# использовать get_or_create # использовать get_or_create
User.objects.create(**validated_data) User.objects.create(**validated_data)
def get_email_confirmed(self, obj): def get_email_confirmed(self, data):
return True if data.get("confirmed_at"):
return True
else:
return False
def get_username(self, obj): def get_username(self, obj):
return obj["email"] return obj["email"]

View File

@ -1,13 +1,16 @@
from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned, ValidationError from django.core.exceptions import MultipleObjectsReturned, ValidationError
from django.db import transaction from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from establishment.models import Establishment, ContactEmail, ContactPhone, EstablishmentType from establishment.models import Establishment, ContactEmail, ContactPhone, EstablishmentType, \
EstablishmentSubType
from location.models import Address from location.models import Address
from timetable.models import Timetable from timetable.models import Timetable
from utils.legacy_parser import parse_legacy_schedule_content from utils.legacy_parser import parse_legacy_schedule_content
from utils.serializers import TimeZoneChoiceField from utils.serializers import TimeZoneChoiceField
from utils.slug_generator import generate_unique_slug from utils.slug_generator import generate_unique_slug
from django.utils.text import slugify
class EstablishmentSerializer(serializers.ModelSerializer): class EstablishmentSerializer(serializers.ModelSerializer):
@ -53,14 +56,16 @@ class EstablishmentSerializer(serializers.ModelSerializer):
) )
def validate(self, data): def validate(self, data):
old_type = data.pop('type', None)
data.update({ data.update({
'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']), 'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']),
'address_id': self.get_address(data['location']), 'address_id': self.get_address(data['location']),
'establishment_type_id': self.get_type(data), 'establishment_type_id': self.get_type(old_type),
'is_publish': data.get('state') == 'published', 'is_publish': data.get('state') == 'published',
'subtype': self.get_subtype(old_type),
}) })
data.pop('location') data.pop('location')
data.pop('type')
data.pop('state') data.pop('state')
return data return data
@ -69,6 +74,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
email = validated_data.pop('email') email = validated_data.pop('email')
phone = validated_data.pop('phone') phone = validated_data.pop('phone')
schedules = validated_data.pop('schedules') schedules = validated_data.pop('schedules')
subtypes = [validated_data.pop('subtype', None)]
establishment = Establishment.objects.create(**validated_data) establishment = Establishment.objects.create(**validated_data)
if email: if email:
@ -86,6 +92,8 @@ class EstablishmentSerializer(serializers.ModelSerializer):
for schedule in new_schedules: for schedule in new_schedules:
establishment.schedule.add(schedule) establishment.schedule.add(schedule)
establishment.save() establishment.save()
if subtypes:
establishment.establishment_subtypes.add(*[i for i in subtypes if i])
return establishment return establishment
@ -97,13 +105,16 @@ class EstablishmentSerializer(serializers.ModelSerializer):
return None return None
@staticmethod @staticmethod
def get_type(data): def get_type(old_type):
types = { types = {
'Restaurant': EstablishmentType.RESTAURANT, 'Restaurant': EstablishmentType.RESTAURANT,
'Shop': EstablishmentType.ARTISAN, 'Shop': EstablishmentType.ARTISAN,
'Wineyard': EstablishmentType.PRODUCER,
} }
obj, _ = EstablishmentType.objects.get_or_create(index_name=types[data['type']]) index_name = types.get(old_type)
return obj.id if index_name:
obj, _ = EstablishmentType.objects.get_or_create(index_name=index_name)
return obj.id
@staticmethod @staticmethod
def get_schedules(schedules): def get_schedules(schedules):
@ -152,3 +163,13 @@ class EstablishmentSerializer(serializers.ModelSerializer):
result.append(obj) result.append(obj)
return result return result
def get_subtype(self, old_type):
if old_type == 'Wineyard':
subtype_name = 'Winery'
establishment_type_id = self.get_type(old_type)
subtype, _ = EstablishmentSubType.objects.get_or_create(
name={settings.FALLBACK_LOCALE: subtype_name},
index_name=slugify(subtype_name),
establishment_type_id=establishment_type_id)
return subtype

View File

@ -1,8 +1,10 @@
from rest_framework import serializers from django.conf import settings
from location.models import Country, Region, City, Address
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from django.contrib.gis.geos import GEOSGeometry from rest_framework import serializers
import json
from location import models
from transfer.mixins import TransferSerializerMixin
from utils.methods import get_point_from_coordinates
class CountrySerializer(serializers.ModelSerializer): class CountrySerializer(serializers.ModelSerializer):
@ -10,7 +12,7 @@ class CountrySerializer(serializers.ModelSerializer):
id = serializers.IntegerField() id = serializers.IntegerField()
class Meta: class Meta:
model = Country model = models.Country
fields = ( fields = (
"id", "id",
"country_code_2", "country_code_2",
@ -27,9 +29,9 @@ class CountrySerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
# Some countries already in database # Some countries already in database
try: try:
country = Country.objects.get(code=validated_data['code']) country = models.Country.objects.get(code=validated_data['code'])
except Country.DoesNotExist: except models.Country.DoesNotExist:
country = Country.objects.create(**validated_data) country = models.Country.objects.create(**validated_data)
return country return country
def get_country_code(self, obj): def get_country_code(self, obj):
@ -43,7 +45,7 @@ class RegionSerializer(serializers.ModelSerializer):
id = serializers.IntegerField() id = serializers.IntegerField()
class Meta: class Meta:
model = Region model = models.Region
fields = ( fields = (
"region_code", "region_code",
"country_code_2", "country_code_2",
@ -60,9 +62,9 @@ class RegionSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
# Some regions may be already in database # Some regions may be already in database
try: try:
region = Region.objects.get(old_id=validated_data['old_id']) region = models.Region.objects.get(old_id=validated_data['old_id'])
except Region.DoesNotExist: except models.Region.DoesNotExist:
region = Region.objects.create(**validated_data) region = models.Region.objects.create(**validated_data)
except Exception as e: except Exception as e:
raise ValueError(f"REGION ERROR: {validated_data}: {e}") raise ValueError(f"REGION ERROR: {validated_data}: {e}")
return region return region
@ -71,7 +73,7 @@ class RegionSerializer(serializers.ModelSerializer):
print(data) print(data)
if "subregion_code" in data and data["subregion_code"] is not None and data["subregion_code"].strip() != "": if "subregion_code" in data and data["subregion_code"] is not None and data["subregion_code"].strip() != "":
try: try:
parent_region = Region.objects.filter(code=str(data['region_code'])).first() parent_region = models.Region.objects.filter(code=str(data['region_code'])).first()
except Exception as e: except Exception as e:
raise ValueError(f"Parent region error with {data}: {e}") raise ValueError(f"Parent region error with {data}: {e}")
@ -86,7 +88,7 @@ class RegionSerializer(serializers.ModelSerializer):
def set_country(self, data): def set_country(self, data):
try: try:
country = Country.objects.get(code=data['country_code_2']) country = models.Country.objects.get(code=data['country_code_2'])
except Exception as e: except Exception as e:
raise ValueError(f"Country error with {data}: {e}") raise ValueError(f"Country error with {data}: {e}")
@ -110,7 +112,7 @@ class CitySerializer(serializers.ModelSerializer):
id = serializers.IntegerField() id = serializers.IntegerField()
class Meta: class Meta:
model = City model = models.City
fields = ( fields = (
"country_code_2", "country_code_2",
"region_code", "region_code",
@ -130,7 +132,7 @@ class CitySerializer(serializers.ModelSerializer):
return data return data
def create(self, validated_data): def create(self, validated_data):
return City.objects.create(**validated_data) return models.City.objects.create(**validated_data)
def set_is_island(self, data): def set_is_island(self, data):
data['is_island'] = True if "is_island" in data \ data['is_island'] = True if "is_island" in data \
@ -145,19 +147,19 @@ class CitySerializer(serializers.ModelSerializer):
def set_relations(self, data): def set_relations(self, data):
try: try:
region = Region.objects.filter(code=data['region_code']).first() region = models.Region.objects.filter(code=data['region_code']).first()
except Region.DoesNotExist as e: except models.Region.DoesNotExist as e:
try: try:
region = Region.objects.filter(code=data['subregion_code']).first() region = models.Region.objects.filter(code=data['subregion_code']).first()
except Region.DoesNotExist as e: except models.Region.DoesNotExist as e:
raise ValueError(f"Region not found with {data}: {e}") raise ValueError(f"Region not found with {data}: {e}")
data['region'] = region data['region'] = region
del(data['subregion_code']) del(data['subregion_code'])
try: try:
country = Country.objects.get(code=data['country_code_2']) country = models.Country.objects.get(code=data['country_code_2'])
except Country.DoesNotExist as e: except models.Country.DoesNotExist as e:
raise ValueError(f"Country not found with {data}: {e}") raise ValueError(f"Country not found with {data}: {e}")
data['country'] = country data['country'] = country
@ -185,7 +187,7 @@ class AddressSerializer(serializers.ModelSerializer):
address = serializers.CharField() address = serializers.CharField()
class Meta: class Meta:
model = Address model = models.Address
fields = ( fields = (
"id", "id",
"city_id", "city_id",
@ -204,7 +206,7 @@ class AddressSerializer(serializers.ModelSerializer):
return data return data
def create(self, validated_data): def create(self, validated_data):
return Address.objects.create(**validated_data) return models.Address.objects.create(**validated_data)
def set_old_id(self, data): def set_old_id(self, data):
data['old_id'] = data.pop("id") data['old_id'] = data.pop("id")
@ -218,8 +220,8 @@ class AddressSerializer(serializers.ModelSerializer):
def set_city(self, data): def set_city(self, data):
try: try:
city = City.objects.filter(old_id=data['city_id']).first() city = models.City.objects.filter(old_id=data['city_id']).first()
except City.DoesNotExist as e: except models.City.DoesNotExist as e:
raise ValueError(f"City not found with {data}: {e}") raise ValueError(f"City not found with {data}: {e}")
data['city'] = city data['city'] = city
@ -266,3 +268,84 @@ class AddressSerializer(serializers.ModelSerializer):
del(data['longitude']) del(data['longitude'])
return data return data
class WineRegion(TransferSerializerMixin):
id = serializers.IntegerField()
name = serializers.CharField()
desc = serializers.CharField(allow_null=True)
latitude = serializers.FloatField(allow_null=True)
longitude = serializers.FloatField(allow_null=True)
class Meta:
model = models.WineRegion
fields = (
'id',
'name',
'desc',
'latitude',
'longitude',
)
def validate(self, attrs):
latitude = attrs.pop('latitude', None)
longitude = attrs.pop('longitude', None)
attrs['old_id'] = attrs.pop('id')
attrs['description'] = {settings.FALLBACK_LOCALE: attrs.pop('desc', None)}
attrs['coordinates'] = get_point_from_coordinates(latitude, longitude)
return attrs
class WineSubRegion(WineRegion):
id = serializers.IntegerField()
name = serializers.CharField()
parent_id = serializers.IntegerField()
class Meta:
model = models.WineSubRegion
fields = (
'id',
'name',
'parent_id',
)
def validate(self, attrs):
parent_id = attrs.pop('parent_id', None)
attrs['old_id'] = attrs.pop('id')
attrs['wine_region'] = self.get_wine_region(parent_id)
return attrs
def get_wine_region(self, parent_id):
qs = models.WineRegion.objects.filter(old_id=parent_id)
if qs.exists():
return qs.first()
class WineVillage(TransferSerializerMixin):
id = serializers.IntegerField()
name = serializers.CharField()
parent_id = serializers.IntegerField()
class Meta:
model = models.WineVillage
fields = (
'id',
'name',
'parent_id',
)
def validate(self, attrs):
parent_id = attrs.pop('parent_id', None)
attrs['old_id'] = attrs.pop('id')
attrs['wine_region'] = self.get_wine_region(parent_id)
return attrs
def get_wine_region(self, parent_id):
qs = models.WineRegion.objects.filter(old_id=parent_id)
if qs.exists():
return qs.first()

View File

@ -0,0 +1,519 @@
from rest_framework import serializers
from django.utils.text import slugify
from product import models
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
from functools import reduce
from gallery.models import Image
class WineColorSerializer(TransferSerializerMixin):
CATEGORY_LABEL = 'Wine color'
CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)
id = serializers.IntegerField()
name = serializers.CharField(allow_null=True)
order_number = serializers.IntegerField(allow_null=True)
class Meta:
model = tag_models.Tag
fields = (
'id',
'name',
'order_number',
)
def validate(self, attrs):
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
class WineTypeSerializer(TransferSerializerMixin):
CATEGORY_LABEL = 'Sugar content'
CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)
id = serializers.IntegerField()
name = serializers.CharField()
class Meta:
model = tag_models.Tag
fields = (
'id',
'name',
)
def validate(self, attrs):
value = attrs.pop('name')
attrs['old_id'] = attrs.pop('id', None)
attrs['label'] = {settings.FALLBACK_LOCALE: value}
attrs['value'] = slugify(value)
attrs['category'] = self.tag_category
return attrs
class WineBottlesProducedSerializer(TransferSerializerMixin):
CATEGORY_LABEL = 'Bottles produced'
CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)
bottles_produced = serializers.CharField(allow_null=True)
class Meta:
model = tag_models.Tag
fields = (
'bottles_produced',
)
def validate(self, attrs):
value = attrs.pop('bottles_produced')
parsed_value = self.parsed_value(value)
attrs['label'] = {settings.FALLBACK_LOCALE: parsed_value}
attrs['value'] = slugify(parsed_value)
attrs['category'] = self.tag_category
return attrs
def parsed_value(self, value):
if value.isdigit():
return int(value)
parted = value.split(' ')
if len(parted) > 1:
values = [int(i) for i in parted if i.isdigit()]
return reduce(lambda a, b: a + b, values)
lowered = value.lower()
if 'magnum' in lowered:
return 1
return 0
class ProductStandardSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
name = serializers.CharField()
type = serializers.CharField(allow_null=True)
longitude = serializers.FloatField(allow_null=True)
latitude = serializers.FloatField(allow_null=True)
class Meta:
model = models.ProductStandard
fields = (
'id',
'name',
'type',
'longitude',
'latitude',
)
def validate(self, attrs):
latitude = attrs.pop('latitude', None)
longitude = attrs.pop('longitude', None)
standard_type = attrs.pop('type', None)
attrs['coordinates'] = get_point_from_coordinates(latitude, longitude)
attrs['old_id'] = attrs.get('id')
attrs['standard_type'] = self.get_standard_type(standard_type)
return attrs
def get_standard_type(self, type: str):
if type == 'Appellation':
return models.ProductStandard.APPELLATION
elif type == 'YardClassification':
return models.ProductStandard.WINEQUALITY
elif type == 'WineQuality':
return models.ProductStandard.YARDCLASSIFICATION
class WineClassificationTypeSerializer(TransferSerializerMixin):
name = serializers.CharField()
class Meta:
model = models.ProductClassificationType
fields = (
'name',
)
def validate(self, attrs):
attrs['product_type'] = self.wine_type
return attrs
@property
def wine_type(self):
qs = models.ProductType.objects.filter(index_name=models.ProductType.WINE)
if qs.exists():
return qs.first()
class ProductClassificationSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
name = serializers.CharField()
possible_type_id = serializers.IntegerField(allow_null=True)
possible_color_id = serializers.IntegerField(allow_null=True)
parent_id = serializers.IntegerField(allow_null=True)
class Meta:
model = models.ProductClassification
fields = (
'id',
'name',
'possible_type_id',
'possible_color_id',
'parent_id',
)
def validate(self, attrs):
classification_name = attrs.pop('name')
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')
attrs['classification_type'] = self.get_classification_type(classification_name)
attrs['possible_subtype_tag'] = self.get_possible_type_tag(possible_type_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)
possible_subtype_tag = validated_data.pop('possible_subtype_tag', None)
obj, _ = self.Meta.model.objects.get_or_create(**validated_data)
if possible_color_tag:
obj.tags.add(possible_color_tag)
if possible_subtype_tag:
obj.tags.add(possible_subtype_tag)
return obj
def get_classification_type(self, classification_name):
qs = models.ProductClassificationType.objects.filter(name__icontains=classification_name)
if qs.exists():
return qs.first()
def get_possible_type_tag(self, possible_type_id):
if possible_type_id:
qs = tag_models.Tag.objects.filter(
old_id=possible_type_id,
category__index_name=WineTypeSerializer.CATEGORY_INDEX_NAME)
if qs.exists():
return qs.first()
else:
import ipdb; ipdb.set_trace()
def get_possible_color_tag(self, possible_color_id):
if possible_color_id:
qs = tag_models.Tag.objects.filter(
old_id=possible_color_id,
category__index_name=WineColorSerializer.CATEGORY_INDEX_NAME)
if qs.exists():
return qs.first()
def get_wine_standard(self, parent_id):
if parent_id:
qs = models.ProductStandard.objects.filter(old_id=parent_id)
if qs.exists():
return qs.first()
class ProductSerializer(TransferSerializerMixin):
establishment_id = serializers.PrimaryKeyRelatedField(
queryset=transfer_models.Establishments.objects.all(),
allow_null=True)
classification_id = serializers.PrimaryKeyRelatedField(
queryset=transfer_models.ProductClassification.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.ProductClassification.objects.all(),
allow_null=True)
village_id = serializers.PrimaryKeyRelatedField(
queryset=transfer_models.WineLocations.objects.all(),
allow_null=True)
# same as wine_village
# vineyard_id = serializers.PrimaryKeyRelatedField(
# queryset=transfer_models.WineLocations.objects.all(),
# allow_null=True)
wine_quality_id = serializers.PrimaryKeyRelatedField(
queryset=transfer_models.ProductClassification.objects.all(),
allow_null=True)
# same as establishment name
id = serializers.IntegerField()
brand = serializers.CharField(allow_null=True, allow_blank=True)
name = serializers.CharField(allow_null=True, allow_blank=True)
vintage = serializers.CharField(allow_null=True)
type = serializers.CharField(allow_null=True)
wine_sub_region_id = serializers.CharField(allow_null=True)
state = serializers.CharField()
bottles_produced = serializers.CharField(allow_null=True, allow_blank=True)
unique_key = serializers.CharField(allow_null=True)
class Meta:
model = models.Product
fields = (
'id',
'establishment_id', # done
'classification_id', # done
'wine_region_id', # done
'wine_type_id', # done
'wine_color_id', # done
'appellation_id', # done
'village_id', # done
# 'vineyard_id',
'wine_quality_id', # done
'brand',
'name', # done
'vintage', # done
'type', # done
'wine_sub_region_id', # done
'state', # done
'bottles_produced', # done
'unique_key', # done
)
def validate(self, attrs):
name = attrs.pop('name', None)
establishment = attrs.pop('establishment_id', None)
brand = attrs.pop('brand', None)
classification = attrs.pop('classification_id', None)
vintage = attrs.pop('vintage', None)
product_type = attrs.pop('type')
wine_region = attrs.pop('wine_region_id', None)
wine_sub_region_id = attrs.pop('wine_sub_region_id', None)
bottles_produced = attrs.pop('bottles_produced', None)
wine_type = attrs.pop('wine_type_id', None)
wine_color = attrs.pop('wine_color_id', None)
appellation = attrs.pop('appellation_id', None)
village = attrs.pop('village_id', None)
wine_quality = attrs.pop('wine_quality_id', None)
name = self.get_name(name, brand)
old_id = attrs.pop('id')
state = self.get_state(attrs.pop('state', None))
attrs['old_id'] = old_id
attrs['name'] = name
attrs['old_unique_key'] = attrs.pop('unique_key')
attrs['establishment'] = self.get_establishment(establishment)
attrs['state'] = state
# attrs['brand'] = self.get_brand(brand)
attrs['wine_classification'] = self.get_wine_classification(classification)
attrs['wine_quality'] = self.get_wine_standard(wine_quality,
models.ProductStandard.WINEQUALITY)
attrs['wine_appellation'] = self.get_wine_standard(appellation,
models.ProductStandard.APPELLATION)
attrs['product_type'] = self.get_product_type(product_type)
attrs['vintage'] = self.get_vintage_year(vintage)
attrs['wine_region'] = self.get_wine_region(wine_region)
attrs['wine_sub_region'] = self.get_wine_sub_region(wine_sub_region_id)
attrs['bottles_produced_tag'] = self.get_tag(bottles_produced,
WineBottlesProducedSerializer.CATEGORY_INDEX_NAME)
attrs['wine_type_tag'] = self.get_tag(wine_type,
WineTypeSerializer.CATEGORY_INDEX_NAME)
attrs['wine_color_tag'] = self.get_tag(wine_color,
WineColorSerializer.CATEGORY_INDEX_NAME)
attrs['wine_village'] = self.get_wine_village(village)
attrs['available'] = self.get_availability(state)
attrs['slug'] = self.get_slug(name, old_id)
return attrs
def create(self, validated_data):
qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))
# classifications
classifications = [validated_data.pop('wine_classification', None)]
# standards
standards = [validated_data.pop('wine_quality', None),
validated_data.pop('wine_appellation', None)]
# tags
tags = [validated_data.pop('bottles_produced_tag', None),
validated_data.pop('wine_type_tag', None),
validated_data.pop('wine_color_tag', None)]
if not qs.exists():
obj = super().create(validated_data)
else:
obj = qs.first()
# adding classification
obj.classifications.add(*[i for i in classifications if i])
# adding standard
obj.standards.add(*[i for i in standards if i])
# adding tags
obj.tags.add(*[i for i in tags if i])
return obj
def get_name(self, name, brand):
if name:
return name
if brand and not name:
return brand
def get_establishment(self, establishment):
if establishment:
qs = establishment_models.Establishment.objects.filter(
old_id=establishment.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_wine_classification(self, classification):
if classification:
classification_qs = models.ProductClassification.objects.filter(
old_id=classification.id)
if classification_qs.exists():
return classification_qs.first()
def get_wine_region(self, wine_region):
if wine_region:
wine_region_qs = location_models.WineRegion.objects.filter(
old_id=wine_region.id)
if wine_region_qs.exists():
return wine_region_qs.first()
def get_wine_sub_region(self, wine_sub_region_id):
if wine_sub_region_id:
sub_region_qs = location_models.WineSubRegion.objects.filter(
old_id=wine_sub_region_id)
if sub_region_qs.exists():
return sub_region_qs.first()
def get_wine_village(self, village):
if village:
village_qs = location_models.WineVillage.objects.filter(
old_id=village.id)
if village_qs.exists():
return village_qs.first()
def get_tag(self, tag, category_index_name: str):
tag = tag.value if hasattr(tag, 'value') else tag
if tag:
qs = tag_models.Tag.objects.filter(
value=tag,
category__index_name=category_index_name)
if qs.exists():
return qs.first()
def get_wine_standard(self, standard, standard_type):
if standard:
if isinstance(standard, transfer_models.ProductClassification):
standard = standard.id
standard_qs = models.ProductStandard.objects.filter(
standard_type=standard_type,
old_id=standard)
if standard_qs.exists():
return standard_qs.first()
def get_availability(self, state: int):
if state == models.Product.PUBLISHED:
return True
return False
class PlateSerializer(TransferSerializerMixin):
PRODUCT_TYPE_INDEX_NAME = 'souvenir'
PRODUCT_SUB_TYPE_INDEX_NAME = 'plate'
id = serializers.IntegerField()
name = serializers.CharField()
vintage = serializers.CharField()
class Meta(ProductSerializer.Meta):
fields = (
'id',
'name',
'vintage',
)
def validate(self, attrs):
old_id = attrs.pop('id')
name = attrs.get('name')
product_type = self.get_product_type(self.PRODUCT_TYPE_INDEX_NAME)
attrs['old_id'] = old_id
attrs['vintage'] = self.get_vintage_year(attrs.pop('vintage'))
attrs['product_type'] = product_type
attrs['state'] = self.Meta.model.PUBLISHED
attrs['subtype'] = self.get_product_sub_type(product_type,
self.PRODUCT_SUB_TYPE_INDEX_NAME)
attrs['slug'] = self.get_slug(name, old_id)
return attrs
def create(self, validated_data):
qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))
# subtypes
subtypes = [validated_data.pop('subtype', None)]
if not qs.exists():
obj = super().create(validated_data)
else:
obj = qs.first()
# adding classification
obj.subtypes.add(*[i for i in subtypes if i])
return obj
class PlateImageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
name = serializers.CharField()
attachment_suffix_url = serializers.CharField()
class Meta:
model = models.ProductGallery
fields = (
'id',
'name',
'attachment_suffix_url',
)
def create(self, validated_data):
image = self.get_image(validated_data.pop('attachment_suffix_url', None),
validated_data.pop('name', None))
product = self.get_product(validated_data.pop('id'))
if product and image:
obj, created = models.ProductGallery.objects.get_or_create(
product=product,
image=image,
is_main=True)
return obj
def get_image(self, image_url, name):
if image_url:
obj, created = Image.objects.get_or_create(
title=name,
image=image_url)
return obj if created else None
def get_product(self, product_id):
if product_id:
product_qs = models.Product.objects.filter(old_id=product_id)
if product_qs.exists():
return product_qs.first()

View File

@ -7,6 +7,7 @@ import string
import requests import requests
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.gis.geos import Point
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.utils.timezone import datetime from django.utils.timezone import datetime
from rest_framework import status from rest_framework import status
@ -118,3 +119,8 @@ def absolute_url_decorator(func):
else: else:
return url_path return url_path
return get_absolute_image_url return get_absolute_image_url
def get_point_from_coordinates(latitude: str, longitude: str):
if latitude and longitude:
return Point(x=longitude, y=latitude, srid=4326)

View File

@ -14,8 +14,7 @@ services:
volumes: volumes:
- .:/code - .:/code
- gm-mysql_db:/var/lib/mysql - gm-mysql_db:/var/lib/mysql
networks:
- mysql_network
# PostgreSQL database # PostgreSQL database
db: db:
@ -31,8 +30,7 @@ services:
- "5436:5432" - "5436:5432"
volumes: volumes:
- gm-db:/var/lib/postgresql/data/ - gm-db:/var/lib/postgresql/data/
networks:
- database_network
elasticsearch: elasticsearch:
image: elasticsearch:7.3.1 image: elasticsearch:7.3.1
@ -46,14 +44,12 @@ services:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node - discovery.type=single-node
- xpack.security.enabled=false - xpack.security.enabled=false
networks:
- elasticsearch_network
# Redis # Redis
redis: redis:
image: redis:alpine image: redis:alpine
networks:
- redis_network
# Celery # Celery
worker: worker:
@ -71,8 +67,7 @@ services:
links: links:
- db - db
- redis - redis
networks:
- redis_network
worker_beat: worker_beat:
build: . build: .
@ -89,8 +84,7 @@ services:
links: links:
- db - db
- redis - redis
networks:
- redis_network
# App: G&M # App: G&M
gm_app: gm_app:
@ -115,11 +109,7 @@ services:
- gm-media:/media-data - gm-media:/media-data
ports: ports:
- "8000:8000" - "8000:8000"
networks:
- redis_network
- database_network
- mysql_network
- elasticsearch_network
volumes: volumes:
gm-mysql_db: gm-mysql_db:
@ -130,12 +120,3 @@ volumes:
name: gm-media name: gm-media
gm-esdata: gm-esdata:
networks:
database_network:
driver: bridge
mysql_network:
driver: bridge
elasticsearch_network:
driver: bridge
redis_network:
driver: bridge

View File

@ -64,7 +64,7 @@ PROJECT_APPS = [
'news.apps.NewsConfig', 'news.apps.NewsConfig',
'notification.apps.NotificationConfig', 'notification.apps.NotificationConfig',
'partner.apps.PartnerConfig', 'partner.apps.PartnerConfig',
# 'product.apps.ProductConfig', Uncomment after refining task and create migrations 'product.apps.ProductConfig',
'recipe.apps.RecipeConfig', 'recipe.apps.RecipeConfig',
'search_indexes.apps.SearchIndexesConfig', 'search_indexes.apps.SearchIndexesConfig',
'translation.apps.TranslationConfig', 'translation.apps.TranslationConfig',
@ -74,9 +74,7 @@ PROJECT_APPS = [
'comment.apps.CommentConfig', 'comment.apps.CommentConfig',
'favorites.apps.FavoritesConfig', 'favorites.apps.FavoritesConfig',
'rating.apps.RatingConfig', 'rating.apps.RatingConfig',
'transfer.apps.TransferConfig',
'tag.apps.TagConfig', 'tag.apps.TagConfig',
'product.apps.ProductConfig',
] ]
EXTERNAL_APPS = [ EXTERNAL_APPS = [
@ -157,16 +155,6 @@ DATABASES = {
'HOST': os.environ.get('DB_HOSTNAME'), 'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'), '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'
# }
} }

View File

@ -38,18 +38,9 @@ sentry_sdk.init(
integrations=[DjangoIntegration()] integrations=[DjangoIntegration()]
) )
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = { # DATABASE
'default': { DATABASES.update({
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USERNAME'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'),
},
'legacy': { 'legacy': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'HOST': os.environ.get('MYSQL_HOSTNAME'), 'HOST': os.environ.get('MYSQL_HOSTNAME'),
@ -57,9 +48,13 @@ DATABASES = {
'NAME': os.environ.get('MYSQL_DATABASE'), 'NAME': os.environ.get('MYSQL_DATABASE'),
'USER': os.environ.get('MYSQL_USER'), 'USER': os.environ.get('MYSQL_USER'),
'PASSWORD': os.environ.get('MYSQL_PASSWORD') 'PASSWORD': os.environ.get('MYSQL_PASSWORD')
} }})
}
# INSTALLED APPS
INSTALLED_APPS.append('transfer.apps.TransferConfig')
BROKER_URL = 'redis://localhost:6379/1' BROKER_URL = 'redis://localhost:6379/1'
CELERY_RESULT_BACKEND = BROKER_URL CELERY_RESULT_BACKEND = BROKER_URL
CELERY_BROKER_URL = BROKER_URL CELERY_BROKER_URL = BROKER_URL

View File

@ -35,6 +35,21 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
THUMBNAIL_DEBUG = True THUMBNAIL_DEBUG = True
# ADDED TRANSFER APP
INSTALLED_APPS.append('transfer.apps.TransferConfig')
# DATABASES
DATABASES.update({
'legacy': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'mysql_db',
'PORT': 3306,
'NAME': 'dev',
'USER': 'dev',
'PASSWORD': 'octosecret123'}})
# LOGGING # LOGGING
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
@ -73,28 +88,6 @@ LOGGING = {
} }
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USERNAME'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'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'
}
}
# ELASTICSEARCH SETTINGS # ELASTICSEARCH SETTINGS
ELASTICSEARCH_DSL = { ELASTICSEARCH_DSL = {
'default': { 'default': {
@ -107,6 +100,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
} }
TESTING = sys.argv[1:2] == ['test'] TESTING = sys.argv[1:2] == ['test']
if TESTING: if TESTING:
ELASTICSEARCH_INDEX_NAMES = {} ELASTICSEARCH_INDEX_NAMES = {}

View File

@ -40,19 +40,6 @@ sentry_sdk.init(
integrations=[DjangoIntegration()] integrations=[DjangoIntegration()]
) )
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USERNAME'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'),
},
}
BROKER_URL = 'redis://redis:6379/1' BROKER_URL = 'redis://redis:6379/1'
CELERY_RESULT_BACKEND = BROKER_URL CELERY_RESULT_BACKEND = BROKER_URL

View File

@ -6,10 +6,12 @@ urlpatterns = [
path('account/', include('account.urls.back')), path('account/', include('account.urls.back')),
path('comment/', include('comment.urls.back')), path('comment/', include('comment.urls.back')),
path('establishments/', include('establishment.urls.back')), path('establishments/', include('establishment.urls.back')),
path('collections/', include('collection.urls.back')),
path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')),
path('location/', include('location.urls.back')), path('location/', include('location.urls.back')),
path('news/', include('news.urls.back')), path('news/', include('news.urls.back')),
path('review/', include('review.urls.back')), path('review/', include('review.urls.back')),
path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')),
path('products/', include(('product.urls.back', 'product'), namespace='product')),
] ]

View File

@ -43,7 +43,6 @@ django-storages==1.7.2
sorl-thumbnail==12.5.0 sorl-thumbnail==12.5.0
# mysqlclient==1.4.4
PyYAML==5.1.2 PyYAML==5.1.2
# temp solution # temp solution