Merge remote-tracking branch 'origin/develop' into feature/add-city-search

# Conflicts:
#	_dockerfiles/db/Dockerfile
#	project/settings/local.py
This commit is contained in:
Dmitriy Kuzmenko 2019-12-09 11:41:02 +03:00
commit dfe4ab92a6
94 changed files with 2700 additions and 323 deletions

4
.gitignore vendored
View File

@ -22,7 +22,9 @@ logs/
# dev # dev
./docker-compose.override.yml ./docker-compose.override.yml
celerybeat-schedule celerybeat-schedule
local_files local_files
celerybeat.pid celerybeat.pid
/gm_viktor.dump
/docker-compose.dump.yml
/gm_production_20191029.sql

View File

@ -1,3 +1,3 @@
FROM mdillon/postgis:latest FROM mdillon/postgis:10
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8 RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
ENV LANG ru_RU.utf8 ENV LANG ru_RU.utf8

View File

@ -0,0 +1,152 @@
from account.models import OldRole, Role, User, UserRole
from main.models import SiteSettings
from django.core.management.base import BaseCommand
from django.db import connections, transaction
from django.db.models import Prefetch
from establishment.management.commands.add_position import namedtuplefetchall
from tqdm import tqdm
class Command(BaseCommand):
help = '''Add site affilations from old db to new db.
Run after migrate account models!!!'''
def map_role_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select distinct
case when role = 'news_editor' then 'CONTENT_PAGE_MANAGER'
when role in ('reviewer', 'reviwer', 'reviewer_manager') then 'REVIEWER_MANGER'
when role = 'admin' then 'SUPERUSER'
when role ='community_manager' then 'COUNTRY_ADMIN'
when role = 'site_admin' then 'COUNTRY_ADMIN'
when role = 'wine_reviewer' then 'WINERY_REVIEWER'
when role in ('salesman', 'sales_man') then 'SALES_MAN'
when role = 'seller' then 'SELLER'
else role
end as new_role,
case when role = 'GUEST' then null else role end as role
from
(
SELECT
DISTINCT
COALESCE(role, 'GUEST') as role
FROM site_affiliations AS sa
) t
''')
return namedtuplefetchall(cursor)
def add_old_roles(self):
objects = []
OldRole.objects.all().delete()
for s in tqdm(self.map_role_sql(), desc='Add permissions old'):
objects.append(
OldRole(new_role=s.new_role, old_role=s.role)
)
OldRole.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Migrated old roles.'))
def site_role_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select site_id,
role
from
(
SELECT
DISTINCT
site_id,
COALESCE(role, 'GUEST') as role
FROM site_affiliations AS sa
) t
where t.role not in ('admin', 'GUEST')
''')
return namedtuplefetchall(cursor)
def add_site_role(self):
objects = []
for s in tqdm(self.site_role_sql(), desc='Add site role'):
old_role = OldRole.objects.get(old_role=s.role)
role_choice = getattr(Role, old_role.new_role)
sites = SiteSettings.objects.filter(old_id=s.site_id)
for site in sites:
role = Role.objects.filter(site=site, role=role_choice)
if not role.exists():
objects.append(
Role(site=site, role=role_choice)
)
Role.objects.bulk_create(objects)
self.stdout.write(self.style.WARNING(f'Added site roles.'))
def update_site_role(self):
roles = Role.objects.filter(country__isnull=True).select_related('site')\
.filter(site__id__isnull=False).select_for_update()
with transaction.atomic():
for role in tqdm(roles, desc='Update role country'):
role.country = role.site.country
role.save()
self.stdout.write(self.style.WARNING(f'Updated site roles.'))
def user_role_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select t.*
from
(
SELECT
site_id,
account_id,
COALESCE(role, 'GUEST') as role
FROM site_affiliations AS sa
) t
join accounts a on a.id = t.account_id
where t.role not in ('admin', 'GUEST')
''')
return namedtuplefetchall(cursor)
def add_role_user(self):
for s in tqdm(self.user_role_sql(), desc='Add role to user'):
sites = SiteSettings.objects.filter(old_id=s.site_id)
old_role = OldRole.objects.get(old_role=s.role)
role_choice = getattr(Role, old_role.new_role)
roles = Role.objects.filter(site__in=[site for site in sites], role=role_choice)
users = User.objects.filter(old_id=s.account_id)
for user in users:
for role in roles:
user_role = UserRole.objects.get_or_create(user=user,
role=role)
self.stdout.write(self.style.WARNING(f'Added users roles.'))
def superuser_role_sql(self):
with connections['legacy'].cursor() as cursor:
cursor.execute('''
select t.*
from
(
SELECT
site_id,
account_id,
COALESCE(role, 'GUEST') as role
FROM site_affiliations AS sa
) t
join accounts a on a.id = t.account_id
where t.role in ('admin')
''')
return namedtuplefetchall(cursor)
def add_superuser(self):
for s in tqdm(self.superuser_role_sql(), desc='Add superuser'):
users = User.objects.filter(old_id=s.account_id).select_for_update()
with transaction.atomic():
for user in users:
user.is_superuser = True
user.save()
self.stdout.write(self.style.WARNING(f'Added superuser.'))
def handle(self, *args, **kwargs):
self.add_old_roles()
self.add_site_role()
self.update_site_role()
self.add_role_user()
self.add_superuser()

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.7 on 2019-12-03 10:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0020_role_site'),
]
operations = [
migrations.CreateModel(
name='OldRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('new_role', models.CharField(max_length=512, verbose_name='New role')),
('old_role', models.CharField(max_length=512, verbose_name='Old role')),
],
options={
'unique_together': {('new_role', 'old_role')},
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-12-03 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0021_oldrole'),
]
operations = [
migrations.AlterField(
model_name='oldrole',
name='old_role',
field=models.CharField(max_length=512, null=True, verbose_name='Old role'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.2.7 on 2019-12-04 09:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0022_auto_20191203_1149'),
]
operations = [
migrations.AlterField(
model_name='role',
name='role',
field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer'), (8, 'Sales man'), (9, 'Winery reviewer'), (10, 'Seller')], verbose_name='Role'),
),
migrations.AlterUniqueTogether(
name='userrole',
unique_together={('user', 'role')},
),
]

View File

@ -32,6 +32,9 @@ class Role(ProjectBaseMixin):
ESTABLISHMENT_MANAGER = 5 ESTABLISHMENT_MANAGER = 5
REVIEWER_MANGER = 6 REVIEWER_MANGER = 6
RESTAURANT_REVIEWER = 7 RESTAURANT_REVIEWER = 7
SALES_MAN = 8
WINERY_REVIEWER = 9
SELLER = 10
ROLE_CHOICES = ( ROLE_CHOICES = (
(STANDARD_USER, 'Standard user'), (STANDARD_USER, 'Standard user'),
@ -40,7 +43,10 @@ class Role(ProjectBaseMixin):
(CONTENT_PAGE_MANAGER, 'Content page manager'), (CONTENT_PAGE_MANAGER, 'Content page manager'),
(ESTABLISHMENT_MANAGER, 'Establishment manager'), (ESTABLISHMENT_MANAGER, 'Establishment manager'),
(REVIEWER_MANGER, 'Reviewer manager'), (REVIEWER_MANGER, 'Reviewer manager'),
(RESTAURANT_REVIEWER, 'Restaurant reviewer') (RESTAURANT_REVIEWER, 'Restaurant reviewer'),
(SALES_MAN, 'Sales man'),
(WINERY_REVIEWER, 'Winery reviewer'),
(SELLER, 'Seller')
) )
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
null=False, blank=False) null=False, blank=False)
@ -287,7 +293,19 @@ class User(AbstractUser):
class UserRole(ProjectBaseMixin): class UserRole(ProjectBaseMixin):
"""UserRole model.""" """UserRole model."""
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) user = models.ForeignKey('account.User',
verbose_name=_('User'),
on_delete=models.CASCADE)
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'), establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
on_delete=models.SET_NULL, null=True, blank=True) on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
unique_together = ['user', 'role']
class OldRole(models.Model):
new_role = models.CharField(verbose_name=_('New role'), max_length=512)
old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True)
class Meta:
unique_together = ('new_role', 'old_role')

View File

@ -13,19 +13,27 @@ class RoleSerializer(serializers.ModelSerializer):
] ]
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserRole
fields = [
'user',
'role'
]
class BackUserSerializer(serializers.ModelSerializer): class BackUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = '__all__' fields = (
'id',
'last_login',
'is_superuser',
'username',
'last_name',
'first_name',
'is_active',
'date_joined',
'image_url',
'cropped_image_url',
'email',
'email_confirmed',
'unconfirmed_email',
'email_confirmed',
'newsletter',
'roles',
)
extra_kwargs = { extra_kwargs = {
'password': {'write_only': True} 'password': {'write_only': True}
} }
@ -49,3 +57,13 @@ class BackDetailUserSerializer(BackUserSerializer):
user.set_password(validated_data['password']) user.set_password(validated_data['password'])
user.save() user.save()
return user return user
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserRole
fields = [
'role',
'user',
'establishment'
]

View File

@ -92,7 +92,12 @@ class UserBaseSerializer(serializers.ModelSerializer):
model = models.User model = models.User
fields = ( fields = (
'id',
'username',
'fullname', 'fullname',
'first_name',
'last_name',
'email',
'cropped_image_url', 'cropped_image_url',
'image_url', 'image_url',
) )

View File

@ -1,5 +1,6 @@
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions from rest_framework import generics, permissions
from rest_framework.filters import OrderingFilter
from account import models from account import models
from account.models import User from account.models import User
@ -13,15 +14,15 @@ class RoleLstView(generics.ListCreateAPIView):
class UserRoleLstView(generics.ListCreateAPIView): class UserRoleLstView(generics.ListCreateAPIView):
serializer_class = serializers.UserRoleSerializer serializer_class = serializers.UserRoleSerializer
queryset = models.Role.objects.all() queryset = models.UserRole.objects.all()
class UserLstView(generics.ListCreateAPIView): class UserLstView(generics.ListCreateAPIView):
"""User list create view.""" """User list create view."""
queryset = User.objects.all() queryset = User.objects.prefetch_related('roles')
serializer_class = serializers.BackUserSerializer serializer_class = serializers.BackUserSerializer
permission_classes = (permissions.IsAdminUser,) permission_classes = (permissions.IsAdminUser,)
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_fields = ( filterset_fields = (
'email_confirmed', 'email_confirmed',
'is_staff', 'is_staff',
@ -29,6 +30,14 @@ class UserLstView(generics.ListCreateAPIView):
'is_superuser', 'is_superuser',
'roles', 'roles',
) )
ordering_fields = (
'email_confirmed',
'is_staff',
'is_active',
'is_superuser',
'roles',
'last_login'
)
class UserRUDView(generics.RetrieveUpdateDestroyAPIView): class UserRUDView(generics.RetrieveUpdateDestroyAPIView):

View File

View File

@ -0,0 +1,8 @@
from booking.urls import common as common_views
app = 'booking'
urlpatterns_api = []
urlpatterns = urlpatterns_api + \
common_views.urlpatterns

8
apps/booking/urls/web.py Normal file
View File

@ -0,0 +1,8 @@
from booking.urls import common as common_views
app = 'booking'
urlpatterns_api = []
urlpatterns = urlpatterns_api + \
common_views.urlpatterns

View File

@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
periods = response['periods'] periods = response['periods']
periods_by_name = {period['period']: period for period in periods if 'period' in period} periods_by_name = {period['period']: period for period in periods if 'period' in period}
if not periods_by_name: if not periods_by_name:
return None return response
period_template = iter(periods_by_name.values()).__next__().copy() period_template = iter(periods_by_name.values()).__next__().copy()
period_template.pop('total_left_seats') period_template.pop('total_left_seats')

View File

@ -1,4 +1,6 @@
from django.contrib.gis import admin from django.contrib.gis import admin
from mptt.admin import DraggableMPTTAdmin, TreeRelatedFieldListFilter
from utils.admin import BaseModelAdminMixin
from collection import models from collection import models
@ -11,3 +13,22 @@ class CollectionAdmin(admin.ModelAdmin):
@admin.register(models.Guide) @admin.register(models.Guide)
class GuideAdmin(admin.ModelAdmin): class GuideAdmin(admin.ModelAdmin):
"""Guide admin.""" """Guide admin."""
@admin.register(models.GuideElementType)
class GuideElementType(admin.ModelAdmin):
"""Guide element admin."""
@admin.register(models.GuideElement)
class GuideElementAdmin(DraggableMPTTAdmin, BaseModelAdminMixin, admin.ModelAdmin):
"""Guide element admin."""
raw_id_fields = [
'guide_element_type', 'establishment', 'review',
'wine_region', 'product', 'city',
'wine_color_section', 'section', 'guide',
'parent',
]
# list_filter = (
# ('parent', TreeRelatedFieldListFilter),
# )

View File

@ -0,0 +1,120 @@
import re
from pprint import pprint
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from location.models import City
from location.models import WineRegion
from product.models import Product
from review.models import Review
from tag.models import Tag
from transfer.models import GuideElements
def decorator(f):
def decorate(self):
print(f'{"-"*20}start {f.__name__}{"-"*20}')
f(self)
print(f'{"-"*20}end {f.__name__}{"-"*20}\n')
return decorate
class Command(BaseCommand):
help = """Check guide dependencies."""
@decorator
def count_of_guide_relative_dependencies(self):
for field in GuideElements._meta.fields:
if field.name not in ['id', 'lft', 'rgt', 'depth',
'children_count', 'parent', 'order_number']:
filters = {f'{field.name}__isnull': False, }
qs = GuideElements.objects.filter(**filters).values_list(field.name, flat=True)
print(f"COUNT OF {field.name}'s: {len(set(qs))}")
@decorator
def check_regions(self):
wine_region_old_ids = set(GuideElements.objects.filter(wine_region_id__isnull=False)
.values_list('wine_region_id', flat=True))
not_existed_wine_regions = []
for old_id in tqdm(wine_region_old_ids):
if not WineRegion.objects.filter(old_id=old_id).exists():
not_existed_wine_regions.append(old_id)
print(f'NOT EXISTED WINE REGIONS: {len(not_existed_wine_regions)}')
pprint(f'{not_existed_wine_regions}')
@decorator
def check_establishments(self):
establishment_old_ids = set(GuideElements.objects.filter(establishment_id__isnull=False)
.values_list('establishment_id', flat=True))
not_existed_establishments = []
for old_id in tqdm(establishment_old_ids):
if not Establishment.objects.filter(old_id=old_id).exists():
not_existed_establishments.append(old_id)
print(f'NOT EXISTED ESTABLISHMENTS: {len(not_existed_establishments)}')
pprint(f'{not_existed_establishments}')
@decorator
def check_reviews(self):
review_old_ids = set(GuideElements.objects.filter(review_id__isnull=False)
.values_list('review_id', flat=True))
not_existed_reviews = []
for old_id in tqdm(review_old_ids):
if not Review.objects.filter(old_id=old_id).exists():
not_existed_reviews.append(old_id)
print(f'NOT EXISTED REVIEWS: {len(not_existed_reviews)}')
pprint(f'{not_existed_reviews}')
@decorator
def check_wines(self):
wine_old_ids = set(GuideElements.objects.filter(wine_id__isnull=False)
.values_list('wine_id', flat=True))
not_existed_wines = []
for old_id in tqdm(wine_old_ids):
if not Product.objects.filter(old_id=old_id).exists():
not_existed_wines.append(old_id)
print(f'NOT EXISTED WINES: {len(not_existed_wines)}')
pprint(f'{not_existed_wines}')
@decorator
def check_wine_color(self):
raw_wine_color_nodes = set(GuideElements.objects.exclude(color__iexact='')
.filter(color__isnull=False)
.values_list('color', flat=True))
raw_wine_colors = [i[:-11] for i in raw_wine_color_nodes]
raw_wine_color_index_names = []
re_exp = '[A-Z][^A-Z]*'
for raw_wine_color in tqdm(raw_wine_colors):
result = re.findall(re_exp, rf'{raw_wine_color}')
if result and len(result) >= 2:
wine_color = '-'.join(result)
else:
wine_color = result[0]
raw_wine_color_index_names.append(wine_color.lower())
not_existed_wine_colors = []
for index_name in raw_wine_color_index_names:
if not Tag.objects.filter(value=index_name).exists():
not_existed_wine_colors.append(index_name)
print(f'NOT EXISTED WINE COLOR: {len(not_existed_wine_colors)}')
pprint(f'{not_existed_wine_colors}')
@decorator
def check_cities(self):
city_old_ids = set(GuideElements.objects.filter(city_id__isnull=False)
.values_list('city_id', flat=True))
not_existed_cities = []
for old_id in tqdm(city_old_ids):
if not City.objects.filter(old_id=old_id).exists():
not_existed_cities.append(old_id)
print(f'NOT EXISTED CITIES: {len(not_existed_cities)}')
pprint(f'{not_existed_cities}')
def handle(self, *args, **kwargs):
self.count_of_guide_relative_dependencies()
self.check_regions()
self.check_establishments()
self.check_reviews()
self.check_wines()
self.check_wine_color()
self.check_cities()

View File

@ -0,0 +1,78 @@
# Generated by Django 2.2.7 on 2019-11-27 10:47
import django.contrib.postgres.fields.jsonb
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('main', '0039_sitefeature_old_id'),
('collection', '0017_collection_old_id'),
]
operations = [
migrations.CreateModel(
name='GuideType',
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')),
('name', models.SlugField(max_length=255, unique=True, verbose_name='code')),
],
options={
'verbose_name': 'guide type',
'verbose_name_plural': 'guide types',
},
),
migrations.RemoveField(
model_name='guide',
name='advertorials',
),
migrations.RemoveField(
model_name='guide',
name='collection',
),
migrations.RemoveField(
model_name='guide',
name='parent',
),
migrations.AddField(
model_name='guide',
name='old_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='guide',
name='site',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site settings'),
),
migrations.AddField(
model_name='guide',
name='slug',
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='slug'),
),
migrations.AddField(
model_name='guide',
name='state',
field=models.PositiveSmallIntegerField(choices=[(0, 'built'), (1, 'waiting'), (2, 'removing'), (3, 'building')], default=1, verbose_name='state'),
),
migrations.AddField(
model_name='guide',
name='vintage',
field=models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2100)], verbose_name='guide vintage year'),
),
migrations.AddField(
model_name='guide',
name='guide_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='collection.GuideType', verbose_name='type'),
),
migrations.AlterField(
model_name='guide',
name='start',
field=models.DateTimeField(null=True, verbose_name='start'),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 2.2.7 on 2019-12-02 10:11
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('collection', '0018_auto_20191127_1047'),
]
operations = [
migrations.CreateModel(
name='GuideFilter',
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')),
('establishment_type_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='establishment types')),
('country_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='countries')),
('region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='regions')),
('sub_region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='sub regions')),
('wine_region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='wine regions')),
('with_mark', models.BooleanField(default=True, help_text='exclude empty marks?', verbose_name='with mark')),
('locale_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='locales')),
('max_mark', models.FloatField(help_text='mark under', null=True, verbose_name='max mark')),
('min_mark', models.FloatField(help_text='mark over', null=True, verbose_name='min mark')),
('review_vintage_json', django.contrib.postgres.fields.jsonb.JSONField(verbose_name='review vintage years')),
('review_state_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='review states')),
('old_id', models.IntegerField(blank=True, null=True)),
('guide', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='collection.Guide', verbose_name='guide')),
],
options={
'verbose_name': 'guide filter',
'verbose_name_plural': 'guide filters',
},
),
]

View File

@ -0,0 +1,56 @@
# Generated by Django 2.2.7 on 2019-12-02 14:05
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('collection', '0019_advertorial_guidefilter'),
]
operations = [
migrations.CreateModel(
name='GuideElementSectionCategory',
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')),
('name', models.CharField(max_length=255, verbose_name='category name')),
],
options={
'verbose_name': 'guide element section category',
'verbose_name_plural': 'guide element section categories',
},
),
migrations.CreateModel(
name='GuideWineColorSection',
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')),
('name', models.CharField(max_length=255, verbose_name='section name')),
],
options={
'verbose_name': 'guide wine color section',
'verbose_name_plural': 'guide wine color sections',
},
),
migrations.CreateModel(
name='GuideElementSection',
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')),
('name', models.CharField(max_length=255, verbose_name='section name')),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='collection.GuideElementSectionCategory', verbose_name='category')),
],
options={
'verbose_name': 'guide element section',
'verbose_name_plural': 'guide element sections',
},
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.7 on 2019-12-02 14:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('collection', '0020_guideelementsection_guideelementsectioncategory_guidewinecolorsection'),
]
operations = [
migrations.CreateModel(
name='GuideElementType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='name')),
],
options={
'verbose_name': 'guide element type',
'verbose_name_plural': 'guide element types',
},
),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 2.2.7 on 2019-12-02 14:44
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('review', '0018_auto_20191117_1117'),
('product', '0018_purchasedproduct'),
('location', '0030_auto_20191120_1010'),
('establishment', '0067_auto_20191122_1244'),
('collection', '0021_guideelementtype'),
]
operations = [
migrations.CreateModel(
name='GuideElement',
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')),
('priority', models.IntegerField(blank=True, default=None, null=True)),
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)),
('city', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.City')),
('establishment', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment')),
('guide', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.Guide')),
('guide_element_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideElementType', verbose_name='guide element type')),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='collection.GuideElement')),
('product', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='product.Product')),
('review', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='review.Review')),
('section', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideElementSection')),
('wine_color_section', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideWineColorSection')),
('wine_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.WineRegion')),
],
options={
'verbose_name': 'guide element',
'verbose_name_plural': 'guide elements',
},
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 2.2.7 on 2019-12-03 13:20
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('collection', '0022_guideelement'),
]
operations = [
migrations.CreateModel(
name='Advertorial',
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')),
('number_of_pages', models.PositiveIntegerField(help_text='the total number of reserved pages', verbose_name='number of pages')),
('right_pages', models.PositiveIntegerField(help_text='the number of right pages (which are part of total number).', verbose_name='number of right pages')),
('old_id', models.IntegerField(blank=True, null=True)),
('guide_element', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='advertorial', to='collection.GuideElement', verbose_name='guide element')),
],
options={
'verbose_name': 'advertorial',
'verbose_name_plural': 'advertorials',
},
),
]

View File

@ -1,5 +1,8 @@
import re
from mptt.models import MPTTModel, TreeForeignKey
from django.contrib.contenttypes.fields import ContentType from django.contrib.contenttypes.fields import ContentType
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -84,27 +87,101 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
verbose_name = _('collection') verbose_name = _('collection')
verbose_name_plural = _('collections') verbose_name_plural = _('collections')
@property
def _related_objects(self) -> list:
"""Return list of related objects."""
related_objects = []
# get related objects
for related_object in self._meta.related_objects:
related_objects.append(related_object)
return related_objects
@property
def count_related_objects(self) -> int:
"""Return count of related objects."""
counter = 0
# count of related objects
for related_object in [related_object.name for related_object in self._related_objects]:
counter += getattr(self, f'{related_object}').count()
return counter
@property
def related_object_names(self) -> list:
"""Return related object names."""
raw_object_names = []
for related_object in [related_object.name for related_object in self._related_objects]:
instances = getattr(self, f'{related_object}')
if instances.exists():
for instance in instances.all():
raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None)
# parse slugs
object_names = []
re_pattern = r'[\w]+'
for raw_name in raw_object_names:
result = re.findall(re_pattern, raw_name)
if result: object_names.append(' '.join(result).capitalize())
return set(object_names)
class GuideTypeQuerySet(models.QuerySet):
"""QuerySet for model GuideType."""
class GuideType(ProjectBaseMixin):
"""GuideType model."""
name = models.SlugField(max_length=255, unique=True,
verbose_name=_('code'))
objects = GuideTypeQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide type')
verbose_name_plural = _('guide types')
def __str__(self):
"""Overridden str dunder method."""
return self.name
class GuideQuerySet(models.QuerySet): class GuideQuerySet(models.QuerySet):
"""QuerySet for Guide.""" """QuerySet for Guide."""
def by_collection_id(self, collection_id):
"""Filter by collection id"""
return self.filter(collection=collection_id)
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""Guide model.""" """Guide model."""
parent = models.ForeignKey( BUILT = 0
'self', verbose_name=_('parent'), on_delete=models.CASCADE, WAITING = 1
null=True, blank=True, default=None REMOVING = 2
BUILDING = 3
STATE_CHOICES = (
(BUILT, 'built'),
(WAITING, 'waiting'),
(REMOVING, 'removing'),
(BUILDING, 'building'),
) )
advertorials = JSONField(
_('advertorials'), null=True, blank=True, start = models.DateTimeField(null=True,
default=None, help_text='{"key":"value"}') verbose_name=_('start'))
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, vintage = models.IntegerField(validators=[MinValueValidator(1900),
null=True, blank=True, default=None, MaxValueValidator(2100)],
verbose_name=_('collection')) null=True,
verbose_name=_('guide vintage year'))
slug = models.SlugField(max_length=255, unique=True, null=True,
verbose_name=_('slug'))
guide_type = models.ForeignKey('GuideType', on_delete=models.PROTECT,
null=True,
verbose_name=_('type'))
site = models.ForeignKey('main.SiteSettings', on_delete=models.SET_NULL,
null=True,
verbose_name=_('site settings'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('state'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideQuerySet.as_manager() objects = GuideQuerySet.as_manager()
@ -116,3 +193,190 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
def __str__(self): def __str__(self):
"""String method.""" """String method."""
return f'{self.name}' return f'{self.name}'
class AdvertorialQuerySet(models.QuerySet):
"""QuerySet for model Advertorial."""
class Advertorial(ProjectBaseMixin):
"""Guide advertorial model."""
number_of_pages = models.PositiveIntegerField(
verbose_name=_('number of pages'),
help_text=_('the total number of reserved pages'))
right_pages = models.PositiveIntegerField(
verbose_name=_('number of right pages'),
help_text=_('the number of right pages (which are part of total number).'))
guide_element = models.OneToOneField('GuideElement', on_delete=models.CASCADE,
related_name='advertorial',
verbose_name=_('guide element'))
old_id = models.IntegerField(blank=True, null=True)
objects = AdvertorialQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('advertorial')
verbose_name_plural = _('advertorials')
class GuideFilterQuerySet(models.QuerySet):
"""QuerySet for model GuideFilter."""
class GuideFilter(ProjectBaseMixin):
"""Guide filter model."""
establishment_type_json = JSONField(blank=True, null=True,
verbose_name='establishment types')
country_json = JSONField(blank=True, null=True,
verbose_name='countries')
region_json = JSONField(blank=True, null=True,
verbose_name='regions')
sub_region_json = JSONField(blank=True, null=True,
verbose_name='sub regions')
wine_region_json = JSONField(blank=True, null=True,
verbose_name='wine regions')
with_mark = models.BooleanField(default=True,
verbose_name=_('with mark'),
help_text=_('exclude empty marks?'))
locale_json = JSONField(blank=True, null=True,
verbose_name='locales')
max_mark = models.FloatField(verbose_name=_('max mark'),
null=True,
help_text=_('mark under'))
min_mark = models.FloatField(verbose_name=_('min mark'),
null=True,
help_text=_('mark over'))
review_vintage_json = JSONField(verbose_name='review vintage years')
review_state_json = JSONField(blank=True, null=True,
verbose_name='review states')
guide = models.OneToOneField(Guide, on_delete=models.CASCADE,
verbose_name=_('guide'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideFilterQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide filter')
verbose_name_plural = _('guide filters')
class GuideElementType(models.Model):
"""Model for type of guide elements."""
name = models.CharField(max_length=50,
verbose_name=_('name'))
class Meta:
"""Meta class."""
verbose_name = _('guide element type')
verbose_name_plural = _('guide element types')
def __str__(self):
"""Overridden str dunder."""
return self.name
class GuideWineColorSectionQuerySet(models.QuerySet):
"""QuerySet for model GuideWineColorSection."""
class GuideWineColorSection(ProjectBaseMixin):
"""Sections for wine colors."""
name = models.CharField(max_length=255, verbose_name=_('section name'))
objects = GuideWineColorSectionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide wine color section')
verbose_name_plural = _('guide wine color sections')
class GuideElementSectionCategoryQuerySet(models.QuerySet):
"""QuerySet for model GuideElementSectionCategory."""
class GuideElementSectionCategory(ProjectBaseMixin):
"""Section category for guide element."""
name = models.CharField(max_length=255,
verbose_name=_('category name'))
objects = GuideElementSectionCategoryQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide element section category')
verbose_name_plural = _('guide element section categories')
class GuideElementSectionQuerySet(models.QuerySet):
"""QuerySet for model GuideElementSection."""
class GuideElementSection(ProjectBaseMixin):
"""Sections for guide element."""
name = models.CharField(max_length=255, verbose_name=_('section name'))
category = models.ForeignKey(GuideElementSectionCategory, on_delete=models.PROTECT,
verbose_name=_('category'))
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('old id'))
objects = GuideElementSectionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide element section')
verbose_name_plural = _('guide element sections')
class GuideElementQuerySet(models.QuerySet):
"""QuerySet for model Guide elements."""
class GuideElement(ProjectBaseMixin, MPTTModel):
"""Frozen state of elements of guide instance."""
guide_element_type = models.ForeignKey('GuideElementType', on_delete=models.SET_NULL,
null=True,
verbose_name=_('guide element type'))
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
review = models.ForeignKey('review.Review', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
product = models.ForeignKey('product.Product', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
priority = models.IntegerField(null=True, blank=True, default=None)
city = models.ForeignKey('location.City', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
wine_color_section = models.ForeignKey('GuideWineColorSection', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
section = models.ForeignKey('GuideElementSection', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
guide = models.ForeignKey('Guide', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
parent = TreeForeignKey('self', on_delete=models.CASCADE,
null=True, blank=True,
related_name='children')
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('old id'))
objects = GuideElementQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide element')
verbose_name_plural = _('guide elements')
class MPTTMeta:
order_insertion_by = ['guide_element_type']
def __str__(self):
"""Overridden dunder method."""
return self.guide_element_type.name if self.guide_element_type else self.id

View File

@ -19,6 +19,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
collection_type_display = serializers.CharField( collection_type_display = serializers.CharField(
source='get_collection_type_display', read_only=True) source='get_collection_type_display', read_only=True)
country = CountrySimpleSerializer(read_only=True) country = CountrySimpleSerializer(read_only=True)
count_related_objects = serializers.IntegerField(read_only=True)
related_object_names = serializers.ListField(read_only=True)
class Meta: class Meta:
model = models.Collection model = models.Collection
@ -36,6 +38,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
'slug', 'slug',
'start', 'start',
'end', 'end',
'count_related_objects',
'related_object_names',
] ]

View File

@ -56,7 +56,4 @@ class GuideSerializer(serializers.ModelSerializer):
'name', 'name',
'start', 'start',
'end', 'end',
'parent',
'advertorials',
'collection'
] ]

View File

@ -0,0 +1,319 @@
from pprint import pprint
from tqdm import tqdm
from establishment.models import Establishment
from review.models import Review
from location.models import WineRegion, City
from product.models import Product
from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \
GuideAds
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
from collection.models import GuideElementSection, GuideElementSectionCategory, \
GuideWineColorSection, GuideElementType, GuideElement, \
Guide, Advertorial
def transfer_guide():
"""Transfer Guide model."""
errors = []
queryset = Guides.objects.exclude(title__icontains='test')
serialized_data = GuideSerializer(
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"ERRORS: {errors}")
print(f'COUNT OF SERIALIZED OBJECTS: {queryset.values().count()}')
def transfer_guide_filter():
"""Transfer GuideFilter model."""
errors = []
queryset = GuideFilters.objects.exclude(guide__title__icontains='test') \
.exclude(guide__isnull=True)
serialized_data = GuideFilterSerializer(
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'ERRORS: {errors}')
print(f"COUNT: {len(errors)}")
print(f'COUNT OF SERIALIZED OBJECTS: {queryset.values().count()}')
def transfer_guide_element_section():
"""Transfer GuideSections model."""
created_count = 0
category, _ = GuideElementSectionCategory.objects.get_or_create(
name='shop_category')
queryset_values = GuideSections.objects.values_list('id', 'value_name')
for old_id, section_name in tqdm(queryset_values):
obj, created = GuideElementSection.objects.get_or_create(
name=section_name,
category=category,
old_id=old_id,
)
if created: created_count += 1
print(f'OBJECTS CREATED: {created_count}')
def transfer_guide_wine_color_section():
"""Transfer GuideElements model (only wine color sections)."""
created_count = 0
queryset_values = GuideElements.objects.raw(
"""
select distinct(color),
1 as id
from guide_elements where color is not null;
"""
)
for section_name in tqdm([i.color for i in queryset_values]):
obj, created = GuideWineColorSection.objects.get_or_create(
name=section_name
)
if created: created_count += 1
print(f'OBJECTS CREATED: {created_count}')
def transfer_guide_element_type():
"""Transfer GuideElements model (only element types)."""
created_count = 0
queryset_values = GuideElements.objects.raw(
"""
select distinct(type),
1 as id
from guide_elements;
"""
)
for element_type in tqdm([i.type for i in queryset_values]):
obj, created = GuideElementType.objects.get_or_create(
name=element_type
)
if created: created_count += 1
print(f'OBJECTS CREATED: {created_count}')
def transfer_guide_elements_bulk():
"""Transfer Guide elements via bulk_create."""
def get_guide_element_type(guide_element_type: str):
if guide_element_type:
qs = GuideElementType.objects.filter(name__iexact=guide_element_type)
if qs.exists():
return qs.first()
def get_establishment(old_id: int):
if old_id:
qs = Establishment.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_review(old_id: int):
if old_id:
qs = Review.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_wine_region(old_id: int):
if old_id:
qs = WineRegion.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_wine(old_id: int):
if old_id:
qs = Product.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_wine_color_section(color_section: str):
if color_section:
qs = GuideWineColorSection.objects.filter(name__iexact=color_section)
if qs.exists():
return qs.first()
def get_city(old_id: int):
if old_id:
qs = City.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_guide_element_section(old_id: int):
if old_id:
qs = GuideElementSection.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_guide(old_id):
if old_id:
qs = Guide.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
def get_parent(old_id):
if old_id:
qs = GuideElement.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
objects_to_update = []
base_queryset = GuideElements.objects.all()
for old_id, type, establishment_id, review_id, wine_region_id, \
wine_id, color, order_number, city_id, section_id, guide_id \
in tqdm(base_queryset.filter(parent_id__isnull=True)
.values_list('id', 'type', 'establishment_id',
'review_id', 'wine_region_id', 'wine_id',
'color', 'order_number', 'city_id',
'section_id', 'guide_id'),
desc='Check parent guide elements'):
if not GuideElement.objects.filter(old_id=old_id).exists():
guide = GuideElement(
old_id=old_id,
guide_element_type=get_guide_element_type(type),
establishment=get_establishment(establishment_id),
review=get_review(review_id),
wine_region=get_wine_region(wine_region_id),
product=get_wine(wine_id),
wine_color_section=get_wine_color_section(color),
priority=order_number,
city=get_city(city_id),
section=get_guide_element_section(section_id),
parent=None,
lft=1,
rght=1,
tree_id=1,
level=1,
)
# check old guide
if not guide_id:
objects_to_update.append(guide)
else:
old_guide = Guides.objects.exclude(title__icontains='test') \
.filter(id=guide_id)
if old_guide.exists():
guide.guide = get_guide(guide_id)
objects_to_update.append(guide)
# create parents
GuideElement.objects.bulk_create(objects_to_update)
pprint(f'CREATED PARENT GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
# attach child guide elements
queryset_values = base_queryset.filter(parent_id__isnull=False) \
.order_by('-parent_id') \
.values_list('id', 'type', 'establishment_id',
'review_id', 'wine_region_id', 'wine_id',
'color', 'order_number', 'city_id',
'section_id', 'guide_id', 'parent_id')
for old_id, type, establishment_id, review_id, wine_region_id, \
wine_id, color, order_number, city_id, section_id, guide_id, parent_id \
in tqdm(sorted(queryset_values, key=lambda value: value[len(value)-1]),
desc='Check child guide elements'):
if not GuideElement.objects.filter(old_id=old_id).exists():
# check old guide
if guide_id:
old_guide = Guides.objects.exclude(title__icontains='test') \
.filter(id=guide_id)
if old_guide.exists():
GuideElement.objects.create(
old_id=old_id,
guide_element_type=get_guide_element_type(type),
establishment=get_establishment(establishment_id),
review=get_review(review_id),
wine_region=get_wine_region(wine_region_id),
product=get_wine(wine_id),
wine_color_section=get_wine_color_section(color),
priority=order_number,
city=get_city(city_id),
section=get_guide_element_section(section_id),
parent=get_parent(parent_id),
lft=1,
rght=1,
tree_id=1,
level=1,
guide=get_guide(guide_id),
)
pprint(f'CREATED CHILD GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
# rebuild trees
GuideElement._tree_manager.rebuild()
def transfer_guide_element_advertorials():
"""Transfer Guide Advertorials model."""
def get_guide_element(old_id: int):
if old_id:
qs = GuideElement.objects.filter(old_id=old_id)
legacy_qs = GuideElements.objects.exclude(guide__isnull=True) \
.exclude(guide__title__icontains='test') \
.filter(id=guide_ad_node_id)
if qs.exists() and legacy_qs.exists():
return qs.first()
elif legacy_qs.exists() and not qs.exists():
raise ValueError(f'Guide element was not transfer correctly - {old_id}.')
objects_to_update = []
advertorials = GuideAds.objects.exclude(nb_pages__isnull=True) \
.exclude(nb_right_pages__isnull=True) \
.exclude(guide_ad_node_id__isnull=True) \
.values_list('id', 'nb_pages', 'nb_right_pages',
'guide_ad_node_id')
for old_id, nb_pages, nb_right_pages, guide_ad_node_id in tqdm(advertorials):
# check guide element
guide_element = get_guide_element(guide_ad_node_id)
if not Advertorial.objects.filter(old_id=old_id).exists() and guide_element:
objects_to_update.append(
Advertorial(
old_id=old_id,
number_of_pages=nb_pages,
right_pages=nb_right_pages,
guide_element=guide_element,
)
)
# create related child
Advertorial.objects.bulk_create(objects_to_update)
pprint(f'CREATED ADVERTORIALS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
data_types = {
'guides': [
transfer_guide,
],
'guide_filters': [
transfer_guide_filter,
],
'guide_element_sections': [
transfer_guide_element_section,
],
'guide_wine_color_sections': [
transfer_guide_wine_color_section,
],
'guide_element_types': [
transfer_guide_element_type,
],
'guide_elements_bulk': [
transfer_guide_elements_bulk,
],
'guide_element_advertorials': [
transfer_guide_element_advertorials
],
'guide_complete': [
transfer_guide, # transfer guides from Guides
transfer_guide_filter, # transfer guide filters from GuideFilters
transfer_guide_element_section, # partial transfer element section from GuideSections
transfer_guide_wine_color_section, # partial transfer wine color section from GuideSections
transfer_guide_element_type, # partial transfer section types from GuideElements
transfer_guide_elements_bulk, # transfer result of GuideFilters from GuideElements
transfer_guide_element_advertorials, # transfer advertorials that linked to GuideElements
]
}

View File

@ -35,6 +35,7 @@ class ContactPhoneInline(admin.TabularInline):
class GalleryImageInline(admin.TabularInline): class GalleryImageInline(admin.TabularInline):
"""Gallery image inline admin.""" """Gallery image inline admin."""
model = models.EstablishmentGallery model = models.EstablishmentGallery
raw_id_fields = ['image', ]
extra = 0 extra = 0
@ -61,17 +62,20 @@ class ProductInline(admin.TabularInline):
class CompanyInline(admin.TabularInline): class CompanyInline(admin.TabularInline):
model = models.Company model = models.Company
raw_id_fields = ['establishment', 'address']
extra = 0 extra = 0
class EstablishmentNote(admin.TabularInline): class EstablishmentNote(admin.TabularInline):
model = models.EstablishmentNote model = models.EstablishmentNote
extra = 0 extra = 0
raw_id_fields = ['user', ]
class PurchasedProduct(admin.TabularInline): class PurchasedProductInline(admin.TabularInline):
model = PurchasedProduct model = PurchasedProduct
extra = 0 extra = 0
raw_id_fields = ['product', ]
@admin.register(models.Establishment) @admin.register(models.Establishment)
@ -80,13 +84,12 @@ class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
list_display = ['id', '__str__', 'image_tag', ] list_display = ['id', '__str__', 'image_tag', ]
search_fields = ['id', 'name', 'index_name', 'slug'] search_fields = ['id', 'name', 'index_name', 'slug']
list_filter = ['public_mark', 'toque_number'] list_filter = ['public_mark', 'toque_number']
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote, inlines = [CompanyInline, EstablishmentNote, GalleryImageInline,
PurchasedProduct] PurchasedProductInline, ]
# inlines = [ # inlines = [
# AwardInline, ContactPhoneInline, ContactEmailInline, # AwardInline, ContactPhoneInline, ContactEmailInline,
# ReviewInline, CommentInline, ProductInline] # ReviewInline, CommentInline, ProductInline]
raw_id_fields = ('address',) raw_id_fields = ('address', 'collections', 'tags', 'schedule')
@admin.register(models.Position) @admin.register(models.Position)
@ -136,3 +139,4 @@ class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin): class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for Company model.""" """Admin conf for Company model."""
raw_id_fields = ['establishment', 'address', ] raw_id_fields = ['establishment', 'address', ]

View File

@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from location.models import WineOriginAddress, EstablishmentWineOriginAddress
from product.models import Product
class Command(BaseCommand):
help = 'Add to establishment wine origin object.'
def handle(self, *args, **kwarg):
create_counter = 0
for product in Product.objects.exclude(establishment__isnull=True):
establishment = product.establishment
if product.wine_origins.exists():
for wine_origin in product.wine_origins.all():
wine_region = wine_origin.wine_region
wine_sub_region = wine_origin.wine_sub_region
if not EstablishmentWineOriginAddress.objects.filter(establishment=establishment,
wine_region=wine_region,
wine_sub_region=wine_sub_region) \
.exists():
EstablishmentWineOriginAddress.objects.create(
establishment=establishment,
wine_region=wine_origin.wine_region,
wine_sub_region=wine_origin.wine_sub_region,
)
create_counter += 1
self.stdout.write(self.style.WARNING(f'COUNT CREATED OBJECTS: {create_counter}'))

View File

@ -0,0 +1,36 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from transfer.models import Establishments
from transfer.serializers.establishment import EstablishmentSerializer
from timetable.models import Timetable
from django.db import transaction
class Command(BaseCommand):
help = 'Fix scheduler'
@transaction.atomic
def handle(self, *args, **kwargs):
count = 0
establishments = Establishment.objects.all()
old_est_list = Establishments.objects.prefetch_related(
'schedules_set',
)
# remove old records of Timetable
Timetable.objects.all().delete()
for est in tqdm(establishments, desc="Fix scheduler"):
old_est = old_est_list.filter(id=est.old_id).first()
if old_est and old_est.schedules_set.exists():
old_schedule = old_est.schedules_set.first()
timetable = old_schedule.timetable
if timetable:
new_schedules = EstablishmentSerializer.get_schedules(timetable)
est.schedule.add(*new_schedules)
est.save()
count += 1
self.stdout.write(self.style.WARNING(f'Update {count} objects.'))

View File

@ -1,8 +1,8 @@
"""Establishment models.""" """Establishment models."""
from datetime import datetime from datetime import datetime
from functools import reduce from functools import reduce
from typing import List
from operator import or_ from operator import or_
from typing import List
import elasticsearch_dsl import elasticsearch_dsl
from django.conf import settings from django.conf import settings
@ -22,6 +22,7 @@ from timezone_field import TimeZoneField
from collection.models import Collection from collection.models import Collection
from location.models import Address from location.models import Address
from location.models import WineOriginAddressMixin
from main.models import Award, Currency from main.models import Award, Currency
from tag.models import Tag from tag.models import Tag
from review.models import Review from review.models import Review
@ -251,6 +252,15 @@ class EstablishmentQuerySet(models.QuerySet):
return self.filter(id__in=subquery_filter_by_distance) \ return self.filter(id__in=subquery_filter_by_distance) \
.order_by('-reviews__published_at') .order_by('-reviews__published_at')
def prefetch_comments(self):
"""Prefetch last comment."""
from comment.models import Comment
return self.prefetch_related(
models.Prefetch('comments',
queryset=Comment.objects.exclude(is_publish=False).order_by('-created'),
to_attr='comments_prefetched')
)
def prefetch_actual_employees(self): def prefetch_actual_employees(self):
"""Prefetch actual employees.""" """Prefetch actual employees."""
return self.prefetch_related( return self.prefetch_related(
@ -448,9 +458,14 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
return super().visible_tags \ return super().visible_tags \
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item', .exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
'business_tag', 'business_tags_de']) \ 'business_tag', 'business_tags_de']) \
\ .exclude(value__in=['rss', 'rss_selection'])
# todo: recalculate toque_number # todo: recalculate toque_number
@property
def visible_tags_detail(self):
"""Removes some tags from detail Establishment representation"""
return self.visible_tags.exclude(category__index_name__in=['tag'])
def recalculate_toque_number(self): def recalculate_toque_number(self):
toque_number = 0 toque_number = 0
if self.address and self.public_mark: if self.address and self.public_mark:
@ -614,6 +629,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
def artisan_category_indexing(self): def artisan_category_indexing(self):
return self.tags.filter(category__index_name='shop_category') return self.tags.filter(category__index_name='shop_category')
@property
def last_comment(self):
if hasattr(self, 'comments_prefetched') and len(self.comments_prefetched):
return self.comments_prefetched[0]
@property
def wine_origins_unique(self):
return self.wine_origins.distinct('wine_region')
class EstablishmentNoteQuerySet(models.QuerySet): class EstablishmentNoteQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentNote.""" """QuerySet for model EstablishmentNote."""

View File

@ -232,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
def validate(self, attrs): def validate(self, attrs):
"""Override validate method.""" """Override validate method."""
establishment_pk = self.get_request_kwargs().get('pk') establishment_pk = self.get_request_kwargs().get('pk')
establishment_slug = self.get_request_kwargs().get('slug')
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
image_id = self.get_request_kwargs().get('image_id') image_id = self.get_request_kwargs().get('image_id')
establishment_qs = models.Establishment.objects.filter(pk=establishment_pk) establishment_qs = models.Establishment.objects.filter(**search_kwargs)
image_qs = Image.objects.filter(id=image_id) image_qs = Image.objects.filter(id=image_id)
if not establishment_qs.exists(): if not establishment_qs.exists():

View File

@ -16,6 +16,8 @@ from utils import exceptions as utils_exceptions
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
from utils.serializers import (ProjectModelSerializer, TranslatedField, from utils.serializers import (ProjectModelSerializer, TranslatedField,
FavoritesCreateSerializer) FavoritesCreateSerializer)
from location.serializers import EstablishmentWineRegionBaseSerializer, \
EstablishmentWineOriginBaseSerializer
class ContactPhonesSerializer(serializers.ModelSerializer): class ContactPhonesSerializer(serializers.ModelSerializer):
@ -237,6 +239,30 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
] ]
class _EstablishmentAddressShortSerializer(serializers.ModelSerializer):
"""Short serializer for establishment."""
city = CitySerializer(source='address.city', allow_null=True)
establishment_type = EstablishmentTypeGeoSerializer()
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
currency = CurrencySerializer(read_only=True)
address = AddressBaseSerializer(read_only=True)
class Meta:
"""Meta class."""
model = models.Establishment
fields = [
'id',
'name',
'index_name',
'slug',
'city',
'establishment_type',
'establishment_subtypes',
'currency',
'address',
]
class EstablishmentProductShortSerializer(serializers.ModelSerializer): class EstablishmentProductShortSerializer(serializers.ModelSerializer):
"""SHORT Serializer for displaying info about an establishment on product page.""" """SHORT Serializer for displaying info about an establishment on product page."""
establishment_type = EstablishmentTypeGeoSerializer() establishment_type = EstablishmentTypeGeoSerializer()
@ -283,6 +309,8 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes') subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
image = serializers.URLField(source='image_url', read_only=True) image = serializers.URLField(source='image_url', read_only=True)
wine_regions = EstablishmentWineRegionBaseSerializer(many=True, source='wine_origins_unique',
read_only=True, allow_null=True)
preview_image = serializers.URLField(source='preview_image_url', preview_image = serializers.URLField(source='preview_image_url',
allow_null=True, allow_null=True,
read_only=True) read_only=True)
@ -312,6 +340,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
'preview_image', 'preview_image',
'new_image', 'new_image',
'tz', 'tz',
'wine_regions',
] ]
@ -364,7 +393,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=True) many=True)
address = AddressDetailSerializer(read_only=True) address = AddressDetailSerializer(read_only=True)
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags_detail')
menu = MenuSerializers(source='menu_set', many=True, 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_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) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
@ -372,6 +401,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
range_price_carte = RangePriceSerializer(read_only=True) range_price_carte = RangePriceSerializer(read_only=True)
vintage_year = serializers.ReadOnlyField() vintage_year = serializers.ReadOnlyField()
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True) gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True)
class Meta(EstablishmentBaseSerializer.Meta): class Meta(EstablishmentBaseSerializer.Meta):
"""Meta class.""" """Meta class."""
@ -398,6 +428,20 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
'transportation', 'transportation',
'vintage_year', 'vintage_year',
'gallery', 'gallery',
'wine_origins',
]
class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer):
"""Serializer for Establishment model for mobiles."""
last_comment = comment_serializers.CommentRUDSerializer(allow_null=True)
class Meta(EstablishmentDetailSerializer.Meta):
"""Meta class."""
fields = EstablishmentDetailSerializer.Meta.fields + [
'last_comment',
] ]
@ -405,6 +449,16 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
"""Serializer for Establishment model.""" """Serializer for Establishment model."""
address = AddressDetailSerializer(read_only=True) address = AddressDetailSerializer(read_only=True)
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
establishment_type = EstablishmentTypeGeoSerializer()
artisan_category = TagBaseSerializer(many=True, allow_null=True)
class Meta(EstablishmentBaseSerializer.Meta):
fields = EstablishmentBaseSerializer.Meta.fields + [
'schedule',
'establishment_type',
'artisan_category',
]
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
@ -492,7 +546,9 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
"""Serializer to carousel object w/ model News.""" """Serializer to carousel object w/ model News."""
def validate(self, attrs): def validate(self, attrs):
establishment = models.Establishment.objects.filter(pk=self.pk).first() search_kwargs = {'pk': self.pk} if self.pk else {'slug': self.slug}
establishment = models.Establishment.objects.filter(**search_kwargs).first()
if not establishment: if not establishment:
raise serializers.ValidationError({'detail': _('Object not found.')}) raise serializers.ValidationError({'detail': _('Object not found.')})

View File

@ -104,18 +104,18 @@ class EstablishmentBTests(BaseTestCase):
response = self.client.post('/api/back/establishments/', data=data, format='json') response = self.client.post('/api/back/establishments/', data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'name': 'Test new establishment' 'name': 'Test new establishment'
} }
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/',
data=update_data) data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/',
format='json') format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -372,22 +372,22 @@ class EstablishmentShedulerTests(ChildTestCase):
'weekday': 1 'weekday': 1
} }
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data) response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
schedule = response.data schedule = response.data
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'weekday': 2 'weekday': 2
} }
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/', response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/',
data=update_data) data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -484,8 +484,8 @@ class EstablishmentCarouselTests(ChildTestCase):
"object_id": self.establishment.id "object_id": self.establishment.id
} }
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/carousels/', data=data) response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/carousels/') response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

View File

@ -8,25 +8,25 @@ app_name = 'establishment'
urlpatterns = [ urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'), path('', views.EstablishmentListCreateView.as_view(), name='list'),
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'), path('slug/<slug:slug>/', views.EstablishmentRUDView.as_view(), name='detail'),
path('<int:pk>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(), path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
name='create-destroy-carousels'), name='create-destroy-carousels'),
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(), path('slug/<slug:slug>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
name='schedule-rud'), name='schedule-rud'),
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(), path('slug/<slug:slug>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
name='schedule-create'), name='schedule-create'),
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(), path('slug/<slug:slug>/gallery/', views.EstablishmentGalleryListView.as_view(),
name='gallery-list'), name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', path('slug/<slug:slug>/gallery/<int:image_id>/',
views.EstablishmentGalleryCreateDestroyView.as_view(), views.EstablishmentGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
path('<int:pk>/companies/', views.EstablishmentCompanyListCreateView.as_view(), path('slug/<slug:slug>/companies/', views.EstablishmentCompanyListCreateView.as_view(),
name='company-list-create'), name='company-list-create'),
path('<int:pk>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(), path('slug/<slug:slug>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(),
name='company-rud'), name='company-rud'),
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(), path('slug/<slug:slug>/notes/', views.EstablishmentNoteListCreateView.as_view(),
name='note-list-create'), name='note-list-create'),
path('<int:pk>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(), path('slug/<slug:slug>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(),
name='note-rud'), name='note-rud'),
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'), path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),

View File

@ -9,7 +9,6 @@ urlpatterns = [
path('', views.EstablishmentListView.as_view(), name='list'), path('', views.EstablishmentListView.as_view(), name='list'),
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
name='recent-reviews'), name='recent-reviews'),
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(), path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),

View File

@ -5,7 +5,8 @@ from establishment import views
from establishment.urls.common import urlpatterns as common_urlpatterns from establishment.urls.common import urlpatterns as common_urlpatterns
urlpatterns = [ urlpatterns = [
path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list') path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list'),
path('slug/<slug:slug>/', views.EstablishmentMobileRetrieveView.as_view(), name='mobile-detail'),
] ]
urlpatterns.extend(common_urlpatterns) urlpatterns.extend(common_urlpatterns)

View File

@ -1,7 +1,11 @@
"""Establishment app web urlconf.""" """Establishment app web urlconf."""
from establishment.urls.common import urlpatterns as common_urlpatterns from establishment.urls.common import urlpatterns as common_urlpatterns
from django.urls import path
from establishment import views
urlpatterns = [] urlpatterns = [
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='web-detail'),
]
urlpatterns.extend(common_urlpatterns) urlpatterns.extend(common_urlpatterns)

View File

@ -31,6 +31,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'slug'
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentRUDSerializer serializer_class = serializers.EstablishmentRUDSerializer
permission_classes = [IsCountryAdmin | IsEstablishmentManager] permission_classes = [IsCountryAdmin | IsEstablishmentManager]
@ -38,6 +39,7 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view""" """Establishment schedule RUD view"""
lookup_field = 'slug'
serializer_class = ScheduleRUDSerializer serializer_class = ScheduleRUDSerializer
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
@ -45,11 +47,11 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
""" """
Returns the object the view is displaying. Returns the object the view is displaying.
""" """
establishment_pk = self.kwargs['pk'] establishment_slug = self.kwargs['slug']
schedule_id = self.kwargs['schedule_id'] schedule_id = self.kwargs['schedule_id']
establishment = get_object_or_404(klass=models.Establishment.objects.all(), establishment = get_object_or_404(klass=models.Establishment.objects.all(),
pk=establishment_pk) slug=establishment_slug)
schedule = get_object_or_404(klass=establishment.schedule, schedule = get_object_or_404(klass=establishment.schedule,
id=schedule_id) id=schedule_id)
@ -62,6 +64,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleCreateView(generics.CreateAPIView): class EstablishmentScheduleCreateView(generics.CreateAPIView):
"""Establishment schedule Create view""" """Establishment schedule Create view"""
lookup_field = 'slug'
serializer_class = ScheduleCreateSerializer serializer_class = ScheduleCreateSerializer
queryset = Timetable.objects.all() queryset = Timetable.objects.all()
permission_classes = [IsEstablishmentManager] permission_classes = [IsEstablishmentManager]
@ -165,7 +168,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
pagination_class = None pagination_class = None
class EstablishmentEmployeeListView(generics.ListAPIView): class EstablishmentEmployeeListView(generics.ListCreateAPIView):
"""Establishment emplyoees list view.""" """Establishment emplyoees list view."""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.EstablishmentEmployeeBackSerializer serializer_class = serializers.EstablishmentEmployeeBackSerializer
@ -210,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
CreateDestroyGalleryViewMixin): CreateDestroyGalleryViewMixin):
"""Resource for a create|destroy gallery for establishment for back-office users.""" """Resource for a create|destroy gallery for establishment for back-office users."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
def get_object(self): def get_object(self):
@ -218,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
""" """
establishment_qs = self.filter_queryset(self.get_queryset()) establishment_qs = self.filter_queryset(self.get_queryset())
establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(establishment_qs, slug=self.kwargs.get('slug'))
gallery = get_object_or_404(establishment.establishment_gallery, gallery = get_object_or_404(establishment.establishment_gallery,
image_id=self.kwargs.get('image_id')) image_id=self.kwargs.get('image_id'))
@ -231,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
class EstablishmentGalleryListView(EstablishmentMixinViews, class EstablishmentGalleryListView(EstablishmentMixinViews,
generics.ListAPIView): generics.ListAPIView):
"""Resource for returning gallery for establishment for back-office users.""" """Resource for returning gallery for establishment for back-office users."""
lookup_field = 'slug'
serializer_class = serializers.ImageBaseSerializer serializer_class = serializers.ImageBaseSerializer
def get_object(self): def get_object(self):
"""Override get_object method.""" """Override get_object method."""
qs = super(EstablishmentGalleryListView, self).get_queryset() qs = super(EstablishmentGalleryListView, self).get_queryset()
establishment = get_object_or_404(qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -252,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView): generics.ListCreateAPIView):
"""List|Create establishment company view.""" """List|Create establishment company view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentCompanyListCreateSerializer serializer_class = serializers.EstablishmentCompanyListCreateSerializer
def get_object(self): def get_object(self):
@ -259,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -275,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
generics.RetrieveUpdateDestroyAPIView): generics.RetrieveUpdateDestroyAPIView):
"""Create|Retrieve|Update|Destroy establishment company view.""" """Create|Retrieve|Update|Destroy establishment company view."""
lookup_field = 'slug'
serializer_class = serializers.CompanyBaseSerializer serializer_class = serializers.CompanyBaseSerializer
def get_object(self): def get_object(self):
@ -282,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk')) company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
# May raise a permission denied # May raise a permission denied
@ -295,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView): generics.ListCreateAPIView):
"""Retrieve|Update|Destroy establishment note view.""" """Retrieve|Update|Destroy establishment note view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentNoteListCreateSerializer serializer_class = serializers.EstablishmentNoteListCreateSerializer
def get_object(self): def get_object(self):
@ -302,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -318,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
generics.RetrieveUpdateDestroyAPIView): generics.RetrieveUpdateDestroyAPIView):
"""Create|Retrieve|Update|Destroy establishment note view.""" """Create|Retrieve|Update|Destroy establishment note view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentNoteBaseSerializer serializer_class = serializers.EstablishmentNoteBaseSerializer
def get_object(self): def get_object(self):
@ -325,7 +334,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk']) note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
# May raise a permission denied # May raise a permission denied

View File

@ -38,7 +38,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
.with_extended_address_related().with_currency_related() \ .with_extended_address_related().with_currency_related() \
.with_certain_tag_category_related('category', 'restaurant_category') \ .with_certain_tag_category_related('category', 'restaurant_category') \
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
.with_ceratin_tag_category_related('shop_category', 'artisan_category') .with_certain_tag_category_related('shop_category', 'artisan_category')
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
@ -51,6 +51,13 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView
return super().get_queryset().with_extended_related() return super().get_queryset().with_extended_related()
class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
serializer_class = serializers.MobileEstablishmentDetailSerializer
def get_queryset(self):
return super().get_queryset().prefetch_comments()
class EstablishmentRecentReviewListView(EstablishmentListView): class EstablishmentRecentReviewListView(EstablishmentListView):
"""List view for last reviewed establishments.""" """List view for last reviewed establishments."""
@ -107,10 +114,7 @@ class EstablishmentCommentListView(generics.ListAPIView):
"""Override get_queryset method""" """Override get_queryset method"""
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug']) establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
return comment_models.Comment.objects.by_content_type(app_label='establishment', return establishment.comments.order_by('-created')
model='establishment') \
.by_object_id(object_id=establishment.pk) \
.order_by('-created')
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -145,6 +149,7 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView): class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
"""View for create/destroy establishment from carousel.""" """View for create/destroy establishment from carousel."""
lookup_field = 'slug'
_model = models.Establishment _model = models.Establishment
serializer_class = serializers.EstablishmentCarouselCreateSerializer serializer_class = serializers.EstablishmentCarouselCreateSerializer

View File

@ -29,7 +29,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Override get_queryset method"""
return Establishment.objects.filter(favorites__user=self.request.user) \ return Establishment.objects.filter(favorites__user=self.request.user) \
.order_by('-favorites') .order_by('-favorites').with_base_related() \
.with_certain_tag_category_related('shop_category', 'artisan_category')
class FavoritesProductListView(generics.ListAPIView): class FavoritesProductListView(generics.ListAPIView):

View File

@ -45,3 +45,15 @@ class AddressAdmin(admin.OSMGeoAdmin):
def geo_lat(self, item): def geo_lat(self, item):
if isinstance(item.coordinates, Point): if isinstance(item.coordinates, Point):
return item.coordinates.y return item.coordinates.y
@admin.register(models.EstablishmentWineOriginAddress)
class EstablishmentWineOriginAddress(admin.ModelAdmin):
"""Admin model for EstablishmentWineOriginAddress."""
raw_id_fields = ['establishment', ]
@admin.register(models.WineOriginAddress)
class WineOriginAddress(admin.ModelAdmin):
"""Admin page for model WineOriginAddress."""
raw_id_fields = ['product', ]

View File

@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from location.models import Address
from transfer.models import Locations
class Command(BaseCommand):
help = """Fix address, clear number field and fill street_name_1 like in old db"""
def handle(self, *args, **kwarg):
addresses = Address.objects.filter(
old_id__isnull=False
).values_list('old_id', flat=True)
old_addresses = Locations.objects.filter(
id__in=list(addresses)
).values_list('id', 'address')
update_address = []
for idx, address in tqdm(old_addresses):
new_address = Address.objects.filter(old_id=idx).first()
if new_address:
new_address.number = 0
new_address.street_name_2 = ''
new_address.street_name_1 = address
update_address.append(new_address)
Address.objects.bulk_update(update_address, ['number', 'street_name_1', 'street_name_2'])
self.stdout.write(self.style.WARNING(f'Updated addresses: {len(update_address)}'))

View File

@ -0,0 +1,42 @@
# Generated by Django 2.2.7 on 2019-12-04 14:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0067_auto_20191122_1244'),
('product', '0019_auto_20191204_1420'),
('location', '0030_auto_20191120_1010'),
]
operations = [
migrations.CreateModel(
name='WineOriginAddress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wine_origins', to='product.Product', verbose_name='product')),
('wine_region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.WineRegion', verbose_name='wine region')),
('wine_sub_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.WineSubRegion', verbose_name='wine sub region')),
],
options={
'verbose_name': 'wine origin address',
'verbose_name_plural': 'wine origin addresses',
},
),
migrations.CreateModel(
name='EstablishmentWineOriginAddress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wine_origins', to='establishment.Establishment', verbose_name='product')),
('wine_region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.WineRegion', verbose_name='wine region')),
('wine_sub_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.WineSubRegion', verbose_name='wine sub region')),
],
options={
'verbose_name': 'establishment wine origin address',
'verbose_name_plural': 'establishment wine origin addresses',
},
),
]

View File

@ -207,12 +207,12 @@ class WineRegionQuerySet(models.QuerySet):
def with_sub_region_related(self): def with_sub_region_related(self):
return self.prefetch_related('wine_sub_region') return self.prefetch_related('wine_sub_region')
def having_wines(self, value = True): def having_wines(self, value=True):
"""Return qs with regions, which have any wine related to them""" """Return qs with regions, which have any wine related to them"""
return self.exclude(wines__isnull=value) return self.exclude(wineoriginaddress__product__isnull=value)
class WineRegion(models.Model, TranslatedFieldsMixin): class WineRegion(TranslatedFieldsMixin, models.Model):
"""Wine region model.""" """Wine region model."""
name = models.CharField(_('name'), max_length=255) name = models.CharField(_('name'), max_length=255)
country = models.ForeignKey(Country, on_delete=models.PROTECT, country = models.ForeignKey(Country, on_delete=models.PROTECT,
@ -293,6 +293,55 @@ class WineVillage(models.Model):
return self.name return self.name
class WineOriginAddressMixin(models.Model):
"""Model for wine origin address."""
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.CASCADE,
verbose_name=_('wine region'))
wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.CASCADE,
blank=True, null=True, default=None,
verbose_name=_('wine sub region'))
class Meta:
"""Meta class."""
abstract = True
class EstablishmentWineOriginAddressQuerySet(models.QuerySet):
"""QuerySet for EstablishmentWineOriginAddress model."""
class EstablishmentWineOriginAddress(WineOriginAddressMixin):
"""Establishment wine origin address model."""
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE,
related_name='wine_origins',
verbose_name=_('product'))
objects = EstablishmentWineOriginAddressQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('establishment wine origin address')
verbose_name_plural = _('establishment wine origin addresses')
class WineOriginAddressQuerySet(models.QuerySet):
"""QuerySet for WineOriginAddress model."""
class WineOriginAddress(WineOriginAddressMixin):
"""Wine origin address model."""
product = models.ForeignKey('product.Product', on_delete=models.CASCADE,
related_name='wine_origins',
verbose_name=_('product'))
objects = WineOriginAddressQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('wine origin address')
verbose_name_plural = _('wine origin addresses')
# todo: Make recalculate price levels # todo: Make recalculate price levels
@receiver(post_save, sender=Country) @receiver(post_save, sender=Country)
def run_recalculate_price_levels(sender, instance, **kwargs): def run_recalculate_price_levels(sender, instance, **kwargs):

View File

@ -191,10 +191,58 @@ class WineSubRegionBaseSerializer(serializers.ModelSerializer):
] ]
class WineRegionSerializer(WineRegionBaseSerializer): class EstablishmentWineRegionBaseSerializer(serializers.ModelSerializer):
"""Wine region w/ subregion serializer""" """Establishment wine region origin serializer."""
wine_sub_region = WineSubRegionBaseSerializer(allow_null=True, many=True) id = serializers.IntegerField(source='wine_region.id')
name = serializers.CharField(source='wine_region.name')
country = CountrySerializer(source='wine_region.country')
class Meta:
"""Meta class."""
model = models.EstablishmentWineOriginAddress
fields = [
'id',
'name',
'country',
]
class EstablishmentWineOriginBaseSerializer(serializers.ModelSerializer):
"""Serializer for intermediate model EstablishmentWineOrigin."""
wine_region = WineRegionBaseSerializer()
wine_sub_region = WineSubRegionBaseSerializer(allow_null=True)
class Meta:
"""Meta class."""
model = models.EstablishmentWineOriginAddress
fields = [
'wine_region',
'wine_sub_region',
]
class WineOriginRegionBaseSerializer(EstablishmentWineRegionBaseSerializer):
"""Product wine region origin serializer."""
class Meta(EstablishmentWineRegionBaseSerializer.Meta):
"""Meta class."""
model = models.WineOriginAddress
class WineOriginBaseSerializer(EstablishmentWineOriginBaseSerializer):
"""Serializer for intermediate model ProductWineOrigin."""
class Meta(EstablishmentWineOriginBaseSerializer.Meta):
"""Meta class."""
model = models.WineOriginAddress
class WineRegionSerializer(serializers.ModelSerializer):
"""Wine region w/ sub region serializer"""
wine_sub_region = WineSubRegionBaseSerializer(source='wine_region.wine_sub_region',
allow_null=True, many=True)
class Meta(WineRegionBaseSerializer.Meta): class Meta(WineRegionBaseSerializer.Meta):
fields = WineRegionBaseSerializer.Meta.fields + [ fields = WineRegionBaseSerializer.Meta.fields + [

View File

@ -0,0 +1,37 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from main.models import Carousel
from transfer.models import HomePages
from location.models import Country
from django.db.models import F
class Command(BaseCommand):
help = '''Add establishment form HomePage to Carousel!'''
@staticmethod
def get_country(country_code):
return Country.objects.filter(code__iexact=country_code).first()
def handle(self, *args, **kwargs):
objects = []
deleted = 0
hp_list = HomePages.objects.annotate(
country=F('site__country_code_2'),
).all()
for hm in tqdm(hp_list, desc='Add home_page.establishments to carousel'):
est = Establishment.objects.filter(old_id=hm.selection_of_week).first()
if est:
if est.carousels.exists():
est.carousels.all().delete()
deleted += 1
carousel = Carousel(
content_object=est,
country=self.get_country(hm.country)
)
objects.append(carousel)
Carousel.objects.bulk_create(objects)
self.stdout.write(
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} carousel objects.'))

View File

@ -17,9 +17,24 @@ class FeatureSerializer(serializers.ModelSerializer):
fields = ( fields = (
'id', 'id',
'slug', 'slug',
'priority' 'priority',
'route',
'site_settings',
) )
class CurrencySerializer(ProjectModelSerializer):
"""Currency serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Currency
fields = [
'id',
'name_translated',
'sign'
]
class SiteFeatureSerializer(serializers.ModelSerializer): class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id') id = serializers.IntegerField(source='feature.id')
@ -42,20 +57,6 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
) )
class CurrencySerializer(ProjectModelSerializer):
"""Currency serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Currency
fields = [
'id',
'name_translated',
'sign'
]
class SiteSettingsSerializer(serializers.ModelSerializer): class SiteSettingsSerializer(serializers.ModelSerializer):
"""Site settings serializer.""" """Site settings serializer."""
@ -99,23 +100,15 @@ class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer):
] ]
class SiteSerializer(serializers.ModelSerializer): class SiteSerializer(SiteSettingsSerializer):
country = CountrySerializer() country = CountrySerializer()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
model = models.SiteSettings model = models.SiteSettings
fields = [ fields = SiteSettingsSerializer.Meta.fields + [
'subdomain', 'id',
'site_url', 'country'
'country',
'default_site',
'pinterest_page_url',
'twitter_page_url',
'facebook_page_url',
'instagram_page_url',
'contact_email',
'currency'
] ]
@ -129,45 +122,6 @@ class SiteShortSerializer(serializers.ModelSerializer):
] ]
class SiteBackOfficeSerializer(SiteSerializer):
"""Serializer for back office."""
class Meta(SiteSerializer.Meta):
"""Meta class."""
fields = SiteSerializer.Meta.fields + [
'id',
]
class FeatureSerializer(serializers.ModelSerializer):
"""Site feature serializer."""
class Meta:
"""Meta class."""
model = models.Feature
fields = (
'id',
'slug',
'priority',
'route',
'site_settings',
)
# class SiteFeatureSerializer(serializers.ModelSerializer):
# """Site feature serializer."""
#
# class Meta:
# """Meta class."""
#
# model = models.SiteFeature
# fields = (
# 'id',
# 'published',
# 'site_settings',
# 'feature',
# )
class AwardBaseSerializer(serializers.ModelSerializer): class AwardBaseSerializer(serializers.ModelSerializer):

View File

@ -13,5 +13,11 @@ urlpatterns = [
path('site-settings/<subdomain>/', views.SiteSettingsBackOfficeView.as_view(), path('site-settings/<subdomain>/', views.SiteSettingsBackOfficeView.as_view(),
name='site-settings'), name='site-settings'),
path('feature/', views.FeatureBackView.as_view(), name='feature-list-create'), path('feature/', views.FeatureBackView.as_view(), name='feature-list-create'),
path('feature/<int:id>/', views.FeatureRUDBackView.as_view(), name='feature-rud') path('feature/<int:id>/', views.FeatureRUDBackView.as_view(), name='feature-rud'),
path('site-feature/', views.SiteFeatureBackView.as_view(),
name='site-feature-list-create'),
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
name='site-feature-rud'),
] ]

View File

@ -44,16 +44,26 @@ class FeatureBackView(generics.ListCreateAPIView):
serializer_class = serializers.FeatureSerializer serializer_class = serializers.FeatureSerializer
class SiteFeatureBackView(generics.ListCreateAPIView):
"""Feature list or create View."""
serializer_class = serializers.SiteFeatureSerializer
class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
"""Feature RUD View.""" """Feature RUD View."""
serializer_class = serializers.FeatureSerializer serializer_class = serializers.FeatureSerializer
class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
"""Feature RUD View."""
serializer_class = serializers.SiteFeatureSerializer
class SiteSettingsBackOfficeView(SiteSettingsView): class SiteSettingsBackOfficeView(SiteSettingsView):
"""Site settings View.""" """Site settings View."""
serializer_class = serializers.SiteSettingsBackOfficeSerializer serializer_class = serializers.SiteSerializer
class SiteListBackOfficeView(SiteListView): class SiteListBackOfficeView(SiteListView):
"""Site settings View.""" """Site settings View."""
serializer_class = serializers.SiteBackOfficeSerializer serializer_class = serializers.SiteSerializer

View File

@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from news.models import News
import re
class Command(BaseCommand):
help = 'Removes empty img html tags from news description'
relative_img_regex = re.compile(r'\<img.+src=(?!https?:\/\/)([^\/].+?)[\"|\']>', re.I)
def handle(self, *args, **kwargs):
for news in News.objects.all():
if isinstance(news.description, dict):
news.description = {locale: self.relative_img_regex.sub('', rich_text)
for locale, rich_text in news.description.items()}
self.stdout.write(self.style.WARNING(f'Replaced {news} empty img html tags...\n'))
news.save()

View File

@ -8,7 +8,7 @@ from partner.serializers import common as serializers
# Mixins # Mixins
class PartnerViewMixin(generics.GenericAPIView): class PartnerViewMixin(generics.GenericAPIView):
"""View mixin for Partner views""" """View mixin for Partner views"""
queryset = models.Partner.objects.all() queryset = models.Partner.objects.distinct("name")
# Views # Views

View File

@ -1,5 +1,6 @@
"""Product admin conf.""" """Product admin conf."""
from django.contrib import admin from django.contrib import admin
from utils.admin import BaseModelAdminMixin from utils.admin import BaseModelAdminMixin
from .models import Product, ProductType, ProductSubType, ProductGallery, Unit from .models import Product, ProductType, ProductSubType, ProductGallery, Unit

View File

@ -9,6 +9,7 @@ class ProductFilterSet(filters.FilterSet):
"""Product filter set.""" """Product filter set."""
establishment_id = filters.NumberFilter() establishment_id = filters.NumberFilter()
current_product = filters.CharFilter(method='without_current_product')
product_type = filters.CharFilter(method='by_product_type') product_type = filters.CharFilter(method='by_product_type')
product_subtype = filters.CharFilter(method='by_product_subtype') product_subtype = filters.CharFilter(method='by_product_subtype')
@ -21,6 +22,11 @@ class ProductFilterSet(filters.FilterSet):
'product_subtype', 'product_subtype',
] ]
def without_current_product(self, queryset, name, value):
if value not in EMPTY_VALUES:
return queryset.without_current_product(value)
return queryset
def by_product_type(self, queryset, name, value): def by_product_type(self, queryset, name, value):
if value not in EMPTY_VALUES: if value not in EMPTY_VALUES:
return queryset.by_product_type(value) return queryset.by_product_type(value)

View File

@ -0,0 +1,25 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from product.models import Product
class Command(BaseCommand):
help = """Add public_mark to product from reviews."""
def handle(self, *args, **kwarg):
update_products = []
products = Product.objects.filter(
public_mark__isnull=True,
reviews__isnull=False).distinct()
for product in tqdm(products):
review = product.reviews.published().filter(
mark__isnull=False).order_by('-published_at').first()
if review:
product.public_mark = review.mark
update_products.append(product)
Product.objects.bulk_update(update_products, ['public_mark', ])
self.stdout.write(
self.style.WARNING(f'Updated products: {len(update_products)}'))

View File

@ -0,0 +1,44 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from location.models import WineOriginAddress
from product.models import Product
from transfer.models import Products
from transfer.serializers.product import ProductSerializer
class Command(BaseCommand):
help = 'Add to product wine origin object.'
def handle(self, *args, **kwarg):
def get_product(old_id: int):
if old_id:
qs = Product.objects.filter(old_id=old_id)
if qs.exists():
return qs.first()
objects_to_create = []
products = Products.objects.exclude(wine_region_id__isnull=True) \
.values_list('id', 'wine_sub_region_id', 'wine_region_id')
for old_id, wine_sub_region_id, wine_region_id in tqdm(products):
product = get_product(old_id)
if product:
wine_sub_region = ProductSerializer.get_wine_sub_region(wine_sub_region_id)
wine_region = ProductSerializer.get_wine_region(wine_region_id)
if wine_region:
filters = {
'product': product,
'wine_region': wine_region}
wine_origin_address = WineOriginAddress(
product=product,
wine_region=wine_region)
if wine_sub_region:
filters.update({'wine_sub_region': wine_sub_region})
wine_origin_address.wine_sub_region = wine_sub_region
if not WineOriginAddress.objects.filter(**filters).exists():
objects_to_create.append(wine_origin_address)
WineOriginAddress.objects.bulk_create(objects_to_create)
self.stdout.write(self.style.WARNING(f'COUNT CREATED OBJECTS: {len(objects_to_create)}'))

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.7 on 2019-12-04 14:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0018_purchasedproduct'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='wine_region',
),
migrations.RemoveField(
model_name='product',
name='wine_sub_region',
),
]

View File

@ -2,11 +2,12 @@
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.contrib.gis.db import models as gis_models from django.contrib.gis.db import models as gis_models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import Case, When from django.db.models import Case, When
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.validators import MaxValueValidator, MinValueValidator
from location.models import WineOriginAddressMixin
from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin, from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin,
TranslatedFieldsMixin, TJSONField, FavoritesMixin, TranslatedFieldsMixin, TJSONField, FavoritesMixin,
GalleryModelMixin, IntermediateGalleryModelMixin) GalleryModelMixin, IntermediateGalleryModelMixin)
@ -89,8 +90,8 @@ class ProductQuerySet(models.QuerySet):
'establishment__address__city', 'establishment__address__city__country', 'establishment__address__city', 'establishment__address__city__country',
'establishment__establishment_subtypes', 'product_gallery', 'establishment__establishment_subtypes', 'product_gallery',
'gallery', 'product_type', 'subtypes', 'gallery', 'product_type', 'subtypes',
'classifications__classification_type', 'classifications__tags') \ 'classifications__classification_type', 'classifications__tags',
.select_related('wine_region', 'wine_sub_region') 'wine_origins__wine_region', 'wine_origins__wine_sub_region', )
def common(self): def common(self):
return self.filter(category=self.model.COMMON) return self.filter(category=self.model.COMMON)
@ -101,6 +102,11 @@ class ProductQuerySet(models.QuerySet):
def wines(self): def wines(self):
return self.filter(type__index_name__icontains=ProductType.WINE) return self.filter(type__index_name__icontains=ProductType.WINE)
def without_current_product(self, current_product: str):
"""Exclude by current product."""
kwargs = {'pk': int(current_product)} if current_product.isdigit() else {'slug': current_product}
return self.exclude(**kwargs)
def by_product_type(self, product_type: str): def by_product_type(self, product_type: str):
"""Filter by type.""" """Filter by type."""
return self.filter(product_type__index_name__icontains=product_type) return self.filter(product_type__index_name__icontains=product_type)
@ -176,14 +182,6 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
verbose_name=_('establishment')) verbose_name=_('establishment'))
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None, public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('public mark'), ) verbose_name=_('public mark'), )
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
related_name='wines',
blank=True, null=True, default=None,
verbose_name=_('wine region'))
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', classifications = models.ManyToManyField('ProductClassification',
blank=True, blank=True,
verbose_name=_('classifications')) verbose_name=_('classifications'))

View File

@ -62,8 +62,9 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
'available', 'available',
'product_type', 'product_type',
'establishment', 'establishment',
'wine_region', # todo: need fix
'wine_sub_region', # 'wine_region',
# 'wine_sub_region',
'wine_village', 'wine_village',
'state', 'state',
] ]

View File

@ -4,15 +4,15 @@ from rest_framework import serializers
from comment.models import Comment from comment.models import Comment
from comment.serializers import CommentSerializer from comment.serializers import CommentSerializer
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer, EstablishmentProductShortSerializer from establishment.serializers import EstablishmentProductShortSerializer
from gallery.models import Image from establishment.serializers.common import _EstablishmentAddressShortSerializer
from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer
from main.serializers import AwardSerializer
from product import models from product import models
from review.serializers import ReviewShortSerializer from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer
from main.serializers import AwardSerializer
from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
class ProductTagSerializer(TagBaseSerializer): class ProductTagSerializer(TagBaseSerializer):
@ -90,7 +90,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True) subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
establishment_detail = EstablishmentProductShortSerializer(source='establishment', read_only=True) establishment_detail = EstablishmentProductShortSerializer(source='establishment', read_only=True)
tags = ProductTagSerializer(source='related_tags', many=True, read_only=True) tags = ProductTagSerializer(source='related_tags', many=True, read_only=True)
wine_region = WineRegionBaseSerializer(read_only=True) wine_regions = WineOriginRegionBaseSerializer(many=True, source='wine_origins', read_only=True)
wine_colors = TagBaseSerializer(many=True, read_only=True) wine_colors = TagBaseSerializer(many=True, read_only=True)
preview_image_url = serializers.URLField(allow_null=True, preview_image_url = serializers.URLField(allow_null=True,
read_only=True) read_only=True)
@ -110,7 +110,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
'vintage', 'vintage',
'tags', 'tags',
'preview_image_url', 'preview_image_url',
'wine_region', 'wine_regions',
'wine_colors', 'wine_colors',
'in_favorites', 'in_favorites',
] ]
@ -119,12 +119,12 @@ class ProductBaseSerializer(serializers.ModelSerializer):
class ProductDetailSerializer(ProductBaseSerializer): class ProductDetailSerializer(ProductBaseSerializer):
"""Product detail serializer.""" """Product detail serializer."""
description_translated = TranslatedField() description_translated = TranslatedField()
establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True) establishment_detail = _EstablishmentAddressShortSerializer(source='establishment', read_only=True)
review = ReviewShortSerializer(source='last_published_review', read_only=True) review = ReviewShortSerializer(source='last_published_review', read_only=True)
awards = AwardSerializer(many=True, read_only=True) awards = AwardSerializer(many=True, read_only=True)
classifications = ProductClassificationBaseSerializer(many=True, read_only=True) classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
standards = ProductStandardBaseSerializer(many=True, read_only=True) standards = ProductStandardBaseSerializer(many=True, read_only=True)
wine_sub_region = WineSubRegionBaseSerializer(read_only=True) wine_origins = WineOriginBaseSerializer(many=True, read_only=True)
bottles_produced = TagBaseSerializer(many=True, read_only=True) bottles_produced = TagBaseSerializer(many=True, read_only=True)
sugar_contents = TagBaseSerializer(many=True, read_only=True) sugar_contents = TagBaseSerializer(many=True, read_only=True)
grape_variety = TagBaseSerializer(many=True, read_only=True) grape_variety = TagBaseSerializer(many=True, read_only=True)
@ -141,7 +141,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
'awards', 'awards',
'classifications', 'classifications',
'standards', 'standards',
'wine_sub_region', 'wine_origins',
'bottles_produced', 'bottles_produced',
'sugar_contents', 'sugar_contents',
'image_url', 'image_url',

View File

@ -60,10 +60,7 @@ class ProductCommentListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Override get_queryset method"""
product = get_object_or_404(Product, slug=self.kwargs['slug']) product = get_object_or_404(Product, slug=self.kwargs['slug'])
return Comment.objects.by_content_type(app_label='product', return product.comments.order_by('-created')
model='product') \
.by_object_id(object_id=product.pk) \
.order_by('-created')
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView): class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):

View File

@ -115,12 +115,8 @@ def transfer_product_reviews():
products = Product.objects.filter( products = Product.objects.filter(
old_id__isnull=False).values_list('old_id', flat=True) old_id__isnull=False).values_list('old_id', flat=True)
users = User.objects.filter(
old_id__isnull=False).values_list('old_id', flat=True)
queryset = Reviews.objects.filter( queryset = Reviews.objects.filter(
product_id__in=list(products), product_id__in=list(products),
reviewer_id__in=list(users),
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'product_id', 'mark', 'vintage') ).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'product_id', 'mark', 'vintage')
serialized_data = ProductReviewSerializer(data=list(queryset.values()), many=True) serialized_data = ProductReviewSerializer(data=list(queryset.values()), many=True)

View File

@ -1,6 +1,7 @@
from search_indexes.documents.establishment import EstablishmentDocument from search_indexes.documents.establishment import EstablishmentDocument
from search_indexes.documents.news import NewsDocument from search_indexes.documents.news import NewsDocument
from search_indexes.documents.product import ProductDocument from search_indexes.documents.product import ProductDocument
from search_indexes.documents.tag_category import TagCategoryDocument
from search_indexes.tasks import es_update from search_indexes.tasks import es_update
# todo: make signal to update documents on related fields # todo: make signal to update documents on related fields
@ -8,5 +9,6 @@ __all__ = [
'EstablishmentDocument', 'EstablishmentDocument',
'NewsDocument', 'NewsDocument',
'ProductDocument', 'ProductDocument',
'TagCategoryDocument',
'es_update', 'es_update',
] ]

View File

@ -82,6 +82,9 @@ class EstablishmentDocument(Document):
}, },
multi=True) multi=True)
products = fields.ObjectField( products = fields.ObjectField(
properties={
'wine_origins': fields.ListField(
fields.ObjectField(
properties={ properties={
'wine_region': fields.ObjectField(properties={ 'wine_region': fields.ObjectField(properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
@ -93,22 +96,45 @@ class EstablishmentDocument(Document):
'code': fields.KeywordField(), 'code': fields.KeywordField(),
}), }),
# 'coordinates': fields.GeoPointField(), # 'coordinates': fields.GeoPointField(),
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES), 'description': fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES)
}), }),
'wine_sub_region': fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
}),
})),
'wine_colors': fields.ObjectField( 'wine_colors': fields.ObjectField(
properties={ properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES), 'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(), 'value': fields.KeywordField(),
}, },
multi=True, multi=True,)},
), multi=True
)
wine_origins = fields.ListField(
fields.ObjectField(
properties={
'wine_region': fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'country': fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'code': fields.KeywordField(),
}),
# 'coordinates': fields.GeoPointField(),
'description': fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES)
}),
'wine_sub_region': fields.ObjectField(properties={ 'wine_sub_region': fields.ObjectField(properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
'name': fields.KeywordField(), 'name': fields.KeywordField(),
}), })})
},
multi=True
) )
schedule = fields.ListField(fields.ObjectField( schedule = fields.ListField(fields.ObjectField(
properties={ properties={
@ -116,6 +142,7 @@ class EstablishmentDocument(Document):
'weekday': fields.IntegerField(attr='weekday'), 'weekday': fields.IntegerField(attr='weekday'),
'weekday_display': fields.KeywordField(attr='get_weekday_display'), 'weekday_display': fields.KeywordField(attr='get_weekday_display'),
'closed_at': fields.KeywordField(attr='closed_at_str'), 'closed_at': fields.KeywordField(attr='closed_at_str'),
'opening_at': fields.KeywordField(attr='opening_at_str'),
} }
)) ))
address = fields.ObjectField( address = fields.ObjectField(

View File

@ -83,7 +83,10 @@ class ProductDocument(Document):
}, },
multi=True, multi=True,
) )
wine_region = fields.ObjectField(properties={ wine_origins = fields.ListField(
fields.ObjectField(
properties={
'wine_region': fields.ObjectField(properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
'name': fields.KeywordField(), 'name': fields.KeywordField(),
'country': fields.ObjectField(properties={ 'country': fields.ObjectField(properties={
@ -93,12 +96,15 @@ class ProductDocument(Document):
'code': fields.KeywordField(), 'code': fields.KeywordField(),
}), }),
# 'coordinates': fields.GeoPointField(), # 'coordinates': fields.GeoPointField(),
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES), 'description': fields.ObjectField(attr='description_indexing',
}) properties=OBJECT_FIELD_PROPERTIES)
wine_sub_region = fields.ObjectField(properties={
}),
'wine_sub_region': fields.ObjectField(properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
'name': fields.KeywordField(), 'name': fields.KeywordField(),
}) })})
)
classifications = fields.ObjectField( # TODO classifications = fields.ObjectField( # TODO
properties={ properties={
'classification_type': fields.ObjectField(properties={}), 'classification_type': fields.ObjectField(properties={}),

View File

@ -0,0 +1,33 @@
"""Product app documents."""
from django.conf import settings
from django_elasticsearch_dsl import Document, Index, fields
from tag import models
TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category'))
TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2)
@TagCategoryIndex.doc_type
class TagCategoryDocument(Document):
"""TagCategory document."""
tags = fields.ListField(fields.ObjectField(
properties={
'id': fields.IntegerField(),
'value': fields.KeywordField(),
},
))
class Django:
model = models.TagCategory
fields = (
'id',
'index_name',
'public',
'value_type'
)
related_models = [models.Tag]
def get_queryset(self):
return super().get_queryset().with_base_related()

View File

@ -4,6 +4,8 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \
FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend
from search_indexes.utils import OBJECT_FIELD_PROPERTIES from search_indexes.utils import OBJECT_FIELD_PROPERTIES
from six import iteritems from six import iteritems
from search_indexes.documents import TagCategoryDocument
from tag.models import TagCategory
class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
@ -11,20 +13,13 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
@staticmethod @staticmethod
def calculate_center(first, second): def calculate_center(first, second):
if second[1] < 0 <= first[1]: if second[1] < first[1]:
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) res_longtitude = first[1] + (360 + abs(first[1]) - abs(second[1])) / 2
diff = (reverse_first + reverse_second) / 2
if reverse_first < reverse_second:
result_part = -180 + (180 + second[1] - diff)
else: else:
result_part = 180 - (180 - first[1] - diff) res_longtitude = first[1] + (second[1] - first[1]) / 2
else: # return (first[0] + second[0]) / 2, result_part
result_part = (first[1] + second[1]) / 2 return (first[0] + second[0]) / 2, res_longtitude
return (first[0] + second[0]) / 2, result_part
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ret = super().filter_queryset(request, queryset, view) ret = super().filter_queryset(request, queryset, view)
@ -52,10 +47,30 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
:param view: :param view:
:return: :return:
""" """
def makefilter(cur_facet): def make_filter(cur_facet):
def myfilter(x): def _filter(x):
return cur_facet['facet']._params['field'] != next(iter(x._params)) return cur_facet['facet']._params['field'] != next(iter(x._params))
return myfilter return _filter
def make_tags_filter(cur_facet, tags_to_remove_ids):
def _filter(x):
if hasattr(x, '_params') and (x._params.get('must') or x._params.get('should')):
ret = []
for t in ['must', 'should']:
terms = x._params.get(t)
if terms:
for term in terms:
if cur_facet['facet']._params['field'] != next(iter(term._params)):
return True # different fields. preserve filter
else:
ret.append(next(iter(term._params.values())) not in tags_to_remove_ids)
return all(ret)
if cur_facet['facet']._params['field'] != next(iter(x._params)):
return True # different fields. preserve filter
else:
return next(iter(x._params.values())) not in tags_to_remove_ids
return _filter
__facets = self.construct_facets(request, view) __facets = self.construct_facets(request, view)
setattr(view.paginator, 'facets_computed', {}) setattr(view.paginator, 'facets_computed', {})
for __field, __facet in iteritems(__facets): for __field, __facet in iteritems(__facets):
@ -67,9 +82,10 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
'global' 'global'
).bucket(__field, agg) ).bucket(__field, agg)
else: else:
if __field != 'tag':
qs = queryset.__copy__() qs = queryset.__copy__()
qs.query = queryset.query._clone() qs.query = queryset.query._clone()
filterer = makefilter(__facet) filterer = make_filter(__facet)
for param_type in ['must', 'must_not', 'should']: for param_type in ['must', 'must_not', 'should']:
if qs.query._proxied._params.get(param_type): if qs.query._proxied._params.get(param_type):
qs.query._proxied._params[param_type] = list( qs.query._proxied._params[param_type] = list(
@ -88,8 +104,51 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
filter=agg_filter filter=agg_filter
).bucket(__field, agg) ).bucket(__field, agg)
view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]})
else:
tag_facets = []
preserve_ids = []
facet_name = '_filter_' + __field
all_tag_categories = TagCategoryDocument.search() \
.filter('term', public=True) \
.filter(Q('term', value_type=TagCategory.LIST) | Q('match', index_name='wine-color'))
for category in all_tag_categories:
tags_to_remove = list(map(lambda t: str(t.id), category.tags))
qs = queryset.__copy__()
qs.query = queryset.query._clone()
filterer = make_tags_filter(__facet, tags_to_remove)
for param_type in ['must', 'should']:
if qs.query._proxied._params.get(param_type):
if qs.query._proxied._params.get(param_type):
qs.query._proxied._params[param_type] = list(
filter(
filterer, qs.query._proxied._params[param_type]
)
)
sh = qs.query._proxied._params.get('should')
if (not sh or not len(sh)) \
and qs.query._proxied._params.get('minimum_should_match'):
qs.query._proxied._params.pop('minimum_should_match')
qs.aggs.bucket(
facet_name,
'filter',
filter=agg_filter
).bucket(__field, agg)
tag_facets.append(qs.execute().aggregations[facet_name])
preserve_ids.append(list(map(int, tags_to_remove)))
view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets, preserve_ids)})
return queryset return queryset
@staticmethod
def merge_buckets(buckets: list, preserve_ids: list):
"""Reduces all buckets preserving class"""
result_bucket = buckets[0]
result_bucket.tag.buckets = list(filter(lambda x: x['key'] in preserve_ids[0], result_bucket.tag.buckets._l_))
for bucket, ids in list(zip(buckets, preserve_ids))[1:]:
for tag in bucket.tag.buckets._l_:
if tag['key'] in ids:
result_bucket.tag.buckets.append(tag)
return result_bucket
class CustomSearchFilterBackend(SearchFilterBackend): class CustomSearchFilterBackend(SearchFilterBackend):
"""Custom SearchFilterBackend.""" """Custom SearchFilterBackend."""

View File

@ -69,6 +69,16 @@ class WineRegionDocumentSerializer(serializers.Serializer):
return instance.wine_region if instance and instance.wine_region else None return instance.wine_region if instance and instance.wine_region else None
class WineSubRegionDocumentSerializer(serializers.Serializer):
"""Wine region ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
def get_attribute(self, instance):
return instance.wine_sub_region if instance and instance.wine_sub_region else None
class TagDocumentSerializer(serializers.Serializer): class TagDocumentSerializer(serializers.Serializer):
"""Tag ES document serializer,""" """Tag ES document serializer,"""
@ -168,6 +178,7 @@ class ScheduleDocumentSerializer(serializers.Serializer):
weekday = serializers.IntegerField() weekday = serializers.IntegerField()
weekday_display = serializers.CharField() weekday_display = serializers.CharField()
closed_at = serializers.CharField() closed_at = serializers.CharField()
opening_at = serializers.CharField()
class InFavoritesMixin(DocumentSerializer): class InFavoritesMixin(DocumentSerializer):
@ -222,6 +233,13 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
return get_translated_value(obj.subtitle) return get_translated_value(obj.subtitle)
class WineOriginSerializer(serializers.Serializer):
"""Wine origin serializer."""
wine_region = WineRegionDocumentSerializer()
wine_sub_region = WineSubRegionDocumentSerializer(allow_null=True)
class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
"""Establishment document serializer.""" """Establishment document serializer."""
@ -233,6 +251,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True) restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
artisan_category = TagsDocumentSerializer(many=True, allow_null=True) artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
schedule = ScheduleDocumentSerializer(many=True, allow_null=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
wine_origins = WineOriginSerializer(many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -258,6 +277,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'works_evening', 'works_evening',
'works_at_weekday', 'works_at_weekday',
'tz', 'tz',
'wine_origins',
# 'works_now', # 'works_now',
# 'collections', # 'collections',
# 'establishment_type', # 'establishment_type',
@ -270,11 +290,11 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
tags = TagsDocumentSerializer(many=True, source='related_tags') tags = TagsDocumentSerializer(many=True, source='related_tags')
subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True)
wine_region = WineRegionDocumentSerializer(allow_null=True)
wine_colors = TagDocumentSerializer(many=True) wine_colors = TagDocumentSerializer(many=True)
grape_variety = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True)
product_type = ProductTypeDocumentSerializer(allow_null=True) product_type = ProductTypeDocumentSerializer(allow_null=True)
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True) establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
wine_origins = WineOriginSerializer(many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -295,10 +315,10 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'tags', 'tags',
'product_type', 'product_type',
'subtypes', 'subtypes',
'wine_region',
'wine_colors', 'wine_colors',
'grape_variety', 'grape_variety',
'establishment_detail', 'establishment_detail',
'average_price', 'average_price',
'created', 'created',
'wine_origins',
) )

View File

@ -158,7 +158,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
}, },
}, },
'wine_region_id': { 'wine_region_id': {
'field': 'products.wine_region.id', 'field': 'wine_origins.wine_region.id',
'facet': TermsFacet,
'enabled': True,
'options': {
'size': utils.FACET_MAX_RESPONSE,
},
},
'wine_sub_region_id': {
'field': 'wine_origins.wine_sub_region.id',
'facet': TermsFacet, 'facet': TermsFacet,
'enabled': True, 'enabled': True,
'options': { 'options': {
@ -213,14 +221,14 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
], ],
}, },
'wine_region_id': { 'wine_region_id': {
'field': 'products.wine_region.id', 'field': 'wine_origins.wine_region.id',
'lookups': [ 'lookups': [
constants.LOOKUP_QUERY_IN, constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE, constants.LOOKUP_QUERY_EXCLUDE,
], ],
}, },
'wine_sub_region_id': { 'wine_sub_region_id': {
'field': 'products.wine_sub_region_id', 'field': 'wine_origins.wine_sub_region.id',
'lookups': [ 'lookups': [
constants.LOOKUP_QUERY_IN, constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE, constants.LOOKUP_QUERY_EXCLUDE,
@ -346,7 +354,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
faceted_search_fields = { faceted_search_fields = {
'tag': { 'tag': {
'field': 'wine_colors.id', 'field': 'tags.id',
'enabled': True, 'enabled': True,
'facet': TermsFacet, 'facet': TermsFacet,
'options': { 'options': {
@ -354,13 +362,21 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
}, },
}, },
'wine_region_id': { 'wine_region_id': {
'field': 'wine_region.id', 'field': 'wine_origins.wine_region.id',
'enabled': True,
'facet': TermsFacet, 'facet': TermsFacet,
'enabled': True,
'options': { 'options': {
'size': utils.FACET_MAX_RESPONSE, 'size': utils.FACET_MAX_RESPONSE,
}, },
}, },
'wine_sub_region_id': {
'field': 'wine_origins.wine_sub_region.id',
'facet': TermsFacet,
'enabled': True,
'options': {
'size': utils.FACET_MAX_RESPONSE,
},
}
} }
translated_search_fields = ( translated_search_fields = (
@ -384,14 +400,14 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
], ],
}, },
'wine_region_id': { 'wine_region_id': {
'field': 'wine_region.id', 'field': 'wine_origins.wine_region.id',
'lookups': [ 'lookups': [
constants.LOOKUP_QUERY_IN, constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE, constants.LOOKUP_QUERY_EXCLUDE,
], ],
}, },
'wine_sub_region_id': { 'wine_sub_region_id': {
'field': 'wine_sub_region_id', 'field': 'wine_origins.wine_sub_region.id',
'lookups': [ 'lookups': [
constants.LOOKUP_QUERY_IN, constants.LOOKUP_QUERY_IN,
constants.LOOKUP_QUERY_EXCLUDE, constants.LOOKUP_QUERY_EXCLUDE,
@ -404,9 +420,9 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
constants.LOOKUP_QUERY_EXCLUDE, constants.LOOKUP_QUERY_EXCLUDE,
] ]
}, },
'wine_from_country_code': { # 'wine_from_country_code': {
'field': 'wine_region.country.code', # 'field': 'wine_origins.wine_region.country.code',
}, # },
'for_establishment': { 'for_establishment': {
'field': 'establishment.slug', 'field': 'establishment.slug',
}, },

View File

@ -73,7 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
def by_establishment_type(self, queryset, name, value): def by_establishment_type(self, queryset, name, value):
if value == EstablishmentType.ARTISAN: if value == EstablishmentType.ARTISAN:
return models.Tag.objects.by_category_index_name('shop_category')[0:8] qs = models.Tag.objects.by_category_index_name('shop_category')
if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES:
qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id')
return qs.exclude(establishments__isnull=True)[0:8]
return queryset.by_establishment_type(value) return queryset.by_establishment_type(value)
# TMP TODO remove it later # TMP TODO remove it later

View File

@ -1,7 +1,8 @@
"""Tag serializers.""" """Tag serializers."""
from rest_framework import serializers from rest_framework import serializers
from establishment.models import (Establishment, EstablishmentType, from rest_framework.fields import SerializerMethodField
EstablishmentSubType)
from establishment.models import (Establishment, EstablishmentType)
from news.models import News, NewsType from news.models import News, NewsType
from tag import models from tag import models
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
@ -12,6 +13,9 @@ from utils.serializers import TranslatedField
class TagBaseSerializer(serializers.ModelSerializer): class TagBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Tag.""" """Serializer for model Tag."""
def get_extra_kwargs(self):
return super().get_extra_kwargs()
label_translated = TranslatedField() label_translated = TranslatedField()
index_name = serializers.CharField(source='value', read_only=True, allow_null=True) index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
@ -37,6 +41,7 @@ class TagBackOfficeSerializer(TagBaseSerializer):
'category' 'category'
) )
class TagCategoryProductSerializer(serializers.ModelSerializer): class TagCategoryProductSerializer(serializers.ModelSerializer):
"""SHORT Serializer for TagCategory""" """SHORT Serializer for TagCategory"""
@ -57,7 +62,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
"""Serializer for model TagCategory.""" """Serializer for model TagCategory."""
label_translated = TranslatedField() label_translated = TranslatedField()
tags = TagBaseSerializer(many=True, read_only=True) tags = SerializerMethodField()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -70,6 +75,25 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
'tags', 'tags',
) )
def get_tags(self, obj):
query_params = dict(self.context['request'].query_params)
if len(query_params) > 1:
return []
params = {}
if 'establishment_type' in query_params:
params = {
'establishments__isnull': False,
}
elif 'product_type' in query_params:
params = {
'products__isnull': False,
}
tags = obj.tags.filter(**params).distinct()
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
class TagCategoryShortSerializer(serializers.ModelSerializer): class TagCategoryShortSerializer(serializers.ModelSerializer):
"""Serializer for model TagCategory.""" """Serializer for model TagCategory."""
@ -174,15 +198,15 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
attrs['tag_category'] = tag_category attrs['tag_category'] = tag_category
if obj_type == self.ESTABLISHMENT_TYPE: if obj_type == self.ESTABLISHMENT_TYPE:
establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ establishment_type = EstablishmentType.objects.filter(pk=obj_id). \
first() first()
if not establishment_type: if not establishment_type:
raise BindingObjectNotFound() raise BindingObjectNotFound()
if request.method == 'POST' and tag_category.establishment_types.\ if request.method == 'POST' and tag_category.establishment_types. \
filter(pk=establishment_type.pk).exists(): filter(pk=establishment_type.pk).exists():
raise ObjectAlreadyAdded() raise ObjectAlreadyAdded()
if request.method == 'DELETE' and not tag_category.\ if request.method == 'DELETE' and not tag_category. \
establishment_types.filter(pk=establishment_type.pk).\ establishment_types.filter(pk=establishment_type.pk). \
exists(): exists():
raise RemovedBindingObjectNotFound() raise RemovedBindingObjectNotFound()
attrs['related_object'] = establishment_type attrs['related_object'] = establishment_type
@ -190,10 +214,10 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
news_type = NewsType.objects.filter(pk=obj_id).first() news_type = NewsType.objects.filter(pk=obj_id).first()
if not news_type: if not news_type:
raise BindingObjectNotFound() raise BindingObjectNotFound()
if request.method == 'POST' and tag_category.news_types.\ if request.method == 'POST' and tag_category.news_types. \
filter(pk=news_type.pk).exists(): filter(pk=news_type.pk).exists():
raise ObjectAlreadyAdded() raise ObjectAlreadyAdded()
if request.method == 'DELETE' and not tag_category.news_types.\ if request.method == 'DELETE' and not tag_category.news_types. \
filter(pk=news_type.pk).exists(): filter(pk=news_type.pk).exists():
raise RemovedBindingObjectNotFound() raise RemovedBindingObjectNotFound()
attrs['related_object'] = news_type attrs['related_object'] = news_type

View File

@ -39,6 +39,10 @@ class Timetable(ProjectBaseMixin):
def closed_at_str(self): def closed_at_str(self):
return str(self.closed_at) if self.closed_at else None return str(self.closed_at) if self.closed_at else None
@property
def opening_at_str(self):
return str(self.opening_at) if self.opening_at else None
@property @property
def opening_time(self): def opening_time(self):
return self.opening_at or self.lunch_start or self.dinner_start return self.opening_at or self.lunch_start or self.dinner_start

View File

@ -45,8 +45,14 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
.parser_context.get('view')\ .parser_context.get('view')\
.kwargs.get('pk') .kwargs.get('pk')
establishment_slug = self.context.get('request')\
.parser_context.get('view')\
.kwargs.get('slug')
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
# Check if establishment exists. # Check if establishment exists.
establishment_qs = Establishment.objects.filter(pk=establishment_pk) establishment_qs = Establishment.objects.filter(**search_kwargs)
if not establishment_qs.exists(): if not establishment_qs.exists():
raise serializers.ValidationError({'detail': _('Establishment not found.')}) raise serializers.ValidationError({'detail': _('Establishment not found.')})
attrs['establishment'] = establishment_qs.first() attrs['establishment'] = establishment_qs.first()

View File

@ -41,6 +41,14 @@ class Command(BaseCommand):
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1 'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
'purchased_plaques', # №6 - перенос купленных тарелок 'purchased_plaques', # №6 - перенос купленных тарелок
'fill_city_gallery', # №3 - перенос галереи городов 'fill_city_gallery', # №3 - перенос галереи городов
'guides',
'guide_filters',
'guide_element_sections',
'guide_wine_color_sections',
'guide_element_types',
'guide_elements_bulk',
'guide_element_advertorials',
'guide_complete',
] ]
def handle(self, *args, **options): def handle(self, *args, **options):

View File

@ -5,11 +5,19 @@
# * Make sure each ForeignKey has `on_delete` set to the desired behavior. # * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table # * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names. # Feel free to rename the models, but don't rename db_table values or field names.
import yaml
from django.contrib.gis.db import models from django.contrib.gis.db import models
from transfer.mixins import MigrateMixin from transfer.mixins import MigrateMixin
def convert_entry(loader, node):
return {e[0]: e[1] for e in loader.construct_pairs(node)}
yaml.add_constructor('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', convert_entry)
# models.ForeignKey(ForeignModel, models.DO_NOTHING, blank=True, null=True) # models.ForeignKey(ForeignModel, models.DO_NOTHING, blank=True, null=True)
class Sites(MigrateMixin): class Sites(MigrateMixin):
@ -362,7 +370,7 @@ class GuideFilters(MigrateMixin):
states = models.CharField(max_length=255, blank=True, null=True) states = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField() created_at = models.DateTimeField()
updated_at = models.DateTimeField() updated_at = models.DateTimeField()
guide_id = models.IntegerField(blank=True, null=True) guide = models.ForeignKey(Guides, models.DO_NOTHING, blank=True, null=True)
class Meta: class Meta:
managed = False managed = False
@ -381,7 +389,7 @@ class GuideSections(MigrateMixin):
class Meta: class Meta:
managed = False managed = False
db_table = 'guide_elements' db_table = 'guide_sections'
class GuideElements(MigrateMixin): class GuideElements(MigrateMixin):
@ -398,7 +406,7 @@ class GuideElements(MigrateMixin):
guide_ad = models.ForeignKey(GuideAds, models.DO_NOTHING, blank=True, null=True) guide_ad = models.ForeignKey(GuideAds, models.DO_NOTHING, blank=True, null=True)
city = models.ForeignKey(Cities, models.DO_NOTHING, blank=True, null=True) city = models.ForeignKey(Cities, models.DO_NOTHING, blank=True, null=True)
section = models.ForeignKey('GuideSections', models.DO_NOTHING, blank=True, null=True) section = models.ForeignKey('GuideSections', models.DO_NOTHING, blank=True, null=True)
guide_id = models.IntegerField(blank=True, null=True) guide = models.ForeignKey('Guides', models.DO_NOTHING, blank=True, null=True)
parent = models.ForeignKey('self', models.DO_NOTHING, blank=True, null=True) parent = models.ForeignKey('self', models.DO_NOTHING, blank=True, null=True)
lft = models.IntegerField() lft = models.IntegerField()
rgt = models.IntegerField() rgt = models.IntegerField()
@ -992,7 +1000,7 @@ class ProductNotes(MigrateMixin):
db_table = 'product_notes' db_table = 'product_notes'
class HomePages(models.Model): class HomePages(MigrateMixin):
using = 'legacy' using = 'legacy'
site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True) site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True)

View File

@ -125,7 +125,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
weekdays = { weekdays = {
'su': Timetable.SUNDAY, 'su': Timetable.SUNDAY,
'mo': Timetable.MONDAY, 'mo': Timetable.MONDAY,
'tu': Timetable.THURSDAY, 'tu': Timetable.TUESDAY,
'we': Timetable.WEDNESDAY, 'we': Timetable.WEDNESDAY,
'th': Timetable.THURSDAY, 'th': Timetable.THURSDAY,
'fr': Timetable.FRIDAY, 'fr': Timetable.FRIDAY,

View File

@ -0,0 +1,356 @@
from itertools import chain
import yaml
from django.utils.text import slugify
from pycountry import countries, subdivisions
from rest_framework import serializers
from collection import models
from establishment.models import EstablishmentType
from location.models import Country, Region, WineRegion
from main.models import SiteSettings
from review.models import Review
from transfer.mixins import TransferSerializerMixin
from translation.models import Language
class GuideSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
title = serializers.CharField()
vintage = serializers.IntegerField()
slug = serializers.CharField()
state = serializers.CharField()
site_id = serializers.IntegerField()
inserter_field = serializers.CharField()
class Meta:
model = models.Guide
fields = (
'id',
'title',
'vintage',
'slug',
'state',
'site_id',
'inserter_field',
)
def validate(self, attrs):
"""Overridden validate method."""
attrs['old_id'] = attrs.pop('id')
attrs['name'] = attrs.pop('title')
attrs['vintage'] = int(attrs.pop('vintage'))
attrs['state'] = self.get_state(attrs.pop('state'))
attrs['site'] = self.get_site(attrs.pop('site_id'))
attrs['guide_type'] = self.get_guide_type(attrs.pop('inserter_field'))
return attrs
def get_state(self, state: str):
if state == 'built':
return models.Guide.BUILT
elif state == 'removing':
return models.Guide.REMOVING
elif state == 'building':
return models.Guide.BUILDING
else:
return models.Guide.WAITING
def get_site(self, site_id):
qs = SiteSettings.objects.filter(old_id=site_id)
if qs.exists():
return qs.first()
def get_guide_type(self, inserter_field):
guide_type, _ = models.GuideType.objects.get_or_create(name=inserter_field)
return guide_type
class GuideFilterSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
year = serializers.CharField(allow_null=True)
establishment_type = serializers.CharField(allow_null=True)
countries = serializers.CharField(allow_null=True)
regions = serializers.CharField(allow_null=True)
subregions = serializers.CharField(allow_null=True)
wine_regions = serializers.CharField(allow_null=True)
locales = serializers.CharField(allow_null=True)
states = serializers.CharField(allow_null=True)
max_mark = serializers.FloatField(allow_null=True)
min_mark = serializers.FloatField(allow_null=True)
marks_only = serializers.NullBooleanField()
guide_id = serializers.IntegerField()
class Meta:
model = models.GuideFilter
fields = (
'id',
'year',
'establishment_type',
'countries',
'regions',
'subregions',
'wine_regions',
'max_mark',
'min_mark',
'marks_only',
'locales',
'states',
'guide_id',
)
def create(self, validated_data):
qs = self.Meta.model.objects.filter(guide=validated_data.get('guide'))
if not qs.exists():
return super().create(validated_data)
@staticmethod
def parse_ruby_helper(raw_value: str):
"""Parse RubyActiveSupport records"""
def convert_entry(loader, node):
return {e[0]: e[1] for e in loader.construct_pairs(node)}
loader = yaml.Loader
loader.add_constructor('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', convert_entry)
return yaml.load(raw_value, Loader=loader) if raw_value else None
@staticmethod
def parse_dictionary(dictionary: dict):
"""
Exclude root values from dictionary.
Convert {key_2: [value_1, value_2]} into
[value_1, value_2]
"""
return list(chain.from_iterable(dictionary.values()))
@staticmethod
def parse_nested_dictionary(dictionary: dict):
"""
Exclude root values from dictionary.
Convert {key_1: {key_2: [value_1, value_2]}} into
[value_1, value_2]
"""
l = []
for i in dictionary:
l.append(list(chain.from_iterable(list(dictionary[i].values()))))
return list(chain.from_iterable(l))
def get_country(self, code_alpha_3: str) -> Country:
country = countries.get(alpha_3=code_alpha_3.upper())
if country:
country_name = country.name
country_code = country.alpha_2
if country_name and country_code:
country, _ = Country.objects.get_or_create(
code__icontains=country_code,
name__contains={'en-GB': country_name},
defaults={
'code': country_code,
'name': {'en-GB': country_name}
}
)
return country
def get_region(self, region_code_alpha_3: str,
country_code_alpha_3: str,
sub_region_code_alpha_3: str = None):
country = self.get_country(country_code_alpha_3)
if country:
country_code_alpha_2 = country.code.upper()
region_qs = Region.objects.filter(code__iexact=region_code_alpha_3,
country__code__iexact=country_code_alpha_2)
if region_qs.exists():
return region_qs.first()
# If region isn't existed, check sub region for parent_code (region code)
if sub_region_code_alpha_3:
# sub region
subdivision = subdivisions.get(
code=f"{country_code_alpha_2}-{sub_region_code_alpha_3}")
if subdivision:
# try with parent code
subdivision_region = subdivisions.get(code=subdivision.__dict__.get('_fields')
.get('parent_code'))
if not subdivision_region:
# try with parent
subdivision_region = subdivisions.get(code=subdivision.__dict__.get('_fields')
.get('parent'))
if subdivision_region:
obj = Region.objects.create(
name=subdivision_region.name,
code=subdivision_region.code,
country=country)
return obj
def validate_year(self, value):
return self.parse_ruby_helper(value)
def validate_establishment_type(self, value):
return self.parse_ruby_helper(value)
def validate_countries(self, value):
return self.parse_ruby_helper(value)
def validate_regions(self, value):
return self.parse_ruby_helper(value)
def validate_subregions(self, value):
return self.parse_ruby_helper(value)
def validate_wine_regions(self, value):
return self.parse_ruby_helper(value)
def validate_wine_classifications(self, value):
return self.parse_ruby_helper(value)
def validate_wine_colors(self, value):
return self.parse_ruby_helper(value)
def validate_wine_types(self, value):
return self.parse_ruby_helper(value)
def validate_locales(self, value):
return self.parse_ruby_helper(value)
def validate_states(self, value):
return self.parse_ruby_helper(value)
def validate(self, attrs):
sub_regions = attrs.pop('subregions')
regions = attrs.pop('regions')
attrs['old_id'] = attrs.pop('id')
attrs['review_vintage_json'] = self.get_review_vintage(attrs.pop('year'))
attrs['establishment_type_json'] = self.get_establishment_type_ids(
attrs.pop('establishment_type'))
attrs['country_json'] = self.get_country_ids(attrs.pop('countries'))
attrs['region_json'] = self.get_region_ids(regions=regions,
sub_regions=sub_regions)
attrs['sub_region_json'] = self.get_sub_region_ids(sub_regions)
attrs['wine_region_json'] = self.get_wine_region_ids(attrs.pop('wine_regions'))
attrs['with_mark'] = attrs.pop('marks_only') or True
attrs['locale_json'] = self.get_locale_ids(attrs.pop('locales'))
attrs['review_state_json'] = self.get_review_state(attrs.pop('states'))
attrs['guide'] = self.get_guide(attrs.pop('guide_id'))
return attrs
def get_review_vintage(self, year):
if hasattr(year, '__iter__'):
return {'vintage': list(set(int(i) for i in set(year) if i.isdigit()))}
return {'vintage': [year, ]}
def get_establishment_type_ids(self, establishment_types):
establishment_type_ids = []
if establishment_types:
for establishment_type in establishment_types:
establishment_type_qs = EstablishmentType.objects.filter(index_name__iexact=establishment_type)
if not establishment_type_qs.exists():
obj = EstablishmentType.objects.create(
name={'en-GB': establishment_type.capitalize()},
index_name=slugify(establishment_type))
else:
obj = establishment_type_qs.first()
establishment_type_ids.append(obj.id)
return {'id': list(set(establishment_type_ids))}
def get_country_ids(self, country_codes_alpha_3):
country_ids = []
if country_codes_alpha_3:
for code_alpha_3 in country_codes_alpha_3:
# Code can be an empty string.
if code_alpha_3 and not code_alpha_3 == 'AAA':
country = self.get_country(code_alpha_3)
if not country:
raise serializers.ValidationError({'detail': f'Country with alpha code 3 -'
f'{code_alpha_3}, is not found.'})
country_ids.append(country.id)
return {'id': list(set(country_ids))}
def get_region_ids(self, regions, sub_regions):
region_ids = []
if regions:
for country_code_alpha_3 in regions:
for region_code_alpha_3 in regions[country_code_alpha_3]:
# Get region from sub region code.
if sub_regions and country_code_alpha_3 in sub_regions:
if region_code_alpha_3 in sub_regions[country_code_alpha_3]:
for sub_region_code_alpha_3 in sub_regions[country_code_alpha_3][region_code_alpha_3]:
region = self.get_region(
region_code_alpha_3=region_code_alpha_3,
country_code_alpha_3=country_code_alpha_3,
sub_region_code_alpha_3=sub_region_code_alpha_3)
if region:
region_ids.append(region.id)
return {'id': list(set(region_ids))}
def get_sub_region_ids(self, sub_regions):
sub_region_ids = []
if sub_regions:
for country_code_alpha_3 in sub_regions:
# FRA etc.
if country_code_alpha_3 in sub_regions:
for region_code_alpha_3 in sub_regions[country_code_alpha_3]:
# B, C, A etc.
if region_code_alpha_3 in sub_regions[country_code_alpha_3]:
for sub_region_code_alpha_3 in sub_regions[country_code_alpha_3][region_code_alpha_3]:
# 24, 32 etc.
# Get parent region
region = self.get_region(
region_code_alpha_3=region_code_alpha_3,
country_code_alpha_3=country_code_alpha_3,
sub_region_code_alpha_3=sub_region_code_alpha_3)
if region:
sub_region_qs = Region.objects.filter(parent_region__code=region.code)
if sub_region_qs.exists():
sub_region_ids.append(sub_region_qs.first().id)
else:
subdivision = subdivisions.get(code=region.code.upper())
if subdivision:
sub_region, _ = Region.objects.get_or_create(
name=subdivision.name,
code=subdivision.code,
parent_region=region,
country=region.country)
sub_region_ids.append(sub_region.id)
return {'id': list(set(sub_region_ids))}
def get_wine_region_ids(self, wine_regions):
wine_region_ids = []
if wine_regions:
for wine_region in wine_regions:
qs = WineRegion.objects.filter(name__iexact=wine_region)
if not qs.exists():
raise serializers.ValidationError({
'detail': f'Wine region - {wine_region}, is not found.'})
wine_region_ids.append(qs.first().id)
return {'id': list(set(wine_region_ids))}
def get_locale_ids(self, locales):
locale_ids = []
if locales:
for locale in [locale for locale in locales if locale]:
if len(locale) == 2:
qs = Language.objects.filter(locale__startswith=locale)
else:
qs = Language.objects.filter(locale=locale)
if not qs.exists():
raise serializers.ValidationError({
'detail': f'Language with locale - {locale}, is not found.'})
locale_ids.extend(qs.values_list('id', flat=True))
return {'id': list(set(locale_ids))}
def get_review_state(self, states):
review_states = []
if states:
for state in [state for state in states if state]:
if state == 'published':
review_states.append(Review.READY)
else:
review_states.append(Review.TO_INVESTIGATE)
return {'state': list(set(review_states))}
def get_guide(self, old_guide_id: int):
qs = models.Guide.objects.filter(old_id=old_guide_id)
if qs.exists():
return qs.first()

View File

@ -340,6 +340,9 @@ class ProductSerializer(TransferSerializerMixin):
def create(self, validated_data): def create(self, validated_data):
qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id')) qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))
wine_region = validated_data.pop('wine_region')
wine_sub_region = validated_data.pop('wine_sub_region')
# classifications # classifications
classifications = [validated_data.pop('wine_classification', None)] classifications = [validated_data.pop('wine_classification', None)]
# standards # standards
@ -361,6 +364,11 @@ class ProductSerializer(TransferSerializerMixin):
obj.standards.add(*[i for i in standards if i and i not in obj.standards.all()]) obj.standards.add(*[i for i in standards if i and i not in obj.standards.all()])
# adding tags # adding tags
obj.tags.add(*[i for i in tags if i and i not in obj.tags.all()]) obj.tags.add(*[i for i in tags if i and i not in obj.tags.all()])
# checking wine origin address
wine_origin_address, _ = location_models.WineOriginAddress.objects.get_or_create(
product=obj,
wine_region=wine_region,
wine_sub_region=wine_sub_region)
return obj return obj
def get_name(self, name, brand): def get_name(self, name, brand):
@ -390,17 +398,23 @@ class ProductSerializer(TransferSerializerMixin):
if classification_qs.exists(): if classification_qs.exists():
return classification_qs.first() return classification_qs.first()
def get_wine_region(self, wine_region): @staticmethod
def get_wine_region(wine_region):
if wine_region: if wine_region:
old_id = wine_region if not isinstance(wine_region, transfer_models.WineLocations) \
else wine_region.id
wine_region_qs = location_models.WineRegion.objects.filter( wine_region_qs = location_models.WineRegion.objects.filter(
old_id=wine_region.id) old_id=old_id)
if wine_region_qs.exists(): if wine_region_qs.exists():
return wine_region_qs.first() return wine_region_qs.first()
def get_wine_sub_region(self, wine_sub_region_id): @staticmethod
if wine_sub_region_id: def get_wine_sub_region(wine_sub_region):
if wine_sub_region:
old_id = wine_sub_region if not isinstance(wine_sub_region, transfer_models.WineLocations) \
else wine_sub_region.id
sub_region_qs = location_models.WineSubRegion.objects.filter( sub_region_qs = location_models.WineSubRegion.objects.filter(
old_id=wine_sub_region_id) old_id=old_id)
if sub_region_qs.exists(): if sub_region_qs.exists():
return sub_region_qs.first() return sub_region_qs.first()

View File

@ -55,7 +55,7 @@ class ProductReviewSerializer(ReviewSerializer):
product_id = serializers.IntegerField() product_id = serializers.IntegerField()
created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S') created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
aasm_state = serializers.CharField(allow_null=True) aasm_state = serializers.CharField(allow_null=True)
reviewer_id = serializers.IntegerField() reviewer_id = serializers.IntegerField(allow_null=True)
id = serializers.IntegerField() id = serializers.IntegerField()
def validate(self, data): def validate(self, data):
@ -82,8 +82,7 @@ class ProductReviewSerializer(ReviewSerializer):
@staticmethod @staticmethod
def get_reviewer(data): def get_reviewer(data):
user = User.objects.filter(old_id=data['reviewer_id']).first() user = User.objects.filter(old_id=data['reviewer_id']).first()
if not user: if user:
raise ValueError(f"User account not found with old_id {data['reviewer_id']}")
return user return user
@staticmethod @staticmethod

View File

@ -1,5 +1,6 @@
"""Custom middleware.""" """Custom middleware."""
from django.utils import translation from django.utils import translation, timezone
from account.models import User
from configuration.models import TranslationSettings from configuration.models import TranslationSettings
from translation.models import Language from translation.models import Language
@ -12,6 +13,14 @@ def get_locale(cookie_dict):
def get_country_code(cookie_dict): def get_country_code(cookie_dict):
return cookie_dict.get('country_code') return cookie_dict.get('country_code')
def user_last_visit(get_response):
"""Updates user last visit w/ current"""
def middleware(request):
response = get_response(request)
if request.user.is_authenticated:
User.objects.filter(pk=request.user.pk).update(last_login=timezone.now())
return response
return middleware
def parse_cookies(get_response): def parse_cookies(get_response):
"""Parse cookies.""" """Parse cookies."""

View File

@ -118,6 +118,10 @@ class CarouselCreateSerializer(serializers.ModelSerializer):
def pk(self): def pk(self):
return self.request.parser_context.get('kwargs').get('pk') return self.request.parser_context.get('kwargs').get('pk')
@property
def slug(self):
return self.request.parser_context.get('kwargs').get('slug')
class RecursiveFieldSerializer(serializers.Serializer): class RecursiveFieldSerializer(serializers.Serializer):
def to_representation(self, value): def to_representation(self, value):

View File

@ -158,7 +158,11 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
lookup_field = 'id' lookup_field = 'id'
def get_base_object(self): def get_base_object(self):
return get_object_or_404(self._model, id=self.kwargs['pk']) establishment_pk = self.kwargs.get('pk')
establishment_slug = self.kwargs.get('slug')
search_kwargs = {'id': establishment_pk} if establishment_pk else {'slug': establishment_slug}
return get_object_or_404(self._model, **search_kwargs)
def get_object(self): def get_object(self):
""" """

View File

@ -13,6 +13,9 @@ services:
MYSQL_ROOT_PASSWORD: rootPassword MYSQL_ROOT_PASSWORD: rootPassword
volumes: volumes:
- gm-mysql_db:/var/lib/mysql - gm-mysql_db:/var/lib/mysql
- .:/code
# PostgreSQL database # PostgreSQL database

2
fabfile.py vendored
View File

@ -54,7 +54,7 @@ def collectstatic():
def deploy(branch=None): def deploy(branch=None):
role = env.roles[0] role = env.roles[0]
if env.roledefs[role]['branch'] != 'develop': if env.roledefs[role]['branch'] == 'develop':
fetch() fetch()
install_requirements() install_requirements()
migrate() migrate()

View File

@ -1,7 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
./manage.py transfer -a ./manage.py transfer -a
#./manage.py transfer -d ./manage.py transfer -d
./manage.py transfer -e ./manage.py transfer -e
./manage.py transfer -n
./manage.py rm_empty_images # команда для удаления картинок с относительным урлом из news.description
./manage.py upd_transportation
./manage.py transfer --fill_city_gallery ./manage.py transfer --fill_city_gallery
./manage.py transfer -l ./manage.py transfer -l
./manage.py transfer --product ./manage.py transfer --product
@ -12,3 +15,4 @@
./manage.py transfer --inquiries ./manage.py transfer --inquiries
./manage.py transfer --assemblage ./manage.py transfer --assemblage
./manage.py transfer --purchased_plaques ./manage.py transfer --purchased_plaques
./manage.py rm_empty_images

View File

@ -99,6 +99,7 @@ EXTERNAL_APPS = [
'storages', 'storages',
'sorl.thumbnail', 'sorl.thumbnail',
'timezonefinder', 'timezonefinder',
'mptt',
] ]
@ -117,6 +118,7 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.middleware.parse_cookies', 'utils.middleware.parse_cookies',
'utils.middleware.user_last_visit',
] ]
ROOT_URLCONF = 'project.urls' ROOT_URLCONF = 'project.urls'
@ -413,10 +415,10 @@ SORL_THUMBNAIL_ALIASES = {
SIMPLE_JWT = { SIMPLE_JWT = {
# Increase access token lifetime b.c. front-end dev's cant send multiple # Increase access token lifetime b.c. front-end dev's cant send multiple
# requests to API in one HTTP request. # requests to API in one HTTP request.
'ACCESS_TOKEN_LIFETIME': timedelta(days=30), 'ACCESS_TOKEN_LIFETIME': timedelta(days=182),
'ACCESS_TOKEN_LIFETIME_SECONDS': 21600, # 6 hours in seconds 'ACCESS_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
'REFRESH_TOKEN_LIFETIME': timedelta(days=30), 'REFRESH_TOKEN_LIFETIME': timedelta(days=182),
'REFRESH_TOKEN_LIFETIME_SECONDS': 2592000, # 30 days in seconds 'REFRESH_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
'ROTATE_REFRESH_TOKENS': True, 'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True, 'BLACKLIST_AFTER_ROTATION': True,
@ -452,7 +454,7 @@ NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html'
# COOKIES # COOKIES
COOKIES_MAX_AGE = 2628000 # 30 days COOKIES_MAX_AGE = 15730000 # 6 months
SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_SAMESITE = None
@ -523,3 +525,6 @@ INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine' THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
COOKIE_DOMAIN = None COOKIE_DOMAIN = None
ELASTICSEARCH_DSL = {}
ELASTICSEARCH_INDEX_NAMES = {}

View File

@ -42,6 +42,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', 'search_indexes.documents.news': 'development_news',
'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.establishment': 'development_establishment',
'search_indexes.documents.product': 'development_product', 'search_indexes.documents.product': 'development_product',
'search_indexes.documents.tag_category': 'development_tag_category',
} }
# ELASTICSEARCH_DSL_AUTOSYNC = False # ELASTICSEARCH_DSL_AUTOSYNC = False

View File

@ -30,18 +30,10 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
THUMBNAIL_DEBUG = True THUMBNAIL_DEBUG = True
# ADDED TRANSFER APP # ADDED TRANSFER APP
# INSTALLED_APPS.append('transfer.apps.TransferConfig') INSTALLED_APPS.append('transfer.apps.TransferConfig')
# DATABASES # DATABASES
DATABASES.update({ DATABASES = {
'legacy': {
'ENGINE': 'django.db.backends.mysql',
# 'HOST': '172.22.0.1',
'HOST': 'mysql_db',
'PORT': 3306,
'NAME': 'dev',
'USER': 'dev',
'PASSWORD': 'octosecret123'},
'default': { 'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis', 'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.environ.get('DB_NAME'), 'NAME': os.environ.get('DB_NAME'),
@ -50,10 +42,19 @@ DATABASES.update({
'HOST': os.environ.get('DB_HOSTNAME'), 'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'), 'PORT': os.environ.get('DB_PORT'),
'OPTIONS': { 'OPTIONS': {
'options': '-c search_path=gm' 'options': '-c search_path=gm,public'
}, },
}, },
}) 'legacy': {
'ENGINE': 'django.db.backends.mysql',
# 'HOST': '172.22.0.1',
'HOST': 'mysql_db',
'PORT': 3306,
'NAME': 'dev',
'USER': 'dev',
'PASSWORD': 'octosecret123'
},
}
# LOGGING # LOGGING
@ -104,6 +105,7 @@ ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'local_news', # 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
'search_indexes.documents.product': 'local_product', 'search_indexes.documents.product': 'local_product',
'search_indexes.documents.tag_category': 'local_tag_category',
} }
ELASTICSEARCH_DSL_AUTOSYNC = False ELASTICSEARCH_DSL_AUTOSYNC = False
@ -111,6 +113,3 @@ TESTING = sys.argv[1:2] == ['test']
if TESTING: if TESTING:
ELASTICSEARCH_INDEX_NAMES = {} ELASTICSEARCH_INDEX_NAMES = {}
ELASTICSEARCH_DSL_AUTOSYNC = False ELASTICSEARCH_DSL_AUTOSYNC = False
# INSTALLED APPS
INSTALLED_APPS.append('transfer.apps.TransferConfig')

View File

@ -36,6 +36,7 @@ ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', # temporarily disabled 'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.establishment': 'development_establishment',
'search_indexes.documents.product': 'development_product', 'search_indexes.documents.product': 'development_product',
'search_indexes.documents.tag_category': 'development_tag_category',
} }
sentry_sdk.init( sentry_sdk.init(

View File

@ -23,6 +23,7 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'stage_news', #temporarily disabled # 'search_indexes.documents.news': 'stage_news', #temporarily disabled
'search_indexes.documents.establishment': 'stage_establishment', 'search_indexes.documents.establishment': 'stage_establishment',
'search_indexes.documents.tag_category': 'stage_tag_category',
} }
COOKIE_DOMAIN = '.id-east.ru' COOKIE_DOMAIN = '.id-east.ru'

View File

@ -3,6 +3,7 @@ from django.urls import path, include
app_name = 'mobile' app_name = 'mobile'
urlpatterns = [ urlpatterns = [
path('booking/', include('booking.urls.web')),
path('establishments/', include('establishment.urls.mobile')), path('establishments/', include('establishment.urls.mobile')),
path('location/', include('location.urls.mobile')), path('location/', include('location.urls.mobile')),
path('main/', include('main.urls.mobile')), path('main/', include('main.urls.mobile')),

View File

@ -19,7 +19,7 @@ app_name = 'web'
urlpatterns = [ urlpatterns = [
path('account/', include('account.urls.web')), path('account/', include('account.urls.web')),
path('booking/', include('booking.urls')), path('booking/', include('booking.urls.web')),
path('re_blocks/', include('advertisement.urls.web')), path('re_blocks/', include('advertisement.urls.web')),
path('collections/', include('collection.urls.web')), path('collections/', include('collection.urls.web')),
path('establishments/', include('establishment.urls.web')), path('establishments/', include('establishment.urls.web')),

View File

@ -57,3 +57,9 @@ redis==3.2.0
django_redis==4.10.0 # used byes indexing cache django_redis==4.10.0 # used byes indexing cache
kombu==4.6.6 kombu==4.6.6
celery==4.3.0 celery==4.3.0
# country information
pycountry==19.8.18
# sql-tree
django-mptt==0.9.1