Merge remote-tracking branch 'origin/develop' into feature/add-city-search
# Conflicts: # _dockerfiles/db/Dockerfile # project/settings/local.py
This commit is contained in:
commit
dfe4ab92a6
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -22,7 +22,9 @@ logs/
|
|||
|
||||
# dev
|
||||
./docker-compose.override.yml
|
||||
|
||||
celerybeat-schedule
|
||||
local_files
|
||||
celerybeat.pid
|
||||
/gm_viktor.dump
|
||||
/docker-compose.dump.yml
|
||||
/gm_production_20191029.sql
|
||||
|
|
|
|||
|
|
@ -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
|
||||
ENV LANG ru_RU.utf8
|
||||
|
|
|
|||
152
apps/account/management/commands/add_affilations.py
Normal file
152
apps/account/management/commands/add_affilations.py
Normal 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()
|
||||
24
apps/account/migrations/0021_oldrole.py
Normal file
24
apps/account/migrations/0021_oldrole.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
18
apps/account/migrations/0022_auto_20191203_1149.py
Normal file
18
apps/account/migrations/0022_auto_20191203_1149.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
22
apps/account/migrations/0023_auto_20191204_0916.py
Normal file
22
apps/account/migrations/0023_auto_20191204_0916.py
Normal 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')},
|
||||
),
|
||||
]
|
||||
|
|
@ -32,6 +32,9 @@ class Role(ProjectBaseMixin):
|
|||
ESTABLISHMENT_MANAGER = 5
|
||||
REVIEWER_MANGER = 6
|
||||
RESTAURANT_REVIEWER = 7
|
||||
SALES_MAN = 8
|
||||
WINERY_REVIEWER = 9
|
||||
SELLER = 10
|
||||
|
||||
ROLE_CHOICES = (
|
||||
(STANDARD_USER, 'Standard user'),
|
||||
|
|
@ -40,7 +43,10 @@ class Role(ProjectBaseMixin):
|
|||
(CONTENT_PAGE_MANAGER, 'Content page manager'),
|
||||
(ESTABLISHMENT_MANAGER, 'Establishment 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,
|
||||
null=False, blank=False)
|
||||
|
|
@ -287,7 +293,19 @@ class User(AbstractUser):
|
|||
|
||||
class UserRole(ProjectBaseMixin):
|
||||
"""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)
|
||||
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
|
||||
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')
|
||||
|
|
@ -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 Meta:
|
||||
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 = {
|
||||
'password': {'write_only': True}
|
||||
}
|
||||
|
|
@ -49,3 +57,13 @@ class BackDetailUserSerializer(BackUserSerializer):
|
|||
user.set_password(validated_data['password'])
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class UserRoleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.UserRole
|
||||
fields = [
|
||||
'role',
|
||||
'user',
|
||||
'establishment'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -92,7 +92,12 @@ class UserBaseSerializer(serializers.ModelSerializer):
|
|||
|
||||
model = models.User
|
||||
fields = (
|
||||
'id',
|
||||
'username',
|
||||
'fullname',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'cropped_image_url',
|
||||
'image_url',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import generics, permissions
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
from account import models
|
||||
from account.models import User
|
||||
|
|
@ -13,15 +14,15 @@ class RoleLstView(generics.ListCreateAPIView):
|
|||
|
||||
class UserRoleLstView(generics.ListCreateAPIView):
|
||||
serializer_class = serializers.UserRoleSerializer
|
||||
queryset = models.Role.objects.all()
|
||||
queryset = models.UserRole.objects.all()
|
||||
|
||||
|
||||
class UserLstView(generics.ListCreateAPIView):
|
||||
"""User list create view."""
|
||||
queryset = User.objects.all()
|
||||
queryset = User.objects.prefetch_related('roles')
|
||||
serializer_class = serializers.BackUserSerializer
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filterset_fields = (
|
||||
'email_confirmed',
|
||||
'is_staff',
|
||||
|
|
@ -29,6 +30,14 @@ class UserLstView(generics.ListCreateAPIView):
|
|||
'is_superuser',
|
||||
'roles',
|
||||
)
|
||||
ordering_fields = (
|
||||
'email_confirmed',
|
||||
'is_staff',
|
||||
'is_active',
|
||||
'is_superuser',
|
||||
'roles',
|
||||
'last_login'
|
||||
)
|
||||
|
||||
|
||||
class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
|
|||
0
apps/booking/urls/__init__.py
Normal file
0
apps/booking/urls/__init__.py
Normal file
8
apps/booking/urls/mobile.py
Normal file
8
apps/booking/urls/mobile.py
Normal 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
8
apps/booking/urls/web.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from booking.urls import common as common_views
|
||||
app = 'booking'
|
||||
|
||||
|
||||
urlpatterns_api = []
|
||||
|
||||
urlpatterns = urlpatterns_api + \
|
||||
common_views.urlpatterns
|
||||
|
|
@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
|||
periods = response['periods']
|
||||
periods_by_name = {period['period']: period for period in periods if 'period' in period}
|
||||
if not periods_by_name:
|
||||
return None
|
||||
return response
|
||||
|
||||
period_template = iter(periods_by_name.values()).__next__().copy()
|
||||
period_template.pop('total_left_seats')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from django.contrib.gis import admin
|
||||
from mptt.admin import DraggableMPTTAdmin, TreeRelatedFieldListFilter
|
||||
from utils.admin import BaseModelAdminMixin
|
||||
|
||||
from collection import models
|
||||
|
||||
|
|
@ -11,3 +13,22 @@ class CollectionAdmin(admin.ModelAdmin):
|
|||
@admin.register(models.Guide)
|
||||
class GuideAdmin(admin.ModelAdmin):
|
||||
"""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),
|
||||
# )
|
||||
|
|
|
|||
120
apps/collection/management/commands/check_guide_dependencies.py
Normal file
120
apps/collection/management/commands/check_guide_dependencies.py
Normal 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()
|
||||
78
apps/collection/migrations/0018_auto_20191127_1047.py
Normal file
78
apps/collection/migrations/0018_auto_20191127_1047.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
41
apps/collection/migrations/0019_advertorial_guidefilter.py
Normal file
41
apps/collection/migrations/0019_advertorial_guidefilter.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
24
apps/collection/migrations/0021_guideelementtype.py
Normal file
24
apps/collection/migrations/0021_guideelementtype.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
48
apps/collection/migrations/0022_guideelement.py
Normal file
48
apps/collection/migrations/0022_guideelement.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
31
apps/collection/migrations/0023_advertorial.py
Normal file
31
apps/collection/migrations/0023_advertorial.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import re
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
from django.contrib.contenttypes.fields import ContentType
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
|
@ -84,27 +87,101 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
|||
verbose_name = _('collection')
|
||||
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):
|
||||
"""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):
|
||||
"""Guide model."""
|
||||
parent = models.ForeignKey(
|
||||
'self', verbose_name=_('parent'), on_delete=models.CASCADE,
|
||||
null=True, blank=True, default=None
|
||||
BUILT = 0
|
||||
WAITING = 1
|
||||
REMOVING = 2
|
||||
BUILDING = 3
|
||||
|
||||
STATE_CHOICES = (
|
||||
(BUILT, 'built'),
|
||||
(WAITING, 'waiting'),
|
||||
(REMOVING, 'removing'),
|
||||
(BUILDING, 'building'),
|
||||
|
||||
)
|
||||
advertorials = JSONField(
|
||||
_('advertorials'), null=True, blank=True,
|
||||
default=None, help_text='{"key":"value"}')
|
||||
collection = models.ForeignKey(Collection, on_delete=models.CASCADE,
|
||||
null=True, blank=True, default=None,
|
||||
verbose_name=_('collection'))
|
||||
|
||||
start = models.DateTimeField(null=True,
|
||||
verbose_name=_('start'))
|
||||
vintage = models.IntegerField(validators=[MinValueValidator(1900),
|
||||
MaxValueValidator(2100)],
|
||||
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()
|
||||
|
||||
|
|
@ -116,3 +193,190 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
|||
def __str__(self):
|
||||
"""String method."""
|
||||
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
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
|||
collection_type_display = serializers.CharField(
|
||||
source='get_collection_type_display', 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:
|
||||
model = models.Collection
|
||||
|
|
@ -36,6 +38,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
|||
'slug',
|
||||
'start',
|
||||
'end',
|
||||
'count_related_objects',
|
||||
'related_object_names',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,4 @@ class GuideSerializer(serializers.ModelSerializer):
|
|||
'name',
|
||||
'start',
|
||||
'end',
|
||||
'parent',
|
||||
'advertorials',
|
||||
'collection'
|
||||
]
|
||||
|
|
|
|||
319
apps/collection/transfer_data.py
Normal file
319
apps/collection/transfer_data.py
Normal 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
|
||||
]
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ class ContactPhoneInline(admin.TabularInline):
|
|||
class GalleryImageInline(admin.TabularInline):
|
||||
"""Gallery image inline admin."""
|
||||
model = models.EstablishmentGallery
|
||||
raw_id_fields = ['image', ]
|
||||
extra = 0
|
||||
|
||||
|
||||
|
|
@ -61,17 +62,20 @@ class ProductInline(admin.TabularInline):
|
|||
|
||||
class CompanyInline(admin.TabularInline):
|
||||
model = models.Company
|
||||
raw_id_fields = ['establishment', 'address']
|
||||
extra = 0
|
||||
|
||||
|
||||
class EstablishmentNote(admin.TabularInline):
|
||||
model = models.EstablishmentNote
|
||||
extra = 0
|
||||
raw_id_fields = ['user', ]
|
||||
|
||||
|
||||
class PurchasedProduct(admin.TabularInline):
|
||||
class PurchasedProductInline(admin.TabularInline):
|
||||
model = PurchasedProduct
|
||||
extra = 0
|
||||
raw_id_fields = ['product', ]
|
||||
|
||||
|
||||
@admin.register(models.Establishment)
|
||||
|
|
@ -80,13 +84,12 @@ class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
|||
list_display = ['id', '__str__', 'image_tag', ]
|
||||
search_fields = ['id', 'name', 'index_name', 'slug']
|
||||
list_filter = ['public_mark', 'toque_number']
|
||||
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote,
|
||||
PurchasedProduct]
|
||||
|
||||
inlines = [CompanyInline, EstablishmentNote, GalleryImageInline,
|
||||
PurchasedProductInline, ]
|
||||
# inlines = [
|
||||
# AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||
# ReviewInline, CommentInline, ProductInline]
|
||||
raw_id_fields = ('address',)
|
||||
raw_id_fields = ('address', 'collections', 'tags', 'schedule')
|
||||
|
||||
|
||||
@admin.register(models.Position)
|
||||
|
|
@ -136,3 +139,4 @@ class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
|||
class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||
"""Admin conf for Company model."""
|
||||
raw_id_fields = ['establishment', 'address', ]
|
||||
|
||||
|
|
|
|||
|
|
@ -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}'))
|
||||
36
apps/establishment/management/commands/fix_scheduler.py
Normal file
36
apps/establishment/management/commands/fix_scheduler.py
Normal 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.'))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
"""Establishment models."""
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from typing import List
|
||||
from operator import or_
|
||||
from typing import List
|
||||
|
||||
import elasticsearch_dsl
|
||||
from django.conf import settings
|
||||
|
|
@ -22,6 +22,7 @@ from timezone_field import TimeZoneField
|
|||
|
||||
from collection.models import Collection
|
||||
from location.models import Address
|
||||
from location.models import WineOriginAddressMixin
|
||||
from main.models import Award, Currency
|
||||
from tag.models import Tag
|
||||
from review.models import Review
|
||||
|
|
@ -251,6 +252,15 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
return self.filter(id__in=subquery_filter_by_distance) \
|
||||
.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):
|
||||
"""Prefetch actual employees."""
|
||||
return self.prefetch_related(
|
||||
|
|
@ -448,9 +458,14 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
|||
return super().visible_tags \
|
||||
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||
'business_tag', 'business_tags_de']) \
|
||||
\
|
||||
.exclude(value__in=['rss', 'rss_selection'])
|
||||
# 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):
|
||||
toque_number = 0
|
||||
if self.address and self.public_mark:
|
||||
|
|
@ -614,6 +629,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
|||
def artisan_category_indexing(self):
|
||||
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):
|
||||
"""QuerySet for model EstablishmentNote."""
|
||||
|
|
|
|||
|
|
@ -232,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
|||
def validate(self, attrs):
|
||||
"""Override validate method."""
|
||||
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')
|
||||
|
||||
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)
|
||||
|
||||
if not establishment_qs.exists():
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ from utils import exceptions as utils_exceptions
|
|||
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
||||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||
FavoritesCreateSerializer)
|
||||
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
||||
EstablishmentWineOriginBaseSerializer
|
||||
|
||||
|
||||
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):
|
||||
"""SHORT Serializer for displaying info about an establishment on product page."""
|
||||
establishment_type = EstablishmentTypeGeoSerializer()
|
||||
|
|
@ -283,6 +309,8 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
|||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||
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',
|
||||
allow_null=True,
|
||||
read_only=True)
|
||||
|
|
@ -312,6 +340,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
|||
'preview_image',
|
||||
'new_image',
|
||||
'tz',
|
||||
'wine_regions',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -364,7 +393,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
|||
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
||||
many=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)
|
||||
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)
|
||||
|
|
@ -372,6 +401,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
|||
range_price_carte = RangePriceSerializer(read_only=True)
|
||||
vintage_year = serializers.ReadOnlyField()
|
||||
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
||||
wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta(EstablishmentBaseSerializer.Meta):
|
||||
"""Meta class."""
|
||||
|
|
@ -398,6 +428,20 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
|||
'transportation',
|
||||
'vintage_year',
|
||||
'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."""
|
||||
|
||||
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):
|
||||
|
|
@ -492,7 +546,9 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
|
|||
"""Serializer to carousel object w/ model News."""
|
||||
|
||||
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:
|
||||
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||
|
||||
|
|
|
|||
|
|
@ -104,18 +104,18 @@ class EstablishmentBTests(BaseTestCase):
|
|||
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
||||
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)
|
||||
|
||||
update_data = {
|
||||
'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)
|
||||
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')
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -372,22 +372,22 @@ class EstablishmentShedulerTests(ChildTestCase):
|
|||
'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)
|
||||
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)
|
||||
|
||||
update_data = {
|
||||
'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)
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -484,8 +484,8 @@ class EstablishmentCarouselTests(ChildTestCase):
|
|||
"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)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -8,25 +8,25 @@ app_name = 'establishment'
|
|||
|
||||
urlpatterns = [
|
||||
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
||||
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||
path('<int:pk>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
|
||||
path('slug/<slug:slug>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||
path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
|
||||
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'),
|
||||
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||
path('slug/<slug:slug>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||
name='schedule-create'),
|
||||
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||
path('slug/<slug:slug>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||
name='gallery-list'),
|
||||
path('<int:pk>/gallery/<int:image_id>/',
|
||||
path('slug/<slug:slug>/gallery/<int:image_id>/',
|
||||
views.EstablishmentGalleryCreateDestroyView.as_view(),
|
||||
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'),
|
||||
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'),
|
||||
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||
path('slug/<slug:slug>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||
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'),
|
||||
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
||||
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ urlpatterns = [
|
|||
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||
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>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ from establishment import views
|
|||
from establishment.urls.common import urlpatterns as common_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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
"""Establishment app web urlconf."""
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
|||
|
||||
|
||||
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
lookup_field = 'slug'
|
||||
queryset = models.Establishment.objects.all()
|
||||
serializer_class = serializers.EstablishmentRUDSerializer
|
||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
||||
|
|
@ -38,6 +39,7 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
|
||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment schedule RUD view"""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = ScheduleRUDSerializer
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
|
@ -45,11 +47,11 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
"""
|
||||
Returns the object the view is displaying.
|
||||
"""
|
||||
establishment_pk = self.kwargs['pk']
|
||||
establishment_slug = self.kwargs['slug']
|
||||
schedule_id = self.kwargs['schedule_id']
|
||||
|
||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||
pk=establishment_pk)
|
||||
slug=establishment_slug)
|
||||
schedule = get_object_or_404(klass=establishment.schedule,
|
||||
id=schedule_id)
|
||||
|
||||
|
|
@ -62,6 +64,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
|
||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||
"""Establishment schedule Create view"""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = ScheduleCreateSerializer
|
||||
queryset = Timetable.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
|
@ -165,7 +168,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
|
|||
pagination_class = None
|
||||
|
||||
|
||||
class EstablishmentEmployeeListView(generics.ListAPIView):
|
||||
class EstablishmentEmployeeListView(generics.ListCreateAPIView):
|
||||
"""Establishment emplyoees list view."""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
serializer_class = serializers.EstablishmentEmployeeBackSerializer
|
||||
|
|
@ -210,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||
CreateDestroyGalleryViewMixin):
|
||||
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -218,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
|||
"""
|
||||
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,
|
||||
image_id=self.kwargs.get('image_id'))
|
||||
|
||||
|
|
@ -231,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
|||
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||
generics.ListAPIView):
|
||||
"""Resource for returning gallery for establishment for back-office users."""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.ImageBaseSerializer
|
||||
|
||||
def get_object(self):
|
||||
"""Override get_object method."""
|
||||
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
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -252,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
|||
generics.ListCreateAPIView):
|
||||
"""List|Create establishment company view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentCompanyListCreateSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -259,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
|||
establishment_qs = models.Establishment.objects.all()
|
||||
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
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -275,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
|||
generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Create|Retrieve|Update|Destroy establishment company view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.CompanyBaseSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -282,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
|||
establishment_qs = models.Establishment.objects.all()
|
||||
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'))
|
||||
|
||||
# May raise a permission denied
|
||||
|
|
@ -295,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
|||
generics.ListCreateAPIView):
|
||||
"""Retrieve|Update|Destroy establishment note view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentNoteListCreateSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -302,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
|||
establishment_qs = models.Establishment.objects.all()
|
||||
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
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -318,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
|||
generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Create|Retrieve|Update|Destroy establishment note view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentNoteBaseSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -325,7 +334,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
|||
establishment_qs = models.Establishment.objects.all()
|
||||
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'])
|
||||
|
||||
# May raise a permission denied
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
|||
.with_extended_address_related().with_currency_related() \
|
||||
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||
.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):
|
||||
|
|
@ -51,6 +51,13 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView
|
|||
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):
|
||||
"""List view for last reviewed establishments."""
|
||||
|
||||
|
|
@ -107,10 +114,7 @@ class EstablishmentCommentListView(generics.ListAPIView):
|
|||
"""Override get_queryset method"""
|
||||
|
||||
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
||||
return comment_models.Comment.objects.by_content_type(app_label='establishment',
|
||||
model='establishment') \
|
||||
.by_object_id(object_id=establishment.pk) \
|
||||
.order_by('-created')
|
||||
return establishment.comments.order_by('-created')
|
||||
|
||||
|
||||
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
@ -145,6 +149,7 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
|
|||
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
|
||||
"""View for create/destroy establishment from carousel."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
_model = models.Establishment
|
||||
serializer_class = serializers.EstablishmentCarouselCreateSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
|||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
return Establishment.objects.filter(favorites__user=self.request.user) \
|
||||
.order_by('-favorites')
|
||||
.order_by('-favorites').with_base_related() \
|
||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||
|
||||
|
||||
class FavoritesProductListView(generics.ListAPIView):
|
||||
|
|
|
|||
|
|
@ -45,3 +45,15 @@ class AddressAdmin(admin.OSMGeoAdmin):
|
|||
def geo_lat(self, item):
|
||||
if isinstance(item.coordinates, Point):
|
||||
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', ]
|
||||
|
|
|
|||
30
apps/location/management/commands/fix_address.py
Normal file
30
apps/location/management/commands/fix_address.py
Normal 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)}'))
|
||||
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -207,12 +207,12 @@ class WineRegionQuerySet(models.QuerySet):
|
|||
def with_sub_region_related(self):
|
||||
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 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."""
|
||||
name = models.CharField(_('name'), max_length=255)
|
||||
country = models.ForeignKey(Country, on_delete=models.PROTECT,
|
||||
|
|
@ -293,6 +293,55 @@ class WineVillage(models.Model):
|
|||
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
|
||||
@receiver(post_save, sender=Country)
|
||||
def run_recalculate_price_levels(sender, instance, **kwargs):
|
||||
|
|
|
|||
|
|
@ -191,10 +191,58 @@ class WineSubRegionBaseSerializer(serializers.ModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class WineRegionSerializer(WineRegionBaseSerializer):
|
||||
"""Wine region w/ subregion serializer"""
|
||||
class EstablishmentWineRegionBaseSerializer(serializers.ModelSerializer):
|
||||
"""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):
|
||||
fields = WineRegionBaseSerializer.Meta.fields + [
|
||||
|
|
|
|||
37
apps/main/management/commands/add_home_page_carousel.py
Normal file
37
apps/main/management/commands/add_home_page_carousel.py
Normal 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.'))
|
||||
|
|
@ -17,9 +17,24 @@ class FeatureSerializer(serializers.ModelSerializer):
|
|||
fields = (
|
||||
'id',
|
||||
'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):
|
||||
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):
|
||||
"""Site settings serializer."""
|
||||
|
||||
|
|
@ -99,23 +100,15 @@ class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer):
|
|||
]
|
||||
|
||||
|
||||
class SiteSerializer(serializers.ModelSerializer):
|
||||
class SiteSerializer(SiteSettingsSerializer):
|
||||
country = CountrySerializer()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
model = models.SiteSettings
|
||||
fields = [
|
||||
'subdomain',
|
||||
'site_url',
|
||||
'country',
|
||||
'default_site',
|
||||
'pinterest_page_url',
|
||||
'twitter_page_url',
|
||||
'facebook_page_url',
|
||||
'instagram_page_url',
|
||||
'contact_email',
|
||||
'currency'
|
||||
fields = SiteSettingsSerializer.Meta.fields + [
|
||||
'id',
|
||||
'country'
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -13,5 +13,11 @@ urlpatterns = [
|
|||
path('site-settings/<subdomain>/', views.SiteSettingsBackOfficeView.as_view(),
|
||||
name='site-settings'),
|
||||
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'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,16 +44,26 @@ class FeatureBackView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.FeatureSerializer
|
||||
|
||||
|
||||
class SiteFeatureBackView(generics.ListCreateAPIView):
|
||||
"""Feature list or create View."""
|
||||
serializer_class = serializers.SiteFeatureSerializer
|
||||
|
||||
|
||||
class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Feature RUD View."""
|
||||
serializer_class = serializers.FeatureSerializer
|
||||
|
||||
|
||||
class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Feature RUD View."""
|
||||
serializer_class = serializers.SiteFeatureSerializer
|
||||
|
||||
|
||||
class SiteSettingsBackOfficeView(SiteSettingsView):
|
||||
"""Site settings View."""
|
||||
serializer_class = serializers.SiteSettingsBackOfficeSerializer
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
||||
|
||||
class SiteListBackOfficeView(SiteListView):
|
||||
"""Site settings View."""
|
||||
serializer_class = serializers.SiteBackOfficeSerializer
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
|
|
|||
17
apps/news/management/commands/rm_empty_images.py
Normal file
17
apps/news/management/commands/rm_empty_images.py
Normal 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()
|
||||
|
|
@ -8,7 +8,7 @@ from partner.serializers import common as serializers
|
|||
# Mixins
|
||||
class PartnerViewMixin(generics.GenericAPIView):
|
||||
"""View mixin for Partner views"""
|
||||
queryset = models.Partner.objects.all()
|
||||
queryset = models.Partner.objects.distinct("name")
|
||||
|
||||
|
||||
# Views
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Product admin conf."""
|
||||
from django.contrib import admin
|
||||
|
||||
from utils.admin import BaseModelAdminMixin
|
||||
from .models import Product, ProductType, ProductSubType, ProductGallery, Unit
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class ProductFilterSet(filters.FilterSet):
|
|||
"""Product filter set."""
|
||||
|
||||
establishment_id = filters.NumberFilter()
|
||||
current_product = filters.CharFilter(method='without_current_product')
|
||||
product_type = filters.CharFilter(method='by_product_type')
|
||||
product_subtype = filters.CharFilter(method='by_product_subtype')
|
||||
|
||||
|
|
@ -21,6 +22,11 @@ class ProductFilterSet(filters.FilterSet):
|
|||
'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):
|
||||
if value not in EMPTY_VALUES:
|
||||
return queryset.by_product_type(value)
|
||||
|
|
|
|||
25
apps/product/management/commands/add_public_mark.py
Normal file
25
apps/product/management/commands/add_public_mark.py
Normal 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)}'))
|
||||
44
apps/product/management/commands/add_wine_origin_address.py
Normal file
44
apps/product/management/commands/add_wine_origin_address.py
Normal 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)}'))
|
||||
21
apps/product/migrations/0019_auto_20191204_1420.py
Normal file
21
apps/product/migrations/0019_auto_20191204_1420.py
Normal 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',
|
||||
),
|
||||
]
|
||||
|
|
@ -2,11 +2,12 @@
|
|||
from django.contrib.contenttypes import fields as generic
|
||||
from django.contrib.gis.db import models as gis_models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import Case, When
|
||||
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,
|
||||
TranslatedFieldsMixin, TJSONField, FavoritesMixin,
|
||||
GalleryModelMixin, IntermediateGalleryModelMixin)
|
||||
|
|
@ -89,8 +90,8 @@ class ProductQuerySet(models.QuerySet):
|
|||
'establishment__address__city', 'establishment__address__city__country',
|
||||
'establishment__establishment_subtypes', 'product_gallery',
|
||||
'gallery', 'product_type', 'subtypes',
|
||||
'classifications__classification_type', 'classifications__tags') \
|
||||
.select_related('wine_region', 'wine_sub_region')
|
||||
'classifications__classification_type', 'classifications__tags',
|
||||
'wine_origins__wine_region', 'wine_origins__wine_sub_region', )
|
||||
|
||||
def common(self):
|
||||
return self.filter(category=self.model.COMMON)
|
||||
|
|
@ -101,6 +102,11 @@ class ProductQuerySet(models.QuerySet):
|
|||
def wines(self):
|
||||
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):
|
||||
"""Filter by type."""
|
||||
return self.filter(product_type__index_name__icontains=product_type)
|
||||
|
|
@ -176,14 +182,6 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
|
|||
verbose_name=_('establishment'))
|
||||
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||
verbose_name=_('public mark'), )
|
||||
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
|
||||
related_name='wines',
|
||||
blank=True, null=True, 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',
|
||||
blank=True,
|
||||
verbose_name=_('classifications'))
|
||||
|
|
|
|||
|
|
@ -62,8 +62,9 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer):
|
|||
'available',
|
||||
'product_type',
|
||||
'establishment',
|
||||
'wine_region',
|
||||
'wine_sub_region',
|
||||
# todo: need fix
|
||||
# 'wine_region',
|
||||
# 'wine_sub_region',
|
||||
'wine_village',
|
||||
'state',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ from rest_framework import serializers
|
|||
|
||||
from comment.models import Comment
|
||||
from comment.serializers import CommentSerializer
|
||||
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer, EstablishmentProductShortSerializer
|
||||
from gallery.models import Image
|
||||
from establishment.serializers import EstablishmentProductShortSerializer
|
||||
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
||||
from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer
|
||||
from main.serializers import AwardSerializer
|
||||
from product import models
|
||||
from review.serializers import ReviewShortSerializer
|
||||
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
|
||||
from utils import exceptions as utils_exceptions
|
||||
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):
|
||||
|
|
@ -90,7 +90,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
|||
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
|
||||
establishment_detail = EstablishmentProductShortSerializer(source='establishment', 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)
|
||||
preview_image_url = serializers.URLField(allow_null=True,
|
||||
read_only=True)
|
||||
|
|
@ -110,7 +110,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
|||
'vintage',
|
||||
'tags',
|
||||
'preview_image_url',
|
||||
'wine_region',
|
||||
'wine_regions',
|
||||
'wine_colors',
|
||||
'in_favorites',
|
||||
]
|
||||
|
|
@ -119,12 +119,12 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
|||
class ProductDetailSerializer(ProductBaseSerializer):
|
||||
"""Product detail serializer."""
|
||||
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)
|
||||
awards = AwardSerializer(many=True, read_only=True)
|
||||
classifications = ProductClassificationBaseSerializer(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)
|
||||
sugar_contents = TagBaseSerializer(many=True, read_only=True)
|
||||
grape_variety = TagBaseSerializer(many=True, read_only=True)
|
||||
|
|
@ -141,7 +141,7 @@ class ProductDetailSerializer(ProductBaseSerializer):
|
|||
'awards',
|
||||
'classifications',
|
||||
'standards',
|
||||
'wine_sub_region',
|
||||
'wine_origins',
|
||||
'bottles_produced',
|
||||
'sugar_contents',
|
||||
'image_url',
|
||||
|
|
|
|||
|
|
@ -60,10 +60,7 @@ class ProductCommentListView(generics.ListAPIView):
|
|||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
product = get_object_or_404(Product, slug=self.kwargs['slug'])
|
||||
return Comment.objects.by_content_type(app_label='product',
|
||||
model='product') \
|
||||
.by_object_id(object_id=product.pk) \
|
||||
.order_by('-created')
|
||||
return product.comments.order_by('-created')
|
||||
|
||||
|
||||
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
|
|||
|
|
@ -115,12 +115,8 @@ def transfer_product_reviews():
|
|||
products = Product.objects.filter(
|
||||
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(
|
||||
product_id__in=list(products),
|
||||
reviewer_id__in=list(users),
|
||||
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'product_id', 'mark', 'vintage')
|
||||
|
||||
serialized_data = ProductReviewSerializer(data=list(queryset.values()), many=True)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from search_indexes.documents.establishment import EstablishmentDocument
|
||||
from search_indexes.documents.news import NewsDocument
|
||||
from search_indexes.documents.product import ProductDocument
|
||||
from search_indexes.documents.tag_category import TagCategoryDocument
|
||||
from search_indexes.tasks import es_update
|
||||
|
||||
# todo: make signal to update documents on related fields
|
||||
|
|
@ -8,5 +9,6 @@ __all__ = [
|
|||
'EstablishmentDocument',
|
||||
'NewsDocument',
|
||||
'ProductDocument',
|
||||
'TagCategoryDocument',
|
||||
'es_update',
|
||||
]
|
||||
|
|
@ -83,39 +83,66 @@ class EstablishmentDocument(Document):
|
|||
multi=True)
|
||||
products = 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_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={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
}),
|
||||
})),
|
||||
'wine_colors': fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(),
|
||||
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||
'value': fields.KeywordField(),
|
||||
},
|
||||
multi=True,
|
||||
),
|
||||
'wine_sub_region': fields.ObjectField(properties={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
}),
|
||||
},
|
||||
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={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
})})
|
||||
)
|
||||
schedule = fields.ListField(fields.ObjectField(
|
||||
properties={
|
||||
'id': fields.IntegerField(attr='id'),
|
||||
'weekday': fields.IntegerField(attr='weekday'),
|
||||
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
||||
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
||||
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
||||
}
|
||||
))
|
||||
address = fields.ObjectField(
|
||||
|
|
|
|||
|
|
@ -83,22 +83,28 @@ class ProductDocument(Document):
|
|||
},
|
||||
multi=True,
|
||||
)
|
||||
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={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
})
|
||||
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={
|
||||
'id': fields.IntegerField(),
|
||||
'name': fields.KeywordField(),
|
||||
})})
|
||||
)
|
||||
classifications = fields.ObjectField( # TODO
|
||||
properties={
|
||||
'classification_type': fields.ObjectField(properties={}),
|
||||
|
|
|
|||
33
apps/search_indexes/documents/tag_category.py
Normal file
33
apps/search_indexes/documents/tag_category.py
Normal 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()
|
||||
|
|
@ -4,6 +4,8 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \
|
|||
FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend
|
||||
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
||||
from six import iteritems
|
||||
from search_indexes.documents import TagCategoryDocument
|
||||
from tag.models import TagCategory
|
||||
|
||||
|
||||
class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
|
||||
|
|
@ -11,20 +13,13 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend):
|
|||
|
||||
@staticmethod
|
||||
def calculate_center(first, second):
|
||||
if second[1] < 0 <= first[1]:
|
||||
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
|
||||
diff = (reverse_first + reverse_second) / 2
|
||||
|
||||
if reverse_first < reverse_second:
|
||||
result_part = -180 + (180 + second[1] - diff)
|
||||
|
||||
else:
|
||||
result_part = 180 - (180 - first[1] - diff)
|
||||
|
||||
if second[1] < first[1]:
|
||||
res_longtitude = first[1] + (360 + abs(first[1]) - abs(second[1])) / 2
|
||||
else:
|
||||
result_part = (first[1] + second[1]) / 2
|
||||
res_longtitude = first[1] + (second[1] - first[1]) / 2
|
||||
|
||||
return (first[0] + second[0]) / 2, result_part
|
||||
# return (first[0] + second[0]) / 2, result_part
|
||||
return (first[0] + second[0]) / 2, res_longtitude
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ret = super().filter_queryset(request, queryset, view)
|
||||
|
|
@ -52,10 +47,30 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
|
|||
:param view:
|
||||
:return:
|
||||
"""
|
||||
def makefilter(cur_facet):
|
||||
def myfilter(x):
|
||||
def make_filter(cur_facet):
|
||||
def _filter(x):
|
||||
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)
|
||||
setattr(view.paginator, 'facets_computed', {})
|
||||
for __field, __facet in iteritems(__facets):
|
||||
|
|
@ -67,29 +82,73 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
|
|||
'global'
|
||||
).bucket(__field, agg)
|
||||
else:
|
||||
qs = queryset.__copy__()
|
||||
qs.query = queryset.query._clone()
|
||||
filterer = makefilter(__facet)
|
||||
for param_type in ['must', 'must_not', 'should']:
|
||||
if qs.query._proxied._params.get(param_type):
|
||||
qs.query._proxied._params[param_type] = list(
|
||||
filter(
|
||||
filterer, qs.query._proxied._params[param_type]
|
||||
if __field != 'tag':
|
||||
qs = queryset.__copy__()
|
||||
qs.query = queryset.query._clone()
|
||||
filterer = make_filter(__facet)
|
||||
for param_type in ['must', 'must_not', 'should']:
|
||||
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')
|
||||
facet_name = '_filter_' + __field
|
||||
qs.aggs.bucket(
|
||||
facet_name,
|
||||
'filter',
|
||||
filter=agg_filter
|
||||
).bucket(__field, agg)
|
||||
view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]})
|
||||
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')
|
||||
facet_name = '_filter_' + __field
|
||||
qs.aggs.bucket(
|
||||
facet_name,
|
||||
'filter',
|
||||
filter=agg_filter
|
||||
).bucket(__field, agg)
|
||||
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
|
||||
|
||||
@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):
|
||||
"""Custom SearchFilterBackend."""
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ class WineRegionDocumentSerializer(serializers.Serializer):
|
|||
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):
|
||||
"""Tag ES document serializer,"""
|
||||
|
||||
|
|
@ -168,6 +178,7 @@ class ScheduleDocumentSerializer(serializers.Serializer):
|
|||
weekday = serializers.IntegerField()
|
||||
weekday_display = serializers.CharField()
|
||||
closed_at = serializers.CharField()
|
||||
opening_at = serializers.CharField()
|
||||
|
||||
|
||||
class InFavoritesMixin(DocumentSerializer):
|
||||
|
|
@ -222,6 +233,13 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
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):
|
||||
"""Establishment document serializer."""
|
||||
|
||||
|
|
@ -233,6 +251,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
|
||||
artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
||||
wine_origins = WineOriginSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -258,6 +277,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
'works_evening',
|
||||
'works_at_weekday',
|
||||
'tz',
|
||||
'wine_origins',
|
||||
# 'works_now',
|
||||
# 'collections',
|
||||
# 'establishment_type',
|
||||
|
|
@ -270,11 +290,11 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
|
||||
tags = TagsDocumentSerializer(many=True, source='related_tags')
|
||||
subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True)
|
||||
wine_region = WineRegionDocumentSerializer(allow_null=True)
|
||||
wine_colors = TagDocumentSerializer(many=True)
|
||||
grape_variety = TagDocumentSerializer(many=True)
|
||||
product_type = ProductTypeDocumentSerializer(allow_null=True)
|
||||
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
|
||||
wine_origins = WineOriginSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -295,10 +315,10 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
'tags',
|
||||
'product_type',
|
||||
'subtypes',
|
||||
'wine_region',
|
||||
'wine_colors',
|
||||
'grape_variety',
|
||||
'establishment_detail',
|
||||
'average_price',
|
||||
'created',
|
||||
'wine_origins',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -158,7 +158,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
|||
},
|
||||
},
|
||||
'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,
|
||||
'enabled': True,
|
||||
'options': {
|
||||
|
|
@ -213,14 +221,14 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
|||
],
|
||||
},
|
||||
'wine_region_id': {
|
||||
'field': 'products.wine_region.id',
|
||||
'field': 'wine_origins.wine_region.id',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
],
|
||||
},
|
||||
'wine_sub_region_id': {
|
||||
'field': 'products.wine_sub_region_id',
|
||||
'field': 'wine_origins.wine_sub_region.id',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
|
|
@ -346,7 +354,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
|||
|
||||
faceted_search_fields = {
|
||||
'tag': {
|
||||
'field': 'wine_colors.id',
|
||||
'field': 'tags.id',
|
||||
'enabled': True,
|
||||
'facet': TermsFacet,
|
||||
'options': {
|
||||
|
|
@ -354,13 +362,21 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
|||
},
|
||||
},
|
||||
'wine_region_id': {
|
||||
'field': 'wine_region.id',
|
||||
'enabled': True,
|
||||
'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,
|
||||
'enabled': True,
|
||||
'options': {
|
||||
'size': utils.FACET_MAX_RESPONSE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
translated_search_fields = (
|
||||
|
|
@ -384,14 +400,14 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
|||
],
|
||||
},
|
||||
'wine_region_id': {
|
||||
'field': 'wine_region.id',
|
||||
'field': 'wine_origins.wine_region.id',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
],
|
||||
},
|
||||
'wine_sub_region_id': {
|
||||
'field': 'wine_sub_region_id',
|
||||
'field': 'wine_origins.wine_sub_region.id',
|
||||
'lookups': [
|
||||
constants.LOOKUP_QUERY_IN,
|
||||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
|
|
@ -404,9 +420,9 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
|||
constants.LOOKUP_QUERY_EXCLUDE,
|
||||
]
|
||||
},
|
||||
'wine_from_country_code': {
|
||||
'field': 'wine_region.country.code',
|
||||
},
|
||||
# 'wine_from_country_code': {
|
||||
# 'field': 'wine_origins.wine_region.country.code',
|
||||
# },
|
||||
'for_establishment': {
|
||||
'field': 'establishment.slug',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,7 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
|
|||
|
||||
def by_establishment_type(self, queryset, name, value):
|
||||
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)
|
||||
|
||||
# TMP TODO remove it later
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class Tag(TranslatedFieldsMixin, models.Model):
|
|||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||
|
||||
old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'),
|
||||
blank=True, null=True, default=None)
|
||||
blank=True, null=True, default=None)
|
||||
|
||||
objects = TagQuerySet.as_manager()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"""Tag serializers."""
|
||||
from rest_framework import serializers
|
||||
from establishment.models import (Establishment, EstablishmentType,
|
||||
EstablishmentSubType)
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
|
||||
from establishment.models import (Establishment, EstablishmentType)
|
||||
from news.models import News, NewsType
|
||||
from tag import models
|
||||
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
||||
|
|
@ -12,6 +13,9 @@ from utils.serializers import TranslatedField
|
|||
class TagBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for model Tag."""
|
||||
|
||||
def get_extra_kwargs(self):
|
||||
return super().get_extra_kwargs()
|
||||
|
||||
label_translated = TranslatedField()
|
||||
index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
|
||||
|
||||
|
|
@ -37,6 +41,7 @@ class TagBackOfficeSerializer(TagBaseSerializer):
|
|||
'category'
|
||||
)
|
||||
|
||||
|
||||
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||
"""SHORT Serializer for TagCategory"""
|
||||
|
||||
|
|
@ -57,7 +62,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
"""Serializer for model TagCategory."""
|
||||
|
||||
label_translated = TranslatedField()
|
||||
tags = TagBaseSerializer(many=True, read_only=True)
|
||||
tags = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -70,6 +75,25 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
'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):
|
||||
"""Serializer for model TagCategory."""
|
||||
|
|
@ -174,15 +198,15 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
|
|||
attrs['tag_category'] = tag_category
|
||||
|
||||
if obj_type == self.ESTABLISHMENT_TYPE:
|
||||
establishment_type = EstablishmentType.objects.filter(pk=obj_id).\
|
||||
establishment_type = EstablishmentType.objects.filter(pk=obj_id). \
|
||||
first()
|
||||
if not establishment_type:
|
||||
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():
|
||||
raise ObjectAlreadyAdded()
|
||||
if request.method == 'DELETE' and not tag_category.\
|
||||
establishment_types.filter(pk=establishment_type.pk).\
|
||||
if request.method == 'DELETE' and not tag_category. \
|
||||
establishment_types.filter(pk=establishment_type.pk). \
|
||||
exists():
|
||||
raise RemovedBindingObjectNotFound()
|
||||
attrs['related_object'] = establishment_type
|
||||
|
|
@ -190,10 +214,10 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
|
|||
news_type = NewsType.objects.filter(pk=obj_id).first()
|
||||
if not news_type:
|
||||
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():
|
||||
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():
|
||||
raise RemovedBindingObjectNotFound()
|
||||
attrs['related_object'] = news_type
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ class Timetable(ProjectBaseMixin):
|
|||
def closed_at_str(self):
|
||||
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
|
||||
def opening_time(self):
|
||||
return self.opening_at or self.lunch_start or self.dinner_start
|
||||
|
|
|
|||
|
|
@ -45,8 +45,14 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
|||
.parser_context.get('view')\
|
||||
.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.
|
||||
establishment_qs = Establishment.objects.filter(pk=establishment_pk)
|
||||
establishment_qs = Establishment.objects.filter(**search_kwargs)
|
||||
if not establishment_qs.exists():
|
||||
raise serializers.ValidationError({'detail': _('Establishment not found.')})
|
||||
attrs['establishment'] = establishment_qs.first()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ class Command(BaseCommand):
|
|||
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
|
||||
'purchased_plaques', # №6 - перенос купленных тарелок
|
||||
'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):
|
||||
|
|
|
|||
|
|
@ -5,11 +5,19 @@
|
|||
# * 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
|
||||
# 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 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)
|
||||
|
||||
class Sites(MigrateMixin):
|
||||
|
|
@ -362,7 +370,7 @@ class GuideFilters(MigrateMixin):
|
|||
states = models.CharField(max_length=255, blank=True, null=True)
|
||||
created_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:
|
||||
managed = False
|
||||
|
|
@ -381,7 +389,7 @@ class GuideSections(MigrateMixin):
|
|||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = 'guide_elements'
|
||||
db_table = 'guide_sections'
|
||||
|
||||
|
||||
class GuideElements(MigrateMixin):
|
||||
|
|
@ -398,7 +406,7 @@ class GuideElements(MigrateMixin):
|
|||
guide_ad = models.ForeignKey(GuideAds, 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)
|
||||
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)
|
||||
lft = models.IntegerField()
|
||||
rgt = models.IntegerField()
|
||||
|
|
@ -992,7 +1000,7 @@ class ProductNotes(MigrateMixin):
|
|||
db_table = 'product_notes'
|
||||
|
||||
|
||||
class HomePages(models.Model):
|
||||
class HomePages(MigrateMixin):
|
||||
using = 'legacy'
|
||||
|
||||
site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True)
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
|||
weekdays = {
|
||||
'su': Timetable.SUNDAY,
|
||||
'mo': Timetable.MONDAY,
|
||||
'tu': Timetable.THURSDAY,
|
||||
'tu': Timetable.TUESDAY,
|
||||
'we': Timetable.WEDNESDAY,
|
||||
'th': Timetable.THURSDAY,
|
||||
'fr': Timetable.FRIDAY,
|
||||
|
|
|
|||
356
apps/transfer/serializers/guide.py
Normal file
356
apps/transfer/serializers/guide.py
Normal 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()
|
||||
|
|
@ -340,6 +340,9 @@ class ProductSerializer(TransferSerializerMixin):
|
|||
|
||||
def create(self, validated_data):
|
||||
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 = [validated_data.pop('wine_classification', None)]
|
||||
# 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()])
|
||||
# adding tags
|
||||
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
|
||||
|
||||
def get_name(self, name, brand):
|
||||
|
|
@ -390,17 +398,23 @@ class ProductSerializer(TransferSerializerMixin):
|
|||
if classification_qs.exists():
|
||||
return classification_qs.first()
|
||||
|
||||
def get_wine_region(self, wine_region):
|
||||
@staticmethod
|
||||
def get_wine_region(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(
|
||||
old_id=wine_region.id)
|
||||
old_id=old_id)
|
||||
if wine_region_qs.exists():
|
||||
return wine_region_qs.first()
|
||||
|
||||
def get_wine_sub_region(self, wine_sub_region_id):
|
||||
if wine_sub_region_id:
|
||||
@staticmethod
|
||||
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(
|
||||
old_id=wine_sub_region_id)
|
||||
old_id=old_id)
|
||||
if sub_region_qs.exists():
|
||||
return sub_region_qs.first()
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ProductReviewSerializer(ReviewSerializer):
|
|||
product_id = serializers.IntegerField()
|
||||
created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
|
||||
aasm_state = serializers.CharField(allow_null=True)
|
||||
reviewer_id = serializers.IntegerField()
|
||||
reviewer_id = serializers.IntegerField(allow_null=True)
|
||||
id = serializers.IntegerField()
|
||||
|
||||
def validate(self, data):
|
||||
|
|
@ -82,9 +82,8 @@ class ProductReviewSerializer(ReviewSerializer):
|
|||
@staticmethod
|
||||
def get_reviewer(data):
|
||||
user = User.objects.filter(old_id=data['reviewer_id']).first()
|
||||
if not user:
|
||||
raise ValueError(f"User account not found with old_id {data['reviewer_id']}")
|
||||
return user
|
||||
if user:
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def get_product(data):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Custom middleware."""
|
||||
from django.utils import translation
|
||||
from django.utils import translation, timezone
|
||||
from account.models import User
|
||||
|
||||
from configuration.models import TranslationSettings
|
||||
from translation.models import Language
|
||||
|
|
@ -12,6 +13,14 @@ def get_locale(cookie_dict):
|
|||
def get_country_code(cookie_dict):
|
||||
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):
|
||||
"""Parse cookies."""
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ class CarouselCreateSerializer(serializers.ModelSerializer):
|
|||
def pk(self):
|
||||
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):
|
||||
def to_representation(self, value):
|
||||
|
|
|
|||
|
|
@ -158,7 +158,11 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
|
|||
lookup_field = 'id'
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ services:
|
|||
MYSQL_ROOT_PASSWORD: rootPassword
|
||||
volumes:
|
||||
- gm-mysql_db:/var/lib/mysql
|
||||
- .:/code
|
||||
|
||||
|
||||
|
||||
|
||||
# PostgreSQL database
|
||||
|
|
|
|||
2
fabfile.py
vendored
2
fabfile.py
vendored
|
|
@ -54,7 +54,7 @@ def collectstatic():
|
|||
|
||||
def deploy(branch=None):
|
||||
role = env.roles[0]
|
||||
if env.roledefs[role]['branch'] != 'develop':
|
||||
if env.roledefs[role]['branch'] == 'develop':
|
||||
fetch()
|
||||
install_requirements()
|
||||
migrate()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
./manage.py transfer -a
|
||||
#./manage.py transfer -d
|
||||
./manage.py transfer -d
|
||||
./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 -l
|
||||
./manage.py transfer --product
|
||||
|
|
@ -11,4 +14,5 @@
|
|||
./manage.py transfer --wine_characteristics
|
||||
./manage.py transfer --inquiries
|
||||
./manage.py transfer --assemblage
|
||||
./manage.py transfer --purchased_plaques
|
||||
./manage.py transfer --purchased_plaques
|
||||
./manage.py rm_empty_images
|
||||
|
|
@ -99,6 +99,7 @@ EXTERNAL_APPS = [
|
|||
'storages',
|
||||
'sorl.thumbnail',
|
||||
'timezonefinder',
|
||||
'mptt',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -117,6 +118,7 @@ MIDDLEWARE = [
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'utils.middleware.parse_cookies',
|
||||
'utils.middleware.user_last_visit',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'project.urls'
|
||||
|
|
@ -413,10 +415,10 @@ SORL_THUMBNAIL_ALIASES = {
|
|||
SIMPLE_JWT = {
|
||||
# Increase access token lifetime b.c. front-end dev's cant send multiple
|
||||
# requests to API in one HTTP request.
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=30),
|
||||
'ACCESS_TOKEN_LIFETIME_SECONDS': 21600, # 6 hours in seconds
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
|
||||
'REFRESH_TOKEN_LIFETIME_SECONDS': 2592000, # 30 days in seconds
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=182),
|
||||
'ACCESS_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=182),
|
||||
'REFRESH_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
|
||||
'ROTATE_REFRESH_TOKENS': True,
|
||||
'BLACKLIST_AFTER_ROTATION': True,
|
||||
|
||||
|
|
@ -452,7 +454,7 @@ NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html'
|
|||
|
||||
|
||||
# COOKIES
|
||||
COOKIES_MAX_AGE = 2628000 # 30 days
|
||||
COOKIES_MAX_AGE = 15730000 # 6 months
|
||||
SESSION_COOKIE_SAMESITE = None
|
||||
|
||||
|
||||
|
|
@ -523,3 +525,6 @@ INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
|
|||
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
|
||||
|
||||
COOKIE_DOMAIN = None
|
||||
|
||||
ELASTICSEARCH_DSL = {}
|
||||
ELASTICSEARCH_INDEX_NAMES = {}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
|||
'search_indexes.documents.news': 'development_news',
|
||||
'search_indexes.documents.establishment': 'development_establishment',
|
||||
'search_indexes.documents.product': 'development_product',
|
||||
'search_indexes.documents.tag_category': 'development_tag_category',
|
||||
}
|
||||
|
||||
# ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||
|
|
|
|||
|
|
@ -30,18 +30,10 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
|
|||
THUMBNAIL_DEBUG = True
|
||||
|
||||
# ADDED TRANSFER APP
|
||||
# INSTALLED_APPS.append('transfer.apps.TransferConfig')
|
||||
INSTALLED_APPS.append('transfer.apps.TransferConfig')
|
||||
|
||||
# DATABASES
|
||||
DATABASES.update({
|
||||
'legacy': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
# 'HOST': '172.22.0.1',
|
||||
'HOST': 'mysql_db',
|
||||
'PORT': 3306,
|
||||
'NAME': 'dev',
|
||||
'USER': 'dev',
|
||||
'PASSWORD': 'octosecret123'},
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||
'NAME': os.environ.get('DB_NAME'),
|
||||
|
|
@ -50,10 +42,19 @@ DATABASES.update({
|
|||
'HOST': os.environ.get('DB_HOSTNAME'),
|
||||
'PORT': os.environ.get('DB_PORT'),
|
||||
'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
|
||||
|
|
@ -104,6 +105,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
|||
# 'search_indexes.documents.news': 'local_news',
|
||||
'search_indexes.documents.establishment': 'local_establishment',
|
||||
'search_indexes.documents.product': 'local_product',
|
||||
'search_indexes.documents.tag_category': 'local_tag_category',
|
||||
}
|
||||
ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||
|
||||
|
|
@ -111,6 +113,3 @@ TESTING = sys.argv[1:2] == ['test']
|
|||
if TESTING:
|
||||
ELASTICSEARCH_INDEX_NAMES = {}
|
||||
ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||
|
||||
# INSTALLED APPS
|
||||
INSTALLED_APPS.append('transfer.apps.TransferConfig')
|
||||
|
|
@ -36,6 +36,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
|||
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
||||
'search_indexes.documents.establishment': 'development_establishment',
|
||||
'search_indexes.documents.product': 'development_product',
|
||||
'search_indexes.documents.tag_category': 'development_tag_category',
|
||||
}
|
||||
|
||||
sentry_sdk.init(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ ELASTICSEARCH_DSL = {
|
|||
ELASTICSEARCH_INDEX_NAMES = {
|
||||
# 'search_indexes.documents.news': 'stage_news', #temporarily disabled
|
||||
'search_indexes.documents.establishment': 'stage_establishment',
|
||||
'search_indexes.documents.tag_category': 'stage_tag_category',
|
||||
}
|
||||
|
||||
COOKIE_DOMAIN = '.id-east.ru'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from django.urls import path, include
|
|||
app_name = 'mobile'
|
||||
|
||||
urlpatterns = [
|
||||
path('booking/', include('booking.urls.web')),
|
||||
path('establishments/', include('establishment.urls.mobile')),
|
||||
path('location/', include('location.urls.mobile')),
|
||||
path('main/', include('main.urls.mobile')),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ app_name = 'web'
|
|||
|
||||
urlpatterns = [
|
||||
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('collections/', include('collection.urls.web')),
|
||||
path('establishments/', include('establishment.urls.web')),
|
||||
|
|
|
|||
|
|
@ -57,3 +57,9 @@ redis==3.2.0
|
|||
django_redis==4.10.0 # used byes indexing cache
|
||||
kombu==4.6.6
|
||||
celery==4.3.0
|
||||
|
||||
# country information
|
||||
pycountry==19.8.18
|
||||
|
||||
# sql-tree
|
||||
django-mptt==0.9.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user