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:
commit
cf226974c8
40
apps/account/management/commands/add_account.py
Normal file
40
apps/account/management/commands/add_account.py
Normal 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.'))
|
||||
31
apps/account/management/commands/add_image.py
Normal file
31
apps/account/management/commands/add_image.py
Normal 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.'))
|
||||
42
apps/account/management/commands/add_social.py
Normal file
42
apps/account/management/commands/add_social.py
Normal 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.'))
|
||||
18
apps/account/migrations/0019_auto_20191108_0827.py
Normal file
18
apps/account/migrations/0019_auto_20191108_0827.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
27
apps/advertisement/migrations/0005_auto_20191108_0923.py
Normal file
27
apps/advertisement/migrations/0005_auto_20191108_0923.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class AdvertisementSerializer(serializers.ModelSerializer):
|
|||
'id',
|
||||
'uuid',
|
||||
'url',
|
||||
'image',
|
||||
'image_url',
|
||||
'width',
|
||||
'height',
|
||||
'block_level',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
9
apps/collection/serializers/back.py
Normal file
9
apps/collection/serializers/back.py
Normal 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__'
|
||||
10
apps/collection/urls/back.py
Normal file
10
apps/collection/urls/back.py
Normal 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'),
|
||||
]
|
||||
19
apps/collection/views/back.py
Normal file
19
apps/collection/views/back.py
Normal 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, )
|
||||
|
|
@ -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',)
|
||||
|
|
|
|||
0
apps/comment/management/__init__.py
Normal file
0
apps/comment/management/__init__.py
Normal file
0
apps/comment/management/commands/__init__.py
Normal file
0
apps/comment/management/commands/__init__.py
Normal file
29
apps/comment/management/commands/add_comment_publish_data.py
Normal file
29
apps/comment/management/commands/add_comment_publish_data.py
Normal 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.'))
|
||||
17
apps/comment/migrations/0005_remove_comment_country.py
Normal file
17
apps/comment/migrations/0005_remove_comment_country.py
Normal 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',
|
||||
),
|
||||
]
|
||||
18
apps/comment/migrations/0006_comment_is_publish.py
Normal file
18
apps/comment/migrations/0006_comment_is_publish.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from .mobile import *
|
||||
from .back import *
|
||||
from .web import *
|
||||
from .common import *
|
||||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
33
apps/establishment/management/commands/update_employee.py
Normal file
33
apps/establishment/management/commands/update_employee.py
Normal 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.'))
|
||||
33
apps/establishment/migrations/0059_establishmentnote.py
Normal file
33
apps/establishment/migrations/0059_establishmentnote.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]
|
||||
|
|
|
|||
69
apps/location/migrations/0021_auto_20191111_0731.py
Normal file
69
apps/location/migrations/0021_auto_20191111_0731.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
28
apps/location/migrations/0022_auto_20191111_0731.py
Normal file
28
apps/location/migrations/0022_auto_20191111_0731.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
17
apps/location/migrations/0023_auto_20191112_0104.py
Normal file
17
apps/location/migrations/0023_auto_20191112_0104.py
Normal 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'},
|
||||
),
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
18
apps/main/migrations/0034_auto_20191112_0104.py
Normal file
18
apps/main/migrations/0034_auto_20191112_0104.py
Normal 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),
|
||||
),
|
||||
]
|
||||
18
apps/main/migrations/0034_sitefeature_nested.py
Normal file
18
apps/main/migrations/0034_sitefeature_nested.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
14
apps/main/migrations/0035_merge_20191112_1218.py
Normal file
14
apps/main/migrations/0035_merge_20191112_1218.py
Normal 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 = [
|
||||
]
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
9
apps/news/urls/common.py
Normal 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
8
apps/news/urls/mobile.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"""News app urlconf."""
|
||||
from news.urls.common import common_urlpatterns
|
||||
|
||||
app_name = 'news'
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
21
apps/product/management/commands/add_assemblage_tag.py
Normal file
21
apps/product/management/commands/add_assemblage_tag.py
Normal 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}'))
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
34
apps/product/management/commands/add_product_sub_type.py
Normal file
34
apps/product/management/commands/add_product_sub_type.py
Normal 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}'))
|
||||
116
apps/product/management/commands/add_product_tag.py
Normal file
116
apps/product/management/commands/add_product_tag.py
Normal 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()
|
||||
21
apps/product/management/commands/add_product_type.py
Normal file
21
apps/product/management/commands/add_product_type.py
Normal 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}'))
|
||||
21
apps/product/management/commands/fix_wine_color_tag.py
Normal file
21
apps/product/management/commands/fix_wine_color_tag.py
Normal 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}'))
|
||||
179
apps/product/migrations/0009_auto_20191111_0731.py
Normal file
179
apps/product/migrations/0009_auto_20191111_0731.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
33
apps/product/migrations/0010_auto_20191111_1227.py
Normal file
33
apps/product/migrations/0010_auto_20191111_1227.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/product/migrations/0011_product_win_import_id.py
Normal file
18
apps/product/migrations/0011_product_win_import_id.py
Normal 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),
|
||||
),
|
||||
]
|
||||
37
apps/product/migrations/0012_auto_20191112_1007.py
Normal file
37
apps/product/migrations/0012_auto_20191112_1007.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from .common import *
|
||||
from .web import *
|
||||
from .mobile import *
|
||||
from .back import *
|
||||
|
|
|
|||
70
apps/product/serializers/back.py
Normal file
70
apps/product/serializers/back.py
Normal 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},
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
@ -94,3 +157,110 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
|
|||
'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)
|
||||
175
apps/product/transfer_data.py
Normal file
175
apps/product/transfer_data.py
Normal 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,
|
||||
]
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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'),
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
"""Product mobile url patterns."""
|
||||
from product.urls.common import urlpatterns as common_urlpatterns
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
66
apps/review/migrations/0007_auto_20191108_0923.py
Normal file
66
apps/review/migrations/0007_auto_20191108_0923.py
Normal 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,
|
||||
),
|
||||
]
|
||||
18
apps/review/migrations/0008_auto_20191108_0927.py
Normal file
18
apps/review/migrations/0008_auto_20191108_0927.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
44
apps/review/migrations/0009_auto_20191110_0615.py
Normal file
44
apps/review/migrations/0009_auto_20191110_0615.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/review/migrations/0010_inquiries_published.py
Normal file
18
apps/review/migrations/0010_inquiries_published.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/review/migrations/0011_auto_20191111_1439.py
Normal file
18
apps/review/migrations/0011_auto_20191111_1439.py
Normal 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',
|
||||
),
|
||||
]
|
||||
18
apps/review/migrations/0012_griditems_old_id.py
Normal file
18
apps/review/migrations/0012_griditems_old_id.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
18
apps/review/migrations/0013_inquiriesgallery_old_id.py
Normal file
18
apps/review/migrations/0013_inquiriesgallery_old_id.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
17
apps/review/migrations/0014_auto_20191112_0538.py
Normal file
17
apps/review/migrations/0014_auto_20191112_0538.py
Normal 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(),
|
||||
),
|
||||
]
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from .back import *
|
||||
from .common import *
|
||||
from .mobile import *
|
||||
from .web import *
|
||||
|
|
@ -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'
|
||||
)
|
||||
|
|
@ -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',
|
||||
)
|
||||
|
|
@ -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%%")
|
||||
WHERE establishments.type IS NOT NULL AND locations.timezone IS NOT NULL
|
||||
AND NOT establishments.type = "Wineyard"
|
||||
AND establishments.type IS NOT NULL AND locations.timezone IS NOT NULL
|
||||
)
|
||||
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
Loading…
Reference in New Issue
Block a user