Merge branch 'develop' into fix/check_tags
This commit is contained in:
commit
f6cb62c49c
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/
|
||||||
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -22,6 +22,9 @@ logs/
|
||||||
|
|
||||||
# dev
|
# dev
|
||||||
./docker-compose.override.yml
|
./docker-compose.override.yml
|
||||||
|
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
local_files
|
local_files
|
||||||
|
celerybeat.pid
|
||||||
|
/gm_viktor.dump
|
||||||
|
/docker-compose.dump.yml
|
||||||
|
/gm_production_20191029.sql
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
FROM mdillon/postgis:9.5
|
FROM mdillon/postgis:10
|
||||||
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
|
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
|
||||||
ENV LANG ru_RU.utf8
|
ENV LANG ru_RU.utf8
|
||||||
|
|
|
||||||
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()
|
||||||
20
apps/account/migrations/0020_role_site.py
Normal file
20
apps/account/migrations/0020_role_site.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-22 08:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0037_sitesettings_old_id'),
|
||||||
|
('account', '0019_auto_20191108_0827'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='role',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='Site settings'),
|
||||||
|
),
|
||||||
|
]
|
||||||
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,16 +43,17 @@ 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)
|
||||||
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||||
null=True, blank=True, on_delete=models.SET_NULL)
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
# is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False)
|
site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'),
|
||||||
# is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False)
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
# is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False)
|
|
||||||
# is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False)
|
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
|
|
@ -289,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,19 +13,27 @@ class RoleSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserRoleSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.UserRole
|
|
||||||
fields = [
|
|
||||||
'user',
|
|
||||||
'role'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BackUserSerializer(serializers.ModelSerializer):
|
class BackUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = '__all__'
|
fields = (
|
||||||
|
'id',
|
||||||
|
'last_login',
|
||||||
|
'is_superuser',
|
||||||
|
'username',
|
||||||
|
'last_name',
|
||||||
|
'first_name',
|
||||||
|
'is_active',
|
||||||
|
'date_joined',
|
||||||
|
'image_url',
|
||||||
|
'cropped_image_url',
|
||||||
|
'email',
|
||||||
|
'email_confirmed',
|
||||||
|
'unconfirmed_email',
|
||||||
|
'email_confirmed',
|
||||||
|
'newsletter',
|
||||||
|
'roles',
|
||||||
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'password': {'write_only': True}
|
'password': {'write_only': True}
|
||||||
}
|
}
|
||||||
|
|
@ -49,3 +57,13 @@ class BackDetailUserSerializer(BackUserSerializer):
|
||||||
user.set_password(validated_data['password'])
|
user.set_password(validated_data['password'])
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.UserRole
|
||||||
|
fields = [
|
||||||
|
'role',
|
||||||
|
'user',
|
||||||
|
'establishment'
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
|
@ -13,15 +14,15 @@ class RoleLstView(generics.ListCreateAPIView):
|
||||||
|
|
||||||
class UserRoleLstView(generics.ListCreateAPIView):
|
class UserRoleLstView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.UserRoleSerializer
|
serializer_class = serializers.UserRoleSerializer
|
||||||
queryset = models.Role.objects.all()
|
queryset = models.UserRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class UserLstView(generics.ListCreateAPIView):
|
class UserLstView(generics.ListCreateAPIView):
|
||||||
"""User list create view."""
|
"""User list create view."""
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.prefetch_related('roles')
|
||||||
serializer_class = serializers.BackUserSerializer
|
serializer_class = serializers.BackUserSerializer
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
filterset_fields = (
|
filterset_fields = (
|
||||||
'email_confirmed',
|
'email_confirmed',
|
||||||
'is_staff',
|
'is_staff',
|
||||||
|
|
@ -29,6 +30,14 @@ class UserLstView(generics.ListCreateAPIView):
|
||||||
'is_superuser',
|
'is_superuser',
|
||||||
'roles',
|
'roles',
|
||||||
)
|
)
|
||||||
|
ordering_fields = (
|
||||||
|
'email_confirmed',
|
||||||
|
'is_staff',
|
||||||
|
'is_active',
|
||||||
|
'is_superuser',
|
||||||
|
'roles',
|
||||||
|
'last_login'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
|
||||||
0
apps/booking/urls/__init__.py
Normal file
0
apps/booking/urls/__init__.py
Normal file
8
apps/booking/urls/mobile.py
Normal file
8
apps/booking/urls/mobile.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from booking.urls import common as common_views
|
||||||
|
app = 'booking'
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns_api = []
|
||||||
|
|
||||||
|
urlpatterns = urlpatterns_api + \
|
||||||
|
common_views.urlpatterns
|
||||||
8
apps/booking/urls/web.py
Normal file
8
apps/booking/urls/web.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from booking.urls import common as common_views
|
||||||
|
app = 'booking'
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns_api = []
|
||||||
|
|
||||||
|
urlpatterns = urlpatterns_api + \
|
||||||
|
common_views.urlpatterns
|
||||||
|
|
@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
periods = response['periods']
|
periods = 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
from django.contrib.gis import admin
|
from django.contrib.gis import admin
|
||||||
|
from mptt.admin import DraggableMPTTAdmin, TreeRelatedFieldListFilter
|
||||||
|
from utils.admin import BaseModelAdminMixin
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
|
|
||||||
|
|
@ -11,3 +13,22 @@ class CollectionAdmin(admin.ModelAdmin):
|
||||||
@admin.register(models.Guide)
|
@admin.register(models.Guide)
|
||||||
class GuideAdmin(admin.ModelAdmin):
|
class GuideAdmin(admin.ModelAdmin):
|
||||||
"""Guide admin."""
|
"""Guide admin."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.GuideElementType)
|
||||||
|
class GuideElementType(admin.ModelAdmin):
|
||||||
|
"""Guide element admin."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.GuideElement)
|
||||||
|
class GuideElementAdmin(DraggableMPTTAdmin, BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
|
"""Guide element admin."""
|
||||||
|
raw_id_fields = [
|
||||||
|
'guide_element_type', 'establishment', 'review',
|
||||||
|
'wine_region', 'product', 'city',
|
||||||
|
'wine_color_section', 'section', 'guide',
|
||||||
|
'parent',
|
||||||
|
]
|
||||||
|
# list_filter = (
|
||||||
|
# ('parent', TreeRelatedFieldListFilter),
|
||||||
|
# )
|
||||||
|
|
|
||||||
120
apps/collection/management/commands/check_guide_dependencies.py
Normal file
120
apps/collection/management/commands/check_guide_dependencies.py
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import re
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from location.models import City
|
||||||
|
from location.models import WineRegion
|
||||||
|
from product.models import Product
|
||||||
|
from review.models import Review
|
||||||
|
from tag.models import Tag
|
||||||
|
from transfer.models import GuideElements
|
||||||
|
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
def decorate(self):
|
||||||
|
print(f'{"-"*20}start {f.__name__}{"-"*20}')
|
||||||
|
f(self)
|
||||||
|
print(f'{"-"*20}end {f.__name__}{"-"*20}\n')
|
||||||
|
return decorate
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Check guide dependencies."""
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def count_of_guide_relative_dependencies(self):
|
||||||
|
for field in GuideElements._meta.fields:
|
||||||
|
if field.name not in ['id', 'lft', 'rgt', 'depth',
|
||||||
|
'children_count', 'parent', 'order_number']:
|
||||||
|
filters = {f'{field.name}__isnull': False, }
|
||||||
|
qs = GuideElements.objects.filter(**filters).values_list(field.name, flat=True)
|
||||||
|
print(f"COUNT OF {field.name}'s: {len(set(qs))}")
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_regions(self):
|
||||||
|
wine_region_old_ids = set(GuideElements.objects.filter(wine_region_id__isnull=False)
|
||||||
|
.values_list('wine_region_id', flat=True))
|
||||||
|
not_existed_wine_regions = []
|
||||||
|
for old_id in tqdm(wine_region_old_ids):
|
||||||
|
if not WineRegion.objects.filter(old_id=old_id).exists():
|
||||||
|
not_existed_wine_regions.append(old_id)
|
||||||
|
print(f'NOT EXISTED WINE REGIONS: {len(not_existed_wine_regions)}')
|
||||||
|
pprint(f'{not_existed_wine_regions}')
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_establishments(self):
|
||||||
|
establishment_old_ids = set(GuideElements.objects.filter(establishment_id__isnull=False)
|
||||||
|
.values_list('establishment_id', flat=True))
|
||||||
|
not_existed_establishments = []
|
||||||
|
for old_id in tqdm(establishment_old_ids):
|
||||||
|
if not Establishment.objects.filter(old_id=old_id).exists():
|
||||||
|
not_existed_establishments.append(old_id)
|
||||||
|
print(f'NOT EXISTED ESTABLISHMENTS: {len(not_existed_establishments)}')
|
||||||
|
pprint(f'{not_existed_establishments}')
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_reviews(self):
|
||||||
|
review_old_ids = set(GuideElements.objects.filter(review_id__isnull=False)
|
||||||
|
.values_list('review_id', flat=True))
|
||||||
|
not_existed_reviews = []
|
||||||
|
for old_id in tqdm(review_old_ids):
|
||||||
|
if not Review.objects.filter(old_id=old_id).exists():
|
||||||
|
not_existed_reviews.append(old_id)
|
||||||
|
print(f'NOT EXISTED REVIEWS: {len(not_existed_reviews)}')
|
||||||
|
pprint(f'{not_existed_reviews}')
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_wines(self):
|
||||||
|
wine_old_ids = set(GuideElements.objects.filter(wine_id__isnull=False)
|
||||||
|
.values_list('wine_id', flat=True))
|
||||||
|
not_existed_wines = []
|
||||||
|
for old_id in tqdm(wine_old_ids):
|
||||||
|
if not Product.objects.filter(old_id=old_id).exists():
|
||||||
|
not_existed_wines.append(old_id)
|
||||||
|
print(f'NOT EXISTED WINES: {len(not_existed_wines)}')
|
||||||
|
pprint(f'{not_existed_wines}')
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_wine_color(self):
|
||||||
|
raw_wine_color_nodes = set(GuideElements.objects.exclude(color__iexact='')
|
||||||
|
.filter(color__isnull=False)
|
||||||
|
.values_list('color', flat=True))
|
||||||
|
raw_wine_colors = [i[:-11] for i in raw_wine_color_nodes]
|
||||||
|
raw_wine_color_index_names = []
|
||||||
|
re_exp = '[A-Z][^A-Z]*'
|
||||||
|
for raw_wine_color in tqdm(raw_wine_colors):
|
||||||
|
result = re.findall(re_exp, rf'{raw_wine_color}')
|
||||||
|
if result and len(result) >= 2:
|
||||||
|
wine_color = '-'.join(result)
|
||||||
|
else:
|
||||||
|
wine_color = result[0]
|
||||||
|
raw_wine_color_index_names.append(wine_color.lower())
|
||||||
|
not_existed_wine_colors = []
|
||||||
|
for index_name in raw_wine_color_index_names:
|
||||||
|
if not Tag.objects.filter(value=index_name).exists():
|
||||||
|
not_existed_wine_colors.append(index_name)
|
||||||
|
print(f'NOT EXISTED WINE COLOR: {len(not_existed_wine_colors)}')
|
||||||
|
pprint(f'{not_existed_wine_colors}')
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def check_cities(self):
|
||||||
|
city_old_ids = set(GuideElements.objects.filter(city_id__isnull=False)
|
||||||
|
.values_list('city_id', flat=True))
|
||||||
|
not_existed_cities = []
|
||||||
|
for old_id in tqdm(city_old_ids):
|
||||||
|
if not City.objects.filter(old_id=old_id).exists():
|
||||||
|
not_existed_cities.append(old_id)
|
||||||
|
print(f'NOT EXISTED CITIES: {len(not_existed_cities)}')
|
||||||
|
pprint(f'{not_existed_cities}')
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
self.count_of_guide_relative_dependencies()
|
||||||
|
self.check_regions()
|
||||||
|
self.check_establishments()
|
||||||
|
self.check_reviews()
|
||||||
|
self.check_wines()
|
||||||
|
self.check_wine_color()
|
||||||
|
self.check_cities()
|
||||||
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']
|
||||||
|
|
|
||||||
78
apps/collection/migrations/0018_auto_20191127_1047.py
Normal file
78
apps/collection/migrations/0018_auto_20191127_1047.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-27 10:47
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0039_sitefeature_old_id'),
|
||||||
|
('collection', '0017_collection_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.SlugField(max_length=255, unique=True, verbose_name='code')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide type',
|
||||||
|
'verbose_name_plural': 'guide types',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guide',
|
||||||
|
name='advertorials',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guide',
|
||||||
|
name='collection',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guide',
|
||||||
|
name='parent',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='old_id',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site settings'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='slug'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='state',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'built'), (1, 'waiting'), (2, 'removing'), (3, 'building')], default=1, verbose_name='state'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='vintage',
|
||||||
|
field=models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2100)], verbose_name='guide vintage year'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guide',
|
||||||
|
name='guide_type',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='collection.GuideType', verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='guide',
|
||||||
|
name='start',
|
||||||
|
field=models.DateTimeField(null=True, verbose_name='start'),
|
||||||
|
),
|
||||||
|
]
|
||||||
41
apps/collection/migrations/0019_advertorial_guidefilter.py
Normal file
41
apps/collection/migrations/0019_advertorial_guidefilter.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-02 10:11
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0018_auto_20191127_1047'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideFilter',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('establishment_type_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='establishment types')),
|
||||||
|
('country_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='countries')),
|
||||||
|
('region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='regions')),
|
||||||
|
('sub_region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='sub regions')),
|
||||||
|
('wine_region_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='wine regions')),
|
||||||
|
('with_mark', models.BooleanField(default=True, help_text='exclude empty marks?', verbose_name='with mark')),
|
||||||
|
('locale_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='locales')),
|
||||||
|
('max_mark', models.FloatField(help_text='mark under', null=True, verbose_name='max mark')),
|
||||||
|
('min_mark', models.FloatField(help_text='mark over', null=True, verbose_name='min mark')),
|
||||||
|
('review_vintage_json', django.contrib.postgres.fields.jsonb.JSONField(verbose_name='review vintage years')),
|
||||||
|
('review_state_json', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='review states')),
|
||||||
|
('old_id', models.IntegerField(blank=True, null=True)),
|
||||||
|
('guide', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='collection.Guide', verbose_name='guide')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide filter',
|
||||||
|
'verbose_name_plural': 'guide filters',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-02 14:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0019_advertorial_guidefilter'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideElementSectionCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='category name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide element section category',
|
||||||
|
'verbose_name_plural': 'guide element section categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideWineColorSection',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='section name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide wine color section',
|
||||||
|
'verbose_name_plural': 'guide wine color sections',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideElementSection',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='section name')),
|
||||||
|
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='collection.GuideElementSectionCategory', verbose_name='category')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide element section',
|
||||||
|
'verbose_name_plural': 'guide element sections',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
24
apps/collection/migrations/0021_guideelementtype.py
Normal file
24
apps/collection/migrations/0021_guideelementtype.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-02 14:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0020_guideelementsection_guideelementsectioncategory_guidewinecolorsection'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideElementType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=50, verbose_name='name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide element type',
|
||||||
|
'verbose_name_plural': 'guide element types',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
48
apps/collection/migrations/0022_guideelement.py
Normal file
48
apps/collection/migrations/0022_guideelement.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-02 14:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import mptt.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('review', '0018_auto_20191117_1117'),
|
||||||
|
('product', '0018_purchasedproduct'),
|
||||||
|
('location', '0030_auto_20191120_1010'),
|
||||||
|
('establishment', '0067_auto_20191122_1244'),
|
||||||
|
('collection', '0021_guideelementtype'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuideElement',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('priority', models.IntegerField(blank=True, default=None, null=True)),
|
||||||
|
('old_id', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id')),
|
||||||
|
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||||
|
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||||
|
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||||
|
('level', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||||
|
('city', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.City')),
|
||||||
|
('establishment', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment')),
|
||||||
|
('guide', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.Guide')),
|
||||||
|
('guide_element_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideElementType', verbose_name='guide element type')),
|
||||||
|
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='collection.GuideElement')),
|
||||||
|
('product', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='product.Product')),
|
||||||
|
('review', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='review.Review')),
|
||||||
|
('section', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideElementSection')),
|
||||||
|
('wine_color_section', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='collection.GuideWineColorSection')),
|
||||||
|
('wine_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.WineRegion')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guide element',
|
||||||
|
'verbose_name_plural': 'guide elements',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
31
apps/collection/migrations/0023_advertorial.py
Normal file
31
apps/collection/migrations/0023_advertorial.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-03 13:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0022_guideelement'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Advertorial',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('number_of_pages', models.PositiveIntegerField(help_text='the total number of reserved pages', verbose_name='number of pages')),
|
||||||
|
('right_pages', models.PositiveIntegerField(help_text='the number of right pages (which are part of total number).', verbose_name='number of right pages')),
|
||||||
|
('old_id', models.IntegerField(blank=True, null=True)),
|
||||||
|
('guide_element', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='advertorial', to='collection.GuideElement', verbose_name='guide element')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'advertorial',
|
||||||
|
'verbose_name_plural': 'advertorials',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import re
|
||||||
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
from django.contrib.contenttypes.fields import ContentType
|
from django.contrib.contenttypes.fields import ContentType
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
@ -84,27 +87,101 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
verbose_name = _('collection')
|
verbose_name = _('collection')
|
||||||
verbose_name_plural = _('collections')
|
verbose_name_plural = _('collections')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _related_objects(self) -> list:
|
||||||
|
"""Return list of related objects."""
|
||||||
|
related_objects = []
|
||||||
|
# get related objects
|
||||||
|
for related_object in self._meta.related_objects:
|
||||||
|
related_objects.append(related_object)
|
||||||
|
return related_objects
|
||||||
|
|
||||||
|
@property
|
||||||
|
def count_related_objects(self) -> int:
|
||||||
|
"""Return count of related objects."""
|
||||||
|
counter = 0
|
||||||
|
# count of related objects
|
||||||
|
for related_object in [related_object.name for related_object in self._related_objects]:
|
||||||
|
counter += getattr(self, f'{related_object}').count()
|
||||||
|
return counter
|
||||||
|
|
||||||
|
@property
|
||||||
|
def related_object_names(self) -> list:
|
||||||
|
"""Return related object names."""
|
||||||
|
raw_object_names = []
|
||||||
|
for related_object in [related_object.name for related_object in self._related_objects]:
|
||||||
|
instances = getattr(self, f'{related_object}')
|
||||||
|
if instances.exists():
|
||||||
|
for instance in instances.all():
|
||||||
|
raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None)
|
||||||
|
|
||||||
|
# parse slugs
|
||||||
|
object_names = []
|
||||||
|
re_pattern = r'[\w]+'
|
||||||
|
for raw_name in raw_object_names:
|
||||||
|
result = re.findall(re_pattern, raw_name)
|
||||||
|
if result: object_names.append(' '.join(result).capitalize())
|
||||||
|
return set(object_names)
|
||||||
|
|
||||||
|
|
||||||
|
class GuideTypeQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model GuideType."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideType(ProjectBaseMixin):
|
||||||
|
"""GuideType model."""
|
||||||
|
|
||||||
|
name = models.SlugField(max_length=255, unique=True,
|
||||||
|
verbose_name=_('code'))
|
||||||
|
|
||||||
|
objects = GuideTypeQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide type')
|
||||||
|
verbose_name_plural = _('guide types')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Overridden str dunder method."""
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class GuideQuerySet(models.QuerySet):
|
class GuideQuerySet(models.QuerySet):
|
||||||
"""QuerySet for Guide."""
|
"""QuerySet for Guide."""
|
||||||
|
|
||||||
def by_collection_id(self, collection_id):
|
|
||||||
"""Filter by collection id"""
|
|
||||||
return self.filter(collection=collection_id)
|
|
||||||
|
|
||||||
|
|
||||||
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
"""Guide model."""
|
"""Guide model."""
|
||||||
parent = models.ForeignKey(
|
BUILT = 0
|
||||||
'self', verbose_name=_('parent'), on_delete=models.CASCADE,
|
WAITING = 1
|
||||||
null=True, blank=True, default=None
|
REMOVING = 2
|
||||||
|
BUILDING = 3
|
||||||
|
|
||||||
|
STATE_CHOICES = (
|
||||||
|
(BUILT, 'built'),
|
||||||
|
(WAITING, 'waiting'),
|
||||||
|
(REMOVING, 'removing'),
|
||||||
|
(BUILDING, 'building'),
|
||||||
|
|
||||||
)
|
)
|
||||||
advertorials = JSONField(
|
|
||||||
_('advertorials'), null=True, blank=True,
|
start = models.DateTimeField(null=True,
|
||||||
default=None, help_text='{"key":"value"}')
|
verbose_name=_('start'))
|
||||||
collection = models.ForeignKey(Collection, on_delete=models.CASCADE,
|
vintage = models.IntegerField(validators=[MinValueValidator(1900),
|
||||||
null=True, blank=True, default=None,
|
MaxValueValidator(2100)],
|
||||||
verbose_name=_('collection'))
|
null=True,
|
||||||
|
verbose_name=_('guide vintage year'))
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, null=True,
|
||||||
|
verbose_name=_('slug'))
|
||||||
|
guide_type = models.ForeignKey('GuideType', on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('type'))
|
||||||
|
site = models.ForeignKey('main.SiteSettings', on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('site settings'))
|
||||||
|
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
||||||
|
verbose_name=_('state'))
|
||||||
|
old_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
objects = GuideQuerySet.as_manager()
|
objects = GuideQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -116,3 +193,190 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String method."""
|
"""String method."""
|
||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertorialQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model Advertorial."""
|
||||||
|
|
||||||
|
|
||||||
|
class Advertorial(ProjectBaseMixin):
|
||||||
|
"""Guide advertorial model."""
|
||||||
|
number_of_pages = models.PositiveIntegerField(
|
||||||
|
verbose_name=_('number of pages'),
|
||||||
|
help_text=_('the total number of reserved pages'))
|
||||||
|
right_pages = models.PositiveIntegerField(
|
||||||
|
verbose_name=_('number of right pages'),
|
||||||
|
help_text=_('the number of right pages (which are part of total number).'))
|
||||||
|
guide_element = models.OneToOneField('GuideElement', on_delete=models.CASCADE,
|
||||||
|
related_name='advertorial',
|
||||||
|
verbose_name=_('guide element'))
|
||||||
|
old_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
objects = AdvertorialQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('advertorial')
|
||||||
|
verbose_name_plural = _('advertorials')
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilterQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model GuideFilter."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilter(ProjectBaseMixin):
|
||||||
|
"""Guide filter model."""
|
||||||
|
establishment_type_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='establishment types')
|
||||||
|
country_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='countries')
|
||||||
|
region_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='regions')
|
||||||
|
sub_region_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='sub regions')
|
||||||
|
wine_region_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='wine regions')
|
||||||
|
with_mark = models.BooleanField(default=True,
|
||||||
|
verbose_name=_('with mark'),
|
||||||
|
help_text=_('exclude empty marks?'))
|
||||||
|
locale_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='locales')
|
||||||
|
max_mark = models.FloatField(verbose_name=_('max mark'),
|
||||||
|
null=True,
|
||||||
|
help_text=_('mark under'))
|
||||||
|
min_mark = models.FloatField(verbose_name=_('min mark'),
|
||||||
|
null=True,
|
||||||
|
help_text=_('mark over'))
|
||||||
|
review_vintage_json = JSONField(verbose_name='review vintage years')
|
||||||
|
review_state_json = JSONField(blank=True, null=True,
|
||||||
|
verbose_name='review states')
|
||||||
|
guide = models.OneToOneField(Guide, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('guide'))
|
||||||
|
old_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
objects = GuideFilterQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide filter')
|
||||||
|
verbose_name_plural = _('guide filters')
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementType(models.Model):
|
||||||
|
"""Model for type of guide elements."""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=50,
|
||||||
|
verbose_name=_('name'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide element type')
|
||||||
|
verbose_name_plural = _('guide element types')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Overridden str dunder."""
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class GuideWineColorSectionQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model GuideWineColorSection."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideWineColorSection(ProjectBaseMixin):
|
||||||
|
"""Sections for wine colors."""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255, verbose_name=_('section name'))
|
||||||
|
|
||||||
|
objects = GuideWineColorSectionQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide wine color section')
|
||||||
|
verbose_name_plural = _('guide wine color sections')
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementSectionCategoryQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model GuideElementSectionCategory."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementSectionCategory(ProjectBaseMixin):
|
||||||
|
"""Section category for guide element."""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255,
|
||||||
|
verbose_name=_('category name'))
|
||||||
|
|
||||||
|
objects = GuideElementSectionCategoryQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide element section category')
|
||||||
|
verbose_name_plural = _('guide element section categories')
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementSectionQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model GuideElementSection."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementSection(ProjectBaseMixin):
|
||||||
|
"""Sections for guide element."""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255, verbose_name=_('section name'))
|
||||||
|
category = models.ForeignKey(GuideElementSectionCategory, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_('category'))
|
||||||
|
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('old id'))
|
||||||
|
|
||||||
|
objects = GuideElementSectionQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide element section')
|
||||||
|
verbose_name_plural = _('guide element sections')
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElementQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model Guide elements."""
|
||||||
|
|
||||||
|
|
||||||
|
class GuideElement(ProjectBaseMixin, MPTTModel):
|
||||||
|
"""Frozen state of elements of guide instance."""
|
||||||
|
|
||||||
|
guide_element_type = models.ForeignKey('GuideElementType', on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('guide element type'))
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
review = models.ForeignKey('review.Review', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
product = models.ForeignKey('product.Product', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
priority = models.IntegerField(null=True, blank=True, default=None)
|
||||||
|
city = models.ForeignKey('location.City', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
wine_color_section = models.ForeignKey('GuideWineColorSection', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
section = models.ForeignKey('GuideElementSection', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
guide = models.ForeignKey('Guide', on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True, default=None)
|
||||||
|
parent = TreeForeignKey('self', on_delete=models.CASCADE,
|
||||||
|
null=True, blank=True,
|
||||||
|
related_name='children')
|
||||||
|
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('old id'))
|
||||||
|
|
||||||
|
objects = GuideElementQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('guide element')
|
||||||
|
verbose_name_plural = _('guide elements')
|
||||||
|
|
||||||
|
class MPTTMeta:
|
||||||
|
order_insertion_by = ['guide_element_type']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Overridden dunder method."""
|
||||||
|
return self.guide_element_type.name if self.guide_element_type else self.id
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
||||||
collection_type_display = serializers.CharField(
|
collection_type_display = serializers.CharField(
|
||||||
source='get_collection_type_display', read_only=True)
|
source='get_collection_type_display', read_only=True)
|
||||||
country = CountrySimpleSerializer(read_only=True)
|
country = CountrySimpleSerializer(read_only=True)
|
||||||
|
count_related_objects = serializers.IntegerField(read_only=True)
|
||||||
|
related_object_names = serializers.ListField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Collection
|
model = models.Collection
|
||||||
|
|
@ -36,6 +38,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
||||||
'slug',
|
'slug',
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
|
'count_related_objects',
|
||||||
|
'related_object_names',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,4 @@ class GuideSerializer(serializers.ModelSerializer):
|
||||||
'name',
|
'name',
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
'parent',
|
|
||||||
'advertorials',
|
|
||||||
'collection'
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
319
apps/collection/transfer_data.py
Normal file
319
apps/collection/transfer_data.py
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
from pprint import pprint
|
||||||
|
from tqdm import tqdm
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from review.models import Review
|
||||||
|
from location.models import WineRegion, City
|
||||||
|
from product.models import Product
|
||||||
|
from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \
|
||||||
|
GuideAds
|
||||||
|
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
|
||||||
|
from collection.models import GuideElementSection, GuideElementSectionCategory, \
|
||||||
|
GuideWineColorSection, GuideElementType, GuideElement, \
|
||||||
|
Guide, Advertorial
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide():
|
||||||
|
"""Transfer Guide model."""
|
||||||
|
errors = []
|
||||||
|
queryset = Guides.objects.exclude(title__icontains='test')
|
||||||
|
serialized_data = GuideSerializer(
|
||||||
|
data=list(queryset.values()),
|
||||||
|
many=True)
|
||||||
|
if serialized_data.is_valid():
|
||||||
|
serialized_data.save()
|
||||||
|
else:
|
||||||
|
for d in serialized_data.errors: errors.append(d) if d else None
|
||||||
|
pprint(f"ERRORS: {errors}")
|
||||||
|
print(f'COUNT OF SERIALIZED OBJECTS: {queryset.values().count()}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_filter():
|
||||||
|
"""Transfer GuideFilter model."""
|
||||||
|
errors = []
|
||||||
|
queryset = GuideFilters.objects.exclude(guide__title__icontains='test') \
|
||||||
|
.exclude(guide__isnull=True)
|
||||||
|
serialized_data = GuideFilterSerializer(
|
||||||
|
data=list(queryset.values()),
|
||||||
|
many=True)
|
||||||
|
if serialized_data.is_valid():
|
||||||
|
serialized_data.save()
|
||||||
|
else:
|
||||||
|
for d in serialized_data.errors: errors.append(d) if d else None
|
||||||
|
pprint(f'ERRORS: {errors}')
|
||||||
|
print(f"COUNT: {len(errors)}")
|
||||||
|
print(f'COUNT OF SERIALIZED OBJECTS: {queryset.values().count()}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_element_section():
|
||||||
|
"""Transfer GuideSections model."""
|
||||||
|
created_count = 0
|
||||||
|
category, _ = GuideElementSectionCategory.objects.get_or_create(
|
||||||
|
name='shop_category')
|
||||||
|
queryset_values = GuideSections.objects.values_list('id', 'value_name')
|
||||||
|
for old_id, section_name in tqdm(queryset_values):
|
||||||
|
obj, created = GuideElementSection.objects.get_or_create(
|
||||||
|
name=section_name,
|
||||||
|
category=category,
|
||||||
|
old_id=old_id,
|
||||||
|
)
|
||||||
|
if created: created_count += 1
|
||||||
|
print(f'OBJECTS CREATED: {created_count}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_wine_color_section():
|
||||||
|
"""Transfer GuideElements model (only wine color sections)."""
|
||||||
|
created_count = 0
|
||||||
|
queryset_values = GuideElements.objects.raw(
|
||||||
|
"""
|
||||||
|
select distinct(color),
|
||||||
|
1 as id
|
||||||
|
from guide_elements where color is not null;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for section_name in tqdm([i.color for i in queryset_values]):
|
||||||
|
obj, created = GuideWineColorSection.objects.get_or_create(
|
||||||
|
name=section_name
|
||||||
|
)
|
||||||
|
if created: created_count += 1
|
||||||
|
print(f'OBJECTS CREATED: {created_count}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_element_type():
|
||||||
|
"""Transfer GuideElements model (only element types)."""
|
||||||
|
created_count = 0
|
||||||
|
queryset_values = GuideElements.objects.raw(
|
||||||
|
"""
|
||||||
|
select distinct(type),
|
||||||
|
1 as id
|
||||||
|
from guide_elements;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for element_type in tqdm([i.type for i in queryset_values]):
|
||||||
|
obj, created = GuideElementType.objects.get_or_create(
|
||||||
|
name=element_type
|
||||||
|
)
|
||||||
|
if created: created_count += 1
|
||||||
|
print(f'OBJECTS CREATED: {created_count}')
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_elements_bulk():
|
||||||
|
"""Transfer Guide elements via bulk_create."""
|
||||||
|
def get_guide_element_type(guide_element_type: str):
|
||||||
|
if guide_element_type:
|
||||||
|
qs = GuideElementType.objects.filter(name__iexact=guide_element_type)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_establishment(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = Establishment.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_review(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = Review.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_wine_region(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = WineRegion.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_wine(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = Product.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_wine_color_section(color_section: str):
|
||||||
|
if color_section:
|
||||||
|
qs = GuideWineColorSection.objects.filter(name__iexact=color_section)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_city(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = City.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_guide_element_section(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = GuideElementSection.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_guide(old_id):
|
||||||
|
if old_id:
|
||||||
|
qs = Guide.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
def get_parent(old_id):
|
||||||
|
if old_id:
|
||||||
|
qs = GuideElement.objects.filter(old_id=old_id)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
|
||||||
|
objects_to_update = []
|
||||||
|
base_queryset = GuideElements.objects.all()
|
||||||
|
|
||||||
|
for old_id, type, establishment_id, review_id, wine_region_id, \
|
||||||
|
wine_id, color, order_number, city_id, section_id, guide_id \
|
||||||
|
in tqdm(base_queryset.filter(parent_id__isnull=True)
|
||||||
|
.values_list('id', 'type', 'establishment_id',
|
||||||
|
'review_id', 'wine_region_id', 'wine_id',
|
||||||
|
'color', 'order_number', 'city_id',
|
||||||
|
'section_id', 'guide_id'),
|
||||||
|
desc='Check parent guide elements'):
|
||||||
|
if not GuideElement.objects.filter(old_id=old_id).exists():
|
||||||
|
guide = GuideElement(
|
||||||
|
old_id=old_id,
|
||||||
|
guide_element_type=get_guide_element_type(type),
|
||||||
|
establishment=get_establishment(establishment_id),
|
||||||
|
review=get_review(review_id),
|
||||||
|
wine_region=get_wine_region(wine_region_id),
|
||||||
|
product=get_wine(wine_id),
|
||||||
|
wine_color_section=get_wine_color_section(color),
|
||||||
|
priority=order_number,
|
||||||
|
city=get_city(city_id),
|
||||||
|
section=get_guide_element_section(section_id),
|
||||||
|
parent=None,
|
||||||
|
lft=1,
|
||||||
|
rght=1,
|
||||||
|
tree_id=1,
|
||||||
|
level=1,
|
||||||
|
)
|
||||||
|
# check old guide
|
||||||
|
if not guide_id:
|
||||||
|
objects_to_update.append(guide)
|
||||||
|
else:
|
||||||
|
old_guide = Guides.objects.exclude(title__icontains='test') \
|
||||||
|
.filter(id=guide_id)
|
||||||
|
if old_guide.exists():
|
||||||
|
guide.guide = get_guide(guide_id)
|
||||||
|
objects_to_update.append(guide)
|
||||||
|
|
||||||
|
# create parents
|
||||||
|
GuideElement.objects.bulk_create(objects_to_update)
|
||||||
|
pprint(f'CREATED PARENT GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
|
||||||
|
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
|
||||||
|
|
||||||
|
# attach child guide elements
|
||||||
|
queryset_values = base_queryset.filter(parent_id__isnull=False) \
|
||||||
|
.order_by('-parent_id') \
|
||||||
|
.values_list('id', 'type', 'establishment_id',
|
||||||
|
'review_id', 'wine_region_id', 'wine_id',
|
||||||
|
'color', 'order_number', 'city_id',
|
||||||
|
'section_id', 'guide_id', 'parent_id')
|
||||||
|
for old_id, type, establishment_id, review_id, wine_region_id, \
|
||||||
|
wine_id, color, order_number, city_id, section_id, guide_id, parent_id \
|
||||||
|
in tqdm(sorted(queryset_values, key=lambda value: value[len(value)-1]),
|
||||||
|
desc='Check child guide elements'):
|
||||||
|
if not GuideElement.objects.filter(old_id=old_id).exists():
|
||||||
|
# check old guide
|
||||||
|
if guide_id:
|
||||||
|
old_guide = Guides.objects.exclude(title__icontains='test') \
|
||||||
|
.filter(id=guide_id)
|
||||||
|
if old_guide.exists():
|
||||||
|
GuideElement.objects.create(
|
||||||
|
old_id=old_id,
|
||||||
|
guide_element_type=get_guide_element_type(type),
|
||||||
|
establishment=get_establishment(establishment_id),
|
||||||
|
review=get_review(review_id),
|
||||||
|
wine_region=get_wine_region(wine_region_id),
|
||||||
|
product=get_wine(wine_id),
|
||||||
|
wine_color_section=get_wine_color_section(color),
|
||||||
|
priority=order_number,
|
||||||
|
city=get_city(city_id),
|
||||||
|
section=get_guide_element_section(section_id),
|
||||||
|
parent=get_parent(parent_id),
|
||||||
|
lft=1,
|
||||||
|
rght=1,
|
||||||
|
tree_id=1,
|
||||||
|
level=1,
|
||||||
|
guide=get_guide(guide_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
pprint(f'CREATED CHILD GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
|
||||||
|
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
|
||||||
|
|
||||||
|
# rebuild trees
|
||||||
|
GuideElement._tree_manager.rebuild()
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_guide_element_advertorials():
|
||||||
|
"""Transfer Guide Advertorials model."""
|
||||||
|
def get_guide_element(old_id: int):
|
||||||
|
if old_id:
|
||||||
|
qs = GuideElement.objects.filter(old_id=old_id)
|
||||||
|
legacy_qs = GuideElements.objects.exclude(guide__isnull=True) \
|
||||||
|
.exclude(guide__title__icontains='test') \
|
||||||
|
.filter(id=guide_ad_node_id)
|
||||||
|
if qs.exists() and legacy_qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
elif legacy_qs.exists() and not qs.exists():
|
||||||
|
raise ValueError(f'Guide element was not transfer correctly - {old_id}.')
|
||||||
|
|
||||||
|
objects_to_update = []
|
||||||
|
advertorials = GuideAds.objects.exclude(nb_pages__isnull=True) \
|
||||||
|
.exclude(nb_right_pages__isnull=True) \
|
||||||
|
.exclude(guide_ad_node_id__isnull=True) \
|
||||||
|
.values_list('id', 'nb_pages', 'nb_right_pages',
|
||||||
|
'guide_ad_node_id')
|
||||||
|
for old_id, nb_pages, nb_right_pages, guide_ad_node_id in tqdm(advertorials):
|
||||||
|
# check guide element
|
||||||
|
guide_element = get_guide_element(guide_ad_node_id)
|
||||||
|
|
||||||
|
if not Advertorial.objects.filter(old_id=old_id).exists() and guide_element:
|
||||||
|
objects_to_update.append(
|
||||||
|
Advertorial(
|
||||||
|
old_id=old_id,
|
||||||
|
number_of_pages=nb_pages,
|
||||||
|
right_pages=nb_right_pages,
|
||||||
|
guide_element=guide_element,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# create related child
|
||||||
|
Advertorial.objects.bulk_create(objects_to_update)
|
||||||
|
|
||||||
|
pprint(f'CREATED ADVERTORIALS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
|
||||||
|
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
|
||||||
|
|
||||||
|
|
||||||
|
data_types = {
|
||||||
|
'guides': [
|
||||||
|
transfer_guide,
|
||||||
|
],
|
||||||
|
'guide_filters': [
|
||||||
|
transfer_guide_filter,
|
||||||
|
],
|
||||||
|
'guide_element_sections': [
|
||||||
|
transfer_guide_element_section,
|
||||||
|
],
|
||||||
|
'guide_wine_color_sections': [
|
||||||
|
transfer_guide_wine_color_section,
|
||||||
|
],
|
||||||
|
'guide_element_types': [
|
||||||
|
transfer_guide_element_type,
|
||||||
|
],
|
||||||
|
'guide_elements_bulk': [
|
||||||
|
transfer_guide_elements_bulk,
|
||||||
|
],
|
||||||
|
'guide_element_advertorials': [
|
||||||
|
transfer_guide_element_advertorials
|
||||||
|
],
|
||||||
|
'guide_complete': [
|
||||||
|
transfer_guide, # transfer guides from Guides
|
||||||
|
transfer_guide_filter, # transfer guide filters from GuideFilters
|
||||||
|
transfer_guide_element_section, # partial transfer element section from GuideSections
|
||||||
|
transfer_guide_wine_color_section, # partial transfer wine color section from GuideSections
|
||||||
|
transfer_guide_element_type, # partial transfer section types from GuideElements
|
||||||
|
transfer_guide_elements_bulk, # transfer result of GuideFilters from GuideElements
|
||||||
|
transfer_guide_element_advertorials, # transfer advertorials that linked to GuideElements
|
||||||
|
]
|
||||||
|
}
|
||||||
20
apps/comment/migrations/0007_comment_site.py
Normal file
20
apps/comment/migrations/0007_comment_site.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-25 08:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0037_sitesettings_old_id'),
|
||||||
|
('comment', '0006_comment_is_publish'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comment',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -35,7 +35,8 @@ class Comment(ProjectBaseMixin):
|
||||||
user = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('User'))
|
user = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('User'))
|
||||||
old_id = models.IntegerField(null=True, blank=True, default=None)
|
old_id = models.IntegerField(null=True, blank=True, default=None)
|
||||||
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
||||||
|
site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
|
||||||
|
on_delete=models.SET_NULL, verbose_name=_('site'))
|
||||||
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,20 @@ from account.models import Role, User, UserRole
|
||||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from utils.tests.tests_permissions import BasePermissionTests
|
from utils.tests.tests_permissions import BasePermissionTests
|
||||||
|
from main.models import SiteSettings
|
||||||
|
|
||||||
|
|
||||||
class CommentModeratorPermissionTests(BasePermissionTests):
|
class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
|
self.site_ru, created = SiteSettings.objects.get_or_create(
|
||||||
|
subdomain='ru'
|
||||||
|
)
|
||||||
|
|
||||||
self.role = Role.objects.create(
|
self.role = Role.objects.create(
|
||||||
role=2,
|
role=2,
|
||||||
country=self.country_ru
|
site=self.site_ru
|
||||||
)
|
)
|
||||||
self.role.save()
|
self.role.save()
|
||||||
|
|
||||||
|
|
@ -33,11 +38,12 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
self.content_type = ContentType.objects.get(app_label='location', model='country')
|
self.content_type = ContentType.objects.get(app_label='location', model='country')
|
||||||
|
|
||||||
self.user_test = get_tokens_for_user()
|
self.user_test = get_tokens_for_user()
|
||||||
|
|
||||||
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
||||||
user=self.user_test["user"],
|
user=self.user_test["user"],
|
||||||
object_id=self.country_ru.pk,
|
object_id=self.country_ru.pk,
|
||||||
content_type_id=self.content_type.id,
|
content_type_id=self.content_type.id,
|
||||||
country=self.country_ru
|
site=self.site_ru
|
||||||
)
|
)
|
||||||
self.comment.save()
|
self.comment.save()
|
||||||
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
||||||
|
|
@ -50,7 +56,7 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
"user": self.user_test["user"].id,
|
"user": self.user_test["user"].id,
|
||||||
"object_id": self.country_ru.pk,
|
"object_id": self.country_ru.pk,
|
||||||
"content_type": self.content_type.id,
|
"content_type": self.content_type.id,
|
||||||
"country_id": self.country_ru.id
|
"site_id": self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(self.url, format='json', data=comment)
|
response = self.client.post(self.url, format='json', data=comment)
|
||||||
|
|
@ -61,7 +67,7 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
"user": self.moderator.id,
|
"user": self.moderator.id,
|
||||||
"object_id": self.country_ru.id,
|
"object_id": self.country_ru.id,
|
||||||
"content_type": self.content_type.id,
|
"content_type": self.content_type.id,
|
||||||
"country_id": self.country_ru.id
|
"site_id": self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = User.create_jwt_tokens(self.moderator)
|
tokens = User.create_jwt_tokens(self.moderator)
|
||||||
|
|
@ -83,8 +89,9 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
"text": "test text moderator",
|
"text": "test text moderator",
|
||||||
"mark": 1,
|
"mark": 1,
|
||||||
"user": self.moderator.id,
|
"user": self.moderator.id,
|
||||||
"object_id": self.comment.country_id,
|
"object_id": self.country_ru.id,
|
||||||
"content_type": self.content_type.id
|
"content_type": self.content_type.id,
|
||||||
|
'site_id': self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.put(self.url, data=data, format='json')
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ class CommentLstView(generics.ListCreateAPIView):
|
||||||
"""Comment list create view."""
|
"""Comment list create view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
# permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Comment RUD view."""
|
"""Comment RUD view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
|
permission_classes = [IsCommentModerator]
|
||||||
permission_classes = [IsCountryAdmin | IsCommentModerator]
|
# permission_classes = [IsCountryAdmin | IsCommentModerator]
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class ContactPhoneInline(admin.TabularInline):
|
||||||
class GalleryImageInline(admin.TabularInline):
|
class GalleryImageInline(admin.TabularInline):
|
||||||
"""Gallery image inline admin."""
|
"""Gallery image inline admin."""
|
||||||
model = models.EstablishmentGallery
|
model = models.EstablishmentGallery
|
||||||
|
raw_id_fields = ['image', ]
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,17 +62,20 @@ class ProductInline(admin.TabularInline):
|
||||||
|
|
||||||
class CompanyInline(admin.TabularInline):
|
class CompanyInline(admin.TabularInline):
|
||||||
model = models.Company
|
model = models.Company
|
||||||
|
raw_id_fields = ['establishment', 'address']
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNote(admin.TabularInline):
|
class EstablishmentNote(admin.TabularInline):
|
||||||
model = models.EstablishmentNote
|
model = models.EstablishmentNote
|
||||||
extra = 0
|
extra = 0
|
||||||
|
raw_id_fields = ['user', ]
|
||||||
|
|
||||||
|
|
||||||
class PurchasedProduct(admin.TabularInline):
|
class PurchasedProductInline(admin.TabularInline):
|
||||||
model = PurchasedProduct
|
model = PurchasedProduct
|
||||||
extra = 0
|
extra = 0
|
||||||
|
raw_id_fields = ['product', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Establishment)
|
@admin.register(models.Establishment)
|
||||||
|
|
@ -80,13 +84,12 @@ class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
list_display = ['id', '__str__', 'image_tag', ]
|
list_display = ['id', '__str__', 'image_tag', ]
|
||||||
search_fields = ['id', 'name', 'index_name', 'slug']
|
search_fields = ['id', 'name', 'index_name', 'slug']
|
||||||
list_filter = ['public_mark', 'toque_number']
|
list_filter = ['public_mark', 'toque_number']
|
||||||
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote,
|
inlines = [CompanyInline, EstablishmentNote, GalleryImageInline,
|
||||||
PurchasedProduct]
|
PurchasedProductInline, ]
|
||||||
|
|
||||||
# inlines = [
|
# inlines = [
|
||||||
# AwardInline, ContactPhoneInline, ContactEmailInline,
|
# AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||||
# ReviewInline, CommentInline, ProductInline]
|
# ReviewInline, CommentInline, ProductInline]
|
||||||
raw_id_fields = ('address',)
|
raw_id_fields = ('address', 'collections', 'tags', 'schedule')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Position)
|
@admin.register(models.Position)
|
||||||
|
|
@ -136,3 +139,4 @@ class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""Admin conf for Company model."""
|
"""Admin conf for Company model."""
|
||||||
raw_id_fields = ['establishment', 'address', ]
|
raw_id_fields = ['establishment', 'address', ]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,23 @@ class EstablishmentTypeTagFilter(filters.FilterSet):
|
||||||
fields = (
|
fields = (
|
||||||
'type_id',
|
'type_id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeBackFilter(filters.FilterSet):
|
||||||
|
"""Employee filter set."""
|
||||||
|
|
||||||
|
search = filters.CharFilter(method='search_by_name_or_last_name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Employee
|
||||||
|
fields = (
|
||||||
|
'search',
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_name_or_last_name(self, queryset, name, value):
|
||||||
|
"""Search by name or last name."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_name_or_last_name(value)
|
||||||
|
return queryset
|
||||||
|
|
|
||||||
|
|
@ -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.'))
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from location.models import WineOriginAddress, EstablishmentWineOriginAddress
|
||||||
|
from product.models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add to establishment wine origin object.'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
create_counter = 0
|
||||||
|
|
||||||
|
for product in Product.objects.exclude(establishment__isnull=True):
|
||||||
|
establishment = product.establishment
|
||||||
|
if product.wine_origins.exists():
|
||||||
|
for wine_origin in product.wine_origins.all():
|
||||||
|
wine_region = wine_origin.wine_region
|
||||||
|
wine_sub_region = wine_origin.wine_sub_region
|
||||||
|
if not EstablishmentWineOriginAddress.objects.filter(establishment=establishment,
|
||||||
|
wine_region=wine_region,
|
||||||
|
wine_sub_region=wine_sub_region) \
|
||||||
|
.exists():
|
||||||
|
EstablishmentWineOriginAddress.objects.create(
|
||||||
|
establishment=establishment,
|
||||||
|
wine_region=wine_origin.wine_region,
|
||||||
|
wine_sub_region=wine_origin.wine_sub_region,
|
||||||
|
)
|
||||||
|
create_counter += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'COUNT CREATED OBJECTS: {create_counter}'))
|
||||||
36
apps/establishment/management/commands/fix_scheduler.py
Normal file
36
apps/establishment/management/commands/fix_scheduler.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from transfer.models import Establishments
|
||||||
|
from transfer.serializers.establishment import EstablishmentSerializer
|
||||||
|
from timetable.models import Timetable
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Fix scheduler'
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
count = 0
|
||||||
|
establishments = Establishment.objects.all()
|
||||||
|
old_est_list = Establishments.objects.prefetch_related(
|
||||||
|
'schedules_set',
|
||||||
|
)
|
||||||
|
# remove old records of Timetable
|
||||||
|
Timetable.objects.all().delete()
|
||||||
|
|
||||||
|
for est in tqdm(establishments, desc="Fix scheduler"):
|
||||||
|
old_est = old_est_list.filter(id=est.old_id).first()
|
||||||
|
|
||||||
|
if old_est and old_est.schedules_set.exists():
|
||||||
|
old_schedule = old_est.schedules_set.first()
|
||||||
|
timetable = old_schedule.timetable
|
||||||
|
if timetable:
|
||||||
|
new_schedules = EstablishmentSerializer.get_schedules(timetable)
|
||||||
|
est.schedule.add(*new_schedules)
|
||||||
|
est.save()
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Update {count} objects.'))
|
||||||
28
apps/establishment/migrations/0066_auto_20191122_1144.py
Normal file
28
apps/establishment/migrations/0066_auto_20191122_1144.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-22 11:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0065_establishment_purchased_products'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='last_name',
|
||||||
|
field=models.CharField(default=None, max_length=255, null=True, verbose_name='Last Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmentemployee',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('I', 'Idle'), ('A', 'Accepted'), ('D', 'Declined')], default='I', max_length=1),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
39
apps/establishment/migrations/0067_auto_20191122_1244.py
Normal file
39
apps/establishment/migrations/0067_auto_20191122_1244.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-22 12:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0066_auto_20191122_1144'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='birth_date',
|
||||||
|
field=models.DateTimeField(default=None, null=True, verbose_name='Birth date'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='Email'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='phone',
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(default=None, max_length=128, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='sex',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'Male'), (1, 'Female')], default=None, null=True, verbose_name='Sex'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='toque_number',
|
||||||
|
field=models.PositiveSmallIntegerField(default=None, null=True, verbose_name='Toque number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from operator import or_
|
from operator import or_
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import elasticsearch_dsl
|
import elasticsearch_dsl
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -13,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
|
||||||
|
|
@ -21,11 +22,14 @@ from timezone_field import TimeZoneField
|
||||||
|
|
||||||
from collection.models import Collection
|
from collection.models import Collection
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
|
from location.models import WineOriginAddressMixin
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
|
from tag.models import Tag
|
||||||
from 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,
|
||||||
IntermediateGalleryModelMixin, HasTagsMixin)
|
IntermediateGalleryModelMixin, HasTagsMixin,
|
||||||
|
FavoritesMixin)
|
||||||
|
|
||||||
|
|
||||||
# todo: establishment type&subtypes check
|
# todo: establishment type&subtypes check
|
||||||
|
|
@ -117,9 +121,10 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
'address__city__country')
|
'address__city__country')
|
||||||
|
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
return self.select_related('establishment_type'). \
|
return self.with_extended_address_related().select_related('establishment_type'). \
|
||||||
prefetch_related('establishment_subtypes', 'awards', 'schedule',
|
prefetch_related('establishment_subtypes', 'awards', 'schedule',
|
||||||
'phones'). \
|
'phones', 'gallery', 'menu_set', 'menu_set__plate_set',
|
||||||
|
'menu_set__plate_set__currency', 'currency'). \
|
||||||
prefetch_actual_employees()
|
prefetch_actual_employees()
|
||||||
|
|
||||||
def with_type_related(self):
|
def with_type_related(self):
|
||||||
|
|
@ -247,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(
|
||||||
|
|
@ -318,8 +332,16 @@ 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, TranslatedFieldsMixin, HasTagsMixin):
|
|
||||||
|
class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
|
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
||||||
# todo: delete image URL fields after moving on gallery
|
# todo: delete image URL fields after moving on gallery
|
||||||
|
|
@ -393,6 +415,7 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, Translat
|
||||||
verbose_name=_('Tag'))
|
verbose_name=_('Tag'))
|
||||||
reviews = generic.GenericRelation(to='review.Review')
|
reviews = generic.GenericRelation(to='review.Review')
|
||||||
comments = generic.GenericRelation(to='comment.Comment')
|
comments = generic.GenericRelation(to='comment.Comment')
|
||||||
|
carousels = generic.GenericRelation(to='main.Carousel')
|
||||||
favorites = generic.GenericRelation(to='favorites.Favorites')
|
favorites = generic.GenericRelation(to='favorites.Favorites')
|
||||||
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
|
@ -432,11 +455,17 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, Translat
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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']) \
|
||||||
|
.exclude(value__in=['rss', 'rss_selection'])
|
||||||
|
# todo: recalculate toque_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visible_tags_detail(self):
|
||||||
|
"""Removes some tags from detail Establishment representation"""
|
||||||
|
return self.visible_tags.exclude(category__index_name__in=['tag'])
|
||||||
|
|
||||||
# todo: recalculate toque_number
|
|
||||||
def recalculate_toque_number(self):
|
def recalculate_toque_number(self):
|
||||||
toque_number = 0
|
toque_number = 0
|
||||||
if self.address and self.public_mark:
|
if self.address and self.public_mark:
|
||||||
|
|
@ -535,6 +564,11 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, Translat
|
||||||
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,
|
||||||
|
|
@ -583,6 +617,27 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, Translat
|
||||||
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
|
||||||
|
def wine_origins_unique(self):
|
||||||
|
return self.wine_origins.distinct('wine_region')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNoteQuerySet(models.QuerySet):
|
class EstablishmentNoteQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model EstablishmentNote."""
|
"""QuerySet for model EstablishmentNote."""
|
||||||
|
|
@ -609,7 +664,6 @@ class EstablishmentNote(ProjectBaseMixin):
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentGallery(IntermediateGalleryModelMixin):
|
class EstablishmentGallery(IntermediateGalleryModelMixin):
|
||||||
|
|
||||||
establishment = models.ForeignKey(Establishment, null=True,
|
establishment = models.ForeignKey(Establishment, null=True,
|
||||||
related_name='establishment_gallery',
|
related_name='establishment_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
@ -660,6 +714,16 @@ class EstablishmentEmployeeQuerySet(models.QuerySet):
|
||||||
class EstablishmentEmployee(BaseAttributes):
|
class EstablishmentEmployee(BaseAttributes):
|
||||||
"""EstablishmentEmployee model."""
|
"""EstablishmentEmployee model."""
|
||||||
|
|
||||||
|
IDLE = 'I'
|
||||||
|
ACCEPTED = 'A'
|
||||||
|
DECLINED = 'D'
|
||||||
|
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
(IDLE, 'Idle'),
|
||||||
|
(ACCEPTED, 'Accepted'),
|
||||||
|
(DECLINED, 'Declined'),
|
||||||
|
)
|
||||||
|
|
||||||
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
||||||
verbose_name=_('Establishment'))
|
verbose_name=_('Establishment'))
|
||||||
employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT,
|
employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT,
|
||||||
|
|
@ -670,19 +734,53 @@ class EstablishmentEmployee(BaseAttributes):
|
||||||
verbose_name=_('To date'))
|
verbose_name=_('To date'))
|
||||||
position = models.ForeignKey(Position, on_delete=models.PROTECT,
|
position = models.ForeignKey(Position, on_delete=models.PROTECT,
|
||||||
verbose_name=_('Position'))
|
verbose_name=_('Position'))
|
||||||
|
|
||||||
|
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default=IDLE)
|
||||||
|
|
||||||
# old_id = affiliations_id
|
# old_id = affiliations_id
|
||||||
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
||||||
|
|
||||||
objects = EstablishmentEmployeeQuerySet.as_manager()
|
objects = EstablishmentEmployeeQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
def _generic_search(self, value, filter_fields_names: List[str]):
|
||||||
|
"""Generic method for searching value in specified fields"""
|
||||||
|
filters = [
|
||||||
|
{f'{field}__icontains': value}
|
||||||
|
for field in filter_fields_names
|
||||||
|
]
|
||||||
|
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
||||||
|
|
||||||
|
def search_by_name_or_last_name(self, value):
|
||||||
|
"""Search by name or last_name."""
|
||||||
|
return self._generic_search(value, ['name', 'last_name'])
|
||||||
|
|
||||||
|
|
||||||
class Employee(BaseAttributes):
|
class Employee(BaseAttributes):
|
||||||
"""Employee model."""
|
"""Employee model."""
|
||||||
|
|
||||||
user = models.OneToOneField('account.User', on_delete=models.PROTECT,
|
user = models.OneToOneField('account.User', on_delete=models.PROTECT,
|
||||||
null=True, blank=True, default=None,
|
null=True, blank=True, default=None,
|
||||||
verbose_name=_('User'))
|
verbose_name=_('User'))
|
||||||
name = models.CharField(max_length=255, verbose_name=_('Last name'))
|
name = models.CharField(max_length=255, verbose_name=_('Name'))
|
||||||
|
last_name = models.CharField(max_length=255, verbose_name=_('Last Name'), null=True, default=None)
|
||||||
|
|
||||||
|
# SEX CHOICES
|
||||||
|
MALE = 0
|
||||||
|
FEMALE = 1
|
||||||
|
|
||||||
|
SEX_CHOICES = (
|
||||||
|
(MALE, _('Male')),
|
||||||
|
(FEMALE, _('Female'))
|
||||||
|
)
|
||||||
|
sex = models.PositiveSmallIntegerField(choices=SEX_CHOICES, verbose_name=_('Sex'), null=True, default=None)
|
||||||
|
birth_date = models.DateTimeField(editable=True, verbose_name=_('Birth date'), null=True, default=None)
|
||||||
|
email = models.EmailField(blank=True, null=True, default=None, verbose_name=_('Email'))
|
||||||
|
phone = PhoneNumberField(null=True, default=None)
|
||||||
|
toque_number = models.PositiveSmallIntegerField(verbose_name=_('Toque number'), null=True, default=None)
|
||||||
|
|
||||||
establishments = models.ManyToManyField(Establishment, related_name='employees',
|
establishments = models.ManyToManyField(Establishment, related_name='employees',
|
||||||
through=EstablishmentEmployee, )
|
through=EstablishmentEmployee, )
|
||||||
awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
|
awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
|
||||||
|
|
@ -691,6 +789,8 @@ class Employee(BaseAttributes):
|
||||||
# old_id = profile_id
|
# old_id = profile_id
|
||||||
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
||||||
|
|
||||||
|
objects = EmployeeQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from establishment import serializers as model_serializers
|
from establishment import serializers as model_serializers
|
||||||
from location.serializers import AddressDetailSerializer
|
from location.serializers import AddressDetailSerializer, TranslatedField
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
|
from main.serializers import AwardSerializer
|
||||||
from utils.decorators import with_base_attributes
|
from utils.decorators import with_base_attributes
|
||||||
from utils.serializers import TimeZoneChoiceField
|
from utils.serializers import TimeZoneChoiceField
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
|
|
@ -161,12 +162,54 @@ class ContactEmailBackSerializers(model_serializers.PlateSerializer):
|
||||||
class EmployeeBackSerializers(serializers.ModelSerializer):
|
class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
"""Employee serializers."""
|
"""Employee serializers."""
|
||||||
|
|
||||||
|
awards = AwardSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Employee
|
model = models.Employee
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'user',
|
'user',
|
||||||
'name'
|
'name',
|
||||||
|
'last_name',
|
||||||
|
'sex',
|
||||||
|
'birth_date',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'toque_number',
|
||||||
|
'awards',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PositionBackSerializer(serializers.ModelSerializer):
|
||||||
|
"""Position Back serializer."""
|
||||||
|
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Position
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'priority',
|
||||||
|
'index_name',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer):
|
||||||
|
"""Establishment Employee serializer."""
|
||||||
|
|
||||||
|
employee = EmployeeBackSerializers()
|
||||||
|
position = PositionBackSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.EstablishmentEmployee
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'employee',
|
||||||
|
'from_date',
|
||||||
|
'to_date',
|
||||||
|
'position',
|
||||||
|
'status',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -189,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
establishment_pk = self.get_request_kwargs().get('pk')
|
establishment_pk = self.get_request_kwargs().get('pk')
|
||||||
|
establishment_slug = self.get_request_kwargs().get('slug')
|
||||||
|
|
||||||
|
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
|
||||||
|
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.get_request_kwargs().get('image_id')
|
||||||
|
|
||||||
establishment_qs = models.Establishment.objects.filter(pk=establishment_pk)
|
establishment_qs = models.Establishment.objects.filter(**search_kwargs)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
||||||
if not establishment_qs.exists():
|
if not establishment_qs.exists():
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@ from review.serializers import ReviewShortSerializer
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from timetable.serialziers import ScheduleRUDSerializer
|
from timetable.serialziers import ScheduleRUDSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import ImageBaseSerializer
|
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
||||||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||||
FavoritesCreateSerializer)
|
FavoritesCreateSerializer)
|
||||||
|
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
||||||
|
EstablishmentWineOriginBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -168,12 +170,51 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
||||||
awards = AwardSerializer(source='employee.awards', many=True)
|
awards = AwardSerializer(source='employee.awards', many=True)
|
||||||
priority = serializers.IntegerField(source='position.priority')
|
priority = serializers.IntegerField(source='position.priority')
|
||||||
position_index_name = serializers.CharField(source='position.index_name')
|
position_index_name = serializers.CharField(source='position.index_name')
|
||||||
|
status = serializers.CharField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Employee
|
model = models.Employee
|
||||||
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name')
|
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name', 'status')
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentEmployeeCreateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for establishment employee relation."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.EstablishmentEmployee
|
||||||
|
fields = ('id',)
|
||||||
|
|
||||||
|
def _validate_entity(self, entity_id_param: str, entity_class):
|
||||||
|
entity_id = self.context.get('request').parser_context.get('kwargs').get(entity_id_param)
|
||||||
|
entity_qs = entity_class.objects.filter(id=entity_id)
|
||||||
|
if not entity_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _(f'{entity_class.__name__} not found.')})
|
||||||
|
return entity_qs.first()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method"""
|
||||||
|
establishment = self._validate_entity("establishment_id", models.Establishment)
|
||||||
|
employee = self._validate_entity("employee_id", models.Employee)
|
||||||
|
position = self._validate_entity("position_id", models.Position)
|
||||||
|
|
||||||
|
attrs['establishment'] = establishment
|
||||||
|
attrs['employee'] = employee
|
||||||
|
attrs['position'] = position
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data, *args, **kwargs):
|
||||||
|
"""Override create method"""
|
||||||
|
validated_data.update({
|
||||||
|
'employee': validated_data.pop('employee'),
|
||||||
|
'establishment': validated_data.pop('establishment'),
|
||||||
|
'position': validated_data.pop("position")
|
||||||
|
})
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentShortSerializer(serializers.ModelSerializer):
|
class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -198,6 +239,30 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class _EstablishmentAddressShortSerializer(serializers.ModelSerializer):
|
||||||
|
"""Short serializer for establishment."""
|
||||||
|
city = CitySerializer(source='address.city', allow_null=True)
|
||||||
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
|
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
||||||
|
currency = CurrencySerializer(read_only=True)
|
||||||
|
address = AddressBaseSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Establishment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'index_name',
|
||||||
|
'slug',
|
||||||
|
'city',
|
||||||
|
'establishment_type',
|
||||||
|
'establishment_subtypes',
|
||||||
|
'currency',
|
||||||
|
'address',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentProductShortSerializer(serializers.ModelSerializer):
|
class EstablishmentProductShortSerializer(serializers.ModelSerializer):
|
||||||
"""SHORT Serializer for displaying info about an establishment on product page."""
|
"""SHORT Serializer for displaying info about an establishment on product page."""
|
||||||
establishment_type = EstablishmentTypeGeoSerializer()
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
|
|
@ -244,10 +309,12 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||||
image = serializers.URLField(source='image_url', read_only=True)
|
image = serializers.URLField(source='image_url', read_only=True)
|
||||||
|
wine_regions = EstablishmentWineRegionBaseSerializer(many=True, source='wine_origins_unique',
|
||||||
|
read_only=True, allow_null=True)
|
||||||
preview_image = serializers.URLField(source='preview_image_url',
|
preview_image = serializers.URLField(source='preview_image_url',
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
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:
|
||||||
|
|
@ -272,6 +339,8 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
'image',
|
'image',
|
||||||
'preview_image',
|
'preview_image',
|
||||||
'new_image',
|
'new_image',
|
||||||
|
'tz',
|
||||||
|
'wine_regions',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -280,12 +349,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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -318,7 +393,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
||||||
many=True)
|
many=True)
|
||||||
address = AddressDetailSerializer(read_only=True)
|
address = AddressDetailSerializer(read_only=True)
|
||||||
|
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags_detail')
|
||||||
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
|
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
|
||||||
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
||||||
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
||||||
|
|
@ -326,6 +401,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
range_price_carte = RangePriceSerializer(read_only=True)
|
range_price_carte = RangePriceSerializer(read_only=True)
|
||||||
vintage_year = serializers.ReadOnlyField()
|
vintage_year = serializers.ReadOnlyField()
|
||||||
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
||||||
|
wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -352,6 +428,20 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
'transportation',
|
'transportation',
|
||||||
'vintage_year',
|
'vintage_year',
|
||||||
'gallery',
|
'gallery',
|
||||||
|
'wine_origins',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer):
|
||||||
|
"""Serializer for Establishment model for mobiles."""
|
||||||
|
|
||||||
|
last_comment = comment_serializers.CommentRUDSerializer(allow_null=True)
|
||||||
|
|
||||||
|
class Meta(EstablishmentDetailSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
fields = EstablishmentDetailSerializer.Meta.fields + [
|
||||||
|
'last_comment',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -359,6 +449,16 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
"""Serializer for Establishment model."""
|
"""Serializer for Establishment model."""
|
||||||
|
|
||||||
address = AddressDetailSerializer(read_only=True)
|
address = AddressDetailSerializer(read_only=True)
|
||||||
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
|
artisan_category = TagBaseSerializer(many=True, allow_null=True)
|
||||||
|
|
||||||
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
|
'schedule',
|
||||||
|
'establishment_type',
|
||||||
|
'artisan_category',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
||||||
|
|
@ -396,6 +496,22 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer):
|
||||||
|
"""Retrieve/Update/Destroy comment serializer."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = comment_models.Comment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
'text',
|
||||||
|
'mark',
|
||||||
|
'nickname',
|
||||||
|
'profile_pic',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
|
class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
"""Serializer to favorite object w/ model Establishment."""
|
"""Serializer to favorite object w/ model Establishment."""
|
||||||
|
|
||||||
|
|
@ -426,6 +542,29 @@ class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
|
||||||
|
"""Serializer to carousel object w/ model News."""
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
search_kwargs = {'pk': self.pk} if self.pk else {'slug': self.slug}
|
||||||
|
|
||||||
|
establishment = models.Establishment.objects.filter(**search_kwargs).first()
|
||||||
|
if not establishment:
|
||||||
|
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||||
|
|
||||||
|
if establishment.carousels.exists():
|
||||||
|
raise utils_exceptions.CarouselError()
|
||||||
|
|
||||||
|
attrs['establishment'] = establishment
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data, *args, **kwargs):
|
||||||
|
validated_data.update({
|
||||||
|
'content_object': validated_data.pop('establishment')
|
||||||
|
})
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class CompanyBaseSerializer(serializers.ModelSerializer):
|
class CompanyBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Company base serializer"""
|
"""Company base serializer"""
|
||||||
phone_list = serializers.SerializerMethodField(source='phones', read_only=True)
|
phone_list = serializers.SerializerMethodField(source='phones', read_only=True)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from translation.models import Language
|
||||||
from account.models import Role, UserRole
|
from account.models import Role, UserRole
|
||||||
from location.models import Country, Address, City, Region
|
from location.models import Country, Address, City, Region
|
||||||
from pytz import timezone as py_tz
|
from pytz import timezone as py_tz
|
||||||
|
from main.models import SiteSettings
|
||||||
|
from timetable.models import Timetable
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(APITestCase):
|
class BaseTestCase(APITestCase):
|
||||||
|
|
@ -102,18 +104,18 @@ class EstablishmentBTests(BaseTestCase):
|
||||||
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json')
|
response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'name': 'Test new establishment'
|
'name': 'Test new establishment'
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/',
|
response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/',
|
||||||
data=update_data)
|
data=update_data)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/',
|
response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/',
|
||||||
format='json')
|
format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
@ -278,13 +280,13 @@ class PlateTests(ChildTestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
menu = Menu.objects.create(
|
menu = Menu.objects.create(
|
||||||
category=json.dumps({"en-GB": "Test category"}),
|
category=json.dumps({"ru-RU": "Test category"}),
|
||||||
establishment=self.establishment
|
establishment=self.establishment
|
||||||
)
|
)
|
||||||
currency = Currency.objects.create(name="Test currency")
|
currency = Currency.objects.create(name="Test currency")
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'name': json.dumps({"en-GB": "Test plate"}),
|
'name': json.dumps({"ru-RU": "Test plate"}),
|
||||||
'establishment': self.establishment.id,
|
'establishment': self.establishment.id,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'menu': menu.id,
|
'menu': menu.id,
|
||||||
|
|
@ -298,7 +300,7 @@ class PlateTests(ChildTestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'name': json.dumps({"en-GB": "Test new plate"})
|
'name': json.dumps({"ru-RU": "Test new plate"})
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch('/api/back/establishments/plates/1/', data=update_data)
|
response = self.client.patch('/api/back/establishments/plates/1/', data=update_data)
|
||||||
|
|
@ -314,7 +316,7 @@ class MenuTests(ChildTestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'category': json.dumps({"en-GB": "Test category"}),
|
'category': json.dumps({"ru-RU": "Test category"}),
|
||||||
'establishment': self.establishment.id
|
'establishment': self.establishment.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,7 +327,7 @@ class MenuTests(ChildTestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'category': json.dumps({"en-GB": "Test new category"})
|
'category': json.dumps({"ru-RU": "Test new category"})
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch('/api/back/establishments/menus/1/', data=update_data)
|
response = self.client.patch('/api/back/establishments/menus/1/', data=update_data)
|
||||||
|
|
@ -336,24 +338,56 @@ class MenuTests(ChildTestCase):
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentShedulerTests(ChildTestCase):
|
class EstablishmentShedulerTests(ChildTestCase):
|
||||||
def test_shedule_CRUD(self):
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.lang, created = Language.objects.get_or_create(
|
||||||
|
title='Russia',
|
||||||
|
locale='ru-RU'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.country_ru, created = Country.objects.get_or_create(
|
||||||
|
name={"en-GB": "Russian"}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.site_ru, created = SiteSettings.objects.get_or_create(
|
||||||
|
subdomain='ru'
|
||||||
|
)
|
||||||
|
|
||||||
|
role, created = Role.objects.get_or_create(
|
||||||
|
role=Role.ESTABLISHMENT_MANAGER,
|
||||||
|
country_id=self.country_ru.id,
|
||||||
|
site_id=self.site_ru.id
|
||||||
|
)
|
||||||
|
|
||||||
|
user_role, created = UserRole.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
role=role,
|
||||||
|
establishment_id=self.establishment.id
|
||||||
|
)
|
||||||
|
user_role.save()
|
||||||
|
|
||||||
|
def test_schedule_CRUD(self):
|
||||||
data = {
|
data = {
|
||||||
'weekday': 1
|
'weekday': 1
|
||||||
}
|
}
|
||||||
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/1/')
|
response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
schedule = response.data
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'weekday': 2
|
'weekday': 2
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/1/', data=update_data)
|
response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/',
|
||||||
|
data=update_data)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/')
|
response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -441,3 +475,17 @@ class EstablishmentWebFavoriteTests(ChildTestCase):
|
||||||
f'/api/web/establishments/slug/{self.establishment.slug}/favorites/',
|
f'/api/web/establishments/slug/{self.establishment.slug}/favorites/',
|
||||||
format='json')
|
format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCarouselTests(ChildTestCase):
|
||||||
|
|
||||||
|
def test_back_carousel_CR(self):
|
||||||
|
data = {
|
||||||
|
"object_id": self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,25 @@ app_name = 'establishment'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
||||||
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
path('slug/<slug:slug>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||||
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
|
path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
|
||||||
|
name='create-destroy-carousels'),
|
||||||
|
path('slug/<slug:slug>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
|
||||||
name='schedule-rud'),
|
name='schedule-rud'),
|
||||||
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
path('slug/<slug:slug>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||||
name='schedule-create'),
|
name='schedule-create'),
|
||||||
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
path('slug/<slug:slug>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||||
name='gallery-list'),
|
name='gallery-list'),
|
||||||
path('<int:pk>/gallery/<int:image_id>/',
|
path('slug/<slug:slug>/gallery/<int:image_id>/',
|
||||||
views.EstablishmentGalleryCreateDestroyView.as_view(),
|
views.EstablishmentGalleryCreateDestroyView.as_view(),
|
||||||
name='gallery-create-destroy'),
|
name='gallery-create-destroy'),
|
||||||
path('<int:pk>/companies/', views.EstablishmentCompanyListCreateView.as_view(),
|
path('slug/<slug:slug>/companies/', views.EstablishmentCompanyListCreateView.as_view(),
|
||||||
name='company-list-create'),
|
name='company-list-create'),
|
||||||
path('<int:pk>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(),
|
path('slug/<slug:slug>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(),
|
||||||
name='company-rud'),
|
name='company-rud'),
|
||||||
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
path('slug/<slug:slug>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||||
name='note-list-create'),
|
name='note-list-create'),
|
||||||
path('<int:pk>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(),
|
path('slug/<slug:slug>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(),
|
||||||
name='note-rud'),
|
name='note-rud'),
|
||||||
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
||||||
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||||
|
|
@ -38,10 +40,19 @@ urlpatterns = [
|
||||||
path('phones/<int:pk>/', views.PhonesRUDView.as_view(), name='phones-rud'),
|
path('phones/<int:pk>/', views.PhonesRUDView.as_view(), name='phones-rud'),
|
||||||
path('emails/', views.EmailListCreateView.as_view(), name='emails'),
|
path('emails/', views.EmailListCreateView.as_view(), name='emails'),
|
||||||
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
|
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
|
||||||
|
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
|
||||||
|
name='establishment-employees'),
|
||||||
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
||||||
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
||||||
|
path('<int:establishment_id>/employee/<int:employee_id>/position/<int:position_id>',
|
||||||
|
views.EstablishmentEmployeeCreateView.as_view(),
|
||||||
|
name='employees-establishment-create'),
|
||||||
|
path('<int:establishment_id>/employee/<int:employee_id>',
|
||||||
|
views.EstablishmentEmployeeDeleteView.as_view(),
|
||||||
|
name='employees-establishment-delete'),
|
||||||
path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'),
|
path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'),
|
||||||
path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'),
|
path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'),
|
||||||
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
||||||
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
||||||
|
path('positions/', views.EstablishmentPositionListView.as_view(), name='position-list'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
@ -17,5 +16,5 @@ urlpatterns = [
|
||||||
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
|
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
|
||||||
name='rud-comment'),
|
name='rud-comment'),
|
||||||
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='create-destroy-favorites')
|
name='create-destroy-favorites'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
"""Establishment app views."""
|
"""Establishment app views."""
|
||||||
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, status
|
||||||
|
|
||||||
|
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||||
|
|
@ -29,6 +31,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
lookup_field = 'slug'
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all()
|
||||||
serializer_class = serializers.EstablishmentRUDSerializer
|
serializer_class = serializers.EstablishmentRUDSerializer
|
||||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
||||||
|
|
@ -36,6 +39,7 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment schedule RUD view"""
|
"""Establishment schedule RUD view"""
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = ScheduleRUDSerializer
|
serializer_class = ScheduleRUDSerializer
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
@ -43,11 +47,11 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
establishment_pk = self.kwargs.get('pk')
|
establishment_slug = self.kwargs['slug']
|
||||||
schedule_id = self.kwargs.get('schedule_id')
|
schedule_id = self.kwargs['schedule_id']
|
||||||
|
|
||||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||||
pk=establishment_pk)
|
slug=establishment_slug)
|
||||||
schedule = get_object_or_404(klass=establishment.schedule,
|
schedule = get_object_or_404(klass=establishment.schedule,
|
||||||
id=schedule_id)
|
id=schedule_id)
|
||||||
|
|
||||||
|
|
@ -60,6 +64,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||||
"""Establishment schedule Create view"""
|
"""Establishment schedule Create view"""
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = ScheduleCreateSerializer
|
serializer_class = ScheduleCreateSerializer
|
||||||
queryset = Timetable.objects.all()
|
queryset = Timetable.objects.all()
|
||||||
permission_classes = [IsEstablishmentManager]
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
@ -156,11 +161,23 @@ class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
class EmployeeListCreateView(generics.ListCreateAPIView):
|
class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
"""Emplyoee list create view."""
|
"""Emplyoee list create view."""
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
filter_class = filters.EmployeeBackFilter
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
queryset = models.Employee.objects.all()
|
queryset = models.Employee.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentEmployeeListView(generics.ListCreateAPIView):
|
||||||
|
"""Establishment emplyoees list view."""
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
serializer_class = serializers.EstablishmentEmployeeBackSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
establishment_id = self.kwargs['establishment_id']
|
||||||
|
return models.EstablishmentEmployee.objects.filter(establishment__id=establishment_id)
|
||||||
|
|
||||||
|
|
||||||
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Employee RUD view."""
|
"""Employee RUD view."""
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
|
|
@ -196,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
CreateDestroyGalleryViewMixin):
|
CreateDestroyGalleryViewMixin):
|
||||||
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -204,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
"""
|
"""
|
||||||
establishment_qs = self.filter_queryset(self.get_queryset())
|
establishment_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(establishment_qs, slug=self.kwargs.get('slug'))
|
||||||
gallery = get_object_or_404(establishment.establishment_gallery,
|
gallery = get_object_or_404(establishment.establishment_gallery,
|
||||||
image_id=self.kwargs.get('image_id'))
|
image_id=self.kwargs.get('image_id'))
|
||||||
|
|
||||||
|
|
@ -217,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||||
generics.ListAPIView):
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for establishment for back-office users."""
|
"""Resource for returning gallery for establishment for back-office users."""
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.ImageBaseSerializer
|
serializer_class = serializers.ImageBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method."""
|
"""Override get_object method."""
|
||||||
qs = super(EstablishmentGalleryListView, self).get_queryset()
|
qs = super(EstablishmentGalleryListView, self).get_queryset()
|
||||||
establishment = get_object_or_404(qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, establishment)
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
@ -238,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
||||||
generics.ListCreateAPIView):
|
generics.ListCreateAPIView):
|
||||||
"""List|Create establishment company view."""
|
"""List|Create establishment company view."""
|
||||||
|
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentCompanyListCreateSerializer
|
serializer_class = serializers.EstablishmentCompanyListCreateSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -245,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
||||||
establishment_qs = models.Establishment.objects.all()
|
establishment_qs = models.Establishment.objects.all()
|
||||||
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, establishment)
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
@ -261,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Create|Retrieve|Update|Destroy establishment company view."""
|
"""Create|Retrieve|Update|Destroy establishment company view."""
|
||||||
|
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.CompanyBaseSerializer
|
serializer_class = serializers.CompanyBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -268,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
||||||
establishment_qs = models.Establishment.objects.all()
|
establishment_qs = models.Establishment.objects.all()
|
||||||
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
|
||||||
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
|
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
|
|
@ -281,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
||||||
generics.ListCreateAPIView):
|
generics.ListCreateAPIView):
|
||||||
"""Retrieve|Update|Destroy establishment note view."""
|
"""Retrieve|Update|Destroy establishment note view."""
|
||||||
|
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentNoteListCreateSerializer
|
serializer_class = serializers.EstablishmentNoteListCreateSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -288,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
||||||
establishment_qs = models.Establishment.objects.all()
|
establishment_qs = models.Establishment.objects.all()
|
||||||
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, establishment)
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
@ -304,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Create|Retrieve|Update|Destroy establishment note view."""
|
"""Create|Retrieve|Update|Destroy establishment note view."""
|
||||||
|
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentNoteBaseSerializer
|
serializer_class = serializers.EstablishmentNoteBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -311,10 +334,43 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
||||||
establishment_qs = models.Establishment.objects.all()
|
establishment_qs = models.Establishment.objects.all()
|
||||||
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
|
establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
|
||||||
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
|
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, note)
|
self.check_object_permissions(self.request, note)
|
||||||
|
|
||||||
return note
|
return note
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentEmployeeCreateView(generics.CreateAPIView):
|
||||||
|
serializer_class = serializers.EstablishmentEmployeeCreateSerializer
|
||||||
|
queryset = models.EstablishmentEmployee.objects.all()
|
||||||
|
# TODO send email to all admins and add endpoint for changing status
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentEmployeeDeleteView(generics.DestroyAPIView):
|
||||||
|
|
||||||
|
def _get_object_to_delete(self, establishment_id, employee_id):
|
||||||
|
result_qs = models.EstablishmentEmployee\
|
||||||
|
.objects\
|
||||||
|
.filter(establishment_id=establishment_id, employee_id=employee_id)
|
||||||
|
if not result_qs.exists():
|
||||||
|
raise Http404
|
||||||
|
return result_qs.first()
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
establishment_id = self.kwargs["establishment_id"]
|
||||||
|
employee_id = self.kwargs["employee_id"]
|
||||||
|
object_to_delete = self._get_object_to_delete(establishment_id, employee_id)
|
||||||
|
object_to_delete.delete()
|
||||||
|
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentPositionListView(generics.ListAPIView):
|
||||||
|
"""Establishment positions list view."""
|
||||||
|
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
queryset = models.Position.objects.all()
|
||||||
|
serializer_class = serializers.PositionBackSerializer
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,11 @@ from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from establishment import filters
|
from comment.serializers import CommentRUDSerializer
|
||||||
from establishment import models, serializers
|
from establishment import filters, models, serializers
|
||||||
from main import methods
|
from main import methods
|
||||||
from utils.pagination import EstablishmentPortionPagination
|
from utils.pagination import EstablishmentPortionPagination
|
||||||
from utils.permissions import IsCountryAdmin
|
from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
|
||||||
from comment.serializers import CommentRUDSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentMixinView:
|
class EstablishmentMixinView:
|
||||||
|
|
@ -35,8 +34,11 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||||
serializer_class = serializers.EstablishmentListRetrieveSerializer
|
serializer_class = serializers.EstablishmentListRetrieveSerializer
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -49,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."""
|
||||||
|
|
||||||
|
|
@ -57,12 +66,11 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
user_ip = methods.get_user_ip(self.request)
|
|
||||||
query_params = self.request.query_params
|
query_params = self.request.query_params
|
||||||
if 'longitude' in query_params and 'latitude' in query_params:
|
if 'longitude' in query_params and 'latitude' in query_params:
|
||||||
longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
|
longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
|
||||||
else:
|
else:
|
||||||
longitude, latitude = methods.determine_coordinates(user_ip)
|
longitude, latitude = methods.determine_coordinates(self.request)
|
||||||
if not longitude or not latitude:
|
if not longitude or not latitude:
|
||||||
return qs.none()
|
return qs.none()
|
||||||
point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
|
point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
|
||||||
|
|
@ -106,10 +114,7 @@ class EstablishmentCommentListView(generics.ListAPIView):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
|
|
||||||
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
||||||
return comment_models.Comment.objects.by_content_type(app_label='establishment',
|
return establishment.comments.order_by('-created')
|
||||||
model='establishment')\
|
|
||||||
.by_object_id(object_id=establishment.pk)\
|
|
||||||
.order_by('-created')
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
@ -134,21 +139,19 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
return comment_obj
|
return comment_obj
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView):
|
class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
|
||||||
"""View for create/destroy establishment from favorites."""
|
"""View for create/destroy establishment from favorites."""
|
||||||
serializer_class = serializers.EstablishmentFavoritesCreateSerializer
|
|
||||||
lookup_field = 'slug'
|
|
||||||
|
|
||||||
def get_object(self):
|
_model = models.Establishment
|
||||||
"""
|
serializer_class = serializers.EstablishmentFavoritesCreateSerializer
|
||||||
Returns the object the view is displaying.
|
|
||||||
"""
|
|
||||||
establishment = get_object_or_404(models.Establishment,
|
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
|
||||||
slug=self.kwargs['slug'])
|
"""View for create/destroy establishment from carousel."""
|
||||||
favorites = get_object_or_404(establishment.favorites.filter(user=self.request.user))
|
|
||||||
# May raise a permission denied
|
lookup_field = 'slug'
|
||||||
self.check_object_permissions(self.request, favorites)
|
_model = models.Establishment
|
||||||
return favorites
|
serializer_class = serializers.EstablishmentCarouselCreateSerializer
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
|
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return Establishment.objects.filter(favorites__user=self.request.user) \
|
return Establishment.objects.filter(favorites__user=self.request.user) \
|
||||||
.order_by('-favorites')
|
.order_by('-favorites').with_base_related() \
|
||||||
|
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||||
|
|
||||||
|
|
||||||
class FavoritesProductListView(generics.ListAPIView):
|
class FavoritesProductListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from sorl.thumbnail.parsers import parse_crop
|
||||||
|
from sorl.thumbnail.parsers import ThumbnailParseError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
@ -29,3 +34,86 @@ class ImageSerializer(serializers.ModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'orientation': {'write_only': True}
|
'orientation': {'write_only': True}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CropImageSerializer(ImageSerializer):
|
||||||
|
"""Serializers for image crops."""
|
||||||
|
|
||||||
|
width = serializers.IntegerField(write_only=True, required=False)
|
||||||
|
height = serializers.IntegerField(write_only=True, required=False)
|
||||||
|
crop = serializers.CharField(write_only=True,
|
||||||
|
required=False,
|
||||||
|
default='center')
|
||||||
|
quality = serializers.IntegerField(write_only=True, required=False,
|
||||||
|
default=settings.THUMBNAIL_QUALITY,
|
||||||
|
validators=[
|
||||||
|
MinValueValidator(1),
|
||||||
|
MaxValueValidator(100)])
|
||||||
|
cropped_image = serializers.DictField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
|
class Meta(ImageSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'url',
|
||||||
|
'orientation_display',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'crop',
|
||||||
|
'quality',
|
||||||
|
'cropped_image',
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Overridden validate method."""
|
||||||
|
file = self._image.image
|
||||||
|
crop_width = attrs.get('width')
|
||||||
|
crop_height = attrs.get('height')
|
||||||
|
crop = attrs.get('crop')
|
||||||
|
|
||||||
|
if (crop_height and crop_width) and (crop and crop != 'smart'):
|
||||||
|
xy_image = (file.width, file.width)
|
||||||
|
xy_window = (crop_width, crop_height)
|
||||||
|
try:
|
||||||
|
parse_crop(crop, xy_image, xy_window)
|
||||||
|
attrs['image'] = file
|
||||||
|
except ThumbnailParseError:
|
||||||
|
raise serializers.ValidationError({'margin': _('Unrecognized crop option: %s') % crop})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
width = validated_data.pop('width', None)
|
||||||
|
height = validated_data.pop('height', None)
|
||||||
|
quality = validated_data.pop('quality')
|
||||||
|
crop = validated_data.pop('crop')
|
||||||
|
|
||||||
|
image = self._image
|
||||||
|
|
||||||
|
if image and width and height:
|
||||||
|
setattr(image,
|
||||||
|
'cropped_image',
|
||||||
|
image.get_cropped_image(
|
||||||
|
geometry=f'{width}x{height}',
|
||||||
|
quality=quality,
|
||||||
|
crop=crop))
|
||||||
|
return image
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view(self):
|
||||||
|
return self.context.get('view')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lookup_field(self):
|
||||||
|
lookup_field = 'pk'
|
||||||
|
|
||||||
|
if lookup_field in self.view.kwargs:
|
||||||
|
return self.view.kwargs.get(lookup_field)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _image(self):
|
||||||
|
"""Return image from url_kwargs."""
|
||||||
|
qs = models.Image.objects.filter(id=self.lookup_field)
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first()
|
||||||
|
raise serializers.ValidationError({'detail': _('Image not found.')})
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from . import views
|
||||||
app_name = 'gallery'
|
app_name = 'gallery'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.ImageListCreateView.as_view(), name='list-create-image'),
|
path('', views.ImageListCreateView.as_view(), name='list-create'),
|
||||||
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy-image'),
|
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy'),
|
||||||
|
path('<int:pk>/crop/', views.CropImageCreateView.as_view(), name='create-crop'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,8 @@ class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
||||||
else:
|
else:
|
||||||
on_commit(lambda: tasks.delete_image(image_id=instance.id))
|
on_commit(lambda: tasks.delete_image(image_id=instance.id))
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class CropImageCreateView(ImageBaseView, generics.CreateAPIView):
|
||||||
|
"""Create crop image."""
|
||||||
|
serializer_class = serializers.CropImageSerializer
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,15 @@ class AddressAdmin(admin.OSMGeoAdmin):
|
||||||
def geo_lat(self, item):
|
def geo_lat(self, item):
|
||||||
if isinstance(item.coordinates, Point):
|
if isinstance(item.coordinates, Point):
|
||||||
return item.coordinates.y
|
return item.coordinates.y
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.EstablishmentWineOriginAddress)
|
||||||
|
class EstablishmentWineOriginAddress(admin.ModelAdmin):
|
||||||
|
"""Admin model for EstablishmentWineOriginAddress."""
|
||||||
|
raw_id_fields = ['establishment', ]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.WineOriginAddress)
|
||||||
|
class WineOriginAddress(admin.ModelAdmin):
|
||||||
|
"""Admin page for model WineOriginAddress."""
|
||||||
|
raw_id_fields = ['product', ]
|
||||||
|
|
|
||||||
30
apps/location/management/commands/fix_address.py
Normal file
30
apps/location/management/commands/fix_address.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from location.models import Address
|
||||||
|
from transfer.models import Locations
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Fix address, clear number field and fill street_name_1 like in old db"""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
addresses = Address.objects.filter(
|
||||||
|
old_id__isnull=False
|
||||||
|
).values_list('old_id', flat=True)
|
||||||
|
|
||||||
|
old_addresses = Locations.objects.filter(
|
||||||
|
id__in=list(addresses)
|
||||||
|
).values_list('id', 'address')
|
||||||
|
|
||||||
|
update_address = []
|
||||||
|
for idx, address in tqdm(old_addresses):
|
||||||
|
new_address = Address.objects.filter(old_id=idx).first()
|
||||||
|
if new_address:
|
||||||
|
new_address.number = 0
|
||||||
|
new_address.street_name_2 = ''
|
||||||
|
new_address.street_name_1 = address
|
||||||
|
update_address.append(new_address)
|
||||||
|
|
||||||
|
Address.objects.bulk_update(update_address, ['number', 'street_name_1', 'street_name_2'])
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated addresses: {len(update_address)}'))
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-04 14:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0067_auto_20191122_1244'),
|
||||||
|
('product', '0019_auto_20191204_1420'),
|
||||||
|
('location', '0030_auto_20191120_1010'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WineOriginAddress',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wine_origins', to='product.Product', verbose_name='product')),
|
||||||
|
('wine_region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.WineRegion', verbose_name='wine region')),
|
||||||
|
('wine_sub_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.WineSubRegion', verbose_name='wine sub region')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'wine origin address',
|
||||||
|
'verbose_name_plural': 'wine origin addresses',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentWineOriginAddress',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wine_origins', to='establishment.Establishment', verbose_name='product')),
|
||||||
|
('wine_region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.WineRegion', verbose_name='wine region')),
|
||||||
|
('wine_sub_region', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.WineSubRegion', verbose_name='wine sub region')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'establishment wine origin address',
|
||||||
|
'verbose_name_plural': 'establishment wine origin addresses',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -192,12 +192,12 @@ class WineRegionQuerySet(models.QuerySet):
|
||||||
def with_sub_region_related(self):
|
def with_sub_region_related(self):
|
||||||
return self.prefetch_related('wine_sub_region')
|
return self.prefetch_related('wine_sub_region')
|
||||||
|
|
||||||
def having_wines(self, value = True):
|
def having_wines(self, value=True):
|
||||||
"""Return qs with regions, which have any wine related to them"""
|
"""Return qs with regions, which have any wine related to them"""
|
||||||
return self.exclude(wines__isnull=value)
|
return self.exclude(wineoriginaddress__product__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class WineRegion(models.Model, TranslatedFieldsMixin):
|
class WineRegion(TranslatedFieldsMixin, models.Model):
|
||||||
"""Wine region model."""
|
"""Wine region model."""
|
||||||
name = models.CharField(_('name'), max_length=255)
|
name = models.CharField(_('name'), max_length=255)
|
||||||
country = models.ForeignKey(Country, on_delete=models.PROTECT,
|
country = models.ForeignKey(Country, on_delete=models.PROTECT,
|
||||||
|
|
@ -278,6 +278,55 @@ class WineVillage(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class WineOriginAddressMixin(models.Model):
|
||||||
|
"""Model for wine origin address."""
|
||||||
|
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('wine region'))
|
||||||
|
wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.CASCADE,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('wine sub region'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWineOriginAddressQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for EstablishmentWineOriginAddress model."""
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWineOriginAddress(WineOriginAddressMixin):
|
||||||
|
"""Establishment wine origin address model."""
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE,
|
||||||
|
related_name='wine_origins',
|
||||||
|
verbose_name=_('product'))
|
||||||
|
|
||||||
|
objects = EstablishmentWineOriginAddressQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('establishment wine origin address')
|
||||||
|
verbose_name_plural = _('establishment wine origin addresses')
|
||||||
|
|
||||||
|
|
||||||
|
class WineOriginAddressQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for WineOriginAddress model."""
|
||||||
|
|
||||||
|
|
||||||
|
class WineOriginAddress(WineOriginAddressMixin):
|
||||||
|
"""Wine origin address model."""
|
||||||
|
product = models.ForeignKey('product.Product', on_delete=models.CASCADE,
|
||||||
|
related_name='wine_origins',
|
||||||
|
verbose_name=_('product'))
|
||||||
|
|
||||||
|
objects = WineOriginAddressQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('wine origin address')
|
||||||
|
verbose_name_plural = _('wine origin addresses')
|
||||||
|
|
||||||
|
|
||||||
# todo: Make recalculate price levels
|
# todo: Make recalculate price levels
|
||||||
@receiver(post_save, sender=Country)
|
@receiver(post_save, sender=Country)
|
||||||
def run_recalculate_price_levels(sender, instance, **kwargs):
|
def run_recalculate_price_levels(sender, instance, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -191,10 +191,58 @@ class WineSubRegionBaseSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WineRegionSerializer(WineRegionBaseSerializer):
|
class EstablishmentWineRegionBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Wine region w/ subregion serializer"""
|
"""Establishment wine region origin serializer."""
|
||||||
|
|
||||||
wine_sub_region = WineSubRegionBaseSerializer(allow_null=True, many=True)
|
id = serializers.IntegerField(source='wine_region.id')
|
||||||
|
name = serializers.CharField(source='wine_region.name')
|
||||||
|
country = CountrySerializer(source='wine_region.country')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.EstablishmentWineOriginAddress
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'country',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWineOriginBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for intermediate model EstablishmentWineOrigin."""
|
||||||
|
wine_region = WineRegionBaseSerializer()
|
||||||
|
wine_sub_region = WineSubRegionBaseSerializer(allow_null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.EstablishmentWineOriginAddress
|
||||||
|
fields = [
|
||||||
|
'wine_region',
|
||||||
|
'wine_sub_region',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WineOriginRegionBaseSerializer(EstablishmentWineRegionBaseSerializer):
|
||||||
|
"""Product wine region origin serializer."""
|
||||||
|
|
||||||
|
class Meta(EstablishmentWineRegionBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.WineOriginAddress
|
||||||
|
|
||||||
|
|
||||||
|
class WineOriginBaseSerializer(EstablishmentWineOriginBaseSerializer):
|
||||||
|
"""Serializer for intermediate model ProductWineOrigin."""
|
||||||
|
|
||||||
|
class Meta(EstablishmentWineOriginBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.WineOriginAddress
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Wine region w/ sub region serializer"""
|
||||||
|
|
||||||
|
wine_sub_region = WineSubRegionBaseSerializer(source='wine_region.wine_sub_region',
|
||||||
|
allow_null=True, many=True)
|
||||||
|
|
||||||
class Meta(WineRegionBaseSerializer.Meta):
|
class Meta(WineRegionBaseSerializer.Meta):
|
||||||
fields = WineRegionBaseSerializer.Meta.fields + [
|
fields = WineRegionBaseSerializer.Meta.fields + [
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ class BaseTestCase(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')})
|
||||||
|
|
||||||
self.lang = Language.objects.get(
|
self.lang, created = Language.objects.get_or_create(
|
||||||
title='Russia',
|
title='Russia',
|
||||||
locale='ru-RU'
|
locale='ru-RU'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.country_ru = Country.objects.get(
|
self.country_ru, created = Country.objects.get_or_create(
|
||||||
name={"en-GB": "Russian"}
|
name={"en-GB": "Russian"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ class CountryTests(BaseTestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'name': json.dumps({"en-GB": "Test new country"})
|
'name': json.dumps({"ru-RU": "Test new country"})
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch(f'/api/back/location/countries/{response_data["id"]}/', data=update_data)
|
response = self.client.patch(f'/api/back/location/countries/{response_data["id"]}/', data=update_data)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,15 @@ from django.contrib import admin
|
||||||
from main import models
|
from main import models
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSettingsInline(admin.TabularInline):
|
||||||
|
model = models.SiteFeature
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.SiteSettings)
|
@admin.register(models.SiteSettings)
|
||||||
class SiteSettingsAdmin(admin.ModelAdmin):
|
class SiteSettingsAdmin(admin.ModelAdmin):
|
||||||
"""Site settings admin conf."""
|
"""Site settings admin conf."""
|
||||||
|
inlines = [SiteSettingsInline,]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Feature)
|
@admin.register(models.Feature)
|
||||||
|
|
|
||||||
37
apps/main/management/commands/add_home_page_carousel.py
Normal file
37
apps/main/management/commands/add_home_page_carousel.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from main.models import Carousel
|
||||||
|
from transfer.models import HomePages
|
||||||
|
from location.models import Country
|
||||||
|
from django.db.models import F
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Add establishment form HomePage to Carousel!'''
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_country(country_code):
|
||||||
|
return Country.objects.filter(code__iexact=country_code).first()
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
objects = []
|
||||||
|
deleted = 0
|
||||||
|
hp_list = HomePages.objects.annotate(
|
||||||
|
country=F('site__country_code_2'),
|
||||||
|
).all()
|
||||||
|
for hm in tqdm(hp_list, desc='Add home_page.establishments to carousel'):
|
||||||
|
est = Establishment.objects.filter(old_id=hm.selection_of_week).first()
|
||||||
|
if est:
|
||||||
|
if est.carousels.exists():
|
||||||
|
est.carousels.all().delete()
|
||||||
|
deleted += 1
|
||||||
|
carousel = Carousel(
|
||||||
|
content_object=est,
|
||||||
|
country=self.get_country(hm.country)
|
||||||
|
)
|
||||||
|
objects.append(carousel)
|
||||||
|
Carousel.objects.bulk_create(objects)
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} carousel objects.'))
|
||||||
94
apps/main/management/commands/add_site_features.py
Normal file
94
apps/main/management/commands/add_site_features.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import connections
|
||||||
|
from django.utils.text import slugify
|
||||||
|
from establishment.management.commands.add_position import namedtuplefetchall
|
||||||
|
from main.models import SiteSettings, Feature, SiteFeature
|
||||||
|
from location.models import Country
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Add site_features for old db to new db.
|
||||||
|
Run after command add_site_settings!'''
|
||||||
|
|
||||||
|
def site_sql(self):
|
||||||
|
with connections['legacy'].cursor() as cursor:
|
||||||
|
cursor.execute('''
|
||||||
|
select s.id, s.country_code_2
|
||||||
|
from sites as s
|
||||||
|
''')
|
||||||
|
return namedtuplefetchall(cursor)
|
||||||
|
|
||||||
|
def update_site_old_id(self):
|
||||||
|
for a in tqdm(self.site_sql(), desc='Update old_id site: '):
|
||||||
|
country = Country.objects.filter(code=a.country_code_2).first()
|
||||||
|
SiteSettings.objects.filter(country=country, old_id__isnull=True)\
|
||||||
|
.update(old_id=a.id)
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated old_id site.'))
|
||||||
|
|
||||||
|
def feature_sql(self):
|
||||||
|
with connections['legacy'].cursor() as cursor:
|
||||||
|
cursor.execute('''
|
||||||
|
select f.id, slug
|
||||||
|
from features as f
|
||||||
|
''')
|
||||||
|
return namedtuplefetchall(cursor)
|
||||||
|
|
||||||
|
def add_feature(self):
|
||||||
|
objects = []
|
||||||
|
for a in tqdm(self.feature_sql(), desc='Add feature: '):
|
||||||
|
features = Feature.objects.filter(slug=slugify(a.slug)).update(old_id=a.id)
|
||||||
|
if features == 0:
|
||||||
|
objects.append(
|
||||||
|
Feature(slug=slugify(a.slug), old_id=a.id)
|
||||||
|
)
|
||||||
|
Feature.objects.bulk_create(objects)
|
||||||
|
self.stdout.write(self.style.WARNING(f'Created feature objects.'))
|
||||||
|
|
||||||
|
def site_features_sql(self):
|
||||||
|
with connections['legacy'].cursor() as cursor:
|
||||||
|
cursor.execute('''
|
||||||
|
select s.id as old_site_feature,
|
||||||
|
s.site_id,
|
||||||
|
case when s.state = 'published'
|
||||||
|
then True
|
||||||
|
else False
|
||||||
|
end as published,
|
||||||
|
s.feature_id,
|
||||||
|
c.country_code_2
|
||||||
|
from features as f
|
||||||
|
join site_features s on s.feature_id=f.id
|
||||||
|
join sites c on c.id = s.site_id
|
||||||
|
''')
|
||||||
|
return namedtuplefetchall(cursor)
|
||||||
|
|
||||||
|
def add_site_features(self):
|
||||||
|
objects = []
|
||||||
|
for a in tqdm(self.site_features_sql(), desc='Add site feature: '):
|
||||||
|
site = SiteSettings.objects.get(old_id=a.site_id,
|
||||||
|
subdomain=a.country_code_2)
|
||||||
|
feature = Feature.objects.get(old_id=a.feature_id)
|
||||||
|
|
||||||
|
site_features = SiteFeature.objects\
|
||||||
|
.filter(site_settings=site,
|
||||||
|
feature=feature
|
||||||
|
).update(old_id=a.old_site_feature, published=a.published)
|
||||||
|
|
||||||
|
if site_features == 0:
|
||||||
|
objects.append(
|
||||||
|
SiteFeature(site_settings=site,
|
||||||
|
feature=feature,
|
||||||
|
published=a.published,
|
||||||
|
main=False,
|
||||||
|
old_id=a.old_site_feature
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SiteFeature.objects.bulk_create(objects)
|
||||||
|
self.stdout.write(self.style.WARNING(f'Site feature add objects.'))
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
self.update_site_old_id()
|
||||||
|
self.add_feature()
|
||||||
|
self.add_site_features()
|
||||||
|
|
||||||
|
|
||||||
56
apps/main/management/commands/add_site_settings.py
Normal file
56
apps/main/management/commands/add_site_settings.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import connections
|
||||||
|
from establishment.management.commands.add_position import namedtuplefetchall
|
||||||
|
from main.models import SiteSettings
|
||||||
|
from location.models import Country
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Add add site settings from old db to new db.
|
||||||
|
Run after country migrate!!!'''
|
||||||
|
|
||||||
|
def site_sql(self):
|
||||||
|
with connections['legacy'].cursor() as cursor:
|
||||||
|
cursor.execute('''
|
||||||
|
select
|
||||||
|
distinct
|
||||||
|
id,
|
||||||
|
country_code_2 as code,
|
||||||
|
pinterest_page_url,
|
||||||
|
twitter_page_url,
|
||||||
|
facebook_page_url,
|
||||||
|
contact_email,
|
||||||
|
config,
|
||||||
|
released,
|
||||||
|
instagram_page_url,
|
||||||
|
ad_config
|
||||||
|
from sites as s
|
||||||
|
''')
|
||||||
|
return namedtuplefetchall(cursor)
|
||||||
|
|
||||||
|
def add_site_settings(self):
|
||||||
|
objects=[]
|
||||||
|
for s in tqdm(self.site_sql(), desc='Add site settings'):
|
||||||
|
country = Country.objects.filter(code=s.code).first()
|
||||||
|
sites = SiteSettings.objects.filter(subdomain=s.code)
|
||||||
|
if not sites.exists():
|
||||||
|
objects.append(
|
||||||
|
SiteSettings(
|
||||||
|
subdomain=s.code,
|
||||||
|
country=country,
|
||||||
|
pinterest_page_url=s.pinterest_page_url,
|
||||||
|
twitter_page_url=s.twitter_page_url,
|
||||||
|
facebook_page_url=s.facebook_page_url,
|
||||||
|
instagram_page_url=s.instagram_page_url,
|
||||||
|
contact_email=s.contact_email,
|
||||||
|
config=s.config,
|
||||||
|
ad_config=s.ad_config,
|
||||||
|
old_id=s.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SiteSettings.objects.bulk_create(objects)
|
||||||
|
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
self.add_site_settings()
|
||||||
|
|
@ -28,31 +28,25 @@ def get_user_ip(request):
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def determine_country_code(ip_addr):
|
def determine_country_code(request):
|
||||||
"""Determine country code."""
|
"""Determine country code."""
|
||||||
country_code = None
|
META = request.META
|
||||||
if ip_addr:
|
country_code = META.get('X-GeoIP-Country-Code',
|
||||||
try:
|
META.get('HTTP_X_GEOIP_COUNTRY_CODE'))
|
||||||
geoip = GeoIP2()
|
if isinstance(country_code, str):
|
||||||
country_code = geoip.country_code(ip_addr)
|
return country_code.lower()
|
||||||
country_code = country_code.lower()
|
|
||||||
except GeoIP2Exception as ex:
|
|
||||||
logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}')
|
|
||||||
except Exception as ex:
|
|
||||||
logger.error(f'GEOIP Base exception: {ex}')
|
|
||||||
return country_code
|
|
||||||
|
|
||||||
|
|
||||||
def determine_coordinates(ip_addr: str) -> Tuple[Optional[float], Optional[float]]:
|
def determine_coordinates(request):
|
||||||
if ip_addr:
|
META = request.META
|
||||||
try:
|
longitude = META.get('X-GeoIP-Longitude',
|
||||||
geoip = GeoIP2()
|
META.get('HTTP_X_GEOIP_LONGITUDE'))
|
||||||
return geoip.coords(ip_addr)
|
latitude = META.get('X-GeoIP-Latitude',
|
||||||
except GeoIP2Exception as ex:
|
META.get('HTTP_X_GEOIP_LATITUDE'))
|
||||||
logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}')
|
try:
|
||||||
except Exception as ex:
|
return float(longitude), float(latitude)
|
||||||
logger.warning(f'GEOIP Base exception: {ex}')
|
except (TypeError, ValueError):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def determine_user_site_url(country_code):
|
def determine_user_site_url(country_code):
|
||||||
|
|
@ -76,15 +70,11 @@ def determine_user_site_url(country_code):
|
||||||
return site.site_url
|
return site.site_url
|
||||||
|
|
||||||
|
|
||||||
def determine_user_city(ip_addr: str) -> Optional[City]:
|
def determine_user_city(request):
|
||||||
try:
|
META = request.META
|
||||||
geoip = GeoIP2()
|
city = META.get('X-GeoIP-City',
|
||||||
return geoip.city(ip_addr)
|
META.get('HTTP_X_GEOIP_CITY'))
|
||||||
except GeoIP2Exception as ex:
|
return city
|
||||||
logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}')
|
|
||||||
except Exception as ex:
|
|
||||||
logger.warning(f'GEOIP Base exception: {ex}')
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def determine_subdivision(
|
def determine_subdivision(
|
||||||
|
|
|
||||||
18
apps/main/migrations/0037_sitesettings_old_id.py
Normal file
18
apps/main/migrations/0037_sitesettings_old_id.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-22 07:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0036_auto_20191115_0750'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitesettings',
|
||||||
|
name='old_id',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/main/migrations/0038_feature_old_id.py
Normal file
18
apps/main/migrations/0038_feature_old_id.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-26 11:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0037_sitesettings_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='feature',
|
||||||
|
name='old_id',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/main/migrations/0039_sitefeature_old_id.py
Normal file
18
apps/main/migrations/0039_sitefeature_old_id.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-26 12:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0038_feature_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitefeature',
|
||||||
|
name='old_id',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -69,6 +69,8 @@ class SiteSettings(ProjectBaseMixin):
|
||||||
verbose_name=_('AD config'))
|
verbose_name=_('AD config'))
|
||||||
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, null=True, default=None)
|
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, null=True, default=None)
|
||||||
|
|
||||||
|
old_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
objects = SiteSettingsQuerySet.as_manager()
|
objects = SiteSettingsQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -105,6 +107,7 @@ class Feature(ProjectBaseMixin, PlatformMixin):
|
||||||
priority = models.IntegerField(unique=True, null=True, default=None)
|
priority = models.IntegerField(unique=True, null=True, default=None)
|
||||||
route = models.ForeignKey('PageType', on_delete=models.PROTECT, null=True, default=None)
|
route = models.ForeignKey('PageType', on_delete=models.PROTECT, null=True, default=None)
|
||||||
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
|
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
|
||||||
|
old_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -136,6 +139,7 @@ class SiteFeature(ProjectBaseMixin):
|
||||||
published = models.BooleanField(default=False, verbose_name=_('Published'))
|
published = models.BooleanField(default=False, verbose_name=_('Published'))
|
||||||
main = models.BooleanField(default=False, verbose_name=_('Main'))
|
main = models.BooleanField(default=False, verbose_name=_('Main'))
|
||||||
nested = models.ManyToManyField('self', symmetrical=False)
|
nested = models.ManyToManyField('self', symmetrical=False)
|
||||||
|
old_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
objects = SiteFeatureQuerySet.as_manager()
|
objects = SiteFeatureQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class CarouselListView(generics.ListAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
country_code = self.request.country_code
|
country_code = self.request.country_code
|
||||||
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in ['www', 'main']:
|
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES:
|
||||||
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
|
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
|
||||||
return qs
|
return qs
|
||||||
qs = models.Carousel.objects.is_parsed().active()
|
qs = models.Carousel.objects.is_parsed().active()
|
||||||
|
|
@ -86,9 +86,8 @@ class DetermineLocation(generics.GenericAPIView):
|
||||||
serializer_class = EmptySerializer
|
serializer_class = EmptySerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
user_ip = methods.get_user_ip(request)
|
longitude, latitude = methods.determine_coordinates(request)
|
||||||
longitude, latitude = methods.determine_coordinates(user_ip)
|
city = methods.determine_user_city(request)
|
||||||
city = methods.determine_user_city(user_ip)
|
|
||||||
if longitude and latitude and city:
|
if longitude and latitude and city:
|
||||||
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
|
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,21 @@ class DetermineSiteView(generics.GenericAPIView):
|
||||||
serializer_class = EmptySerializer
|
serializer_class = EmptySerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
user_ip = methods.get_user_ip(request)
|
country_code = methods.determine_country_code(request)
|
||||||
country_code = methods.determine_country_code(user_ip)
|
|
||||||
url = methods.determine_user_site_url(country_code)
|
url = methods.determine_user_site_url(country_code)
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,16 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
tag_value__in = filters.CharFilter(method='in_tags')
|
tag_value__in = filters.CharFilter(method='in_tags')
|
||||||
type = filters.CharFilter(method='by_type')
|
type = filters.CharFilter(method='by_type')
|
||||||
|
|
||||||
|
state = filters.NumberFilter()
|
||||||
|
|
||||||
|
SORT_BY_CREATED_CHOICE = "created"
|
||||||
|
SORT_BY_START_CHOICE = "start"
|
||||||
|
SORT_BY_CHOICES = (
|
||||||
|
(SORT_BY_CREATED_CHOICE, "created"),
|
||||||
|
(SORT_BY_START_CHOICE, "start"),
|
||||||
|
)
|
||||||
|
sort_by = filters.ChoiceFilter(method='sort_by_field', choices=SORT_BY_CHOICES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
model = models.News
|
model = models.News
|
||||||
|
|
@ -29,6 +39,8 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
'tag_group',
|
'tag_group',
|
||||||
'tag_value__exclude',
|
'tag_value__exclude',
|
||||||
'tag_value__in',
|
'tag_value__in',
|
||||||
|
'state',
|
||||||
|
'sort_by',
|
||||||
)
|
)
|
||||||
|
|
||||||
def in_tags(self, queryset, name, value):
|
def in_tags(self, queryset, name, value):
|
||||||
|
|
@ -58,3 +70,6 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
return queryset.filter(news_type__name=value)
|
return queryset.filter(news_type__name=value)
|
||||||
else:
|
else:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def sort_by_field(self, queryset, name, value):
|
||||||
|
return queryset.order_by(f'-{value}')
|
||||||
|
|
|
||||||
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.'))
|
||||||
17
apps/news/management/commands/rm_empty_images.py
Normal file
17
apps/news/management/commands/rm_empty_images.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from news.models import News
|
||||||
|
import re
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Removes empty img html tags from news description'
|
||||||
|
|
||||||
|
relative_img_regex = re.compile(r'\<img.+src=(?!https?:\/\/)([^\/].+?)[\"|\']>', re.I)
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
for news in News.objects.all():
|
||||||
|
if isinstance(news.description, dict):
|
||||||
|
news.description = {locale: self.relative_img_regex.sub('', rich_text)
|
||||||
|
for locale, rich_text in news.description.items()}
|
||||||
|
self.stdout.write(self.style.WARNING(f'Replaced {news} empty img html tags...\n'))
|
||||||
|
news.save()
|
||||||
20
apps/news/migrations/0036_news_site.py
Normal file
20
apps/news/migrations/0036_news_site.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-22 09:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0037_sitesettings_old_id'),
|
||||||
|
('news', '0035_news_views_count'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site settings'),
|
||||||
|
),
|
||||||
|
]
|
||||||
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -8,7 +8,8 @@ from rest_framework.reverse import reverse
|
||||||
|
|
||||||
from rating.models import Rating, ViewCount
|
from rating.models import Rating, ViewCount
|
||||||
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
|
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin)
|
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin,
|
||||||
|
FavoritesMixin)
|
||||||
from utils.querysets import TranslationQuerysetMixin
|
from utils.querysets import TranslationQuerysetMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
@ -126,7 +127,8 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin):
|
class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
|
FavoritesMixin):
|
||||||
"""News model."""
|
"""News model."""
|
||||||
|
|
||||||
STR_FIELD_NAME = 'title'
|
STR_FIELD_NAME = 'title'
|
||||||
|
|
@ -172,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,
|
||||||
|
|
@ -194,6 +197,7 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
views_count = models.OneToOneField('rating.ViewCount', blank=True, null=True, on_delete=models.SET_NULL)
|
views_count = models.OneToOneField('rating.ViewCount', blank=True, null=True, on_delete=models.SET_NULL)
|
||||||
ratings = generic.GenericRelation(Rating)
|
ratings = generic.GenericRelation(Rating)
|
||||||
favorites = generic.GenericRelation(to='favorites.Favorites')
|
favorites = generic.GenericRelation(to='favorites.Favorites')
|
||||||
|
carousels = generic.GenericRelation(to='main.Carousel')
|
||||||
agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
|
agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
verbose_name=_('agenda'))
|
verbose_name=_('agenda'))
|
||||||
|
|
@ -201,7 +205,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
banner = models.ForeignKey('news.NewsBanner', blank=True, null=True,
|
banner = models.ForeignKey('news.NewsBanner', blank=True, null=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
verbose_name=_('banner'))
|
verbose_name=_('banner'))
|
||||||
|
site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
|
||||||
|
on_delete=models.SET_NULL, verbose_name=_('site settings'))
|
||||||
objects = NewsQuerySet.as_manager()
|
objects = NewsQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -217,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})
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
from account.serializers.common import UserBaseSerializer
|
from account.serializers.common import UserBaseSerializer
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
|
from main.models import SiteSettings
|
||||||
from location import models as location_models
|
from location import models as location_models
|
||||||
from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
|
from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
|
||||||
from news import models
|
from news import models
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import (TranslatedField, ProjectModelSerializer,
|
from utils.serializers import (TranslatedField, ProjectModelSerializer,
|
||||||
FavoritesCreateSerializer, ImageBaseSerializer)
|
FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer)
|
||||||
|
|
||||||
|
|
||||||
class AgendaSerializer(ProjectModelSerializer):
|
class AgendaSerializer(ProjectModelSerializer):
|
||||||
|
|
@ -65,7 +66,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
subtitle_translated = TranslatedField()
|
subtitle_translated = TranslatedField()
|
||||||
news_type = NewsTypeSerializer(read_only=True)
|
news_type = NewsTypeSerializer(read_only=True)
|
||||||
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags')
|
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags')
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
in_favorites = serializers.BooleanField(allow_null=True, read_only=True)
|
||||||
view_counter = serializers.IntegerField(read_only=True)
|
view_counter = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -80,7 +81,6 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
'slug',
|
'slug',
|
||||||
'in_favorites',
|
|
||||||
'view_counter',
|
'view_counter',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -162,6 +162,7 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
|
|
||||||
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
"""News back office base serializer."""
|
"""News back office base serializer."""
|
||||||
|
is_published = serializers.BooleanField(source='is_publish', read_only=True)
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -169,6 +170,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
fields = NewsBaseSerializer.Meta.fields + (
|
fields = NewsBaseSerializer.Meta.fields + (
|
||||||
'title',
|
'title',
|
||||||
'subtitle',
|
'subtitle',
|
||||||
|
'is_published',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -182,6 +184,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
country_id = serializers.PrimaryKeyRelatedField(
|
country_id = serializers.PrimaryKeyRelatedField(
|
||||||
source='country', write_only=True,
|
source='country', write_only=True,
|
||||||
queryset=location_models.Country.objects.all())
|
queryset=location_models.Country.objects.all())
|
||||||
|
site_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
source='site', write_only=True,
|
||||||
|
queryset=SiteSettings.objects.all())
|
||||||
template_display = serializers.CharField(source='get_template_display',
|
template_display = serializers.CharField(source='get_template_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
|
||||||
|
|
@ -193,8 +198,10 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
'description',
|
'description',
|
||||||
'news_type_id',
|
'news_type_id',
|
||||||
'country_id',
|
'country_id',
|
||||||
|
'site_id',
|
||||||
'template',
|
'template',
|
||||||
'template_display',
|
'template_display',
|
||||||
|
'is_international',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -267,3 +274,24 @@ class NewsFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
'content_object': validated_data.pop('news')
|
'content_object': validated_data.pop('news')
|
||||||
})
|
})
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class NewsCarouselCreateSerializer(CarouselCreateSerializer):
|
||||||
|
"""Serializer to carousel object w/ model News."""
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
news = models.News.objects.filter(pk=self.pk).first()
|
||||||
|
if not news:
|
||||||
|
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||||
|
|
||||||
|
if news.carousels.exists():
|
||||||
|
raise utils_exceptions.CarouselError()
|
||||||
|
|
||||||
|
attrs['news'] = news
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data, *args, **kwargs):
|
||||||
|
validated_data.update({
|
||||||
|
'content_object': validated_data.pop('news')
|
||||||
|
})
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from rest_framework.test import APITestCase
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from main.models import SiteSettings
|
||||||
from news.models import NewsType, News
|
from news.models import NewsType, News
|
||||||
from account.models import User, Role, UserRole
|
from account.models import User, Role, UserRole
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
|
|
@ -30,18 +31,23 @@ class BaseTestCase(APITestCase):
|
||||||
'refresh_token': tokens.get('refresh_token')})
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
self.test_news_type = NewsType.objects.create(name="Test news type")
|
self.test_news_type = NewsType.objects.create(name="Test news type")
|
||||||
|
|
||||||
self.lang = Language.objects.get(
|
|
||||||
|
self.lang, created = Language.objects.get_or_create(
|
||||||
title='Russia',
|
title='Russia',
|
||||||
locale='ru-RU'
|
locale='ru-RU'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.country_ru = Country.objects.get(
|
self.country_ru, created = Country.objects.get_or_create(
|
||||||
name={"en-GB": "Russian"}
|
name={"en-GB": "Russian"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.site_ru, created = SiteSettings.objects.get_or_create(
|
||||||
|
subdomain='ru'
|
||||||
|
)
|
||||||
|
|
||||||
role = Role.objects.create(
|
role = Role.objects.create(
|
||||||
role=Role.CONTENT_PAGE_MANAGER,
|
role=Role.CONTENT_PAGE_MANAGER,
|
||||||
country=self.country_ru
|
site_id=self.site_ru.id
|
||||||
)
|
)
|
||||||
role.save()
|
role.save()
|
||||||
|
|
||||||
|
|
@ -51,16 +57,18 @@ class BaseTestCase(APITestCase):
|
||||||
)
|
)
|
||||||
user_role.save()
|
user_role.save()
|
||||||
|
|
||||||
|
|
||||||
self.test_news = News.objects.create(
|
self.test_news = News.objects.create(
|
||||||
created_by=self.user, modified_by=self.user,
|
created_by=self.user, modified_by=self.user,
|
||||||
title={"en-GB": "Test news"},
|
title={"ru-RU": "Test news"},
|
||||||
news_type=self.test_news_type,
|
news_type=self.test_news_type,
|
||||||
description={"en-GB": "Description test news"},
|
description={"ru-RU": "Description test news"},
|
||||||
start=datetime.now() + timedelta(hours=-2),
|
start=datetime.now() + timedelta(hours=-2),
|
||||||
end=datetime.now() + timedelta(hours=2),
|
end=datetime.now() + timedelta(hours=2),
|
||||||
state=News.PUBLISHED,
|
state=News.PUBLISHED,
|
||||||
slug='test-news-slug',
|
slug='test-news-slug',
|
||||||
country=self.country_ru,
|
country=self.country_ru,
|
||||||
|
site=self.site_ru
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,14 +78,15 @@ class NewsTestCase(BaseTestCase):
|
||||||
|
|
||||||
def test_news_post(self):
|
def test_news_post(self):
|
||||||
test_news = {
|
test_news = {
|
||||||
"title": {"en-GB": "Test news POST"},
|
"title": {"ru-RU": "Test news POST"},
|
||||||
"news_type_id": self.test_news_type.id,
|
"news_type_id": self.test_news_type.id,
|
||||||
"description": {"en-GB": "Description test news"},
|
"description": {"ru-RU": "Description test news"},
|
||||||
"start": datetime.now() + timedelta(hours=-2),
|
"start": datetime.now() + timedelta(hours=-2),
|
||||||
"end": datetime.now() + timedelta(hours=2),
|
"end": datetime.now() + timedelta(hours=2),
|
||||||
"state": News.PUBLISHED,
|
"state": News.PUBLISHED,
|
||||||
"slug": 'test-news-slug_post',
|
"slug": 'test-news-slug_post',
|
||||||
"country_id": self.country_ru.id,
|
"country_id": self.country_ru.id,
|
||||||
|
"site_id": self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse("back:news:list-create")
|
url = reverse("back:news:list-create")
|
||||||
|
|
@ -107,11 +116,12 @@ class NewsTestCase(BaseTestCase):
|
||||||
url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id})
|
url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id})
|
||||||
data = {
|
data = {
|
||||||
'id': self.test_news.id,
|
'id': self.test_news.id,
|
||||||
'description': {"en-GB": "Description test news!"},
|
'description': {"ru-RU": "Description test news!"},
|
||||||
'slug': self.test_news.slug,
|
'slug': self.test_news.slug,
|
||||||
'start': self.test_news.start,
|
'start': self.test_news.start,
|
||||||
'news_type_id': self.test_news.news_type_id,
|
'news_type_id': self.test_news.news_type_id,
|
||||||
'country_id': self.country_ru.id
|
'country_id': self.country_ru.id,
|
||||||
|
"site_id": self.site_ru.id
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.put(url, data=data, format='json')
|
response = self.client.put(url, data=data, format='json')
|
||||||
|
|
@ -128,3 +138,17 @@ class NewsTestCase(BaseTestCase):
|
||||||
|
|
||||||
response = self.client.delete(f'/api/web/news/slug/{self.test_news.slug}/favorites/', format='json')
|
response = self.client.delete(f'/api/web/news/slug/{self.test_news.slug}/favorites/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class NewsCarouselTests(BaseTestCase):
|
||||||
|
|
||||||
|
def test_back_carousel_CR(self):
|
||||||
|
data = {
|
||||||
|
"object_id": self.test_news.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(f'/api/back/news/{self.test_news.id}/carousels/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/news/{self.test_news.id}/carousels/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ urlpatterns = [
|
||||||
name='gallery-list'),
|
name='gallery-list'),
|
||||||
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
||||||
name='gallery-create-destroy'),
|
name='gallery-create-destroy'),
|
||||||
]
|
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@ common_urlpatterns = [
|
||||||
path('', views.NewsListView.as_view(), name='list'),
|
path('', views.NewsListView.as_view(), name='list'),
|
||||||
path('types/', views.NewsTypeListView.as_view(), name='type'),
|
path('types/', views.NewsTypeListView.as_view(), name='type'),
|
||||||
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
|
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
|
||||||
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(), name='create-destroy-favorites')
|
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(),
|
||||||
|
name='create-destroy-favorites'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from rest_framework import generics, permissions
|
||||||
from news import filters, models, serializers
|
from news import filters, models, serializers
|
||||||
from rating.tasks import add_rating
|
from rating.tasks import add_rating
|
||||||
from utils.permissions import IsCountryAdmin, IsContentPageManager
|
from utils.permissions import IsCountryAdmin, IsContentPageManager
|
||||||
from utils.views import CreateDestroyGalleryViewMixin
|
from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
|
||||||
from utils.serializers import ImageBaseSerializer
|
from utils.serializers import ImageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -84,6 +84,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
|
||||||
serializer_class = serializers.NewsBackOfficeBaseSerializer
|
serializer_class = serializers.NewsBackOfficeBaseSerializer
|
||||||
filter_class = filters.NewsListFilterSet
|
filter_class = filters.NewsListFilterSet
|
||||||
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
|
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
|
||||||
|
|
||||||
permission_classes = [IsCountryAdmin | IsContentPageManager]
|
permission_classes = [IsCountryAdmin | IsContentPageManager]
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
|
|
@ -150,18 +151,15 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
|
||||||
return self.retrieve(request, *args, **kwargs)
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class NewsFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView):
|
class NewsFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
|
||||||
"""View for create/destroy news from favorites."""
|
"""View for create/destroy news from favorites."""
|
||||||
|
|
||||||
|
_model = models.News
|
||||||
serializer_class = serializers.NewsFavoritesCreateSerializer
|
serializer_class = serializers.NewsFavoritesCreateSerializer
|
||||||
lookup_field = 'slug'
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
"""
|
class NewsCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
|
||||||
Returns the object the view is displaying.
|
"""View for create/destroy news from carousel."""
|
||||||
"""
|
|
||||||
news = get_object_or_404(models.News, slug=self.kwargs.get('slug'))
|
_model = models.News
|
||||||
favorites = get_object_or_404(news.favorites.filter(user=self.request.user))
|
serializer_class = serializers.NewsCarouselCreateSerializer
|
||||||
# May raise a permission denied
|
|
||||||
self.check_object_permissions(self.request, favorites)
|
|
||||||
return favorites
|
|
||||||
|
|
|
||||||
21
apps/partner/serializers/back.py
Normal file
21
apps/partner/serializers/back.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Back account serializers"""
|
||||||
|
from rest_framework import serializers
|
||||||
|
from partner.models import Partner
|
||||||
|
|
||||||
|
|
||||||
|
class BackPartnerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Partner
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'old_id',
|
||||||
|
'name',
|
||||||
|
'url',
|
||||||
|
'image',
|
||||||
|
'establishment',
|
||||||
|
'establishment_id',
|
||||||
|
'type',
|
||||||
|
'starting_date',
|
||||||
|
'expiry_date',
|
||||||
|
'price_per_month',
|
||||||
|
)
|
||||||
|
|
@ -1,16 +1,90 @@
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
from rest_framework.test import APITestCase
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import User, Role, UserRole
|
||||||
|
from establishment.models import EstablishmentType, Establishment
|
||||||
|
from location.models import Country, Region, City, Address
|
||||||
from partner.models import Partner
|
from partner.models import Partner
|
||||||
|
from translation.models import Language
|
||||||
|
|
||||||
|
|
||||||
class PartnerTestCase(APITestCase):
|
class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.test_url = "www.example.com"
|
self.username = 'test_user'
|
||||||
self.test_partner = Partner.objects.create(url=self.test_url)
|
self.password = 'test_user_password'
|
||||||
|
self.email = 'test_user@mail.com'
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username=self.username,
|
||||||
|
email=self.email,
|
||||||
|
password=self.password,
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
|
self.client.cookies = SimpleCookie({
|
||||||
|
'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('refresh_token'),
|
||||||
|
})
|
||||||
|
|
||||||
|
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
|
||||||
|
self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER)
|
||||||
|
|
||||||
|
self.establishment = Establishment.objects.create(
|
||||||
|
name="Test establishment",
|
||||||
|
establishment_type_id=self.establishment_type.id,
|
||||||
|
is_publish=True,
|
||||||
|
slug="test",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user_role = UserRole.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
role=self.role,
|
||||||
|
establishment=self.establishment,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.partner = Partner.objects.create(
|
||||||
|
url='www.ya.ru',
|
||||||
|
establishment=self.establishment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerWebTestCase(BaseTestCase):
|
||||||
|
|
||||||
def test_partner_list(self):
|
def test_partner_list(self):
|
||||||
response = self.client.get("/api/web/partner/")
|
response = self.client.get("/api/web/partner/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerBackTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def test_partner_list(self):
|
||||||
|
response = self.client.get('/api/back/partner/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_partner_post(self):
|
||||||
|
test_partner = {
|
||||||
|
'url': 'http://google.com',
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/back/partner/', data=test_partner, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def test_partner_detail(self):
|
||||||
|
response = self.client.get(f'/api/back/partner/{self.partner.id}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_partner_detail_put(self):
|
||||||
|
data = {
|
||||||
|
'url': 'http://yandex.com',
|
||||||
|
'name': 'Yandex',
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.put(f'/api/back/partner/{self.partner.id}/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_partner_delete(self):
|
||||||
|
response = self.client.delete(f'/api/back/partner/{self.partner.id}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
|
from partner.models import Partner
|
||||||
from transfer.models import EstablishmentBacklinks
|
from transfer.models import EstablishmentBacklinks
|
||||||
from transfer.serializers.partner import PartnerSerializer
|
from transfer.serializers.partner import PartnerSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_partner():
|
def transfer_partner():
|
||||||
|
"""
|
||||||
|
Transfer data to Partner model only after transfer Establishment
|
||||||
|
"""
|
||||||
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
||||||
queryset = EstablishmentBacklinks.objects.filter(
|
queryset = EstablishmentBacklinks.objects.filter(
|
||||||
establishment_id__in=list(establishments),
|
establishment_id__in=list(establishments),
|
||||||
|
|
@ -24,6 +28,7 @@ def transfer_partner():
|
||||||
|
|
||||||
serialized_data = PartnerSerializer(data=list(queryset), many=True)
|
serialized_data = PartnerSerializer(data=list(queryset), many=True)
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
|
Partner.objects.all().delete() # TODO: закоментить, если требуется сохранить старые записи
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"Partner serializer errors: {serialized_data.errors}")
|
pprint(f"Partner serializer errors: {serialized_data.errors}")
|
||||||
|
|
|
||||||
11
apps/partner/urls/back.py
Normal file
11
apps/partner/urls/back.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""Back account URLs"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from partner.views import back as views
|
||||||
|
|
||||||
|
app_name = 'partner'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.PartnerLstView.as_view(), name='partner-list-create'),
|
||||||
|
path('<int:id>/', views.PartnerRUDView.as_view(), name='partner-rud'),
|
||||||
|
]
|
||||||
27
apps/partner/views/back.py
Normal file
27
apps/partner/views/back.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend, filters
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
|
from partner.models import Partner
|
||||||
|
from partner.serializers import back as serializers
|
||||||
|
from utils.permissions import IsEstablishmentManager
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerLstView(generics.ListCreateAPIView):
|
||||||
|
"""Partner list create view."""
|
||||||
|
queryset = Partner.objects.all()
|
||||||
|
serializer_class = serializers.BackPartnerSerializer
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = [permissions.IsAdminUser | IsEstablishmentManager]
|
||||||
|
filter_backends = (DjangoFilterBackend,)
|
||||||
|
filterset_fields = (
|
||||||
|
'establishment',
|
||||||
|
'type',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Partner RUD view."""
|
||||||
|
queryset = Partner.objects.all()
|
||||||
|
serializer_class = serializers.BackPartnerSerializer
|
||||||
|
permission_classes = [permissions.IsAdminUser | IsEstablishmentManager]
|
||||||
|
lookup_field = 'id'
|
||||||
|
|
@ -8,7 +8,7 @@ from partner.serializers import common as serializers
|
||||||
# Mixins
|
# Mixins
|
||||||
class PartnerViewMixin(generics.GenericAPIView):
|
class PartnerViewMixin(generics.GenericAPIView):
|
||||||
"""View mixin for Partner views"""
|
"""View mixin for Partner views"""
|
||||||
queryset = models.Partner.objects.all()
|
queryset = models.Partner.objects.distinct("name")
|
||||||
|
|
||||||
|
|
||||||
# Views
|
# Views
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# Create your views here.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user