Merge branch 'develop' into feature/fix-country-region-city-transfer

# Conflicts:
#	apps/collection/models.py
#	apps/location/serializers/common.py
#	apps/location/transfer_data.py
#	apps/transfer/management/commands/transfer.py
#	apps/transfer/serializers/location.py
#	project/settings/base.py
This commit is contained in:
littlewolf 2019-11-13 00:28:49 +03:00
commit cf226974c8
142 changed files with 4693 additions and 540 deletions

View File

@ -0,0 +1,40 @@
from django.core.management.base import BaseCommand
from django.db import connections
from django.db.models import Q, F, Value
from django.db.models.functions import ConcatPair
from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User
class Command(BaseCommand):
help = 'Add account from old db to new db'
def account_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
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
where a.email is not null and a.nickname!='admin'
''')
return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs):
objects = []
for a in self.account_sql():
users = User.objects.filter(Q(email=a.email) | Q(old_id=a.account_id))
if not users.exists():
objects.append(User(email=a.email,
unconfirmed_email=a.unconfirmed_email,
email_confirmed=a.confirmed_at,
old_id=a.account_id,
password=a.encrypted_password,
username=a.nickname
))
User.objects.bulk_create(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

@ -0,0 +1,31 @@
from django.core.management.base import BaseCommand
from django.db import connections
from django.db.models import Q
from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User
class Command(BaseCommand):
help = 'Update accounts image from old db to new db'
def account_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
url as image_url,
t.account_id
from
(
select a.account_id, a.attachment_file_name,
trim(CONCAT(u.url, a.attachment_suffix_url)) as url
from account_pictures a,
(select 'https://s3.eu-central-1.amazonaws.com/gm-test.com/media/' as url) u
) t
''')
return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs):
for a in self.account_sql():
users = User.objects.filter(old_id=a.account_id)
users.update(image_url= a.image_url)
self.stdout.write(self.style.WARNING(f'Update accounts image url.'))

View File

@ -0,0 +1,42 @@
from django.core.management.base import BaseCommand
from django.db import connections
from social_django.models import UserSocialAuth
from establishment.management.commands.add_position import namedtuplefetchall
from account.models import User
class Command(BaseCommand):
help = '''Add account social networks from old db to new db.
Run after add_account!!!'''
def social_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
DISTINCT
i.account_id, i.provider, i.uid
from
(
select a.email, a.id as account_id
from accounts as a
where a.email is not null
) t
join identities i on i.account_id = t.account_id
''')
return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs):
objects = []
for s in self.social_sql():
user = User.objects.filter(old_id=s.account_id)
if user.count() > 0:
social = UserSocialAuth.objects.filter(user=user.first(),
provider=s.provider,
uid=s.uid)
if social.count() == 0:
objects.append(UserSocialAuth(user=user.first(), provider=s.provider,
uid=s.uid)
)
UserSocialAuth.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Created social objects.'))

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-08 08:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0018_user_old_id'),
]
operations = [
migrations.AlterField(
model_name='user',
name='image_url',
field=models.URLField(blank=True, default=None, max_length=500, null=True, verbose_name='Image URL path'),
),
]

View File

@ -89,7 +89,8 @@ class UserQuerySet(models.QuerySet):
class User(AbstractUser):
"""Base user model."""
image_url = models.URLField(verbose_name=_('Image URL path'),
blank=True, null=True, default=None)
blank=True, null=True, default=None,
max_length=500)
cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'),
blank=True, null=True, default=None)
email = models.EmailField(_('email address'), unique=True,

View File

@ -7,11 +7,11 @@ from transfer.serializers.account import UserSerializer
from transfer.serializers.user_social_auth import UserSocialAuthSerializer
STOP_LIST = (
'cyril@tomatic.net',
'cyril2@tomatic.net',
'd.sadykova@id-east.ru',
'd.sadykova@octopod.ru',
'n.yurchenko@id-east.ru',
# 'cyril@tomatic.net',
# 'cyril2@tomatic.net',
# 'd.sadykova@id-east.ru',
# 'd.sadykova@octopod.ru',
# 'n.yurchenko@id-east.ru',
)
@ -20,7 +20,7 @@ def transfer_user():
# queryset = queryset.annotate(nickname=F('account__nickname'))
# 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)
@ -33,7 +33,7 @@ def transfer_user():
def transfer_identities():
queryset = Identities.objects.exclude(
Q(account_id__isnull=True) |
Q(account__confirmed_at__isnull=True) |
# Q(account__confirmed_at__isnull=True) |
Q(account__email__in=STOP_LIST)
).values_list(
'account_id',

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.4 on 2019-11-08 09:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0004_auto_20191025_0903'),
]
operations = [
migrations.RemoveField(
model_name='advertisement',
name='image',
),
migrations.AddField(
model_name='advertisement',
name='image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
),
migrations.AddField(
model_name='advertisement',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
]

View File

@ -5,16 +5,17 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from translation.models import Language
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin, URLImageMixin
class Advertisement(ImageMixin, ProjectBaseMixin, PlatformMixin):
class Advertisement(URLImageMixin, ProjectBaseMixin, PlatformMixin):
"""Advertisement model."""
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
url = models.URLField(verbose_name=_('Ad URL'))
width = models.PositiveIntegerField(verbose_name=_('Block width'))
height = models.PositiveIntegerField(verbose_name=_('Block height'))
width = models.PositiveIntegerField(verbose_name=_('Block width')) # 300
height = models.PositiveIntegerField(verbose_name=_('Block height')) # 250
block_level = models.CharField(verbose_name=_('Block level'), max_length=10, blank=True, null=True)
target_languages = models.ManyToManyField(Language)

View File

@ -14,7 +14,7 @@ class AdvertisementSerializer(serializers.ModelSerializer):
'id',
'uuid',
'url',
'image',
'image_url',
'width',
'height',
'block_level',

View File

@ -1,11 +1,11 @@
from pprint import pprint
from django.db.models import Value, IntegerField, F
from transfer.models import Ads
from transfer.serializers.advertisement import AdvertisementSerializer
def transfer_advertisement():
queryset = Ads.objects.filter(href__isnull=False)
queryset = Ads.objects.filter(href__isnull=False).values_list('id', 'href', 'attachment_suffix_url')
serialized_data = AdvertisementSerializer(data=list(queryset.values()), many=True)
@ -17,4 +17,4 @@ def transfer_advertisement():
data_types = {
"commercial": [transfer_advertisement]
}
}

View File

@ -38,13 +38,13 @@ class SignupSerializer(serializers.ModelSerializer):
valid = utils_methods.username_validator(username=value)
if not valid:
raise utils_exceptions.NotValidUsernameError()
if account_models.User.objects.filter(username__ilike=value).exists():
if account_models.User.objects.filter(username__iexact=value).exists():
raise serializers.ValidationError()
return value
def validate_email(self, value):
"""Validate email"""
if account_models.User.objects.filter(email__ilike=value).exists():
if account_models.User.objects.filter(email__iexact=value).exists():
raise serializers.ValidationError()
return value

View File

@ -75,7 +75,6 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
default=None, help_text='{"en-GB":"some text"}')
slug = models.SlugField(max_length=50, unique=True,
verbose_name=_('Collection slug'), editable=True, null=True)
old_id = models.IntegerField(null=True, blank=True)
objects = CollectionQuerySet.as_manager()

View File

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

View File

@ -0,0 +1,10 @@
"""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.CollectionBackOfficeSerializer
# 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.CollectionBackOfficeSerializer
# todo: conf. permissions by TT
permission_classes = (permissions.IsAuthenticated, )

View File

@ -6,4 +6,4 @@ from . import models
@admin.register(models.Comment)
class CommentModelAdmin(admin.ModelAdmin):
"""Model admin for model Comment"""
raw_id_fields = ('user', 'country')
raw_id_fields = ('user',)

View File

View File

@ -0,0 +1,29 @@
from django.core.management.base import BaseCommand
from account.models import User
from establishment.models import Establishment
from transfer.models import Comments
from comment.models import Comment as NewComment
class Command(BaseCommand):
help = 'Add publish values from old db to new db'
def handle(self, *args, **kwargs):
count = 0
establishments = Establishment.objects.all().values_list('old_id', flat=True)
users = User.objects.all().values_list('old_id', flat=True)
queryset = Comments.objects.filter(
establishment_id__in=list(establishments),
account_id__in=list(users),
)
for comment in queryset:
obj = NewComment.objects.filter(old_id=comment.id).first()
if obj:
count += 1
obj.created = comment.created_at
obj.modified = comment.updated_at
obj.is_publish = comment.state == 'published'
obj.save()
self.stdout.write(self.style.WARNING(f'Updated {count} objects.'))

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-11-12 13:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('comment', '0004_comment_old_id'),
]
operations = [
migrations.RemoveField(
model_name='comment',
name='country',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-12 15:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comment', '0005_remove_comment_country'),
]
operations = [
migrations.AddField(
model_name='comment',
name='is_publish',
field=models.BooleanField(default=False, verbose_name='Publish status'),
),
]

View File

@ -4,10 +4,9 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from account.models import User
from translation.models import Language
from utils.models import ProjectBaseMixin
from utils.querysets import ContentTypeQuerySetMixin
from translation.models import Language
from location.models import Country
class CommentQuerySet(ContentTypeQuerySetMixin):
@ -34,8 +33,8 @@ class Comment(ProjectBaseMixin):
text = models.TextField(verbose_name=_('Comment text'))
mark = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Mark'))
user = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('User'))
country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.SET_NULL, null=True)
old_id = models.IntegerField(null=True, blank=True, default=None)
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()

View File

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

View File

@ -1,7 +1,7 @@
"""Common serializers for app comment."""
from rest_framework import serializers
from comment import models
from comment.models import Comment
class CommentSerializer(serializers.ModelSerializer):
@ -14,7 +14,7 @@ class CommentSerializer(serializers.ModelSerializer):
class Meta:
"""Serializer for model Comment"""
model = models.Comment
model = Comment
fields = [
'id',
'user_id',
@ -25,3 +25,17 @@ class CommentSerializer(serializers.ModelSerializer):
'nickname',
'profile_pic'
]
class CommentRUDSerializer(CommentSerializer):
"""Retrieve/Update/Destroy comment serializer."""
class Meta(CommentSerializer.Meta):
"""Meta class."""
fields = [
'id',
'created',
'text',
'mark',
'nickname',
'profile_pic',
]

View File

@ -1,8 +1,7 @@
from pprint import pprint
from django.db.models import Q
from account.transfer_data import STOP_LIST
from account.models import User
from establishment.models import Establishment
from transfer.models import Comments
from transfer.serializers.comments import CommentSerializer
@ -10,19 +9,15 @@ from transfer.serializers.comments import CommentSerializer
def transfer_comments():
# В queryset исключены объекты по условию в связанные моделях
# см. transfer_establishment() и transfer_user()
queryset = Comments.objects.exclude(
Q(establishment__type='Wineyard') |
Q(establishment__location__timezone__isnull=True) |
Q(account__confirmed_at__isnull=True) |
Q(account__email__in=STOP_LIST)
).filter(
account__isnull=False,
mark__isnull=False
establishments = Establishment.objects.all().values_list('old_id', flat=True)
users = User.objects.all().values_list('old_id', flat=True)
queryset = Comments.objects.filter(
establishment_id__in=list(establishments),
account_id__in=list(users),
).only(
'id',
'comment',
'mark',
'locale',
'establishment_id',
'account_id',
)

View File

@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand
from timetable.models import Timetable
class Command(BaseCommand):
help = '''Add closed_at, opening_at Timetable fields'''
def handle(self, *args, **options):
for tt in Timetable.objects.all():
end = tt.dinner_end or tt.lunch_end
start = tt.lunch_start or tt.dinner_start
if end or start:
tt.closed_at = end
tt.opening_at = start
tt.save()

View File

@ -27,8 +27,7 @@ class Command(BaseCommand):
a.role,
a.start_date,
a.end_date,
trim(CONCAT(p.firstname, ' ', p.lastname, ' ',
p.email,'')
trim(CONCAT(p.firstname, ' ', p.lastname)
) as name
from affiliations as a
join profiles p on p.id = a.profile_id

View File

@ -27,10 +27,12 @@ class Command(BaseCommand):
name = currency_dict['name']
if name:
slug = slugify(name)
code = currency_dict['code']
currency, _ = Currency.objects.get_or_create(
slug=slug,
)
currency.name = {"en-GB": name}
currency.code = code
currency.sign = currency_dict["symbol"]
currency.save()
establishments = Establishment.objects.filter(address__city__country=country)

View File

@ -24,7 +24,6 @@ class Command(BaseCommand):
return namedtuplefetchall(cursor)
def handle(self, *args, **kwargs):
objects = []
for p in self.position_sql():
count = Position.objects.filter(name={"en-GB": p.position_name}).count()

View File

@ -0,0 +1,54 @@
"""Run recalculating toque number for establishments without indexing."""
from django.core.management.base import BaseCommand
from establishment.models import Establishment, RatingStrategy
from location.models import Country
class Command(BaseCommand):
help = 'Recalculation toque number for all establishments without indexing.'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def handle(self, *args, **options):
restaurants = Establishment.objects.restaurants()
# update establishments with a specific rating strategy
strategies = RatingStrategy.objects.with_country()
for strategy in strategies:
qs = restaurants.by_country(strategy.country). \
by_public_mark_range(strategy.public_mark_min_value,
strategy.public_mark_max_value)
qs.update(toque_number=strategy.toque_number)
countries = Country.objects.filter(pk__in=strategies.values('country'))
for country in countries:
rating_strategies = RatingStrategy.objects.by_country(country)
qs = Establishment.objects.restaurants(). \
by_country(country). \
exclude_public_mark_ranges(
ranges=[(strategy.public_mark_min_value,
strategy.public_mark_max_value) for
strategy in rating_strategies])
qs.update(toque_number=0)
# update establishments for other countries
strategy_for_other_countries = RatingStrategy.objects.with_country(False)
for strategy in strategy_for_other_countries:
qs = Establishment.objects.restaurants(). \
exclude_countries(countries). \
by_public_mark_range(strategy.public_mark_min_value,
strategy.public_mark_max_value)
qs.update(toque_number=strategy.toque_number)
# set null for others
qs = Establishment.objects.restaurants(). \
exclude_countries(countries). \
exclude_public_mark_ranges(
ranges=[(strategy.public_mark_min_value,
strategy.public_mark_max_value)
for strategy in
strategy_for_other_countries])
qs.update(toque_number=0)

View File

@ -0,0 +1,33 @@
from django.core.management.base import BaseCommand
from django.db import connections
from establishment.management.commands.add_position import namedtuplefetchall
from establishment.models import Employee
from tqdm import tqdm
class Command(BaseCommand):
help = 'Add employee from old db to new db.'
def employees_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select t.profile_id, t.name
from
(
select
DISTINCT
a.profile_id,
trim(CONCAT(p.firstname, ' ', p.lastname)
) as name
from affiliations as a
join profiles p on p.id = a.profile_id
) t
where t.name is not null
''')
return namedtuplefetchall(cursor)
def handle(self, *args, **options):
objects = []
for e in tqdm(self.employees_sql()):
empl = Employee.objects.filter(old_id=e.profile_id).update(name=e.name)
self.stdout.write(self.style.WARNING(f'Update employee name objects.'))

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.7 on 2019-11-12 10:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('establishment', '0058_merge_20191106_0921'),
]
operations = [
migrations.CreateModel(
name='EstablishmentNote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('old_id', models.PositiveIntegerField(blank=True, null=True)),
('text', models.TextField(verbose_name='text')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='establishment_notes', to='establishment.Establishment', verbose_name='establishment')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='establishment_notes', to=settings.AUTH_USER_MODEL, verbose_name='author')),
],
options={
'verbose_name': 'product notes',
'verbose_name_plural': 'product note',
},
),
]

