Merge branch 'develop' into feature/transfer-guide
# Conflicts: # apps/establishment/models.py # apps/establishment/serializers/common.py
This commit is contained in:
commit
9dc8e34e3a
113
.dockerignore
Normal file
113
.dockerignore
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
_*/
|
||||||
|
|
||||||
|
.git/
|
||||||
|
.idea/
|
||||||
|
_files/
|
||||||
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
FROM python:3.7
|
FROM python:3.7
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
RUN apt-get update; apt-get --assume-yes install binutils libproj-dev gdal-bin gettext
|
RUN apt-get update; apt-get --assume-yes install binutils libproj-dev gdal-bin gettext
|
||||||
RUN mkdir /code
|
RUN mkdir /code /requirements
|
||||||
|
ADD ./requirements /requirements
|
||||||
|
RUN pip install --no-cache-dir -r /requirements/base.txt && \
|
||||||
|
pip install --no-cache-dir -r /requirements/development.txt
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
ADD . /code/
|
ADD . /code/
|
||||||
RUN pip install --no-cache-dir -r /code/requirements/base.txt && \
|
|
||||||
pip install --no-cache-dir -r /code/requirements/development.txt
|
|
||||||
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
|
ESTABLISHMENT_MANAGER = 5
|
||||||
REVIEWER_MANGER = 6
|
REVIEWER_MANGER = 6
|
||||||
RESTAURANT_REVIEWER = 7
|
RESTAURANT_REVIEWER = 7
|
||||||
|
SALES_MAN = 8
|
||||||
|
WINERY_REVIEWER = 9
|
||||||
|
SELLER = 10
|
||||||
|
|
||||||
ROLE_CHOICES = (
|
ROLE_CHOICES = (
|
||||||
(STANDARD_USER, 'Standard user'),
|
(STANDARD_USER, 'Standard user'),
|
||||||
|
|
@ -40,7 +43,10 @@ class Role(ProjectBaseMixin):
|
||||||
(CONTENT_PAGE_MANAGER, 'Content page manager'),
|
(CONTENT_PAGE_MANAGER, 'Content page manager'),
|
||||||
(ESTABLISHMENT_MANAGER, 'Establishment manager'),
|
(ESTABLISHMENT_MANAGER, 'Establishment manager'),
|
||||||
(REVIEWER_MANGER, 'Reviewer manager'),
|
(REVIEWER_MANGER, 'Reviewer manager'),
|
||||||
(RESTAURANT_REVIEWER, 'Restaurant reviewer')
|
(RESTAURANT_REVIEWER, 'Restaurant reviewer'),
|
||||||
|
(SALES_MAN, 'Sales man'),
|
||||||
|
(WINERY_REVIEWER, 'Winery reviewer'),
|
||||||
|
(SELLER, 'Seller')
|
||||||
)
|
)
|
||||||
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
||||||
null=False, blank=False)
|
null=False, blank=False)
|
||||||
|
|
@ -287,7 +293,19 @@ class User(AbstractUser):
|
||||||
|
|
||||||
class UserRole(ProjectBaseMixin):
|
class UserRole(ProjectBaseMixin):
|
||||||
"""UserRole model."""
|
"""UserRole model."""
|
||||||
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
|
user = models.ForeignKey('account.User',
|
||||||
|
verbose_name=_('User'),
|
||||||
|
on_delete=models.CASCADE)
|
||||||
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
|
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
|
||||||
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
|
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
|
||||||
on_delete=models.SET_NULL, null=True, blank=True)
|
on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
class Meta:
|
||||||
|
unique_together = ['user', 'role']
|
||||||
|
|
||||||
|
|
||||||
|
class OldRole(models.Model):
|
||||||
|
new_role = models.CharField(verbose_name=_('New role'), max_length=512)
|
||||||
|
old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('new_role', 'old_role')
|
||||||
|
|
@ -13,15 +13,6 @@ class RoleSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserRoleSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.UserRole
|
|
||||||
fields = [
|
|
||||||
'user',
|
|
||||||
'role'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BackUserSerializer(serializers.ModelSerializer):
|
class BackUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
@ -49,3 +40,13 @@ class BackDetailUserSerializer(BackUserSerializer):
|
||||||
user.set_password(validated_data['password'])
|
user.set_password(validated_data['password'])
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.UserRole
|
||||||
|
fields = [
|
||||||
|
'role',
|
||||||
|
'user',
|
||||||
|
'establishment'
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,12 @@ class UserBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = (
|
fields = (
|
||||||
|
'id',
|
||||||
|
'username',
|
||||||
'fullname',
|
'fullname',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'email',
|
||||||
'cropped_image_url',
|
'cropped_image_url',
|
||||||
'image_url',
|
'image_url',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class RoleLstView(generics.ListCreateAPIView):
|
||||||
|
|
||||||
class UserRoleLstView(generics.ListCreateAPIView):
|
class UserRoleLstView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.UserRoleSerializer
|
serializer_class = serializers.UserRoleSerializer
|
||||||
queryset = models.Role.objects.all()
|
queryset = models.UserRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class UserLstView(generics.ListCreateAPIView):
|
class UserLstView(generics.ListCreateAPIView):
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
periods = response['periods']
|
periods = response['periods']
|
||||||
periods_by_name = {period['period']: period for period in periods if 'period' in period}
|
periods_by_name = {period['period']: period for period in periods if 'period' in period}
|
||||||
if not periods_by_name:
|
if not periods_by_name:
|
||||||
raise ValueError('Empty guestonline response')
|
return response
|
||||||
|
|
||||||
period_template = iter(periods_by_name.values()).__next__().copy()
|
period_template = iter(periods_by_name.values()).__next__().copy()
|
||||||
period_template.pop('total_left_seats')
|
period_template.pop('total_left_seats')
|
||||||
|
|
@ -84,8 +84,10 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
|
|
||||||
service_response = self._preprocess_guestonline_response(service.response) \
|
service_response = self._preprocess_guestonline_response(service.response) \
|
||||||
if establishment.guestonline_id is not None \
|
if establishment.guestonline_id is not None \
|
||||||
else service.response
|
else service.response if service else None
|
||||||
response.update({'details': service_response} if service and service.response else {})
|
response.update({'details': service_response})
|
||||||
|
if service_response is None:
|
||||||
|
response['available'] = False
|
||||||
return Response(data=response, status=200)
|
return Response(data=response, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
32
apps/collection/management/commands/fix_collection.py
Normal file
32
apps/collection/management/commands/fix_collection.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from collection.models import Collection
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Fix existed collections."""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
update_collections = []
|
||||||
|
collections = Collection.objects.values_list('id', 'collection_type', 'description')
|
||||||
|
for id, collection_type, description in tqdm(collections):
|
||||||
|
collection = Collection.objects.get(id=id)
|
||||||
|
description = collection.description
|
||||||
|
collection_updated = False
|
||||||
|
|
||||||
|
if isinstance(description, str):
|
||||||
|
if description.lower().find('pop') != -1:
|
||||||
|
collection.collection_type = Collection.POP
|
||||||
|
collection_updated = True
|
||||||
|
|
||||||
|
if not isinstance(description, dict):
|
||||||
|
collection.description = {settings.FALLBACK_LOCALE: collection.description}
|
||||||
|
collection_updated = True
|
||||||
|
|
||||||
|
if collection_updated:
|
||||||
|
update_collections.append(collection)
|
||||||
|
|
||||||
|
Collection.objects.bulk_update(update_collections, ['collection_type', 'description', ])
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated products: {len(update_collections)}'))
|
||||||
|
|
@ -3,6 +3,7 @@ from establishment.models import Establishment
|
||||||
from location.models import Country, Language
|
from location.models import Country, Language
|
||||||
from transfer.models import Collections
|
from transfer.models import Collections
|
||||||
from collection.models import Collection
|
from collection.models import Collection
|
||||||
|
from django.conf import settings
|
||||||
from news.models import News
|
from news.models import News
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -93,9 +94,11 @@ class Command(BaseCommand):
|
||||||
country = Country.objects.filter(code=obj['country_code']).first()
|
country = Country.objects.filter(code=obj['country_code']).first()
|
||||||
if country:
|
if country:
|
||||||
objects.append(
|
objects.append(
|
||||||
Collection(name={"en-GB": obj['title']}, collection_type=Collection.ORDINARY,
|
Collection(name={settings.FALLBACK_LOCALE: obj['title']},
|
||||||
|
collection_type=Collection.POP if obj['description'].lower().find('pop') != -1
|
||||||
|
else Collection.ORDINARY,
|
||||||
country=country,
|
country=country,
|
||||||
description=obj['description'],
|
description={settings.FALLBACK_LOCALE: obj['description']},
|
||||||
slug=obj['slug'], old_id=obj['collection_id'],
|
slug=obj['slug'], old_id=obj['collection_id'],
|
||||||
start=obj['start'],
|
start=obj['start'],
|
||||||
image_url='https://s3.eu-central-1.amazonaws.com/gm-test.com/media/'+obj['attachment_suffix_url']
|
image_url='https://s3.eu-central-1.amazonaws.com/gm-test.com/media/'+obj['attachment_suffix_url']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from establishment.models import Establishment, EstablishmentSubType, EstablishmentType
|
||||||
|
from transfer.models import Metadata
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add subtype for establishment artisan'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
artisans = Establishment.objects.artisans().filter(
|
||||||
|
old_id__isnull=False,
|
||||||
|
).prefetch_related('tags')
|
||||||
|
|
||||||
|
old_tags = Metadata.objects.filter(
|
||||||
|
establishment__in=list(artisans.values_list('old_id', flat=True)),
|
||||||
|
key='shop_category',
|
||||||
|
)
|
||||||
|
|
||||||
|
tags = []
|
||||||
|
for tag in tqdm(old_tags):
|
||||||
|
tags.append(tag.value)
|
||||||
|
subtypes = set(tags)
|
||||||
|
|
||||||
|
es_type, _ = EstablishmentType.objects.get_or_create(
|
||||||
|
index_name='artisan',
|
||||||
|
defaults={
|
||||||
|
'index_name': 'artisan',
|
||||||
|
'name': {'en-GB': 'artisan'},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for artisan in tqdm(artisans):
|
||||||
|
artisan_tags = artisan.tags.all()
|
||||||
|
for t in artisan_tags:
|
||||||
|
if t.value in subtypes:
|
||||||
|
tag = 'coffee_shop' if t.value == 'coffe_shop' else t.value
|
||||||
|
subtype, _ = EstablishmentSubType.objects.get_or_create(
|
||||||
|
index_name=tag,
|
||||||
|
defaults={
|
||||||
|
'index_name': tag,
|
||||||
|
'name': {'en-GB': ' '.join(tag.split('_')).capitalize()},
|
||||||
|
'establishment_type': es_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
artisan.establishment_subtypes.add(subtype)
|
||||||
|
artisan.save()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Artisans subtype updated.'))
|
||||||
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.'))
|
||||||
|
|
@ -14,7 +14,7 @@ from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
@ -24,6 +24,7 @@ from collection.models import Collection
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from location.models import WineOriginAddressMixin
|
from location.models import WineOriginAddressMixin
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
|
from tag.models import Tag
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
||||||
|
|
@ -126,7 +127,6 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
'menu_set__plate_set__currency', 'currency'). \
|
'menu_set__plate_set__currency', 'currency'). \
|
||||||
prefetch_actual_employees()
|
prefetch_actual_employees()
|
||||||
|
|
||||||
|
|
||||||
def with_type_related(self):
|
def with_type_related(self):
|
||||||
return self.prefetch_related('establishment_subtypes')
|
return self.prefetch_related('establishment_subtypes')
|
||||||
|
|
||||||
|
|
@ -252,6 +252,15 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
return self.filter(id__in=subquery_filter_by_distance) \
|
return self.filter(id__in=subquery_filter_by_distance) \
|
||||||
.order_by('-reviews__published_at')
|
.order_by('-reviews__published_at')
|
||||||
|
|
||||||
|
def prefetch_comments(self):
|
||||||
|
"""Prefetch last comment."""
|
||||||
|
from comment.models import Comment
|
||||||
|
return self.prefetch_related(
|
||||||
|
models.Prefetch('comments',
|
||||||
|
queryset=Comment.objects.exclude(is_publish=False).order_by('-created'),
|
||||||
|
to_attr='comments_prefetched')
|
||||||
|
)
|
||||||
|
|
||||||
def prefetch_actual_employees(self):
|
def prefetch_actual_employees(self):
|
||||||
"""Prefetch actual employees."""
|
"""Prefetch actual employees."""
|
||||||
return self.prefetch_related(
|
return self.prefetch_related(
|
||||||
|
|
@ -323,6 +332,13 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""Exclude countries."""
|
"""Exclude countries."""
|
||||||
return self.exclude(address__city__country__in=countries)
|
return self.exclude(address__city__country__in=countries)
|
||||||
|
|
||||||
|
def with_certain_tag_category_related(self, index_name, attr_name):
|
||||||
|
"""Includes extra tags."""
|
||||||
|
return self.prefetch_related(
|
||||||
|
Prefetch('tags', queryset=Tag.objects.filter(category__index_name=index_name),
|
||||||
|
to_attr=attr_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
||||||
|
|
@ -441,7 +457,7 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def visible_tags(self):
|
def visible_tags(self):
|
||||||
return super().visible_tags \
|
return super().visible_tags \
|
||||||
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||||
'business_tag', 'business_tags_de']) \
|
'business_tag', 'business_tags_de', 'tag', ]) \
|
||||||
\
|
\
|
||||||
# todo: recalculate toque_number
|
# todo: recalculate toque_number
|
||||||
|
|
||||||
|
|
@ -543,6 +559,11 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
time_at_est_tz = now_at_est_tz.time()
|
time_at_est_tz = now_at_est_tz.time()
|
||||||
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
|
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timezone_as_str(self):
|
||||||
|
""" Returns tz in str format"""
|
||||||
|
return self.tz.localize(datetime.now()).strftime('%z')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tags_indexing(self):
|
def tags_indexing(self):
|
||||||
return [{'id': tag.metadata.id,
|
return [{'id': tag.metadata.id,
|
||||||
|
|
@ -591,6 +612,23 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
return qs.first().image
|
return qs.first().image
|
||||||
|
|
||||||
|
@property
|
||||||
|
def restaurant_category_indexing(self):
|
||||||
|
return self.tags.filter(category__index_name='category')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def restaurant_cuisine_indexing(self):
|
||||||
|
return self.tags.filter(category__index_name='cuisine')
|
||||||
|
|
||||||
|
@property
|
||||||
|
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
|
@property
|
||||||
def wine_origins_unique(self):
|
def wine_origins_unique(self):
|
||||||
return self.wine_origins.distinct('wine_region')
|
return self.wine_origins.distinct('wine_region')
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
preview_image = serializers.URLField(source='preview_image_url',
|
preview_image = serializers.URLField(source='preview_image_url',
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
tz = serializers.CharField(read_only=True, source='timezone_as_str')
|
||||||
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
|
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -315,6 +315,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
'image',
|
'image',
|
||||||
'preview_image',
|
'preview_image',
|
||||||
'new_image',
|
'new_image',
|
||||||
|
'tz',
|
||||||
'wine_regions',
|
'wine_regions',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -324,12 +325,18 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
|
||||||
|
|
||||||
address = AddressDetailSerializer()
|
address = AddressDetailSerializer()
|
||||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
|
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
fields = EstablishmentBaseSerializer.Meta.fields + [
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
'schedule',
|
'schedule',
|
||||||
|
'restaurant_category',
|
||||||
|
'restaurant_cuisine',
|
||||||
|
'artisan_category',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -401,10 +408,31 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
"""Serializer for Establishment model."""
|
"""Serializer for Establishment model."""
|
||||||
|
|
||||||
address = AddressDetailSerializer(read_only=True)
|
address = AddressDetailSerializer(read_only=True)
|
||||||
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
|
|
||||||
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
|
'schedule',
|
||||||
|
'establishment_type',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
name='recent-reviews'),
|
name='recent-reviews'),
|
||||||
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
|
||||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
||||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from establishment import views
|
||||||
from establishment.urls.common import urlpatterns as common_urlpatterns
|
from establishment.urls.common import urlpatterns as common_urlpatterns
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list')
|
path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list'),
|
||||||
|
path('slug/<slug:slug>/', views.EstablishmentMobileRetrieveView.as_view(), name='mobile-detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns.extend(common_urlpatterns)
|
urlpatterns.extend(common_urlpatterns)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
"""Establishment app web urlconf."""
|
"""Establishment app web urlconf."""
|
||||||
from establishment.urls.common import urlpatterns as common_urlpatterns
|
from establishment.urls.common import urlpatterns as common_urlpatterns
|
||||||
|
from django.urls import path
|
||||||
|
from establishment import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = []
|
urlpatterns = [
|
||||||
|
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='web-detail'),
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns.extend(common_urlpatterns)
|
urlpatterns.extend(common_urlpatterns)
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentEmployeeListView(generics.ListAPIView):
|
class EstablishmentEmployeeListView(generics.ListCreateAPIView):
|
||||||
"""Establishment emplyoees list view."""
|
"""Establishment emplyoees list view."""
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.EstablishmentEmployeeBackSerializer
|
serializer_class = serializers.EstablishmentEmployeeBackSerializer
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,10 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().with_schedule() \
|
return super().get_queryset().with_schedule() \
|
||||||
.with_extended_address_related().with_currency_related()
|
.with_extended_address_related().with_currency_related() \
|
||||||
|
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||||
|
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||||
|
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
||||||
|
|
@ -48,6 +51,13 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView
|
||||||
return super().get_queryset().with_extended_related()
|
return super().get_queryset().with_extended_related()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
|
||||||
|
serializer_class = serializers.MobileEstablishmentDetailSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().prefetch_comments()
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRecentReviewListView(EstablishmentListView):
|
class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
"""List view for last reviewed establishments."""
|
"""List view for last reviewed establishments."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return Establishment.objects.filter(favorites__user=self.request.user) \
|
return Establishment.objects.filter(favorites__user=self.request.user) \
|
||||||
.order_by('-favorites')
|
.order_by('-favorites').with_base_related()
|
||||||
|
|
||||||
|
|
||||||
class FavoritesProductListView(generics.ListAPIView):
|
class FavoritesProductListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"""Main app serializers."""
|
"""Main app serializers."""
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from location.serializers import CountrySerializer
|
from location.serializers import CountrySerializer
|
||||||
|
|
@ -16,9 +17,24 @@ class FeatureSerializer(serializers.ModelSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'slug',
|
'slug',
|
||||||
'priority'
|
'priority',
|
||||||
|
'route',
|
||||||
|
'site_settings',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class CurrencySerializer(ProjectModelSerializer):
|
||||||
|
"""Currency serializer."""
|
||||||
|
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Currency
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'sign'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteFeatureSerializer(serializers.ModelSerializer):
|
class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(source='feature.id')
|
id = serializers.IntegerField(source='feature.id')
|
||||||
|
|
@ -41,20 +57,6 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CurrencySerializer(ProjectModelSerializer):
|
|
||||||
"""Currency serializer."""
|
|
||||||
|
|
||||||
name_translated = TranslatedField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Currency
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'name_translated',
|
|
||||||
'sign'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SiteSettingsSerializer(serializers.ModelSerializer):
|
class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
"""Site settings serializer."""
|
"""Site settings serializer."""
|
||||||
|
|
||||||
|
|
@ -71,7 +73,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.SiteSettings
|
model = models.SiteSettings
|
||||||
fields = (
|
fields = [
|
||||||
'country_code',
|
'country_code',
|
||||||
'time_format',
|
'time_format',
|
||||||
'subdomain',
|
'subdomain',
|
||||||
|
|
@ -85,16 +87,29 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
'published_features',
|
'published_features',
|
||||||
'currency',
|
'currency',
|
||||||
'country_name',
|
'country_name',
|
||||||
)
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteSerializer(serializers.ModelSerializer):
|
class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer):
|
||||||
|
"""Site settings serializer for back office."""
|
||||||
|
|
||||||
|
class Meta(SiteSettingsSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = SiteSettingsSerializer.Meta.fields + [
|
||||||
|
'id',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSerializer(SiteSettingsSerializer):
|
||||||
country = CountrySerializer()
|
country = CountrySerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
model = models.SiteSettings
|
model = models.SiteSettings
|
||||||
fields = ('subdomain', 'site_url', 'country')
|
fields = SiteSettingsSerializer.Meta.fields + [
|
||||||
|
'id',
|
||||||
|
'country'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteShortSerializer(serializers.ModelSerializer):
|
class SiteShortSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -107,19 +122,6 @@ class SiteShortSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# class SiteFeatureSerializer(serializers.ModelSerializer):
|
|
||||||
# """Site feature serializer."""
|
|
||||||
#
|
|
||||||
# class Meta:
|
|
||||||
# """Meta class."""
|
|
||||||
#
|
|
||||||
# model = models.SiteFeature
|
|
||||||
# fields = (
|
|
||||||
# 'id',
|
|
||||||
# 'published',
|
|
||||||
# 'site_settings',
|
|
||||||
# 'feature',
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
class AwardBaseSerializer(serializers.ModelSerializer):
|
class AwardBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -216,3 +218,11 @@ class PageTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeBackSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer fro model ContentType."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ContentType
|
||||||
|
fields = '__all__'
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from location.models import Country
|
||||||
from main.models import Award, AwardType
|
from main.models import Award, AwardType
|
||||||
|
|
||||||
|
|
||||||
class AwardTestCase(APITestCase):
|
class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
|
|
@ -25,6 +25,12 @@ class AwardTestCase(APITestCase):
|
||||||
{'access_token': tokens.get('access_token'),
|
{'access_token': tokens.get('access_token'),
|
||||||
'refresh_token': tokens.get('refresh_token')})
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
|
|
||||||
|
class AwardTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
self.country_ru = Country.objects.create(
|
self.country_ru = Country.objects.create(
|
||||||
name={'en-GB': 'Russian'},
|
name={'en-GB': 'Russian'},
|
||||||
code='RU',
|
code='RU',
|
||||||
|
|
@ -71,3 +77,13 @@ class AwardTestCase(APITestCase):
|
||||||
|
|
||||||
response = self.client.delete(f'/api/back/main/awards/{self.award.id}/')
|
response = self.client.delete(f'/api/back/main/awards/{self.award.id}/')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_content_type_list(self):
|
||||||
|
response = self.client.get('/api/back/main/content_type/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
"""Back main URLs"""
|
"""Back main URLs"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from main.views import back as views
|
from main import views
|
||||||
|
|
||||||
app_name = 'main'
|
app_name = 'main'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
|
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
|
||||||
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
|
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
|
||||||
|
path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'),
|
||||||
|
path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'),
|
||||||
|
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('site-feature/', views.SiteFeatureBackView.as_view(),
|
||||||
|
name='site-feature-list-create'),
|
||||||
|
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
|
||||||
|
name='site-feature-rud'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Main app urls."""
|
"""Main app urls."""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from main.views.common import *
|
from main.views import *
|
||||||
|
|
||||||
app = 'main'
|
app = 'main'
|
||||||
|
|
||||||
|
|
@ -8,5 +8,5 @@ common_urlpatterns = [
|
||||||
path('awards/', AwardView.as_view(), name='awards_list'),
|
path('awards/', AwardView.as_view(), name='awards_list'),
|
||||||
path('awards/<int:pk>/', AwardRetrieveView.as_view(), name='awards_retrieve'),
|
path('awards/<int:pk>/', AwardRetrieveView.as_view(), name='awards_retrieve'),
|
||||||
path('carousel/', CarouselListView.as_view(), name='carousel-list'),
|
path('carousel/', CarouselListView.as_view(), name='carousel-list'),
|
||||||
path('determine-location/', DetermineLocation.as_view(), name='determine-location')
|
path('determine-location/', DetermineLocation.as_view(), name='determine-location'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
from main.urls.common import common_urlpatterns
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from main.urls.common import common_urlpatterns
|
||||||
from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView
|
from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('determine-site/', DetermineSiteView.as_view(), name='determine-site'),
|
path('determine-site/', DetermineSiteView.as_view(), name='determine-site'),
|
||||||
path('sites/', SiteListView.as_view(), name='site-list'),
|
path('sites/', SiteListView.as_view(), name='site-list'),
|
||||||
path('site-settings/<subdomain>/', SiteSettingsView.as_view(), name='site-settings'), ]
|
path('site-settings/<subdomain>/', SiteSettingsView.as_view(), name='site-settings'),
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns.extend(common_urlpatterns)
|
urlpatterns.extend(common_urlpatterns)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .common import *
|
||||||
|
from .mobile import *
|
||||||
|
from .web import *
|
||||||
|
from .back import *
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from main import serializers
|
from main import serializers
|
||||||
from main.filters import AwardFilter
|
from main.filters import AwardFilter
|
||||||
from main.models import Award
|
from main.models import Award
|
||||||
|
from main.views import SiteSettingsView, SiteListView
|
||||||
|
|
||||||
|
|
||||||
class AwardLstView(generics.ListCreateAPIView):
|
class AwardLstView(generics.ListCreateAPIView):
|
||||||
|
|
@ -19,3 +22,48 @@ class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
serializer_class = serializers.BackAwardSerializer
|
serializer_class = serializers.BackAwardSerializer
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeView(generics.ListAPIView):
|
||||||
|
"""ContentType list view"""
|
||||||
|
queryset = ContentType.objects.all()
|
||||||
|
serializer_class = serializers.ContentTypeBackSerializer
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
filter_backends = (DjangoFilterBackend, )
|
||||||
|
ordering_fields = '__all__'
|
||||||
|
lookup_field = 'id'
|
||||||
|
filterset_fields = (
|
||||||
|
'id',
|
||||||
|
'model',
|
||||||
|
'app_label',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureBackView(generics.ListCreateAPIView):
|
||||||
|
"""Feature list or create View."""
|
||||||
|
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.SiteSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SiteListBackOfficeView(SiteListView):
|
||||||
|
"""Site settings View."""
|
||||||
|
serializer_class = serializers.SiteSerializer
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,16 @@ class DetermineSiteView(generics.GenericAPIView):
|
||||||
return Response(data={'url': url})
|
return Response(data={'url': url})
|
||||||
|
|
||||||
|
|
||||||
class SiteSettingsView(generics.RetrieveAPIView):
|
class SiteSettingsView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Site settings View."""
|
"""Site settings View."""
|
||||||
|
|
||||||
lookup_field = 'subdomain'
|
lookup_field = 'subdomain'
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
queryset = models.SiteSettings.objects.all()
|
queryset = models.SiteSettings.objects.all()
|
||||||
serializer_class = serializers.SiteSettingsSerializer
|
serializer_class = serializers.SiteSettingsBackOfficeSerializer
|
||||||
|
|
||||||
|
|
||||||
class SiteListView(generics.ListAPIView):
|
class SiteListView(generics.ListCreateAPIView):
|
||||||
"""Site settings View."""
|
"""Site settings View."""
|
||||||
|
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
|
||||||
29
apps/news/management/commands/add_author.py
Normal file
29
apps/news/management/commands/add_author.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db.models import F
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from news.models import News
|
||||||
|
from transfer.models import PageTexts
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add author of News'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
count = 0
|
||||||
|
news_list = News.objects.filter(created_by__isnull=True)
|
||||||
|
|
||||||
|
for news in tqdm(news_list, desc="Find author for exist news"):
|
||||||
|
old_news = PageTexts.objects.filter(id=news.old_id).annotate(
|
||||||
|
account_id=F('page__account_id'),
|
||||||
|
).first()
|
||||||
|
if old_news:
|
||||||
|
user = User.objects.filter(old_id=old_news.account_id).first()
|
||||||
|
if user:
|
||||||
|
news.created_by = user
|
||||||
|
news.modified_by = user
|
||||||
|
news.save()
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Update {count} objects.'))
|
||||||
18
apps/news/migrations/0037_auto_20191129_1320.py
Normal file
18
apps/news/migrations/0037_auto_20191129_1320.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-29 13:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0036_news_site'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='start',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Start'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -174,7 +174,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
description = TJSONField(blank=True, null=True, default=None,
|
description = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
start = models.DateTimeField(verbose_name=_('Start'))
|
start = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('Start'))
|
||||||
end = models.DateTimeField(blank=True, null=True, default=None,
|
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('End'))
|
verbose_name=_('End'))
|
||||||
slug = models.SlugField(unique=True, max_length=255,
|
slug = models.SlugField(unique=True, max_length=255,
|
||||||
|
|
@ -221,6 +222,10 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
def is_publish(self):
|
def is_publish(self):
|
||||||
return self.state in self.PUBLISHED_STATES
|
return self.state in self.PUBLISHED_STATES
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_international(self):
|
||||||
|
return self.INTERNATIONAL_TAG_VALUE in map(lambda tag: tag.value, self.tags.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def web_url(self):
|
def web_url(self):
|
||||||
return reverse('web:news:rud', kwargs={'slug': self.slug})
|
return reverse('web:news:rud', kwargs={'slug': self.slug})
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
'site_id',
|
'site_id',
|
||||||
'template',
|
'template',
|
||||||
'template_display',
|
'template_display',
|
||||||
|
'is_international',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ def transfer_news():
|
||||||
image=F('page__attachment_suffix_url'),
|
image=F('page__attachment_suffix_url'),
|
||||||
template=F('page__template'),
|
template=F('page__template'),
|
||||||
tags=GroupConcat('page__tags__id'),
|
tags=GroupConcat('page__tags__id'),
|
||||||
|
account_id=F('page__account_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class NewsMixinView:
|
||||||
qs = models.News.objects.published() \
|
qs = models.News.objects.published() \
|
||||||
.with_base_related() \
|
.with_base_related() \
|
||||||
.annotate_in_favorites(self.request.user) \
|
.annotate_in_favorites(self.request.user) \
|
||||||
.order_by('-is_highlighted', '-created')
|
.order_by('-is_highlighted', '-start')
|
||||||
|
|
||||||
country_code = self.request.country_code
|
country_code = self.request.country_code
|
||||||
if country_code:
|
if country_code:
|
||||||
|
|
|
||||||
0
apps/review/management/__init__.py
Normal file
0
apps/review/management/__init__.py
Normal file
0
apps/review/management/commands/__init__.py
Normal file
0
apps/review/management/commands/__init__.py
Normal file
23
apps/review/management/commands/add_review_priority.py
Normal file
23
apps/review/management/commands/add_review_priority.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from review.models import Review
|
||||||
|
from transfer.models import Reviews
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Add review priority from old db to new db.'''
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
reviews = Review.objects.all().values_list('old_id', flat=True)
|
||||||
|
queryset = Reviews.objects.exclude(product_id__isnull=False).filter(
|
||||||
|
id__in=list(reviews),
|
||||||
|
).values_list('id', 'priority')
|
||||||
|
|
||||||
|
for old_id, priority in tqdm(queryset, desc='Add priority to reviews'):
|
||||||
|
review = Review.objects.filter(old_id=old_id).first()
|
||||||
|
if review:
|
||||||
|
review.priority = priority
|
||||||
|
review.save()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Priority added to review objects.'))
|
||||||
18
apps/review/migrations/0019_review_priority.py
Normal file
18
apps/review/migrations/0019_review_priority.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-28 13:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('review', '0018_auto_20191117_1117'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='review',
|
||||||
|
name='priority',
|
||||||
|
field=models.PositiveSmallIntegerField(blank=True, default=None, null=True, verbose_name='Priority'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -39,7 +39,6 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
|
||||||
(TO_REVIEW, _('To review')),
|
(TO_REVIEW, _('To review')),
|
||||||
(READY, _('Ready')),
|
(READY, _('Ready')),
|
||||||
)
|
)
|
||||||
|
|
||||||
reviewer = models.ForeignKey(
|
reviewer = models.ForeignKey(
|
||||||
'account.User',
|
'account.User',
|
||||||
related_name='reviews',
|
related_name='reviews',
|
||||||
|
|
@ -83,6 +82,7 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
|
||||||
)
|
)
|
||||||
vintage = models.IntegerField(_('Year of review'), validators=[MinValueValidator(1900), MaxValueValidator(2100)])
|
vintage = models.IntegerField(_('Year of review'), validators=[MinValueValidator(1900), MaxValueValidator(2100)])
|
||||||
mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None)
|
mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None)
|
||||||
|
priority = models.PositiveSmallIntegerField(_('Priority'), blank=True, null=True, default=None)
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
|
||||||
objects = ReviewQuerySet.as_manager()
|
objects = ReviewQuerySet.as_manager()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,54 @@
|
||||||
"""Review app back serializers."""
|
"""Review app back serializers."""
|
||||||
from review import models
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from review.models import Review
|
||||||
|
|
||||||
|
|
||||||
|
class _ReviewerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'username',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'email',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _ContentTypeSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ContentType
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'app_label',
|
||||||
|
'model',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewBackSerializer(serializers.ModelSerializer):
|
||||||
|
reviewer_data = _ReviewerSerializer(read_only=True, source='reviewer')
|
||||||
|
content_type_data = _ContentTypeSerializer(read_only=True, source='content_type')
|
||||||
|
status_display = serializers.CharField(read_only=True, source='get_status_display')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Review
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'reviewer',
|
||||||
|
'reviewer_data',
|
||||||
|
'text',
|
||||||
|
'status',
|
||||||
|
'status_display',
|
||||||
|
'mark',
|
||||||
|
'priority',
|
||||||
|
# 'child',
|
||||||
|
'published_at',
|
||||||
|
'vintage',
|
||||||
|
# 'country',
|
||||||
|
'content_type',
|
||||||
|
'content_type_data',
|
||||||
|
'object_id',
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class ReviewBaseSerializer(serializers.ModelSerializer):
|
||||||
'id',
|
'id',
|
||||||
'reviewer',
|
'reviewer',
|
||||||
'text',
|
'text',
|
||||||
|
'priority',
|
||||||
'status',
|
'status',
|
||||||
'child',
|
'child',
|
||||||
'published_at',
|
'published_at',
|
||||||
|
|
@ -33,6 +34,7 @@ class ReviewShortSerializer(ReviewBaseSerializer):
|
||||||
|
|
||||||
class InquiriesBaseSerializer(serializers.ModelSerializer):
|
class InquiriesBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model Inquiries."""
|
"""Serializer for model Inquiries."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Inquiries
|
model = Inquiries
|
||||||
fields = (
|
fields = (
|
||||||
|
|
@ -56,6 +58,7 @@ class InquiriesBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class GridItemsBaseSerializer(serializers.ModelSerializer):
|
class GridItemsBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model GridItems."""
|
"""Serializer for model GridItems."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = GridItems
|
model = GridItems
|
||||||
fields = (
|
fields = (
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,34 @@ from review import filters
|
||||||
from review import models
|
from review import models
|
||||||
from review import serializers
|
from review import serializers
|
||||||
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
|
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
|
||||||
|
from review.serializers.back import ReviewBackSerializer
|
||||||
|
|
||||||
|
|
||||||
class ReviewLstView(generics.ListCreateAPIView):
|
class ReviewLstView(generics.ListCreateAPIView):
|
||||||
"""Comment list create view."""
|
"""Review list create view.
|
||||||
serializer_class = serializers.ReviewBaseSerializer
|
|
||||||
|
status values:
|
||||||
|
|
||||||
|
TO_INVESTIGATE = 0
|
||||||
|
TO_REVIEW = 1
|
||||||
|
READY = 2
|
||||||
|
"""
|
||||||
|
serializer_class = ReviewBackSerializer
|
||||||
queryset = models.Review.objects.all()
|
queryset = models.Review.objects.all()
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
|
||||||
filterset_class = filters.ReviewFilter
|
filterset_class = filters.ReviewFilter
|
||||||
|
|
||||||
|
|
||||||
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Comment RUD view."""
|
"""Review RUD view.
|
||||||
serializer_class = serializers.ReviewBaseSerializer
|
|
||||||
|
status values:
|
||||||
|
|
||||||
|
TO_INVESTIGATE = 0
|
||||||
|
TO_REVIEW = 1
|
||||||
|
READY = 2
|
||||||
|
"""
|
||||||
|
serializer_class = ReviewBackSerializer
|
||||||
queryset = models.Review.objects.all()
|
queryset = models.Review.objects.all()
|
||||||
permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer]
|
permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer]
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from search_indexes.documents.establishment import EstablishmentDocument
|
from search_indexes.documents.establishment import EstablishmentDocument
|
||||||
from search_indexes.documents.news import NewsDocument
|
from search_indexes.documents.news import NewsDocument
|
||||||
from search_indexes.documents.product import ProductDocument
|
from search_indexes.documents.product import ProductDocument
|
||||||
|
from search_indexes.documents.tag_category import TagCategoryDocument
|
||||||
from search_indexes.tasks import es_update
|
from search_indexes.tasks import es_update
|
||||||
|
|
||||||
# todo: make signal to update documents on related fields
|
# todo: make signal to update documents on related fields
|
||||||
|
|
@ -8,5 +9,6 @@ __all__ = [
|
||||||
'EstablishmentDocument',
|
'EstablishmentDocument',
|
||||||
'NewsDocument',
|
'NewsDocument',
|
||||||
'ProductDocument',
|
'ProductDocument',
|
||||||
|
'TagCategoryDocument',
|
||||||
'es_update',
|
'es_update',
|
||||||
]
|
]
|
||||||
|
|
@ -49,6 +49,30 @@ class EstablishmentDocument(Document):
|
||||||
'value': fields.KeywordField(),
|
'value': fields.KeywordField(),
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
|
restaurant_category = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'value': fields.KeywordField(),
|
||||||
|
},
|
||||||
|
multi=True, attr='restaurant_category_indexing')
|
||||||
|
restaurant_cuisine = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'value': fields.KeywordField(),
|
||||||
|
},
|
||||||
|
multi=True, attr='restaurant_cuisine_indexing')
|
||||||
|
artisan_category = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'value': fields.KeywordField(),
|
||||||
|
},
|
||||||
|
multi=True, attr='artisan_category_indexing')
|
||||||
visible_tags = fields.ObjectField(
|
visible_tags = fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(attr='id'),
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
|
@ -92,6 +116,7 @@ class EstablishmentDocument(Document):
|
||||||
'weekday': fields.IntegerField(attr='weekday'),
|
'weekday': fields.IntegerField(attr='weekday'),
|
||||||
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
||||||
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
||||||
|
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
address = fields.ObjectField(
|
address = fields.ObjectField(
|
||||||
|
|
@ -124,6 +149,7 @@ class EstablishmentDocument(Document):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
||||||
|
tz = fields.KeywordField(attr='timezone_as_str')
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,12 @@ class NewsDocument(Document):
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
||||||
|
start = fields.DateField(attr='start')
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
model = models.News
|
model = models.News
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'start',
|
|
||||||
'end',
|
'end',
|
||||||
'slug',
|
'slug',
|
||||||
'state',
|
'state',
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,7 @@ class ProductDocument(Document):
|
||||||
name_ru = fields.TextField(attr='display_name', analyzer='russian')
|
name_ru = fields.TextField(attr='display_name', analyzer='russian')
|
||||||
name_fr = fields.TextField(attr='display_name', analyzer='french')
|
name_fr = fields.TextField(attr='display_name', analyzer='french')
|
||||||
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
favorites_for_users = fields.ListField(field=fields.IntegerField())
|
||||||
|
created = fields.DateField(attr='created') # publishing date (?)
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
model = models.Product
|
model = models.Product
|
||||||
|
|
|
||||||
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()
|
||||||
|
|
@ -1,7 +1,164 @@
|
||||||
"""Search indexes filters."""
|
"""Search indexes filters."""
|
||||||
from elasticsearch_dsl.query import Q
|
from elasticsearch_dsl.query import Q
|
||||||
from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend
|
from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \
|
||||||
|
FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend
|
||||||
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
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):
|
||||||
|
"""Automatically adds centering and sorting within bounding box."""
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
elif second[1] < 0 > first[1] or second[1] > 0 < first[1]:
|
||||||
|
reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1])
|
||||||
|
result_part = ((reverse_first + reverse_second) / 2) * (-1 + (second[1] < 0) * 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
result_part = (first[1] + second[1]) / 2
|
||||||
|
|
||||||
|
return (first[0] + second[0]) / 2, result_part
|
||||||
|
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
ret = super().filter_queryset(request, queryset, view)
|
||||||
|
bb = request.query_params.get('location__geo_bounding_box')
|
||||||
|
if bb:
|
||||||
|
center = self.calculate_center(*map(lambda point: list(map(float, point.split(','))), bb.split('__')))
|
||||||
|
request.GET._mutable = True
|
||||||
|
request.query_params.update({
|
||||||
|
'ordering': f'location__{center[0]}__{center[1]}__km'
|
||||||
|
})
|
||||||
|
request.GET._mutable = False
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.facets_computed = {}
|
||||||
|
|
||||||
|
def aggregate(self, request, queryset, view):
|
||||||
|
"""Aggregate.
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param queryset:
|
||||||
|
:param view:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
def make_filter(cur_facet):
|
||||||
|
def _filter(x):
|
||||||
|
return cur_facet['facet']._params['field'] != next(iter(x._params))
|
||||||
|
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):
|
||||||
|
agg = __facet['facet'].get_aggregation()
|
||||||
|
agg_filter = Q('match_all')
|
||||||
|
if __facet['global']:
|
||||||
|
queryset.aggs.bucket(
|
||||||
|
'_filter_' + __field,
|
||||||
|
'global'
|
||||||
|
).bucket(__field, agg)
|
||||||
|
else:
|
||||||
|
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]})
|
||||||
|
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):
|
class CustomSearchFilterBackend(SearchFilterBackend):
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@ class ProductEstablishmentDocumentSerializer(serializers.Serializer):
|
||||||
index_name = serializers.CharField()
|
index_name = serializers.CharField()
|
||||||
city = AnotherCityDocumentShortSerializer()
|
city = AnotherCityDocumentShortSerializer()
|
||||||
|
|
||||||
|
def get_attribute(self, instance):
|
||||||
|
return instance.establishment if instance and instance.establishment else None
|
||||||
|
|
||||||
|
|
||||||
class AddressDocumentSerializer(serializers.Serializer):
|
class AddressDocumentSerializer(serializers.Serializer):
|
||||||
"""Address serializer for ES Document."""
|
"""Address serializer for ES Document."""
|
||||||
|
|
@ -165,6 +168,7 @@ class ScheduleDocumentSerializer(serializers.Serializer):
|
||||||
weekday = serializers.IntegerField()
|
weekday = serializers.IntegerField()
|
||||||
weekday_display = serializers.CharField()
|
weekday_display = serializers.CharField()
|
||||||
closed_at = serializers.CharField()
|
closed_at = serializers.CharField()
|
||||||
|
opening_at = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class InFavoritesMixin(DocumentSerializer):
|
class InFavoritesMixin(DocumentSerializer):
|
||||||
|
|
@ -206,6 +210,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'preview_image_url',
|
'preview_image_url',
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
|
'start',
|
||||||
'slug',
|
'slug',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -225,6 +230,9 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
||||||
address = AddressDocumentSerializer(allow_null=True)
|
address = AddressDocumentSerializer(allow_null=True)
|
||||||
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||||
|
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
|
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
|
artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -243,10 +251,14 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'preview_image',
|
'preview_image',
|
||||||
'address',
|
'address',
|
||||||
'tags',
|
'tags',
|
||||||
|
'restaurant_category',
|
||||||
|
'restaurant_cuisine',
|
||||||
|
'artisan_category',
|
||||||
'schedule',
|
'schedule',
|
||||||
'works_noon',
|
'works_noon',
|
||||||
'works_evening',
|
'works_evening',
|
||||||
'works_at_weekday',
|
'works_at_weekday',
|
||||||
|
'tz',
|
||||||
# 'works_now',
|
# 'works_now',
|
||||||
# 'collections',
|
# 'collections',
|
||||||
# 'establishment_type',
|
# 'establishment_type',
|
||||||
|
|
@ -289,4 +301,5 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'grape_variety',
|
'grape_variety',
|
||||||
'establishment_detail',
|
'establishment_detail',
|
||||||
'average_price',
|
'average_price',
|
||||||
|
'created',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ from search_indexes import views
|
||||||
router = routers.SimpleRouter()
|
router = routers.SimpleRouter()
|
||||||
# router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled
|
# router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled
|
||||||
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
|
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
|
||||||
router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile')
|
router.register(r'mobile/establishments', views.MobileEstablishmentDocumentViewSet, basename='establishment-mobile')
|
||||||
router.register(r'news', views.NewsDocumentViewSet, basename='news')
|
router.register(r'news', views.NewsDocumentViewSet, basename='news')
|
||||||
|
router.register(r'mobile/news', views.MobileNewsDocumentViewSet, basename='news-mobile')
|
||||||
router.register(r'products', views.ProductDocumentViewSet, basename='product')
|
router.register(r'products', views.ProductDocumentViewSet, basename='product')
|
||||||
|
router.register(r'mobile/products', views.MobileProductDocumentViewSet, basename='product-mobile')
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from django_elasticsearch_dsl_drf import constants
|
||||||
from django_elasticsearch_dsl_drf.filter_backends import (
|
from django_elasticsearch_dsl_drf.filter_backends import (
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
GeoSpatialFilteringFilterBackend,
|
||||||
FacetedSearchFilterBackend,
|
GeoSpatialOrderingFilterBackend,
|
||||||
|
OrderingFilterBackend,
|
||||||
)
|
)
|
||||||
from elasticsearch_dsl import TermsFacet
|
from elasticsearch_dsl import TermsFacet
|
||||||
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
||||||
|
|
@ -26,9 +27,16 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
FacetedSearchFilterBackend,
|
filters.CustomFacetedSearchFilterBackend,
|
||||||
|
OrderingFilterBackend
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering_fields = {
|
||||||
|
'start': {
|
||||||
|
'field': 'start',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
faceted_search_fields = {
|
faceted_search_fields = {
|
||||||
'tag': {
|
'tag': {
|
||||||
'field': 'tags.id',
|
'field': 'tags.id',
|
||||||
|
|
@ -78,6 +86,14 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MobileNewsDocumentViewSet(NewsDocumentViewSet):
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
filters.CustomSearchFilterBackend,
|
||||||
|
FilteringFilterBackend,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
"""Establishment document ViewSet."""
|
"""Establishment document ViewSet."""
|
||||||
|
|
||||||
|
|
@ -94,8 +110,9 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
filters.CustomGeoSpatialFilteringFilterBackend,
|
||||||
FacetedSearchFilterBackend,
|
filters.CustomFacetedSearchFilterBackend,
|
||||||
|
GeoSpatialOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
faceted_search_fields = {
|
faceted_search_fields = {
|
||||||
|
|
@ -125,7 +142,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
},
|
},
|
||||||
'tag': {
|
'tag': {
|
||||||
'field': 'visible_tags.id',
|
'field': 'tags.id',
|
||||||
'facet': TermsFacet,
|
'facet': TermsFacet,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'options': {
|
'options': {
|
||||||
|
|
@ -277,6 +294,21 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geo_spatial_ordering_fields = {
|
||||||
|
'location': {
|
||||||
|
'field': 'address.coordinates',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet):
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
FilteringFilterBackend,
|
||||||
|
filters.CustomSearchFilterBackend,
|
||||||
|
GeoSpatialFilteringFilterBackend,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProductDocumentViewSet(BaseDocumentViewSet):
|
class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
"""Product document ViewSet."""
|
"""Product document ViewSet."""
|
||||||
|
|
@ -289,9 +321,17 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
FacetedSearchFilterBackend,
|
filters.CustomFacetedSearchFilterBackend,
|
||||||
|
OrderingFilterBackend,
|
||||||
|
# GeoSpatialOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering_fields = {
|
||||||
|
'created': {
|
||||||
|
'field': 'created',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
search_fields = {
|
search_fields = {
|
||||||
'name': {'fuzziness': 'auto:2,5',
|
'name': {'fuzziness': 'auto:2,5',
|
||||||
'boost': 8},
|
'boost': 8},
|
||||||
|
|
@ -306,7 +346,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
|
|
||||||
faceted_search_fields = {
|
faceted_search_fields = {
|
||||||
'tag': {
|
'tag': {
|
||||||
'field': 'wine_colors.id',
|
'field': 'tags.id',
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'facet': TermsFacet,
|
'facet': TermsFacet,
|
||||||
'options': {
|
'options': {
|
||||||
|
|
@ -380,4 +420,12 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
constants.LOOKUP_QUERY_EXCLUDE,
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MobileProductDocumentViewSet(ProductDocumentViewSet):
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
FilteringFilterBackend,
|
||||||
|
filters.CustomSearchFilterBackend,
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
|
||||||
|
|
||||||
def by_establishment_type(self, queryset, name, value):
|
def by_establishment_type(self, queryset, name, value):
|
||||||
if value == EstablishmentType.ARTISAN:
|
if value == EstablishmentType.ARTISAN:
|
||||||
return models.Tag.objects.by_category_index_name('shop_category')[0:8]
|
qs = models.Tag.objects.by_category_index_name('shop_category')
|
||||||
|
if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES:
|
||||||
|
qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id')
|
||||||
|
return qs.exclude(establishments__isnull=True)[0:8]
|
||||||
return queryset.by_establishment_type(value)
|
return queryset.by_establishment_type(value)
|
||||||
|
|
||||||
# TMP TODO remove it later
|
# TMP TODO remove it later
|
||||||
|
|
@ -86,7 +89,7 @@ class TagsFilterSet(TagsBaseFilterSet):
|
||||||
if self.NEWS in value:
|
if self.NEWS in value:
|
||||||
queryset = queryset.for_news().filter(value__in=settings.NEWS_CHOSEN_TAGS).distinct('value')
|
queryset = queryset.for_news().filter(value__in=settings.NEWS_CHOSEN_TAGS).distinct('value')
|
||||||
if self.ESTABLISHMENT in value:
|
if self.ESTABLISHMENT in value:
|
||||||
queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
|
queryset = queryset.for_establishments().filter(category__value_type='list').filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
|
||||||
'value')
|
'value')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class Tag(TranslatedFieldsMixin, models.Model):
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
|
||||||
old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'),
|
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()
|
objects = TagQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
"""Tag serializers."""
|
"""Tag serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from establishment.models import (Establishment, EstablishmentType,
|
from rest_framework.fields import SerializerMethodField
|
||||||
EstablishmentSubType)
|
|
||||||
|
from establishment.models import (Establishment, EstablishmentType)
|
||||||
from news.models import News, NewsType
|
from news.models import News, NewsType
|
||||||
from tag import models
|
from tag import models
|
||||||
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
||||||
|
|
@ -12,6 +13,9 @@ from utils.serializers import TranslatedField
|
||||||
class TagBaseSerializer(serializers.ModelSerializer):
|
class TagBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model Tag."""
|
"""Serializer for model Tag."""
|
||||||
|
|
||||||
|
def get_extra_kwargs(self):
|
||||||
|
return super().get_extra_kwargs()
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
label_translated = TranslatedField()
|
||||||
index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
|
index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
|
||||||
|
|
||||||
|
|
@ -37,6 +41,7 @@ class TagBackOfficeSerializer(TagBaseSerializer):
|
||||||
'category'
|
'category'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||||
"""SHORT Serializer for TagCategory"""
|
"""SHORT Serializer for TagCategory"""
|
||||||
|
|
||||||
|
|
@ -57,7 +62,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
label_translated = TranslatedField()
|
||||||
tags = TagBaseSerializer(many=True, read_only=True)
|
tags = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -70,6 +75,25 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
'tags',
|
'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_tags(self, obj):
|
||||||
|
query_params = dict(self.context['request'].query_params)
|
||||||
|
|
||||||
|
if len(query_params) > 1:
|
||||||
|
return []
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
if 'establishment_type' in query_params:
|
||||||
|
params = {
|
||||||
|
'establishments__isnull': False,
|
||||||
|
}
|
||||||
|
elif 'product_type' in query_params:
|
||||||
|
params = {
|
||||||
|
'products__isnull': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = obj.tags.filter(**params).distinct()
|
||||||
|
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
@ -174,15 +198,15 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
|
||||||
attrs['tag_category'] = tag_category
|
attrs['tag_category'] = tag_category
|
||||||
|
|
||||||
if obj_type == self.ESTABLISHMENT_TYPE:
|
if obj_type == self.ESTABLISHMENT_TYPE:
|
||||||
establishment_type = EstablishmentType.objects.filter(pk=obj_id).\
|
establishment_type = EstablishmentType.objects.filter(pk=obj_id). \
|
||||||
first()
|
first()
|
||||||
if not establishment_type:
|
if not establishment_type:
|
||||||
raise BindingObjectNotFound()
|
raise BindingObjectNotFound()
|
||||||
if request.method == 'POST' and tag_category.establishment_types.\
|
if request.method == 'POST' and tag_category.establishment_types. \
|
||||||
filter(pk=establishment_type.pk).exists():
|
filter(pk=establishment_type.pk).exists():
|
||||||
raise ObjectAlreadyAdded()
|
raise ObjectAlreadyAdded()
|
||||||
if request.method == 'DELETE' and not tag_category.\
|
if request.method == 'DELETE' and not tag_category. \
|
||||||
establishment_types.filter(pk=establishment_type.pk).\
|
establishment_types.filter(pk=establishment_type.pk). \
|
||||||
exists():
|
exists():
|
||||||
raise RemovedBindingObjectNotFound()
|
raise RemovedBindingObjectNotFound()
|
||||||
attrs['related_object'] = establishment_type
|
attrs['related_object'] = establishment_type
|
||||||
|
|
@ -190,10 +214,10 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
|
||||||
news_type = NewsType.objects.filter(pk=obj_id).first()
|
news_type = NewsType.objects.filter(pk=obj_id).first()
|
||||||
if not news_type:
|
if not news_type:
|
||||||
raise BindingObjectNotFound()
|
raise BindingObjectNotFound()
|
||||||
if request.method == 'POST' and tag_category.news_types.\
|
if request.method == 'POST' and tag_category.news_types. \
|
||||||
filter(pk=news_type.pk).exists():
|
filter(pk=news_type.pk).exists():
|
||||||
raise ObjectAlreadyAdded()
|
raise ObjectAlreadyAdded()
|
||||||
if request.method == 'DELETE' and not tag_category.news_types.\
|
if request.method == 'DELETE' and not tag_category.news_types. \
|
||||||
filter(pk=news_type.pk).exists():
|
filter(pk=news_type.pk).exists():
|
||||||
raise RemovedBindingObjectNotFound()
|
raise RemovedBindingObjectNotFound()
|
||||||
attrs['related_object'] = news_type
|
attrs['related_object'] = news_type
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ class Timetable(ProjectBaseMixin):
|
||||||
def closed_at_str(self):
|
def closed_at_str(self):
|
||||||
return str(self.closed_at) if self.closed_at else None
|
return str(self.closed_at) if self.closed_at else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opening_at_str(self):
|
||||||
|
return str(self.opening_at) if self.opening_at else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opening_time(self):
|
def opening_time(self):
|
||||||
return self.opening_at or self.lunch_start or self.dinner_start
|
return self.opening_at or self.lunch_start or self.dinner_start
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
weekdays = {
|
weekdays = {
|
||||||
'su': Timetable.SUNDAY,
|
'su': Timetable.SUNDAY,
|
||||||
'mo': Timetable.MONDAY,
|
'mo': Timetable.MONDAY,
|
||||||
'tu': Timetable.THURSDAY,
|
'tu': Timetable.TUESDAY,
|
||||||
'we': Timetable.WEDNESDAY,
|
'we': Timetable.WEDNESDAY,
|
||||||
'th': Timetable.THURSDAY,
|
'th': Timetable.THURSDAY,
|
||||||
'fr': Timetable.FRIDAY,
|
'fr': Timetable.FRIDAY,
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ from tag.models import Tag
|
||||||
from transfer.models import PageMetadata
|
from transfer.models import PageMetadata
|
||||||
from utils.legacy_parser import parse_legacy_news_content
|
from utils.legacy_parser import parse_legacy_news_content
|
||||||
from utils.slug_generator import generate_unique_slug
|
from utils.slug_generator import generate_unique_slug
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
class NewsSerializer(serializers.Serializer):
|
class NewsSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
|
account_id = serializers.IntegerField(allow_null=True)
|
||||||
tag_cat_id = serializers.IntegerField()
|
tag_cat_id = serializers.IntegerField()
|
||||||
news_type_id = serializers.IntegerField()
|
news_type_id = serializers.IntegerField()
|
||||||
news_title = serializers.CharField()
|
news_title = serializers.CharField()
|
||||||
|
|
@ -39,6 +41,8 @@ class NewsSerializer(serializers.Serializer):
|
||||||
'state': self.get_state(validated_data),
|
'state': self.get_state(validated_data),
|
||||||
'template': self.get_template(validated_data),
|
'template': self.get_template(validated_data),
|
||||||
'country': self.get_country(validated_data),
|
'country': self.get_country(validated_data),
|
||||||
|
'created_by': self.get_account(validated_data),
|
||||||
|
'modified_by': self.get_account(validated_data),
|
||||||
}
|
}
|
||||||
obj = News.objects.create(**payload)
|
obj = News.objects.create(**payload)
|
||||||
|
|
||||||
|
|
@ -126,3 +130,8 @@ class NewsSerializer(serializers.Serializer):
|
||||||
else:
|
else:
|
||||||
content = {data['locale']: data['title']}
|
content = {data['locale']: data['title']}
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_account(data):
|
||||||
|
"""Get account"""
|
||||||
|
return User.objects.filter(old_id=data['account_id']).first()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from django.conf import settings
|
||||||
from rest_framework.pagination import CursorPagination, PageNumberPagination
|
from rest_framework.pagination import CursorPagination, PageNumberPagination
|
||||||
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination
|
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination
|
||||||
|
|
||||||
|
|
||||||
class ProjectPageNumberPagination(PageNumberPagination):
|
class ProjectPageNumberPagination(PageNumberPagination):
|
||||||
"""Customized pagination class."""
|
"""Customized pagination class."""
|
||||||
|
|
||||||
|
|
@ -65,6 +64,23 @@ class ESDocumentPagination(ESPagination):
|
||||||
return None
|
return None
|
||||||
return self.page.previous_page_number()
|
return self.page.previous_page_number()
|
||||||
|
|
||||||
|
def get_facets(self, page=None):
|
||||||
|
"""Get facets.
|
||||||
|
|
||||||
|
:param page:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if page is None:
|
||||||
|
page = self.page
|
||||||
|
|
||||||
|
if hasattr(self, 'facets_computed'):
|
||||||
|
ret = {}
|
||||||
|
for filter_field, bucket_data in self.facets_computed.items():
|
||||||
|
ret.update({filter_field: bucket_data.__dict__['_d_']})
|
||||||
|
return ret
|
||||||
|
elif hasattr(page, 'facets') and hasattr(page.facets, '_d_'):
|
||||||
|
return page.facets._d_
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentPortionPagination(ProjectMobilePagination):
|
class EstablishmentPortionPagination(ProjectMobilePagination):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ services:
|
||||||
mysql_db:
|
mysql_db:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3316:3306"
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: dev
|
MYSQL_DATABASE: dev
|
||||||
MYSQL_USER: dev
|
MYSQL_USER: dev
|
||||||
|
|
|
||||||
2
fabfile.py
vendored
2
fabfile.py
vendored
|
|
@ -54,7 +54,7 @@ def collectstatic():
|
||||||
|
|
||||||
def deploy(branch=None):
|
def deploy(branch=None):
|
||||||
role = env.roles[0]
|
role = env.roles[0]
|
||||||
if env.roledefs[role]['branch'] != 'develop':
|
if env.roledefs[role]['branch'] == 'develop':
|
||||||
fetch()
|
fetch()
|
||||||
install_requirements()
|
install_requirements()
|
||||||
migrate()
|
migrate()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
./manage.py transfer -a
|
./manage.py transfer -a
|
||||||
#./manage.py transfer -d
|
./manage.py transfer -d
|
||||||
./manage.py transfer -e
|
./manage.py transfer -e
|
||||||
./manage.py transfer --fill_city_gallery
|
./manage.py transfer --fill_city_gallery
|
||||||
./manage.py transfer -l
|
./manage.py transfer -l
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
||||||
'search_indexes.documents.news': 'development_news',
|
'search_indexes.documents.news': 'development_news',
|
||||||
'search_indexes.documents.establishment': 'development_establishment',
|
'search_indexes.documents.establishment': 'development_establishment',
|
||||||
'search_indexes.documents.product': 'development_product',
|
'search_indexes.documents.product': 'development_product',
|
||||||
|
'search_indexes.documents.tag_category': 'development_tag_category',
|
||||||
}
|
}
|
||||||
|
|
||||||
# ELASTICSEARCH_DSL_AUTOSYNC = False
|
# ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
||||||
# 'search_indexes.documents.news': 'local_news',
|
# 'search_indexes.documents.news': 'local_news',
|
||||||
'search_indexes.documents.establishment': 'local_establishment',
|
'search_indexes.documents.establishment': 'local_establishment',
|
||||||
'search_indexes.documents.product': 'local_product',
|
'search_indexes.documents.product': 'local_product',
|
||||||
|
'search_indexes.documents.tag_category': 'local_tag_category',
|
||||||
}
|
}
|
||||||
ELASTICSEARCH_DSL_AUTOSYNC = False
|
ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||||
|
|
||||||
|
|
@ -112,3 +113,6 @@ TESTING = sys.argv[1:2] == ['test']
|
||||||
if TESTING:
|
if TESTING:
|
||||||
ELASTICSEARCH_INDEX_NAMES = {}
|
ELASTICSEARCH_INDEX_NAMES = {}
|
||||||
ELASTICSEARCH_DSL_AUTOSYNC = False
|
ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||||
|
|
||||||
|
# INSTALLED APPS
|
||||||
|
INSTALLED_APPS.append('transfer.apps.TransferConfig')
|
||||||
|
|
@ -36,6 +36,7 @@ ELASTICSEARCH_INDEX_NAMES = {
|
||||||
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
||||||
'search_indexes.documents.establishment': 'development_establishment',
|
'search_indexes.documents.establishment': 'development_establishment',
|
||||||
'search_indexes.documents.product': 'development_product',
|
'search_indexes.documents.product': 'development_product',
|
||||||
|
'search_indexes.documents.tag_category': 'development_tag_category',
|
||||||
}
|
}
|
||||||
|
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ ELASTICSEARCH_DSL = {
|
||||||
ELASTICSEARCH_INDEX_NAMES = {
|
ELASTICSEARCH_INDEX_NAMES = {
|
||||||
# 'search_indexes.documents.news': 'stage_news', #temporarily disabled
|
# 'search_indexes.documents.news': 'stage_news', #temporarily disabled
|
||||||
'search_indexes.documents.establishment': 'stage_establishment',
|
'search_indexes.documents.establishment': 'stage_establishment',
|
||||||
|
'search_indexes.documents.tag_category': 'stage_tag_category',
|
||||||
}
|
}
|
||||||
|
|
||||||
COOKIE_DOMAIN = '.id-east.ru'
|
COOKIE_DOMAIN = '.id-east.ru'
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
||||||
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
||||||
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
||||||
<a href="#" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
<a href="https://{{ country_code }}.{{ domain_uri }}/" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
||||||
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
||||||
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
||||||
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
||||||
<a href="#" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
<a href="https://{{ country_code }}.{{ domain_uri }}/" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
||||||
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
||||||
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
||||||
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
||||||
<a href="#" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
<a href="https://{{ country_code }}.{{ domain_uri }}/" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
||||||
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
||||||
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
||||||
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
||||||
<a href="#" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
<a href="https://{{ country_code }}.{{ domain_uri }}/" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
||||||
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
<div class="letter__content" style="position: relative;margin: 0 16px 40px;padding: 0 0 1px;">
|
||||||
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
<div class="letter__header" style="margin: 1.875rem 0 2.875rem;text-align: center;">
|
||||||
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
<div class="letter__logo" style="display: block;width: 7.9375rem;height: 4.6875rem;margin: 0 auto 14px auto;">
|
||||||
<a href="#" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
<a href="https://{{ country_code }}.{{ domain_uri }}/" class="letter__logo-photo" style="color: #000;font-weight: 700;text-decoration: none;padding: 0;border-bottom: 1.5px solid #ffee29;cursor: pointer;display: block;border: none;">
|
||||||
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
<img alt="" style="width:100%;" src="https://s3.eu-central-1.amazonaws.com/gm-test.com/manually_uploaded/1.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user