View File

@ -1,6 +1,7 @@
"""Establishment models."""
from datetime import datetime
from functools import reduce
from operator import or_
import elasticsearch_dsl
from django.conf import settings
@ -111,6 +112,19 @@ class EstablishmentQuerySet(models.QuerySet):
return self.select_related('address', 'establishment_type').\
prefetch_related('tags')
def with_schedule(self):
"""Return qs with related schedule."""
return self.prefetch_related('schedule')
def with_currency_related(self):
"""Return qs with related """
return self.prefetch_related('currency')
def with_extended_address_related(self):
"""Return qs with deeply related address models."""
return self.select_related('address__city', 'address__city__region', 'address__city__region__country',
'address__city__country')
def with_extended_related(self):
return self.select_related('establishment_type').\
prefetch_related('establishment_subtypes', 'awards', 'schedule',
@ -146,6 +160,10 @@ class EstablishmentQuerySet(models.QuerySet):
ids = [result.meta.id for result in search]
return self.filter(id__in=ids)
def by_country(self, country):
"""Return establishments by country code"""
return self.filter(address__city__country=country)
def by_country_code(self, code):
"""Return establishments by country code"""
return self.filter(address__city__country__code=code)
@ -295,6 +313,20 @@ class EstablishmentQuerySet(models.QuerySet):
"""Return QuerySet with subtype by value."""
return self.filter(establishment_subtypes__index_name=value)
def by_public_mark_range(self, min_value, max_value):
"""Filter by public mark range."""
return self.filter(public_mark__gte=min_value, public_mark__lte=max_value)
def exclude_public_mark_ranges(self, ranges):
"""Exclude public mark ranges."""
return self.exclude(reduce(or_, [Q(public_mark__gte=r[0],
public_mark__lte=r[1])
for r in ranges]))
def exclude_countries(self, countries):
"""Exclude countries."""
return self.exclude(address__city__country__in=countries)
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
"""Establishment model."""
@ -461,6 +493,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
""" Used for indexing working by day """
return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon]
@property
def works_at_weekday(self):
""" Used for indexing by working whole day criteria """
return [ret.weekday for ret in self.schedule.all()]
@property
def works_evening(self):
""" Used for indexing working by day """
@ -520,6 +557,30 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
return self.products.wines()
class EstablishmentNoteQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentNote."""
class EstablishmentNote(ProjectBaseMixin):
"""Note model for Establishment entity."""
old_id = models.PositiveIntegerField(null=True, blank=True)
text = models.TextField(verbose_name=_('text'))
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
related_name='establishment_notes',
verbose_name=_('establishment'))
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
null=True,
related_name='establishment_notes',
verbose_name=_('author'))
objects = EstablishmentNoteQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name_plural = _('product note')
verbose_name = _('product notes')
class Position(BaseAttributes, TranslatedFieldsMixin):
"""Position model."""
@ -728,6 +789,10 @@ class RatingStrategyQuerySet(models.QuerySet):
"""Filter by country."""
return self.filter(country=country)
def with_country(self, switcher=True):
"""With country."""
return self.exclude(country__isnull=switcher)
def for_public_mark(self, public_mark):
"""Filter for value."""
return self.filter(public_mark_min_value__lte=public_mark,

View File

@ -45,6 +45,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
'is_publish',
'guestonline_id',
'lastable_id',
'tags',
'tz',
]
@ -78,7 +79,8 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
'image_url',
# TODO: check in admin filters
'is_publish',
'address'
'address',
'tags',
]

View File

@ -5,14 +5,14 @@ from rest_framework import serializers
from comment import models as comment_models
from comment.serializers import common as comment_serializers
from establishment import models
from location.serializers import AddressBaseSerializer
from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer
from main.serializers import AwardSerializer, CurrencySerializer
from review import models as review_models
from tag.serializers import TagBaseSerializer
from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions
from utils.serializers import (ProjectModelSerializer, TranslatedField,
FavoritesCreateSerializer)
from review.serializers import ReviewShortSerializer
class ContactPhonesSerializer(serializers.ModelSerializer):
@ -87,18 +87,6 @@ class MenuRUDSerializers(ProjectModelSerializer):
]
class ReviewSerializer(serializers.ModelSerializer):
"""Serializer for model Review."""
text_translated = serializers.CharField(read_only=True)
class Meta:
"""Meta class."""
model = review_models.Review
fields = (
'text_translated',
)
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
"""Serializer for EstablishmentType model."""
name_translated = TranslatedField()
@ -117,6 +105,7 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
'use_subtypes': {'write_only': True},
}
class EstablishmentTypeGeoSerializer(EstablishmentTypeBaseSerializer):
"""Serializer for EstablishmentType model w/ index_name."""
@ -180,6 +169,33 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name')
class EstablishmentShortSerializer(serializers.ModelSerializer):
"""Short serializer for establishment."""
city = CitySerializer(source='address.city', allow_null=True)
class Meta:
"""Meta class."""
model = models.Establishment
fields = [
'id',
'name',
'slug',
'city',
]
class EstablishmentProductSerializer(EstablishmentShortSerializer):
"""Serializer for displaying info about an establishment on product page."""
address = AddressBaseSerializer()
class Meta(EstablishmentShortSerializer.Meta):
"""Meta class."""
fields = EstablishmentShortSerializer.Meta.fields + [
'address',
]
class EstablishmentBaseSerializer(ProjectModelSerializer):
"""Base serializer for Establishment model."""
@ -209,6 +225,19 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
'currency'
]
class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
"""Establishment with city serializer."""
address = AddressDetailSerializer()
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
class Meta(EstablishmentBaseSerializer.Meta):
"""Meta class."""
fields = EstablishmentBaseSerializer.Meta.fields + [
'schedule',
]
class EstablishmentGeoSerializer(EstablishmentBaseSerializer):
"""Serializer for Geo view."""
@ -241,9 +270,11 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True)
emails = ContactEmailsSerializer(read_only=True, many=True)
review = ReviewSerializer(source='last_published_review', allow_null=True)
review = ReviewShortSerializer(source='last_published_review', allow_null=True)
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=True)
address = AddressDetailSerializer(read_only=True)
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
@ -315,21 +346,6 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
return super().create(validated_data)
class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer):
"""Retrieve/Update/Destroy comment serializer."""
class Meta:
"""Meta class."""
model = comment_models.Comment
fields = [
'id',
'created',
'text',
'mark',
'nickname',
'profile_pic',
]
class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
"""Serializer to favorite object w/ model Establishment."""

View File

@ -4,8 +4,9 @@ from django.db.models import Q, F
from establishment.models import Establishment
from location.models import Address
from transfer.models import Establishments, Dishes
from transfer.serializers.establishment import EstablishmentSerializer
from transfer.models import Establishments, Dishes, EstablishmentNotes
from transfer.serializers.establishment import EstablishmentSerializer, \
EstablishmentNoteSerializer
from transfer.serializers.plate import PlateSerializer
@ -15,9 +16,6 @@ def transfer_establishment():
# todo: filter(location__city__name__icontains='paris')
old_establishments = Establishments.objects.exclude(
id__in=list(Establishment.objects.all().values_list('old_id', flat=True))
).exclude(
Q(type='Wineyard') |
Q(location__timezone__isnull=True),
).prefetch_related(
'establishmentinfos_set',
'schedules_set',
@ -28,7 +26,7 @@ def transfer_establishment():
data = {
'old_id': item.id,
'name': item.name,
'name_translated': item.index_name,
'transliterated_name': item.index_name,
'slug': item.slug,
'type': item.type,
'phone': item.phone,
@ -47,7 +45,7 @@ def transfer_establishment():
if item.location:
data.update({
'location': item.location.id,
'tz': item.location.timezone,
'tz': item.location.timezone or 'UTC',
})
# Инфо
@ -127,10 +125,26 @@ def transfer_establishment_addresses():
establishment.save()
def transfer_establishment_note():
errors = []
queryset = EstablishmentNotes.objects.exclude(text__exact='') \
.exclude(text__isnull=True) \
.exclude(establishment_id__isnull=True)
serialized_data = EstablishmentNoteSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_establishment_note errors: {errors}")
data_types = {
"establishment": [
transfer_establishment,
],
"establishment_note": [transfer_establishment_note],
"location_establishment": [
transfer_establishment_addresses
],

View File

@ -10,6 +10,7 @@ from establishment import models, serializers
from main import methods
from utils.pagination import EstablishmentPortionPagination
from utils.permissions import IsCountryAdmin
from comment.serializers import CommentRUDSerializer
class EstablishmentMixinView:
@ -31,7 +32,11 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
"""Resource for getting a list of establishments."""
filter_class = filters.EstablishmentFilter
serializer_class = serializers.EstablishmentBaseSerializer
serializer_class = serializers.EstablishmentListRetrieveSerializer
def get_queryset(self):
return super().get_queryset().with_schedule()\
.with_extended_address_related().with_currency_related()
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
@ -109,7 +114,7 @@ class EstablishmentCommentListView(generics.ListAPIView):
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
"""View for retrieve/update/destroy establishment comment."""
serializer_class = serializers.EstablishmentCommentRUDSerializer
serializer_class = CommentRUDSerializer
queryset = models.Establishment.objects.all()
def get_object(self):

View File

@ -10,4 +10,6 @@ urlpatterns = [
name='establishment-list'),
path('products/', views.FavoritesProductListView.as_view(),
name='product-list'),
path('news/', views.FavoritesNewsListView.as_view(),
name='news-list'),
]

View File

@ -3,6 +3,9 @@ from rest_framework import generics
from establishment.models import Establishment
from establishment.filters import EstablishmentFilter
from establishment.serializers import EstablishmentBaseSerializer
from news.filters import NewsListFilterSet
from news.models import News
from news.serializers import NewsBaseSerializer
from product.models import Product
from product.serializers import ProductBaseSerializer
from product.filters import ProductFilterSet
@ -25,8 +28,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
def get_queryset(self):
"""Override get_queryset method"""
return Establishment.objects.filter(favorites__user=self.request.user)\
.order_by('-favorites')
return Establishment.objects.filter(favorites__user=self.request.user) \
.order_by('-favorites')
class FavoritesProductListView(generics.ListAPIView):
@ -37,5 +40,16 @@ class FavoritesProductListView(generics.ListAPIView):
def get_queryset(self):
"""Override get_queryset method"""
return Product.objects.filter(favorites__user=self.request.user)\
.order_by('-favorites')
return Product.objects.filter(favorites__user=self.request.user) \
.order_by('-favorites')
class FavoritesNewsListView(generics.ListAPIView):
"""List views for news in favorites."""
serializer_class = NewsBaseSerializer
filter_class = NewsListFilterSet
def get_queryset(self):
"""Override get_queryset method"""
return News.objects.filter(favorites__user=self.request.user).order_by('-favorites')

View File

@ -19,22 +19,6 @@ class CityAdmin(admin.ModelAdmin):
"""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)
class AddressAdmin(admin.OSMGeoAdmin):
"""Address admin."""

View File

@ -21,5 +21,5 @@ class Migration(migrations.Migration):
]
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

@ -0,0 +1,17 @@
# Generated by Django 2.2.4 on 2019-11-12 01:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('location', '0022_auto_20191111_0731'),
]
operations = [
migrations.AlterModelOptions(
name='winesubregion',
options={'verbose_name': 'wine sub region', 'verbose_name_plural': 'wine sub regions'},
),
]

View File

@ -200,14 +200,19 @@ class WineRegionQuerySet(models.QuerySet):
"""Wine region queryset."""
class WineRegion(TranslatedFieldsMixin, models.Model):
class WineRegion(models.Model):
"""Wine region model."""
STR_FIELD_NAME = 'name'
name = TJSONField(verbose_name=_('Name'),
help_text='{"en-GB":"some text"}')
name = models.CharField(_('name'), max_length=255)
country = models.ForeignKey(Country, on_delete=models.PROTECT,
blank=True, null=True, default=None,
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()
@ -217,26 +222,47 @@ class WineRegion(TranslatedFieldsMixin, models.Model):
verbose_name = _('wine region')
class WineAppellationQuerySet(models.QuerySet):
"""Wine appellation queryset."""
class WineSubRegionQuerySet(models.QuerySet):
"""Wine sub region QuerySet."""
class WineAppellation(TranslatedFieldsMixin, models.Model):
"""Wine appellation model."""
STR_FIELD_NAME = 'name'
name = TJSONField(verbose_name=_('Name'),
help_text='{"en-GB":"some text"}')
class WineSubRegion(models.Model):
"""Wine sub region model."""
name = models.CharField(_('name'), max_length=255)
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
related_name='appellations',
verbose_name=_('wine region'))
verbose_name=_('wine sub region'))
old_id = models.PositiveIntegerField(_('old id'), default=None,
blank=True, null=True)
objects = WineAppellationQuerySet.as_manager()
objects = WineSubRegionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name_plural = _('wine appellations')
verbose_name = _('wine appellation')
verbose_name_plural = _('wine sub regions')
verbose_name = _('wine sub 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

View File

@ -10,8 +10,7 @@ from gallery.models import Image
class CountrySerializer(serializers.ModelSerializer):
"""Country serializer."""
name_trans = serializers.CharField(source='name_translated', read_only=True,
allow_null=True)
name_translated = TranslatedField()
class Meta:
model = models.Country
@ -19,7 +18,7 @@ class CountrySerializer(serializers.ModelSerializer):
'id',
'code',
'svg_image',
'name_trans',
'name_translated',
]
@ -250,22 +249,8 @@ 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):
"""Wine region serializer."""
name_translated = TranslatedField()
country = CountrySerializer()
class Meta:
@ -273,6 +258,18 @@ class WineRegionBaseSerializer(serializers.ModelSerializer):
model = models.WineRegion
fields = [
'id',
'name_translated',
'name',
'country',
]
class WineSubRegionBaseSerializer(serializers.ModelSerializer):
"""Wine sub region serializer."""
class Meta:
"""Meta class."""
model = models.WineSubRegion
fields = [
'id',
'name',
]

View File

@ -1,7 +1,6 @@
from transfer.serializers.location import CountrySerializer, RegionSerializer, \
CitySerializer, AddressSerializer, CityMapSerializer, CityGallerySerializer, \
Country
from transfer.models import Cities, Locations, CityPhotos
from transfer.serializers import location as location_serializers
from transfer import models as transfer_models
from location.models import Country
from pprint import pprint
from requests import get
@ -9,7 +8,7 @@ import json
def transfer_countries():
queryset = Cities.objects.raw("""
queryset = transfer_models.Cities.objects.raw("""
SELECT cities.id, cities.country_code_2
FROM cities
WHERE country_code_2 IS NOT NULL AND
@ -19,7 +18,7 @@ def transfer_countries():
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():
serialized_data.save()
else:
@ -27,7 +26,7 @@ def transfer_countries():
def transfer_regions():
regions_without_subregion_queryset = Cities.objects.raw("""
regions_without_subregion_queryset = transfer_models.Cities.objects.raw("""
SELECT cities.id,
cities.region_code,
cities.country_code_2,
@ -48,13 +47,14 @@ def transfer_regions():
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():
serialized_without_subregion.save()
else:
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
cities.id,
cities.region_code,
@ -85,7 +85,8 @@ def transfer_regions():
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():
serialized_with_subregion.save()
else:
@ -93,7 +94,7 @@ def transfer_regions():
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
FROM cities WHERE
region_code IS NOT NULL AND
@ -104,7 +105,7 @@ def transfer_cities():
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():
serialized_data.save()
else:
@ -112,7 +113,7 @@ def transfer_cities():
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
FROM locations WHERE
locations.address != "" AND
@ -127,13 +128,46 @@ def transfer_addresses():
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():
serialized_data.save()
else:
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():
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"
@ -203,10 +237,13 @@ def get_ruby_socket(region_params):
data_types = {
"dictionaries": [
transfer_countries,
transfer_regions,
transfer_cities,
transfer_addresses
# transfer_countries,
# transfer_regions,
# transfer_cities,
# transfer_addresses,
transfer_wine_region,
transfer_wine_sub_region,
transfer_wine_village,
],
"update_country_flag": [
update_flags

View File

@ -38,6 +38,7 @@ class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
# Region
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
"""Create view for model Region"""
pagination_class = None
serializer_class = serializers.RegionSerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]

View File

@ -1,11 +1,13 @@
"""Main app methods."""
import logging
from typing import Tuple, Optional
from django.conf import settings
from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception
from geoip2.models import City
from typing import Union, Tuple, Optional
import pycountry
from main import models
logger = logging.getLogger(__name__)
@ -83,3 +85,16 @@ def determine_user_city(ip_addr: str) -> Optional[City]:
except Exception as ex:
logger.warning(f'GEOIP Base exception: {ex}')
return None
def determine_subdivision(
country_alpha2_code: str,
subdivision_code: Union[str, int]
) -> pycountry.Subdivision:
"""
:param country_alpha2_code: country code according to ISO 3166-1 alpha-2 standard
:param subdivision_code: subdivision code according to ISO 3166-2 without country code prefix
:return: subdivision
"""
iso3166_2_subdivision_code = f'{country_alpha2_code}-{subdivision_code}'
return pycountry.subdivisions.get(code=iso3166_2_subdivision_code)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-12 01:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0033_auto_20191106_0744'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='slug',
field=models.SlugField(max_length=255, unique=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-07 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0033_auto_20191106_0744'),
]
operations = [
migrations.AddField(
model_name='sitefeature',
name='nested',
field=models.ManyToManyField(to='main.SiteFeature'),
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.7 on 2019-11-12 12:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0034_sitefeature_nested'),
('main', '0034_auto_20191112_0104'),
]
operations = [
]

View File

@ -25,7 +25,7 @@ class Currency(TranslatedFieldsMixin, models.Model):
_('name'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}')
sign = models.CharField(_('sign'), max_length=255)
slug = models.SlugField(max_length=5, unique=True)
slug = models.SlugField(max_length=255, unique=True)
code = models.CharField(max_length=5, unique=True, null=True, default=None)
class Meta:
@ -151,6 +151,7 @@ class SiteFeature(ProjectBaseMixin):
feature = models.ForeignKey(Feature, on_delete=models.PROTECT)
published = models.BooleanField(default=False, verbose_name=_('Published'))
main = models.BooleanField(default=False, verbose_name=_('Main'))
nested = models.ManyToManyField('self', symmetrical=False)
objects = SiteFeatureQuerySet.as_manager()

View File

@ -4,7 +4,7 @@ from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer
from main import models
from utils.serializers import ProjectModelSerializer, TranslatedField
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
class FeatureSerializer(serializers.ModelSerializer):
@ -27,6 +27,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
priority = serializers.IntegerField(source='feature.priority')
route = serializers.CharField(source='feature.route.page_name')
source = serializers.IntegerField(source='feature.source')
nested = RecursiveFieldSerializer(many=True, allow_null=True)
class Meta:
"""Meta class."""
@ -36,8 +37,9 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
'slug',
'priority',
'route',
'source'
)
'source',
'nested',
)
class CurrencySerializer(ProjectModelSerializer):

View File

@ -1,14 +1,21 @@
"""Filters from application News"""
import django_filters
from django_filters import rest_framework as filters
from django.utils.translation import gettext_lazy as _
from news import models
class NewsListFilterSet(django_filters.FilterSet):
class NewsListFilterSet(filters.FilterSet):
"""FilterSet for News list"""
is_highlighted = django_filters.BooleanFilter()
title = django_filters.CharFilter(method='by_title')
is_highlighted = filters.BooleanFilter()
title = filters.CharFilter(method='by_title')
tag_group = filters.ChoiceFilter(
choices=(
(models.News.RECIPES_TAG_VALUE, _('Recipes')),
),
method='by_tag_group'
)
class Meta:
"""Meta class"""
@ -16,8 +23,14 @@ class NewsListFilterSet(django_filters.FilterSet):
fields = (
'title',
'is_highlighted',
'tag_group',
)
def by_tag_group(self, queryset, name, value):
if value == models.News.RECIPES_TAG_VALUE:
queryset = queryset.recipe_news()
return queryset
def by_title(self, queryset, name, value):
"""Crappy search by title according to locale"""
if value:

View File

@ -22,7 +22,8 @@ class Command(BaseCommand):
# Make Tag
new_tag, created = Tag.objects.get_or_create(category=tag_cat, value=old_tag.value)
if created:
new_tag.label = {'en-GB': new_tag.value}
text_value = ' '.join(new_tag.value.split('_'))
new_tag.label = {'en-GB': text_value}
new_tag.save()
for id in old_id_list:
if isinstance(id, int):

View File

@ -53,6 +53,10 @@ class NewsQuerySet(TranslationQuerysetMixin):
"""Filter collection by country code."""
return self.filter(country__code=code)
def recipe_news(self):
"""Returns news with tag 'cook' qs."""
return self.filter(tags__value=News.RECIPES_TAG_VALUE)
def published(self):
"""Return only published news"""
now = timezone.now()
@ -127,6 +131,8 @@ class News(BaseAttributes, TranslatedFieldsMixin):
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
)
RECIPES_TAG_VALUE = 'cook'
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
verbose_name=_('news type'))
@ -159,7 +165,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
verbose_name=_('Tags'))
gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
ratings = generic.GenericRelation(Rating)
favorites = generic.GenericRelation(to='favorites.Favorites')
agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
on_delete=models.SET_NULL,
verbose_name=_('agenda'))

View File

@ -8,7 +8,8 @@ from location import models as location_models
from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
from news import models
from tag.serializers import TagBaseSerializer
from utils.serializers import TranslatedField, ProjectModelSerializer
from utils import exceptions as utils_exceptions
from utils.serializers import TranslatedField, ProjectModelSerializer, FavoritesCreateSerializer
class AgendaSerializer(ProjectModelSerializer):
@ -293,3 +294,33 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
attrs['image'] = image
return attrs
class NewsFavoritesCreateSerializer(FavoritesCreateSerializer):
"""Serializer to favorite object w/ model News."""
def validate(self, attrs):
"""Overridden validate method"""
# Check establishment object
news_qs = models.News.objects.filter(slug=self.slug)
# Check establishment obj by slug from lookup_kwarg
if not news_qs.exists():
raise serializers.ValidationError({'detail': _('Object not found.')})
else:
news = news_qs.first()
# Check existence in favorites
if news.favorites.filter(user=self.user).exists():
raise utils_exceptions.FavoritesError()
attrs['news'] = news
return attrs
def create(self, validated_data, *args, **kwargs):
"""Overridden create method"""
validated_data.update({
'user': self.user,
'content_object': validated_data.pop('news')
})
return super().create(validated_data)

View File

@ -9,6 +9,8 @@ from news.models import NewsType, News
from account.models import User, Role, UserRole
from translation.models import Language
from location.models import Country
# Create your tests here.
@ -67,7 +69,7 @@ class NewsTestCase(BaseTestCase):
super().setUp()
def test_news_post(self):
test_news ={
test_news = {
"title": {"en-GB": "Test news POST"},
"news_type_id": self.test_news_type.id,
"description": {"en-GB": "Description test news"},
@ -108,9 +110,21 @@ class NewsTestCase(BaseTestCase):
'description': {"en-GB": "Description test news!"},
'slug': self.test_news.slug,
'start': self.test_news.start,
'news_type_id':self.test_news.news_type_id,
'news_type_id': self.test_news.news_type_id,
'country_id': self.country_ru.id
}
response = self.client.put(url, data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_web_favorite_create_delete(self):
data = {
"user": self.user.id,
"object_id": self.test_news.id
}
response = self.client.post(f'/api/web/news/slug/{self.test_news.slug}/favorites/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.delete(f'/api/web/news/slug/{self.test_news.slug}/favorites/', format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

View File

@ -24,7 +24,7 @@ class GroupConcat(Aggregate):
def transfer_news():
news_type, _ = NewsType.objects.get_or_create(name='News')
tag_cat, _ = TagCategory.objects.get_or_create(index_name='tag', public=True)
tag_cat, _ = TagCategory.objects.get_or_create(index_name='tag')
news_type.tag_categories.add(tag_cat)
news_type.save()

9
apps/news/urls/common.py Normal file
View File

@ -0,0 +1,9 @@
from django.urls import path
from news import views
common_urlpatterns = [
path('', views.NewsListView.as_view(), name='list'),
path('types/', views.NewsTypeListView.as_view(), name='type'),
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(), name='create-destroy-favorites')
]

8
apps/news/urls/mobile.py Normal file
View File

@ -0,0 +1,8 @@
"""News app urlconf."""
from news.urls.common import common_urlpatterns
app_name = 'news'
urlpatterns = []
urlpatterns.extend(common_urlpatterns)

View File

@ -1,11 +1,8 @@
"""News app urlconf."""
from django.urls import path
from news import views
from news.urls.common import common_urlpatterns
app_name = 'news'
urlpatterns = [
path('', views.NewsListView.as_view(), name='list'),
path('types/', views.NewsTypeListView.as_view(), name='type'),
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
]
urlpatterns = []
urlpatterns.extend(common_urlpatterns)

View File

@ -20,8 +20,8 @@ class NewsMixinView:
def get_queryset(self, *args, **kwargs):
"""Override get_queryset method."""
qs = models.News.objects.published() \
.with_base_related() \
.order_by('-is_highlighted', '-created')
.with_base_related() \
.order_by('-is_highlighted', '-created')
country_code = self.request.country_code
if country_code:
qs = qs.by_country_code(country_code)
@ -51,7 +51,7 @@ class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
pagination_class = None
permission_classes = (permissions.AllowAny, )
permission_classes = (permissions.AllowAny,)
queryset = models.NewsType.objects.all()
serializer_class = serializers.NewsTypeSerializer
@ -70,7 +70,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
serializer_class = serializers.NewsBackOfficeBaseSerializer
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
permission_classes = [IsCountryAdmin|IsContentPageManager]
permission_classes = [IsCountryAdmin | IsContentPageManager]
def get_serializer_class(self):
"""Override serializer class."""
@ -146,9 +146,26 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
"""Resource for detailed information about news for back-office users."""
serializer_class = serializers.NewsBackOfficeDetailSerializer
permission_classes = [IsCountryAdmin|IsContentPageManager]
permission_classes = [IsCountryAdmin | IsContentPageManager]
def get(self, request, pk, *args, **kwargs):
add_rating(remote_addr=request.META.get('REMOTE_ADDR'),
pk=pk, model='news', app_label='news')
return self.retrieve(request, *args, **kwargs)
class NewsFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView):
"""View for create/destroy news from favorites."""
serializer_class = serializers.NewsFavoritesCreateSerializer
lookup_field = 'slug'
def get_object(self):
"""
Returns the object the view is displaying.
"""
news = get_object_or_404(models.News, slug=self.kwargs['slug'])
favorites = get_object_or_404(news.favorites.filter(user=self.request.user))
# May raise a permission denied
self.check_object_permissions(self.request, favorites)
return favorites

View File

@ -1,12 +1,23 @@
"""Product admin conf."""
from django.contrib import admin
from utils.admin import BaseModelAdminMixin
from .models import Product, ProductType, ProductSubType
from .models import Product, ProductType, ProductSubType, ProductGallery, Unit
@admin.register(Product)
class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""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(ProductGallery)
class ProductGalleryAdmin(admin.ModelAdmin):
"""Admin page for model ProductGallery."""
raw_id_fields = ('product', 'image', )
@admin.register(ProductType)
@ -17,3 +28,8 @@ class ProductTypeAdmin(admin.ModelAdmin):
@admin.register(ProductSubType)
class ProductSubTypeAdmin(admin.ModelAdmin):
"""Admin page for model ProductSubType."""
@admin.register(Unit)
class UnitAdmin(admin.ModelAdmin):
"""Admin page for model Unit."""

View File

@ -0,0 +1,21 @@
from django.core.management.base import BaseCommand
from transfer.models import Assemblages
from transfer.serializers.product import AssemblageTagSerializer
class Command(BaseCommand):
help = 'Add assemblage tag to product'
def handle(self, *args, **kwarg):
errors = []
legacy_products = Assemblages.objects.filter(product_id__isnull=False)
serialized_data = AssemblageTagSerializer(
data=list(legacy_products.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
self.stdout.write(self.style.WARNING(f'Error count: {len(errors)}\nErrors: {errors}'))

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,116 @@
from django.core.management.base import BaseCommand
from django.db import connections
from establishment.management.commands.add_position import namedtuplefetchall
from tag.models import Tag, TagCategory
from product.models import Product
from tqdm import tqdm
from django.db.models.functions import Lower
class Command(BaseCommand):
help = '''Add add product tags networks from old db to new db.
Run after add_product!!!'''
def category_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
DISTINCT
trim(CONVERT(v.key_name USING utf8)) as category,
trim(CONVERT(v.value_type USING utf8)) as value_type
FROM product_metadata m
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
''')
return namedtuplefetchall(cursor)
def add_category_tag(self):
objects = []
for c in tqdm(self.category_sql(), desc='Add category tags'):
categories = TagCategory.objects.filter(index_name=c.category,
value_type=c.value_type
)
if not categories.exists():
objects.append(
TagCategory(label={"en-GB": c.category},
value_type=c.value_type,
index_name=c.category
)
)
TagCategory.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
def tag_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
DISTINCT
trim(CONVERT(m.value USING utf8)) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m
join product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
''')
return namedtuplefetchall(cursor)
def add_tag(self):
objects = []
for t in tqdm(self.tag_sql(), desc='Add tags'):
category = TagCategory.objects.get(index_name=t.tag_category)
tag = Tag.objects.filter(
category=category,
value=t.tag_value
)
if not tag.exists():
objects.append(Tag(label={"en-GB": t.tag_value},
category=category,
value=t.tag_value
))
Tag.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
def product_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select
DISTINCT
m.product_id,
lower(trim(CONVERT(m.value USING utf8))) as tag_value,
trim(CONVERT(v.key_name USING utf8)) as tag_category
FROM product_metadata m
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
''')
return namedtuplefetchall(cursor)
def add_product_tag(self):
objects = []
for t in tqdm(self.product_sql(), desc='Add product tag'):
category = TagCategory.objects.get(index_name=t.tag_category)
tag = Tag.objects.annotate(lower_value=Lower('value'))
tag.filter(lower_value=t.tag_value, category=category)
products = Product.objects.filter(old_id=t.product_id)
if products.exists():
products.tags.add(tag)
products.save()
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
def check_tag(self):
tags = Tag.objects.filter(value__contains='_').all()
for tag in tqdm(tags, desc='Check label for tag'):
new_label = {}
for k, v in tag.label.items():
if isinstance(v, str) and '_' in v:
sp = v.split('_')
v = ' '.join([sp[0].capitalize()] + sp[1:])
new_label[k] = v
tag.label = new_label
tag.save()
def handle(self, *args, **kwargs):
self.add_category_tag()
self.add_tag()
self.add_product_tag()
self.check_tag()

View File

@ -0,0 +1,21 @@
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(**{
'index_name': product_type
})
subtype.name = {settings.FALLBACK_LOCALE: product_type}
subtype.save()
if created:
created += 1
self.stdout.write(self.style.WARNING(f'Created: {create_counter}'))

View File

@ -0,0 +1,21 @@
from django.core.management.base import BaseCommand
from transfer import models as transfer_models
from transfer.serializers.product import WineColorTagSerializer
class Command(BaseCommand):
help = 'Fix wine color tag'
def handle(self, *args, **kwarg):
errors = []
legacy_products = transfer_models.Products.objects.filter(wine_color__isnull=False)
serialized_data = WineColorTagSerializer(
data=list(legacy_products.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
self.stdout.write(self.style.WARNING(f'Error count: {len(errors)}\nErrors: {errors}'))

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

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-12 08:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0010_auto_20191111_1227'),
]
operations = [
migrations.AddField(
model_name='product',
name='win_import_id',
field=models.CharField(blank=True, default=None, help_text='attribute from legacy db', max_length=255, null=True),
),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 2.2.7 on 2019-11-12 10:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('product', '0011_product_win_import_id'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='win_import_id',
),
migrations.CreateModel(
name='ProductNote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('old_id', models.PositiveIntegerField(blank=True, null=True)),
('text', models.TextField(verbose_name='text')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='product_notes', to='product.Product', verbose_name='product')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='product_notes', to=settings.AUTH_USER_MODEL, verbose_name='author')),
],
options={
'verbose_name': 'product notes',
'verbose_name_plural': 'product note',
},
),
]

View File

@ -1,9 +1,11 @@
"""Product app models."""
from django.db import models
from django.contrib.contenttypes import fields as generic
from django.contrib.gis.db import models as gis_models
from django.core.exceptions import ValidationError
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.validators import MaxValueValidator, MinValueValidator
from utils.models import (BaseAttributes, ProjectBaseMixin,
TranslatedFieldsMixin, TJSONField)
@ -17,11 +19,15 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
FOOD = 'food'
WINE = 'wine'
LIQUOR = 'liquor'
SOUVENIR = 'souvenir'
BOOK = 'book'
INDEX_NAME_TYPES = (
(FOOD, _('Food')),
(WINE, _('Wine')),
(LIQUOR, _('Liquor')),
(SOUVENIR, _('Souvenir')),
(BOOK, _('Book')),
)
name = TJSONField(blank=True, null=True, default=None,
@ -30,6 +36,9 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
unique=True, db_index=True,
verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
tag_categories = models.ManyToManyField('tag.TagCategory',
related_name='product_types',
verbose_name=_('Tag'))
class Meta:
"""Meta class."""
@ -45,29 +54,13 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
# INDEX NAME CHOICES
RUM = 'rum'
PLATE = 'plate'
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 = (
(RUM, _('Rum')),
(PLATE, _('Plate')),
(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,
@ -99,7 +92,7 @@ class ProductQuerySet(models.QuerySet):
def with_base_related(self):
return self.select_related('product_type', 'establishment') \
.prefetch_related('product_type__subtypes', 'country')
.prefetch_related('product_type__subtypes')
def common(self):
return self.filter(category=self.model.COMMON)
@ -122,7 +115,8 @@ class ProductQuerySet(models.QuerySet):
class Product(TranslatedFieldsMixin, BaseAttributes):
"""Product models."""
STR_FIELD_NAME = 'name'
EARLIEST_VINTAGE_YEAR = 1700
LATEST_VINTAGE_YEAR = 2100
COMMON = 0
ONLINE = 1
@ -132,38 +126,76 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
(ONLINE, _('Online')),
)
PUBLISHED = 0
OUT_OF_PRODUCTION = 1
WAITING = 2
STATE_CHOICES = (
(PUBLISHED, _('Published')),
(OUT_OF_PRODUCTION, _('Out_of_production')),
(WAITING, _('Waiting')),
)
category = models.PositiveIntegerField(choices=CATEGORY_CHOICES,
default=COMMON)
name = TJSONField(_('Name'), null=True, blank=True, default=None,
help_text='{"en-GB":"some text"}')
description = TJSONField(_('Description'), null=True, blank=True,
name = models.CharField(max_length=255,
default=None, null=True,
verbose_name=_('name'))
description = TJSONField(_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}')
#TODO set null=False
characteristics = JSONField(_('Characteristics'), null=True)
country = models.ManyToManyField('location.Country',
verbose_name=_('Country'))
available = models.BooleanField(_('Available'), default=True)
available = models.BooleanField(_('available'), default=True)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
null=True,
related_name='products', verbose_name=_('Type'))
subtypes = models.ManyToManyField(ProductSubType, blank=True,
related_name='products',
verbose_name=_('Subtypes'))
establishment = models.ForeignKey('establishment.Establishment',
on_delete=models.PROTECT,
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.PROTECT,
blank=True, null=True,
related_name='products',
verbose_name=_('establishment'))
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('public mark'),)
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
related_name='wines',
blank=True, null=True,
blank=True, null=True, default=None,
verbose_name=_('wine region'))
wine_appellation = models.ForeignKey('location.WineAppellation', on_delete=models.PROTECT,
blank=True, null=True,
verbose_name=_('wine appellation'))
wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.PROTECT,
related_name='wines',
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,
verbose_name=_('Establishment slug'))
verbose_name=_('Slug'))
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')
comments = generic.GenericRelation(to='comment.Comment')
awards = generic.GenericRelation(to='main.Award', related_query_name='product')
objects = ProductManager.from_queryset(ProductQuerySet)()
@ -173,15 +205,60 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
verbose_name = _('Product')
verbose_name_plural = _('Products')
def __str__(self):
"""Override str dunder method."""
return f'{self.name}'
def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude)
if self.product_type.index_name == ProductType.WINE and not self.wine_region:
raise ValidationError(_('wine_region field must be specified.'))
if not self.product_type.index_name == ProductType.WINE and self.wine_region:
raise ValidationError(_('wine_region field must not be specified.'))
if (self.wine_region and self.wine_appellation) and \
self.wine_appellation not in self.wine_region.appellations.all():
raise ValidationError(_('Wine appellation not exists in wine region.'))
# if (self.wine_region and self.wine_appellation) and \
# self.wine_appellation not in self.wine_region.appellations.all():
# raise ValidationError(_('Wine appellation not exists in wine region.'))
@property
def product_type_translated_name(self):
"""Get translated name of product type."""
return self.product_type.name_translated if self.product_type else None
@property
def last_published_review(self):
"""Return last published review"""
return self.reviews.published().order_by('-published_at').first()
@property
def sugar_contents(self):
return self.tags.filter(category__index_name='sugar-content')
@property
def wine_colors(self):
return self.tags.filter(category__index_name='wine-color')
@property
def bottles_produced(self):
return self.tags.filter(category__index_name='bottles-produced')
@property
def main_image(self):
qs = ProductGallery.objects.filter(product=self, is_main=True)
if qs.exists():
return qs.first().image
@property
def main_image_url(self):
return self.main_image.image if self.main_image else None
@property
def preview_main_image_url(self):
return self.main_image.get_image_url('product_preview') if self.main_image else None
@property
def related_tags(self):
return self.tags.exclude(
category__index_name__in=['sugar-content', 'wine-color', 'bottles-produced'])
class OnlineProductManager(ProductManager):
@ -203,3 +280,153 @@ class OnlineProduct(Product):
proxy = True
verbose_name = _('Online product')
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')
class ProductNoteQuerySet(models.QuerySet):
"""QuerySet for model ProductNote."""
class ProductNote(ProjectBaseMixin):
"""Note model for Product entity."""
old_id = models.PositiveIntegerField(null=True, blank=True)
text = models.TextField(verbose_name=_('text'))
product = models.ForeignKey(Product, on_delete=models.PROTECT,
related_name='product_notes',
verbose_name=_('product'))
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
null=True,
related_name='product_notes',
verbose_name=_('author'))
objects = ProductNoteQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name_plural = _('product note')
verbose_name = _('product notes')

View File

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

View File

@ -0,0 +1,70 @@
"""Product app back-office serializers."""
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from product import models
from product.serializers import ProductDetailSerializer
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
class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
class Meta(ProductDetailSerializer.Meta):
fields = ProductDetailSerializer.Meta.fields + [
'description',
'available',
'product_type',
'establishment',
'wine_region',
'wine_sub_region',
'wine_village',
'state',
]
extra_kwargs = {
'description': {'write_only': True},
'available': {'write_only': True},
'product_type': {'write_only': True},
'establishment': {'write_only': True},
'wine_region': {'write_only': True},
'wine_sub_region': {'write_only': True},
'wine_village': {'write_only': True},
'state': {'write_only': True},
}

View File

@ -1,11 +1,18 @@
"""Product app serializers."""
from rest_framework import serializers
from utils.serializers import TranslatedField, FavoritesCreateSerializer
from product.models import Product, ProductSubType, ProductType
from utils import exceptions as utils_exceptions
from django.utils.translation import gettext_lazy as _
from location.serializers import (WineRegionBaseSerializer, WineAppellationBaseSerializer,
CountrySimpleSerializer)
from rest_framework import serializers
from comment.models import Comment
from comment.serializers import CommentSerializer
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer
from gallery.models import Image
from product import models
from review.serializers import ReviewShortSerializer
from utils import exceptions as utils_exceptions
from utils.serializers import TranslatedField, FavoritesCreateSerializer
from main.serializers import AwardSerializer
from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer
from tag.serializers import TagBaseSerializer
class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
@ -14,7 +21,7 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
index_name_display = serializers.CharField(source='get_index_name_display')
class Meta:
model = ProductSubType
model = models.ProductSubType
fields = [
'id',
'name_translated',
@ -28,7 +35,7 @@ class ProductTypeBaseSerializer(serializers.ModelSerializer):
index_name_display = serializers.CharField(source='get_index_name_display')
class Meta:
model = ProductType
model = models.ProductType
fields = [
'id',
'name_translated',
@ -36,33 +43,89 @@ class ProductTypeBaseSerializer(serializers.ModelSerializer):
]
class ProductBaseSerializer(serializers.ModelSerializer):
"""Product base serializer."""
name_translated = TranslatedField()
description_translated = TranslatedField()
category_display = serializers.CharField(source='get_category_display')
product_type = ProductTypeBaseSerializer()
subtypes = ProductSubTypeBaseSerializer(many=True)
wine_region = WineRegionBaseSerializer(allow_null=True)
wine_appellation = WineAppellationBaseSerializer(allow_null=True)
available_countries = CountrySimpleSerializer(source='country', many=True)
class ProductClassificationBaseSerializer(serializers.ModelSerializer):
"""Serializer for model ProductClassification."""
name = serializers.CharField(source='classification_type.name')
class Meta:
"""Meta class."""
model = Product
model = models.ProductClassification
fields = (
'name',
)
class ProductStandardBaseSerializer(serializers.ModelSerializer):
"""Serializer for model ProductStandard."""
standard_type = serializers.CharField(source='get_standard_type_display')
class Meta:
"""Meta class."""
model = models.ProductStandard
fields = (
'name',
'standard_type',
)
class ProductBaseSerializer(serializers.ModelSerializer):
"""Product base serializer."""
product_type = serializers.CharField(source='product_type_translated_name', read_only=True)
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True)
tags = TagBaseSerializer(source='related_tags', many=True, read_only=True)
preview_image_url = serializers.URLField(source='preview_main_image_url',
allow_null=True,
read_only=True)
class Meta:
"""Meta class."""
model = models.Product
fields = [
'id',
'slug',
'name_translated',
'category_display',
'description_translated',
'available',
'name',
'product_type',
'subtypes',
'public_mark',
'establishment_detail',
'vintage',
'tags',
'preview_image_url',
]
class ProductDetailSerializer(ProductBaseSerializer):
"""Product detail serializer."""
description_translated = TranslatedField()
review = ReviewShortSerializer(source='last_published_review', read_only=True)
awards = AwardSerializer(many=True, read_only=True)
classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
standards = ProductStandardBaseSerializer(many=True, read_only=True)
wine_region = WineRegionBaseSerializer(read_only=True)
wine_sub_region = WineSubRegionBaseSerializer(read_only=True)
wine_colors = TagBaseSerializer(many=True, read_only=True)
bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True)
image_url = serializers.ImageField(source='main_image_url',
allow_null=True,
read_only=True)
class Meta(ProductBaseSerializer.Meta):
fields = ProductBaseSerializer.Meta.fields + [
'description_translated',
'review',
'awards',
'classifications',
'standards',
'wine_region',
'wine_appellation',
'available_countries',
'wine_sub_region',
'wine_colors',
'bottles_produced',
'sugar_contents',
'image_url',
]
@ -71,10 +134,10 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
def validate(self, attrs):
"""Overridden validate method"""
# Check establishment object
product_qs = Product.objects.filter(slug=self.slug)
# Check product object
product_qs = models.Product.objects.filter(slug=self.slug)
# Check establishment obj by slug from lookup_kwarg
# Check product obj by slug from lookup_kwarg
if not product_qs.exists():
raise serializers.ValidationError({'detail': _('Object not found.')})
else:
@ -93,4 +156,111 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
'user': self.user,
'content_object': validated_data.pop('product')
})
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}
}
class ProductCommentCreateSerializer(CommentSerializer):
"""Create comment serializer"""
mark = serializers.IntegerField()
class Meta:
"""Serializer for model Comment"""
model = Comment
fields = [
'id',
'created',
'text',
'mark',
'nickname',
'profile_pic',
]
def validate(self, attrs):
"""Override validate method"""
# Check product object
product_slug = self.context.get('request').parser_context.get('kwargs').get('slug')
product_qs = models.Product.objects.filter(slug=product_slug)
if not product_qs.exists():
raise serializers.ValidationError({'detail': _('Product not found.')})
attrs['product'] = product_qs.first()
return attrs
def create(self, validated_data, *args, **kwargs):
"""Override create method"""
validated_data.update({
'user': self.context.get('request').user,
'content_object': validated_data.pop('product')
})
return super().create(validated_data)

View File

@ -0,0 +1,175 @@
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():
errors = []
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:
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_product errors: {errors}")
def transfer_product_note():
errors = []
queryset = transfer_models.ProductNotes.objects.exclude(text='')
serialized_data = product_serializers.ProductNoteSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_product_note errors: {errors}")
def transfer_plate():
errors = []
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:
for d in serialized_data.errors: errors.append(d) if d else None
pprint(f"transfer_plates errors: {errors}")
def transfer_plate_image():
errors = []
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:
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,
],
"product_note": [
transfer_product_note,
],
"souvenir": [
transfer_plate,
transfer_plate_image,
]
}

View File

@ -0,0 +1,14 @@
"""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>/', views.ProductDetailBackOfficeView.as_view(), name='rud'),
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,14 @@ app_name = 'product'
urlpatterns = [
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(),
name='create-destroy-favorites')
name='create-destroy-favorites'),
path('slug/<slug:slug>/comments/', views.ProductCommentListView.as_view(),
name='list-comments'),
path('slug/<slug:slug>/comments/create/', views.ProductCommentCreateView.as_view(),
name='create-comment'),
path('slug/<slug:slug>/comments/<int:comment_id>/', views.ProductCommentRUDView.as_view(),
name='rud-comment'),
]

View File

@ -0,0 +1,7 @@
"""Product mobile url patterns."""
from product.urls.common import urlpatterns as common_urlpatterns
urlpatterns = [
]
urlpatterns.extend(common_urlpatterns)

View File

@ -0,0 +1,80 @@
"""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()
class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView):
"""Product back-office R/U/D view."""
serializer_class = serializers.ProductBackOfficeDetailSerializer

View File

@ -2,12 +2,15 @@
from rest_framework import generics, permissions
from django.shortcuts import get_object_or_404
from product.models import Product
from comment.models import Comment
from product import serializers
from product import filters
from comment.serializers import CommentRUDSerializer
class ProductBaseView(generics.GenericAPIView):
"""Product base view"""
permission_classes = (permissions.AllowAny, )
def get_queryset(self):
"""Override get_queryset method."""
@ -16,11 +19,16 @@ class ProductBaseView(generics.GenericAPIView):
class ProductListView(ProductBaseView, generics.ListAPIView):
"""List view for model Product."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.ProductBaseSerializer
filter_class = filters.ProductFilterSet
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
"""Detail view fro model Product."""
lookup_field = 'slug'
serializer_class = serializers.ProductDetailSerializer
class CreateFavoriteProductView(generics.CreateAPIView,
generics.DestroyAPIView):
"""View for create/destroy product in favorites."""
@ -37,3 +45,45 @@ class CreateFavoriteProductView(generics.CreateAPIView,
# May raise a permission denied
self.check_object_permissions(self.request, favorites)
return favorites
class ProductCommentCreateView(generics.CreateAPIView):
"""View for create new comment."""
lookup_field = 'slug'
serializer_class = serializers.ProductCommentCreateSerializer
queryset = Comment.objects.all()
class ProductCommentListView(generics.ListAPIView):
"""View for return list of product comments."""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.ProductCommentCreateSerializer
def get_queryset(self):
"""Override get_queryset method"""
product = get_object_or_404(Product, slug=self.kwargs['slug'])
return Comment.objects.by_content_type(app_label='product',
model='product')\
.by_object_id(object_id=product.pk)\
.order_by('-created')
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
"""View for retrieve/update/destroy product comment."""
serializer_class = CommentRUDSerializer
queryset = Product.objects.all()
def get_object(self):
"""Returns the object the view is displaying."""
queryset = self.filter_queryset(self.get_queryset())
product_obj = get_object_or_404(queryset,
slug=self.kwargs['slug'])
comment_obj = get_object_or_404(product_obj.comments.by_user(self.request.user),
pk=self.kwargs['comment_id'])
# May raise a permission denied
self.check_object_permissions(self.request, comment_obj)
return comment_obj

View File

@ -7,3 +7,5 @@ from utils.admin import BaseModelAdminMixin
@admin.register(models.Review)
class ReviewAdminModel(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin model for model Review."""
raw_id_fields = ('reviewer', 'language', 'child', 'country')

View File

@ -0,0 +1,74 @@
# Generated by Django 2.2.4 on 2019-11-07 15:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('review', '0005_review_old_id'),
]
operations = [
migrations.CreateModel(
name='Incuiries',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('comment', models.TextField(blank=True, null=True, verbose_name='comment')),
('final_comment', models.TextField(blank=True, null=True, verbose_name='final comment')),
('mark', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='mark')),
('attachment_file', models.URLField(blank=True, default=None, max_length=255, null=True, verbose_name='attachment')),
('bill_file', models.URLField(blank=True, default=None, max_length=255, null=True, verbose_name='bill')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='price')),
('moment', models.PositiveSmallIntegerField(choices=[(0, 'none'), (1, 'diner'), (2, 'lanch')], default=0)),
('decibles', models.CharField(blank=True, max_length=255, null=True)),
('nomination', models.CharField(blank=True, max_length=255, null=True)),
('nominee', models.CharField(blank=True, max_length=255, null=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incuiries', to=settings.AUTH_USER_MODEL, verbose_name='author')),
('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incuiries', to='review.Review', verbose_name='review')),
],
options={
'verbose_name': 'Incuiry',
'verbose_name_plural': 'Incuiries',
},
),
migrations.CreateModel(
name='IncuiryPhoto',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('attachment_file', models.URLField(max_length=255, verbose_name='attachment')),
('incuiry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='review.Incuiries', verbose_name='incuiry')),
],
options={
'verbose_name': 'incuiry photo',
'verbose_name_plural': 'incuiry photos',
},
),
migrations.CreateModel(
name='GridItems',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('sub_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='sub name')),
('name', models.CharField(blank=True, max_length=255, null=True, verbose_name='name')),
('value', models.FloatField(blank=True, null=True, verbose_name='value')),
('decs', models.TextField(blank=True, null=True, verbose_name='description')),
('dish_title', models.CharField(blank=True, max_length=255, null=True, verbose_name='dish title')),
('incuiry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grids', to='review.Incuiries', verbose_name='incuiry')),
],
options={
'verbose_name': 'incuiry grid',
'verbose_name_plural': 'incuiry grids',
},
),
]

View File

@ -0,0 +1,66 @@
# Generated by Django 2.2.4 on 2019-11-08 09:23
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('review', '0006_griditems_incuiries_incuiryphoto'),
]
operations = [
migrations.CreateModel(
name='Inquiries',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('comment', models.TextField(blank=True, null=True, verbose_name='comment')),
('final_comment', models.TextField(blank=True, null=True, verbose_name='final comment')),
('mark', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='mark')),
('attachment_file', models.URLField(blank=True, default=None, max_length=255, null=True, verbose_name='attachment')),
('bill_file', models.URLField(blank=True, default=None, max_length=255, null=True, verbose_name='bill')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='price')),
('moment', models.PositiveSmallIntegerField(choices=[(0, 'none'), (1, 'diner'), (2, 'lanch')], default=0)),
('decibels', models.CharField(blank=True, max_length=255, null=True)),
('nomination', models.CharField(blank=True, max_length=255, null=True)),
('nominee', models.CharField(blank=True, max_length=255, null=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incuiries', to=settings.AUTH_USER_MODEL, verbose_name='author')),
('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inquiries', to='review.Review', verbose_name='review')),
],
options={
'verbose_name': 'Inquiry',
'verbose_name_plural': 'Inquiries',
},
),
migrations.RemoveField(
model_name='incuiryphoto',
name='incuiry',
),
migrations.AlterModelOptions(
name='griditems',
options={'verbose_name': 'inquiry grid', 'verbose_name_plural': 'inquiry grids'},
),
migrations.RemoveField(
model_name='griditems',
name='incuiry',
),
migrations.DeleteModel(
name='Incuiries',
),
migrations.DeleteModel(
name='IncuiryPhoto',
),
migrations.AddField(
model_name='griditems',
name='inquiry',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='grids', to='review.Inquiries', verbose_name='inquiry'),
preserve_default=False,
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-08 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0007_auto_20191108_0923'),
]
operations = [
migrations.AlterField(
model_name='inquiries',
name='mark',
field=models.FloatField(blank=True, default=None, null=True, verbose_name='mark'),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 2.2.4 on 2019-11-10 06:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('gallery', '0006_merge_20191027_1758'),
('review', '0008_auto_20191108_0927'),
]
operations = [
migrations.AddField(
model_name='inquiries',
name='attachment_content_type',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='inquiries',
name='bill_content_type',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.CreateModel(
name='InquiriesGallery',
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='inquiries_gallery', to='gallery.Image', verbose_name='gallery')),
('inquiry', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inquiries_gallery', to='review.Inquiries', verbose_name='inquiry')),
],
options={
'verbose_name': 'inquiry gallery',
'verbose_name_plural': 'inquiry galleries',
'unique_together': {('inquiry', 'is_main'), ('inquiry', 'image')},
},
),
migrations.AddField(
model_name='inquiries',
name='gallery',
field=models.ManyToManyField(through='review.InquiriesGallery', to='gallery.Image'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-11 11:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0009_auto_20191110_0615'),
]
operations = [
migrations.AddField(
model_name='inquiries',
name='published',
field=models.BooleanField(default=False, verbose_name='is published'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-11 14:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('review', '0010_inquiries_published'),
]
operations = [
migrations.RenameField(
model_name='griditems',
old_name='decs',
new_name='desc',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-11 14:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0011_auto_20191111_1439'),
]
operations = [
migrations.AddField(
model_name='griditems',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-12 05:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('review', '0012_griditems_old_id'),
]
operations = [
migrations.AddField(
model_name='inquiriesgallery',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-11-12 05:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('review', '0013_inquiriesgallery_old_id'),
]
operations = [
migrations.AlterUniqueTogether(
name='inquiriesgallery',
unique_together=set(),
),
]

View File

@ -4,7 +4,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from utils.models import BaseAttributes, TranslatedFieldsMixin
from utils.models import BaseAttributes, TranslatedFieldsMixin, ProjectBaseMixin
from utils.models import TJSONField
@ -77,3 +77,89 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
"""Meta class."""
verbose_name = _('Review')
verbose_name_plural = _('Reviews')
class Inquiries(ProjectBaseMixin):
NONE = 0
DINER = 1
LUNCH = 2
MOMENTS = (
(NONE, _('none')),
(DINER, _('diner')),
(LUNCH, _('lanch')),
)
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
review = models.ForeignKey(Review, verbose_name=_('review'), related_name='inquiries', on_delete=models.CASCADE)
comment = models.TextField(_('comment'), blank=True, null=True)
final_comment = models.TextField(_('final comment'), blank=True, null=True)
mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None)
attachment_file = models.URLField(verbose_name=_('attachment'), max_length=255, blank=True, null=True, default=None)
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
author = models.ForeignKey('account.User', related_name='incuiries', on_delete=models.CASCADE,
verbose_name=_('author'))
bill_file = models.URLField(verbose_name=_('bill'), max_length=255, blank=True, null=True, default=None)
bill_content_type = models.CharField(max_length=255, blank=True, null=True)
price = models.DecimalField(_('price'), max_digits=7, decimal_places=2, blank=True, null=True)
moment = models.PositiveSmallIntegerField(choices=MOMENTS, default=NONE)
gallery = models.ManyToManyField('gallery.Image', through='review.InquiriesGallery')
decibels = models.CharField(max_length=255, blank=True, null=True)
nomination = models.CharField(max_length=255, blank=True, null=True)
nominee = models.CharField(max_length=255, blank=True, null=True)
published = models.BooleanField(_('is published'), default=False)
class Meta:
verbose_name = _('Inquiry')
verbose_name_plural = _('Inquiries')
def __str__(self):
return f'id: {self.id}, review: {self.review.id}, author: {self.author.id}'
class GridItems(ProjectBaseMixin):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
inquiry = models.ForeignKey(Inquiries, verbose_name=_('inquiry'), on_delete=models.CASCADE, related_name='grids')
sub_name = models.CharField(_('sub name'), max_length=255, blank=True, null=True)
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
value = models.FloatField(_('value'), blank=True, null=True)
desc = models.TextField(_('description'), blank=True, null=True)
dish_title = models.CharField(_('dish title'), max_length=255, blank=True, null=True)
class Meta:
verbose_name = _('inquiry grid')
verbose_name_plural = _('inquiry grids')
def __str__(self):
return f'inquiry: {self.inquiry.id}, grid id: {self.id}'
class InquiriesGalleryQuerySet(models.QuerySet):
"""QuerySet for model Inquiries"""
def main_image(self):
"""Return objects with flag is_main is True"""
return self.filter(is_main=True)
class InquiriesGallery(models.Model):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
inquiry = models.ForeignKey(
Inquiries,
null=True,
related_name='inquiries_gallery',
on_delete=models.CASCADE,
verbose_name=_('inquiry'),
)
image = models.ForeignKey(
'gallery.Image',
null=True,
related_name='inquiries_gallery',
on_delete=models.CASCADE,
verbose_name=_('gallery'),
)
is_main = models.BooleanField(default=False, verbose_name=_('Is the main image'))
objects = InquiriesGalleryQuerySet.as_manager()
class Meta:
verbose_name = _('inquiry gallery')
verbose_name_plural = _('inquiry galleries')

View File

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

View File

@ -1,18 +1,3 @@
"""Review app common serializers."""
"""Review app back serializers."""
from review import models
from rest_framework import serializers
class ReviewBaseSerializer(serializers.ModelSerializer):
class Meta:
model = models.Review
fields = ('id',
'reviewer',
'text',
'language',
'status',
'child',
'published_at',
'vintage',
'country'
)

View File

@ -0,0 +1,29 @@
from rest_framework import serializers
from review.models import Review
class ReviewBaseSerializer(serializers.ModelSerializer):
class Meta:
model = Review
fields = (
'id',
'reviewer',
'text',
'language',
'status',
'child',
'published_at',
'vintage',
'country'
)
class ReviewShortSerializer(ReviewBaseSerializer):
"""Serializer for model Review."""
text_translated = serializers.CharField(read_only=True)
class Meta(ReviewBaseSerializer.Meta):
"""Meta class."""
fields = (
'text_translated',
)

View File

@ -1,7 +1,15 @@
from transfer.models import Reviews, ReviewTexts
from transfer.serializers.reviews import LanguageSerializer, ReviewSerializer, Establishment
from pprint import pprint
import json
from pprint import pprint
from django.db.models import Q
from account.transfer_data import STOP_LIST
from review.models import Inquiries as NewInquiries, Review
from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos
from transfer.serializers.grid import GridItemsSerializer
from transfer.serializers.inquiries import InquiriesSerializer
from transfer.serializers.inquiry_gallery import InquiryGallerySerializer
from transfer.serializers.reviews import LanguageSerializer, ReviewSerializer, Establishment
def transfer_languages():
@ -20,6 +28,7 @@ def transfer_languages():
def transfer_reviews():
# TODO: убрать LIKE UPPER("%%paris%%"), accounts.email IN
queryset = Reviews.objects.raw("""SELECT reviews.id, reviews.vintage, reviews.establishment_id,
reviews.reviewer_id, review_texts.text AS text, reviews.mark, reviews.published_at,
review_texts.created_at AS published, review_texts.locale AS locale,
@ -51,9 +60,8 @@ def transfer_reviews():
ON (establishments.location_id = locations.id)
INNER JOIN cities
ON (locations.city_id = cities.id)
WHERE UPPER(cities.name) LIKE UPPER("%%paris%%")
AND NOT establishments.type = "Wineyard"
AND establishments.type IS NOT NULL AND locations.timezone IS NOT NULL
WHERE establishments.type IS NOT NULL AND locations.timezone IS NOT NULL
AND NOT establishments.type = "Wineyard"
)
ORDER BY review_texts.created_at DESC
""")
@ -71,7 +79,7 @@ def transfer_reviews():
]
else:
establishments_mark_list[query['establishment_id']] = int(query['mark'])
del(query['mark'])
del (query['mark'])
queryset_result.append(query)
serialized_data = ReviewSerializer(data=queryset_result, many=True)
@ -93,9 +101,50 @@ def transfer_reviews():
pprint(serialized_data.errors)
def transfer_inquiries():
reviews = Review.objects.all().values_list('old_id', flat=True)
inquiries = Inquiries.objects.exclude(
Q(account__confirmed_at__isnull=True) |
Q(account__email__in=STOP_LIST)
).filter(review_id__in=list(reviews))
serialized_data = InquiriesSerializer(data=list(inquiries.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"Inquiries serializer errors: {serialized_data.errors}")
def transfer_grid():
inquiries = NewInquiries.objects.all().values_list('old_id', flat=True)
grids = GridItems.objects.filter(inquiry_id__in=list(inquiries))
serialized_data = GridItemsSerializer(data=list(grids.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"GridItems serializer errors: {serialized_data.errors}")
def transfer_inquiry_photos():
inquiries = NewInquiries.objects.all().values_list('old_id', flat=True)
grids = InquiryPhotos.objects.filter(inquiry_id__in=list(inquiries), attachment_suffix_url__isnull=False)
serialized_data = InquiryGallerySerializer(data=list(grids.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"InquiryGallery serializer errors: {serialized_data.errors}")
data_types = {
"overlook": [
transfer_languages,
transfer_reviews
],
'inquiries': [
transfer_inquiries,
transfer_grid,
transfer_inquiry_photos,
]
}

Some files were not shown because too many files have changed in this diff Show More