Merge branch 'develop' into feature/product
This commit is contained in:
commit
45a3cd4b2b
28
README.md
28
README.md
|
|
@ -1,2 +1,30 @@
|
||||||
# gm-backend
|
# gm-backend
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
1. ``git clone ssh://git@gl.id-east.ru:222/gm/gm-backend.git``
|
||||||
|
1. ``cd ./gm-backend``
|
||||||
|
1. ``git checkout develop``
|
||||||
|
1. ``docker-compose build``
|
||||||
|
1. First start database: ``docker-compose up db``
|
||||||
|
1. ``docker-compose up -d``
|
||||||
|
### Migrate data
|
||||||
|
|
||||||
|
1.Connect to container with django ``docker exec -it gm-backend_gm_app_1 bash``
|
||||||
|
|
||||||
|
#### In docker container(django)
|
||||||
|
|
||||||
|
1. Migrate ``python manage.py migrate``
|
||||||
|
1. Create super-user ``python manage.py createsuperuser``
|
||||||
|
|
||||||
|
Backend is available at localhost:8000 or 0.0.0.0:8000
|
||||||
|
|
||||||
|
URL for admin http://0.0.0.0:8000/admin
|
||||||
|
URL for swagger http://0.0.0.0:8000/docs/
|
||||||
|
URL for redocs http://0.0.0.0:8000/redocs/
|
||||||
|
|
||||||
|
## Start and stop backend containers
|
||||||
|
|
||||||
|
Demonize start ``docker-compose up -d``
|
||||||
|
Stop ``docker-compose down``
|
||||||
|
Stop and remove volumes ``docker-compose down -v``
|
||||||
|
|
@ -2,10 +2,19 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Role)
|
||||||
|
class RoleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['role', 'country']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.UserRole)
|
||||||
|
class UserRoleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['user', 'role', 'establishment']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.User)
|
@admin.register(models.User)
|
||||||
class UserAdmin(BaseUserAdmin):
|
class UserAdmin(BaseUserAdmin):
|
||||||
"""User model admin settings."""
|
"""User model admin settings."""
|
||||||
|
|
|
||||||
48
apps/account/migrations/0009_auto_20191011_1123.py
Normal file
48
apps/account/migrations/0009_auto_20191011_1123.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-11 11:23
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0011_country_languages'),
|
||||||
|
('account', '0008_auto_20190912_1325'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Role',
|
||||||
|
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')),
|
||||||
|
('role', models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator')], verbose_name='Role')),
|
||||||
|
('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserRole',
|
||||||
|
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')),
|
||||||
|
('role', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='account.Role', verbose_name='Role')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='roles',
|
||||||
|
field=models.ManyToManyField(through='account.UserRole', to='account.Role', verbose_name='Roles'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/account/migrations/0009_user_unconfirmed_email.py
Normal file
18
apps/account/migrations/0009_user_unconfirmed_email.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-10 14:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0008_auto_20190912_1325'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='unconfirmed_email',
|
||||||
|
field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='unconfirmed email'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/account/migrations/0010_user_password_confirmed.py
Normal file
18
apps/account/migrations/0010_user_password_confirmed.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-10 17:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0009_user_unconfirmed_email'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='password_confirmed',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='is new password confirmed'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/account/migrations/0011_merge_20191014_0839.py
Normal file
14
apps/account/migrations/0011_merge_20191014_0839.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-14 08:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0009_auto_20191011_1123'),
|
||||||
|
('account', '0010_user_password_confirmed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
18
apps/account/migrations/0011_merge_20191014_1258.py
Normal file
18
apps/account/migrations/0011_merge_20191014_1258.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-14 12:58
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0009_auto_20191011_1123'),
|
||||||
|
('account', '0010_user_password_confirmed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='password_confirmed',
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/account/migrations/0012_merge_20191015_0708.py
Normal file
14
apps/account/migrations/0012_merge_20191015_0708.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-15 07:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0011_merge_20191014_0839'),
|
||||||
|
('account', '0011_merge_20191014_1258'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
30
apps/account/migrations/0013_auto_20191016_0810.py
Normal file
30
apps/account/migrations/0013_auto_20191016_0810.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-16 08:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'),
|
||||||
|
('account', '0012_merge_20191015_0708'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userrole',
|
||||||
|
name='establishment',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='role',
|
||||||
|
name='country',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'),
|
||||||
|
),
|
||||||
|
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')], verbose_name='Role'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -13,11 +13,42 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from authorization.models import Application
|
from authorization.models import Application
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from location.models import Country
|
||||||
from utils.models import GMTokenGenerator
|
from utils.models import GMTokenGenerator
|
||||||
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
||||||
from utils.tokens import GMRefreshToken
|
from utils.tokens import GMRefreshToken
|
||||||
|
|
||||||
|
|
||||||
|
class Role(ProjectBaseMixin):
|
||||||
|
"""Base Role model."""
|
||||||
|
STANDARD_USER = 1
|
||||||
|
COMMENTS_MODERATOR = 2
|
||||||
|
COUNTRY_ADMIN = 3
|
||||||
|
CONTENT_PAGE_MANAGER = 4
|
||||||
|
ESTABLISHMENT_MANAGER = 5
|
||||||
|
REVIEWER_MANGER = 6
|
||||||
|
RESTAURANT_REVIEWER = 7
|
||||||
|
|
||||||
|
ROLE_CHOICES = (
|
||||||
|
(STANDARD_USER, 'Standard user'),
|
||||||
|
(COMMENTS_MODERATOR, 'Comments moderator'),
|
||||||
|
(COUNTRY_ADMIN, 'Country admin'),
|
||||||
|
(CONTENT_PAGE_MANAGER, 'Content page manager'),
|
||||||
|
(ESTABLISHMENT_MANAGER, 'Establishment manager'),
|
||||||
|
(REVIEWER_MANGER, 'Reviewer manager'),
|
||||||
|
(RESTAURANT_REVIEWER, 'Restaurant reviewer')
|
||||||
|
)
|
||||||
|
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
||||||
|
null=False, blank=False)
|
||||||
|
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||||
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
# is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False)
|
||||||
|
# is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False)
|
||||||
|
# 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):
|
||||||
"""Extended manager for User model."""
|
"""Extended manager for User model."""
|
||||||
|
|
||||||
|
|
@ -60,6 +91,7 @@ class User(AbstractUser):
|
||||||
blank=True, null=True, default=None)
|
blank=True, null=True, default=None)
|
||||||
email = models.EmailField(_('email address'), blank=True,
|
email = models.EmailField(_('email address'), blank=True,
|
||||||
null=True, default=None)
|
null=True, default=None)
|
||||||
|
unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None)
|
||||||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||||
newsletter = models.NullBooleanField(default=True)
|
newsletter = models.NullBooleanField(default=True)
|
||||||
|
|
||||||
|
|
@ -67,6 +99,7 @@ class User(AbstractUser):
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
REQUIRED_FIELDS = ['email']
|
REQUIRED_FIELDS = ['email']
|
||||||
|
|
||||||
|
roles = models.ManyToManyField(Role, verbose_name=_('Roles'), through='UserRole')
|
||||||
objects = UserManager.from_queryset(UserQuerySet)()
|
objects = UserManager.from_queryset(UserQuerySet)()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -112,6 +145,9 @@ class User(AbstractUser):
|
||||||
|
|
||||||
def confirm_email(self):
|
def confirm_email(self):
|
||||||
"""Method to confirm user email address"""
|
"""Method to confirm user email address"""
|
||||||
|
if self.unconfirmed_email is not None:
|
||||||
|
self.email = self.unconfirmed_email
|
||||||
|
self.unconfirmed_email = None
|
||||||
self.email_confirmed = True
|
self.email_confirmed = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
@ -195,3 +231,11 @@ class User(AbstractUser):
|
||||||
return render_to_string(
|
return render_to_string(
|
||||||
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRole(ProjectBaseMixin):
|
||||||
|
"""UserRole model."""
|
||||||
|
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
|
||||||
|
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
|
||||||
|
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
|
||||||
|
on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
21
apps/account/serializers/back.py
Normal file
21
apps/account/serializers/back.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Back account serializers"""
|
||||||
|
from rest_framework import serializers
|
||||||
|
from account import models
|
||||||
|
|
||||||
|
|
||||||
|
class RoleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Role
|
||||||
|
fields = [
|
||||||
|
'role',
|
||||||
|
'country'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.UserRole
|
||||||
|
fields = [
|
||||||
|
'user',
|
||||||
|
'role'
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Common account serializers"""
|
"""Common account serializers"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import password_validation as password_validators
|
from django.contrib.auth import password_validation as password_validators
|
||||||
from fcm_django.models import FCMDevice
|
from fcm_django.models import FCMDevice
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
|
@ -60,9 +61,12 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
"""Override update method"""
|
"""Override update method"""
|
||||||
|
old_email = instance.email
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
if 'email' in validated_data:
|
if 'email' in validated_data:
|
||||||
instance.email_confirmed = False
|
instance.email_confirmed = False
|
||||||
|
instance.email = old_email
|
||||||
|
instance.unconfirmed_email = validated_data['email']
|
||||||
instance.save()
|
instance.save()
|
||||||
# Send verification link on user email for change email address
|
# Send verification link on user email for change email address
|
||||||
if settings.USE_CELERY:
|
if settings.USE_CELERY:
|
||||||
|
|
@ -76,27 +80,52 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class UserBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer is used to display brief information about the user."""
|
||||||
|
|
||||||
|
fullname = serializers.CharField(source='get_full_name', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.User
|
||||||
|
fields = (
|
||||||
|
'fullname',
|
||||||
|
'cropped_image_url',
|
||||||
|
'image_url',
|
||||||
|
)
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordSerializer(serializers.ModelSerializer):
|
class ChangePasswordSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model User."""
|
"""Serializer for model User."""
|
||||||
|
|
||||||
password = serializers.CharField(write_only=True)
|
password = serializers.CharField(write_only=True)
|
||||||
|
old_password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = ('password', )
|
fields = (
|
||||||
|
'password',
|
||||||
|
'old_password',
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
password = attrs.get('password')
|
password = attrs.get('password')
|
||||||
|
old_password = attrs.get('old_password')
|
||||||
try:
|
try:
|
||||||
|
# Check old password
|
||||||
|
if not self.instance.check_password(raw_password=old_password):
|
||||||
|
raise serializers.ValidationError(_('Old password mismatch.'))
|
||||||
# Compare new password with the old ones
|
# Compare new password with the old ones
|
||||||
if self.instance.check_password(raw_password=password):
|
if self.instance.check_password(raw_password=password):
|
||||||
raise utils_exceptions.PasswordsAreEqual()
|
raise serializers.ValidationError(_('Password is already in use'))
|
||||||
# Validate password
|
# Validate password
|
||||||
password_validators.validate_password(password=password)
|
password_validators.validate_password(password=password)
|
||||||
except serializers.ValidationError as e:
|
except serializers.ValidationError as e:
|
||||||
raise serializers.ValidationError(str(e))
|
raise serializers.ValidationError({'detail': e.detail})
|
||||||
else:
|
else:
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
@ -146,38 +175,6 @@ class ChangeEmailSerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class ConfirmEmailSerializer(serializers.ModelSerializer):
|
|
||||||
"""Confirm user email serializer"""
|
|
||||||
x = serializers.CharField(default=None)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class"""
|
|
||||||
model = models.User
|
|
||||||
fields = (
|
|
||||||
'email',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method"""
|
|
||||||
email_confirmed = self.instance.email_confirmed
|
|
||||||
if email_confirmed:
|
|
||||||
raise utils_exceptions.EmailConfirmedError()
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
"""
|
|
||||||
Override update method
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Send verification link on user email for change email address
|
|
||||||
if settings.USE_CELERY:
|
|
||||||
tasks.confirm_new_email_address.delay(instance.id)
|
|
||||||
else:
|
|
||||||
tasks.confirm_new_email_address(instance.id)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
# Firebase Cloud Messaging serializers
|
# Firebase Cloud Messaging serializers
|
||||||
class FCMDeviceSerializer(serializers.ModelSerializer):
|
class FCMDeviceSerializer(serializers.ModelSerializer):
|
||||||
"""FCM Device model serializer"""
|
"""FCM Device model serializer"""
|
||||||
|
|
|
||||||
81
apps/account/tests/tests_back.py
Normal file
81
apps/account/tests/tests_back.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
|
from django.urls import reverse
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
from location.models import Country
|
||||||
|
from account.models import Role, User, UserRole
|
||||||
|
|
||||||
|
class RoleTests(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': self.data['tokens'].get('access_token'),
|
||||||
|
'refresh_token': self.data['tokens'].get('access_token')})
|
||||||
|
|
||||||
|
def test_role_get(self):
|
||||||
|
url = reverse('back:account:role-list-create')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_role_post(self):
|
||||||
|
url = reverse('back:account:role-list-create')
|
||||||
|
country = Country.objects.create(
|
||||||
|
name='{"ru-RU":"Russia"}',
|
||||||
|
code='23',
|
||||||
|
low_price=15,
|
||||||
|
high_price=150000
|
||||||
|
)
|
||||||
|
country.save()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"role": 2,
|
||||||
|
"country": country.pk
|
||||||
|
}
|
||||||
|
response = self.client.post(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleTests(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': self.data['tokens'].get('access_token'),
|
||||||
|
'refresh_token': self.data['tokens'].get('access_token')})
|
||||||
|
|
||||||
|
self.country_ru = Country.objects.create(
|
||||||
|
name='{"ru-RU":"Russia"}',
|
||||||
|
code='23',
|
||||||
|
low_price=15,
|
||||||
|
high_price=150000
|
||||||
|
)
|
||||||
|
self.country_ru.save()
|
||||||
|
|
||||||
|
self.country_en = Country.objects.create(
|
||||||
|
name='{"en-GB":"England"}',
|
||||||
|
code='25',
|
||||||
|
low_price=15,
|
||||||
|
high_price=150000
|
||||||
|
)
|
||||||
|
self.country_en.save()
|
||||||
|
|
||||||
|
self.role = Role.objects.create(
|
||||||
|
role=2,
|
||||||
|
country=self.country_ru
|
||||||
|
)
|
||||||
|
self.role.save()
|
||||||
|
|
||||||
|
self.user_test = User.objects.create_user(username='test',
|
||||||
|
email='testemail@mail.com',
|
||||||
|
password='passwordtest')
|
||||||
|
|
||||||
|
def test_user_role_post(self):
|
||||||
|
url = reverse('back:account:user-role-list-create')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"user": self.user_test.id,
|
||||||
|
"role": self.role.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
78
apps/account/tests/tests_common.py
Normal file
78
apps/account/tests/tests_common.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
from account.models import User
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
class AccountUserTests(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
def test_user_url(self):
|
||||||
|
url = reverse('web:account:user-retrieve-update')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': self.data['tokens'].get('access_token'),
|
||||||
|
'refresh_token': self.data['tokens'].get('access_token')})
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"username": self.data["username"],
|
||||||
|
"first_name": "Test first name",
|
||||||
|
"last_name": "Test last name",
|
||||||
|
"cropped_image_url": "http://localhost/image/cropped.png",
|
||||||
|
"image_url": "http://localhost/image/avatar.png",
|
||||||
|
"email": "sedragurdatest@desoz.com",
|
||||||
|
"newsletter": self.data["newsletter"]
|
||||||
|
}
|
||||||
|
response = self.client.patch(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data["email"] = "sedragurdatest2@desoz.com"
|
||||||
|
|
||||||
|
response = self.client.put(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountChangePasswordTests(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
def test_change_password(self):
|
||||||
|
data = {
|
||||||
|
"old_password": self.data["password"],
|
||||||
|
"password": "new password"
|
||||||
|
}
|
||||||
|
url = reverse('web:account:change-password')
|
||||||
|
|
||||||
|
response = self.client.patch(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': self.data['tokens'].get('access_token'),
|
||||||
|
'refresh_token': self.data['tokens'].get('access_token')})
|
||||||
|
|
||||||
|
response = self.client.patch(url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountChangePasswordTests(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
def test_confirm_email(self):
|
||||||
|
user = User.objects.get(email=self.data["email"])
|
||||||
|
token = user.confirm_email_token
|
||||||
|
uidb64 = user.get_user_uidb64
|
||||||
|
url = reverse('web:account:confirm-email', kwargs={
|
||||||
|
'uidb64': uidb64,
|
||||||
|
'token': token
|
||||||
|
})
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
49
apps/account/tests/tests_web.py
Normal file
49
apps/account/tests/tests_web.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
|
from django.urls import reverse
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class AccountResetPassWordTests(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
def test_reset_password(self):
|
||||||
|
url = reverse('web:account:password-reset')
|
||||||
|
data = {
|
||||||
|
"username_or_email": self.data["email"]
|
||||||
|
}
|
||||||
|
response = self.client.post(url, data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"username_or_email": self.data["username"]
|
||||||
|
}
|
||||||
|
response = self.client.post(url, data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountResetPassWordTests(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
def test_reset_password_confirm(self):
|
||||||
|
data ={
|
||||||
|
"password": "newpasswordnewpassword"
|
||||||
|
}
|
||||||
|
user = User.objects.get(email=self.data["email"])
|
||||||
|
token = user.reset_password_token
|
||||||
|
uidb64 = user.get_user_uidb64
|
||||||
|
|
||||||
|
url = reverse('web:account:password-reset-confirm', kwargs={
|
||||||
|
'uidb64': uidb64,
|
||||||
|
'token': token
|
||||||
|
})
|
||||||
|
response = self.client.patch(url, data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
12
apps/account/urls/back.py
Normal file
12
apps/account/urls/back.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""Back account URLs"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from account.views import back as views
|
||||||
|
|
||||||
|
app_name = 'account'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
||||||
|
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
@ -8,5 +8,6 @@ app_name = 'account'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'),
|
path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'),
|
||||||
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
|
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
|
||||||
|
path('email/confirm/', views.SendConfirmationEmailView.as_view(), name='send-confirm-email'),
|
||||||
path('email/confirm/<uidb64>/<token>/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
path('email/confirm/<uidb64>/<token>/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
13
apps/account/views/back.py
Normal file
13
apps/account/views/back.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from rest_framework import generics
|
||||||
|
from account.serializers import back as serializers
|
||||||
|
from account import models
|
||||||
|
|
||||||
|
|
||||||
|
class RoleLstView(generics.ListCreateAPIView):
|
||||||
|
serializer_class = serializers.RoleSerializer
|
||||||
|
queryset = models.Role.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleLstView(generics.ListCreateAPIView):
|
||||||
|
serializer_class = serializers.UserRoleSerializer
|
||||||
|
queryset = models.Role.objects.all()
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"""Common account views"""
|
"""Common account views"""
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_decode
|
||||||
from fcm_django.models import FCMDevice
|
from fcm_django.models import FCMDevice
|
||||||
|
|
@ -9,6 +10,7 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
from account.serializers import common as serializers
|
from account.serializers import common as serializers
|
||||||
|
from authorization.tasks import send_confirm_email
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.models import GMTokenGenerator
|
from utils.models import GMTokenGenerator
|
||||||
from utils.views import JWTGenericViewMixin
|
from utils.views import JWTGenericViewMixin
|
||||||
|
|
@ -38,19 +40,26 @@ class ChangePasswordView(generics.GenericAPIView):
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class SendConfirmationEmailView(JWTGenericViewMixin):
|
class SendConfirmationEmailView(generics.GenericAPIView):
|
||||||
"""Confirm email view."""
|
"""Confirm email view."""
|
||||||
serializer_class = serializers.ConfirmEmailSerializer
|
|
||||||
queryset = models.User.objects.all()
|
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Implement PATCH-method"""
|
"""Override create method"""
|
||||||
# Get user instance
|
user = self.request.user
|
||||||
instance = self.request.user
|
country_code = self.request.country_code
|
||||||
|
|
||||||
serializer = self.get_serializer(data=request.data, instance=instance)
|
if user.email_confirmed:
|
||||||
serializer.is_valid(raise_exception=True)
|
raise utils_exceptions.EmailConfirmedError()
|
||||||
serializer.save()
|
|
||||||
|
# Send verification link on user email for change email address
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
send_confirm_email.delay(
|
||||||
|
user_id=user.id,
|
||||||
|
country_code=country_code)
|
||||||
|
else:
|
||||||
|
send_confirm_email(
|
||||||
|
user_id=user.id,
|
||||||
|
country_code=country_code)
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,7 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
||||||
queryset = models.User.objects.active()
|
queryset = models.User.objects.active()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method
|
"""Override get_object method"""
|
||||||
"""
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
uidb64 = self.kwargs.get('uidb64')
|
uidb64 = self.kwargs.get('uidb64')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,20 @@ from django.db import migrations, models
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def fill_uuid(apps, schemaeditor):
|
||||||
|
Advertisement = apps.get_model('advertisement', 'Advertisement')
|
||||||
|
for a in Advertisement.objects.all():
|
||||||
|
a.uuid = uuid.uuid4()
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
|
||||||
|
def fill_block_level(apps, schemaeditor):
|
||||||
|
Advertisement = apps.get_model('advertisement', 'Advertisement')
|
||||||
|
for a in Advertisement.objects.all():
|
||||||
|
a.block_level = ''
|
||||||
|
a.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -23,6 +37,12 @@ class Migration(migrations.Migration):
|
||||||
field=models.ManyToManyField(to='translation.Language'),
|
field=models.ManyToManyField(to='translation.Language'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
model_name='advertisement',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, editable=False),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_uuid, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
model_name='advertisement',
|
model_name='advertisement',
|
||||||
name='uuid',
|
name='uuid',
|
||||||
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||||
|
|
@ -32,8 +52,14 @@ class Migration(migrations.Migration):
|
||||||
name='block_level',
|
name='block_level',
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
model_name='advertisement',
|
||||||
|
name='block_level',
|
||||||
|
field=models.CharField(blank=True, null=True, max_length=10, verbose_name='Block level')
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_block_level, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
model_name='advertisement',
|
model_name='advertisement',
|
||||||
name='block_level',
|
name='block_level',
|
||||||
field=models.CharField(max_length=10, verbose_name='Block level')
|
field=models.CharField(max_length=10, verbose_name='Block level')
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth import password_validation as password_validators
|
from django.contrib.auth import password_validation as password_validators
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework import validators as rest_validators
|
from rest_framework.generics import get_object_or_404
|
||||||
|
|
||||||
from account import models as account_models
|
from account import models as account_models
|
||||||
from authorization import tasks
|
from authorization import tasks
|
||||||
|
|
@ -81,7 +81,6 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
|
||||||
"""Serializer for login user"""
|
"""Serializer for login user"""
|
||||||
# REQUEST
|
# REQUEST
|
||||||
username_or_email = serializers.CharField(write_only=True)
|
username_or_email = serializers.CharField(write_only=True)
|
||||||
password = serializers.CharField(write_only=True)
|
|
||||||
|
|
||||||
# For cookie properties (Max-Age)
|
# For cookie properties (Max-Age)
|
||||||
remember = serializers.BooleanField(write_only=True)
|
remember = serializers.BooleanField(write_only=True)
|
||||||
|
|
@ -101,21 +100,24 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
|
||||||
'refresh_token',
|
'refresh_token',
|
||||||
'access_token',
|
'access_token',
|
||||||
)
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'password': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
username_or_email = attrs.pop('username_or_email')
|
username_or_email = attrs.pop('username_or_email')
|
||||||
password = attrs.pop('password')
|
password = attrs.pop('password')
|
||||||
user_qs = account_models.User.objects.filter(Q(username=username_or_email) |
|
user_qs = account_models.User.objects.filter(Q(username=username_or_email) |
|
||||||
(Q(email=username_or_email)))
|
Q(email=username_or_email))
|
||||||
if not user_qs.exists():
|
if not user_qs.exists():
|
||||||
raise utils_exceptions.UserNotFoundError()
|
raise utils_exceptions.WrongAuthCredentials()
|
||||||
else:
|
else:
|
||||||
user = user_qs.first()
|
user = user_qs.first()
|
||||||
authentication = authenticate(username=user.get_username(),
|
authentication = authenticate(username=user.get_username(),
|
||||||
password=password)
|
password=password)
|
||||||
if not authentication:
|
if not authentication:
|
||||||
raise utils_exceptions.UserNotFoundError()
|
raise utils_exceptions.WrongAuthCredentials()
|
||||||
self.instance = user
|
self.instance = user
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
@ -127,10 +129,6 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
|
||||||
return super().to_representation(instance)
|
return super().to_representation(instance)
|
||||||
|
|
||||||
|
|
||||||
class LogoutSerializer(SourceSerializerMixin):
|
|
||||||
"""Serializer for Logout endpoint."""
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenSerializer(SourceSerializerMixin):
|
class RefreshTokenSerializer(SourceSerializerMixin):
|
||||||
"""Serializer for refresh token view"""
|
"""Serializer for refresh token view"""
|
||||||
refresh_token = serializers.CharField(read_only=True)
|
refresh_token = serializers.CharField(read_only=True)
|
||||||
|
|
@ -169,7 +167,3 @@ class RefreshTokenSerializer(SourceSerializerMixin):
|
||||||
class OAuth2Serialzier(SourceSerializerMixin):
|
class OAuth2Serialzier(SourceSerializerMixin):
|
||||||
"""Serializer OAuth2 authorization"""
|
"""Serializer OAuth2 authorization"""
|
||||||
token = serializers.CharField(max_length=255)
|
token = serializers.CharField(max_length=255)
|
||||||
|
|
||||||
|
|
||||||
class OAuth2LogoutSerializer(SourceSerializerMixin):
|
|
||||||
"""Serializer for logout"""
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
"""Authorization app celery tasks."""
|
"""Authorization app celery tasks."""
|
||||||
import logging
|
import logging
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from account import models as account_models
|
from account import models as account_models
|
||||||
|
|
||||||
|
|
@ -10,12 +11,12 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def send_confirm_email(user_id, country_code):
|
def send_confirm_email(user_id: int, country_code: str):
|
||||||
"""Send verification email to user."""
|
"""Send verification email to user."""
|
||||||
try:
|
try:
|
||||||
obj = account_models.User.objects.get(id=user_id)
|
obj = account_models.User.objects.get(id=user_id)
|
||||||
obj.send_email(subject=_('Email confirmation'),
|
obj.send_email(subject=_('Email confirmation'),
|
||||||
message=obj.confirm_email_template(country_code))
|
message=obj.confirm_email_template(country_code))
|
||||||
except:
|
except Exception as e:
|
||||||
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
|
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
|
||||||
f'DETAIL: Exception occurred for user: {user_id}')
|
f'DETAIL: user {user_id}, - {e}')
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
from account.models import User
|
|
||||||
# Create your tests here.
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationTests(APITestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.username = 'sedragurda'
|
|
||||||
self.password = 'sedragurdaredips19'
|
|
||||||
self.email = 'sedragurda@desoz.com'
|
|
||||||
self.newsletter = True
|
|
||||||
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
|
|
||||||
self.tokkens = User.create_jwt_tokens(self.user)
|
|
||||||
|
|
||||||
def LoginTests(self):
|
|
||||||
data ={
|
|
||||||
'username_or_email': self.username,
|
|
||||||
'password': self.password,
|
|
||||||
'remember': True
|
|
||||||
}
|
|
||||||
response = self.client.post('/api/auth/login/', data=data)
|
|
||||||
self.assertEqual(response.data['access_token'], self.tokkens.get('access_token'))
|
|
||||||
self.assertEqual(response.data['refresh_token'], self.tokkens.get('refresh_token'))
|
|
||||||
|
|
||||||
39
apps/authorization/tests/tests_authorization.py
Normal file
39
apps/authorization/tests/tests_authorization.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from account.models import User
|
||||||
|
from django.urls import reverse
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
|
||||||
|
def get_tokens_for_user(
|
||||||
|
username='sedragurda', password='sedragurdaredips19', email='sedragurda@desoz.com'):
|
||||||
|
|
||||||
|
user = User.objects.create_user(username=username, email=email, password=password)
|
||||||
|
tokens = User.create_jwt_tokens(user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"email": email,
|
||||||
|
"newsletter": True,
|
||||||
|
"user": user,
|
||||||
|
"tokens": tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationTests(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
data = get_tokens_for_user()
|
||||||
|
self.username = data["username"]
|
||||||
|
self.password = data["password"]
|
||||||
|
|
||||||
|
def LoginTests(self):
|
||||||
|
data ={
|
||||||
|
'username_or_email': self.username,
|
||||||
|
'password': self.password,
|
||||||
|
'remember': True
|
||||||
|
}
|
||||||
|
response = self.client.post(reverse('auth:authorization:login'), data=data)
|
||||||
|
self.assertEqual(response.data['access_token'], self.tokens.get('access_token'))
|
||||||
|
self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token'))
|
||||||
|
|
||||||
|
|
@ -27,24 +27,6 @@ from utils.permissions import IsAuthenticatedAndTokenIsValid
|
||||||
from utils.views import JWTGenericViewMixin
|
from utils.views import JWTGenericViewMixin
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
|
||||||
# JWTAuthView mixin
|
|
||||||
class JWTAuthViewMixin(JWTGenericViewMixin):
|
|
||||||
"""Mixin for authentication views"""
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
"""Implement POST method"""
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
response = Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
access_token = serializer.data.get('access_token')
|
|
||||||
refresh_token = serializer.data.get('refresh_token')
|
|
||||||
return self._put_cookies_in_response(
|
|
||||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
|
||||||
refresh_token=refresh_token),
|
|
||||||
response=response)
|
|
||||||
|
|
||||||
|
|
||||||
# OAuth2
|
# OAuth2
|
||||||
class BaseOAuth2ViewMixin(generics.GenericAPIView):
|
class BaseOAuth2ViewMixin(generics.GenericAPIView):
|
||||||
"""BaseMixin for classic auth views"""
|
"""BaseMixin for classic auth views"""
|
||||||
|
|
@ -112,6 +94,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
|
||||||
# Preparing request data
|
# Preparing request data
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
request_data = self.prepare_request_data(serializer.validated_data)
|
request_data = self.prepare_request_data(serializer.validated_data)
|
||||||
source = serializer.validated_data.get('source')
|
source = serializer.validated_data.get('source')
|
||||||
request_data.update({
|
request_data.update({
|
||||||
|
|
@ -144,7 +127,8 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
|
||||||
status=status.HTTP_200_OK)
|
status=status.HTTP_200_OK)
|
||||||
return self._put_cookies_in_response(
|
return self._put_cookies_in_response(
|
||||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||||
refresh_token=refresh_token),
|
refresh_token=refresh_token,
|
||||||
|
permanent=True),
|
||||||
response=response)
|
response=response)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -195,7 +179,7 @@ class ConfirmationEmailView(JWTGenericViewMixin):
|
||||||
|
|
||||||
|
|
||||||
# Login by username|email + password
|
# Login by username|email + password
|
||||||
class LoginByUsernameOrEmailView(JWTAuthViewMixin):
|
class LoginByUsernameOrEmailView(JWTGenericViewMixin):
|
||||||
"""Login by email and password"""
|
"""Login by email and password"""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.LoginByUsernameOrEmailSerializer
|
serializer_class = serializers.LoginByUsernameOrEmailSerializer
|
||||||
|
|
@ -204,10 +188,12 @@ class LoginByUsernameOrEmailView(JWTAuthViewMixin):
|
||||||
"""Implement POST method"""
|
"""Implement POST method"""
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
response = Response(serializer.data, status=status.HTTP_200_OK)
|
response = Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
access_token = serializer.data.get('access_token')
|
access_token = serializer.data.get('access_token')
|
||||||
refresh_token = serializer.data.get('refresh_token')
|
refresh_token = serializer.data.get('refresh_token')
|
||||||
is_permanent = serializer.validated_data.get('remember')
|
is_permanent = serializer.validated_data.get('remember')
|
||||||
|
|
||||||
return self._put_cookies_in_response(
|
return self._put_cookies_in_response(
|
||||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||||
refresh_token=refresh_token,
|
refresh_token=refresh_token,
|
||||||
|
|
@ -242,9 +228,11 @@ class RefreshTokenView(JWTGenericViewMixin):
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
response = Response(serializer.data, status=status.HTTP_201_CREATED)
|
response = Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
access_token = serializer.data.get('access_token')
|
access_token = serializer.data.get('access_token')
|
||||||
refresh_token = serializer.data.get('refresh_token')
|
refresh_token = serializer.data.get('refresh_token')
|
||||||
|
|
||||||
return self._put_cookies_in_response(
|
return self._put_cookies_in_response(
|
||||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||||
refresh_token=refresh_token),
|
refresh_token=refresh_token),
|
||||||
|
|
|
||||||
8
apps/booking/admin.py
Normal file
8
apps/booking/admin.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from booking.models import models
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Booking)
|
||||||
|
class BookingModelAdmin(admin.ModelAdmin):
|
||||||
|
"""Model admin for model Comment"""
|
||||||
7
apps/booking/apps.py
Normal file
7
apps/booking/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class BookingConfig(AppConfig):
|
||||||
|
name = 'booking'
|
||||||
|
verbose_name = _('Booking')
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-03 10:38
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
replaces = [('booking', '0001_initial'), ('booking', '0002_booking_user'), ('booking', '0003_auto_20190916_1533'), ('booking', '0004_auto_20190916_1646'), ('booking', '0005_auto_20190918_1308'), ('booking', '0006_booking_country_code'), ('booking', '0007_booking_booking_id'), ('booking', '0008_auto_20190919_2008'), ('booking', '0009_booking_user'), ('booking', '0010_auto_20190920_1206')]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Booking',
|
||||||
|
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')),
|
||||||
|
('type', models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2, verbose_name='Guestonline or Lastable')),
|
||||||
|
('booking_user_locale', models.CharField(default='en', max_length=10, verbose_name='booking locale')),
|
||||||
|
('restaurant_id', models.PositiveIntegerField(default=None, verbose_name='booking service establishment id')),
|
||||||
|
('pending_booking_id', models.TextField(default=None, verbose_name='external service pending booking')),
|
||||||
|
('booking_id', models.TextField(db_index=True, default=None, null=True, verbose_name='external service booking id')),
|
||||||
|
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='booking owner')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'verbose_name': 'Booking',
|
||||||
|
'verbose_name_plural': 'Booking',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
22
apps/booking/migrations/0002_auto_20191003_1601.py
Normal file
22
apps/booking/migrations/0002_auto_20191003_1601.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-03 16:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('booking', '0001_initial_squashed_0010_auto_20190920_1206'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='booking',
|
||||||
|
name='restaurant_id',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='booking',
|
||||||
|
name='restaurant_id',
|
||||||
|
field=models.TextField(default=None, verbose_name='booking service establishment id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
apps/booking/models/__init__.py
Normal file
0
apps/booking/models/__init__.py
Normal file
67
apps/booking/models/models.py
Normal file
67
apps/booking/models/models.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
from utils.models import ProjectBaseMixin
|
||||||
|
from booking.models.services import LastableService, GuestonlineService
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class BookingManager(models.QuerySet):
|
||||||
|
def by_user(self, user: User):
|
||||||
|
return self.filter(user=user)
|
||||||
|
|
||||||
|
|
||||||
|
class Booking(ProjectBaseMixin):
|
||||||
|
LASTABLE = 'L'
|
||||||
|
GUESTONLINE = 'G'
|
||||||
|
AVAILABLE_SERVICES = (
|
||||||
|
(LASTABLE, 'Lastable'),
|
||||||
|
(GUESTONLINE, 'GuestOnline')
|
||||||
|
)
|
||||||
|
type = models.CharField(max_length=2, choices=AVAILABLE_SERVICES, verbose_name=_('Guestonline or Lastable'))
|
||||||
|
restaurant_id = models.TextField(verbose_name=_('booking service establishment id'), default=None)
|
||||||
|
booking_user_locale = models.CharField(verbose_name=_('booking locale'), default='en', max_length=10)
|
||||||
|
pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None)
|
||||||
|
booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True,
|
||||||
|
db_index=True, )
|
||||||
|
user = models.ForeignKey(
|
||||||
|
'account.User', verbose_name=_('booking owner'), null=True,
|
||||||
|
related_name='bookings',
|
||||||
|
blank=True, default=None, on_delete=models.CASCADE)
|
||||||
|
objects = BookingManager.as_manager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accept_email_spam(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accept_sms_spam(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_service_by_type(cls, type):
|
||||||
|
if type == cls.GUESTONLINE:
|
||||||
|
return GuestonlineService()
|
||||||
|
elif type == cls.LASTABLE:
|
||||||
|
return LastableService()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_booking_id_by_type(cls, establishment, type):
|
||||||
|
if type == cls.GUESTONLINE:
|
||||||
|
return establishment.guestonline_id
|
||||||
|
elif type == cls.LASTABLE:
|
||||||
|
return establishment.lastable_id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
service = self.get_service_by_type(self.type)
|
||||||
|
if not service.cancel_booking(self.booking_id):
|
||||||
|
raise serializers.ValidationError(detail='Something went wrong! Unable to cancel.')
|
||||||
|
super().delete(using, keep_parents)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Booking')
|
||||||
|
verbose_name_plural = _('Booking')
|
||||||
166
apps/booking/models/services.py
Normal file
166
apps/booking/models/services.py
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
from rest_framework import status
|
||||||
|
import booking.models.models as models
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractBookingService(ABC):
|
||||||
|
""" Abstract class for Guestonline && Lastable booking services"""
|
||||||
|
|
||||||
|
def __init__(self, service):
|
||||||
|
self.service = None
|
||||||
|
self.response = None
|
||||||
|
if service not in [models.Booking.LASTABLE, models.Booking.GUESTONLINE]:
|
||||||
|
raise Exception('Service %s is not implemented yet' % service)
|
||||||
|
self.service = service
|
||||||
|
if service == models.Booking.GUESTONLINE:
|
||||||
|
self.token = settings.GUESTONLINE_TOKEN
|
||||||
|
self.url = settings.GUESTONLINE_SERVICE
|
||||||
|
elif service == models.Booking.LASTABLE:
|
||||||
|
self.token = settings.LASTABLE_TOKEN
|
||||||
|
self.url = settings.LASTABLE_SERVICE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_certain_keys(d: dict, keys_to_preserve: set) -> dict:
|
||||||
|
""" Helper """
|
||||||
|
return {key: d[key] for key in d.keys() & keys_to_preserve}
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def check_whether_booking_available(self, restaurant_id, date):
|
||||||
|
""" checks whether booking is available """
|
||||||
|
if date is None:
|
||||||
|
raise serializers.ValidationError(detail='date query param is required')
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cancel_booking(self, payload):
|
||||||
|
""" cancels booking and returns the result """
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def create_booking(self, payload):
|
||||||
|
""" returns pending booking id if created. otherwise False """
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def update_booking(self, payload):
|
||||||
|
""" updates pending booking with contacts """
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_common_headers(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_booking_details(self, payload):
|
||||||
|
""" fetches booking details from external service """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GuestonlineService(AbstractBookingService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(models.Booking.GUESTONLINE)
|
||||||
|
|
||||||
|
def get_common_headers(self):
|
||||||
|
return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'}
|
||||||
|
|
||||||
|
def check_whether_booking_available(self, restaurant_id, date: str):
|
||||||
|
super().check_whether_booking_available(restaurant_id, date)
|
||||||
|
url = f'{self.url}v1/runtime_services'
|
||||||
|
params = {'restaurant_id': restaurant_id, 'date': date, 'expands[]': 'table_availabilities'}
|
||||||
|
r = requests.get(url, headers=self.get_common_headers(), params=params)
|
||||||
|
if not status.is_success(r.status_code):
|
||||||
|
return False
|
||||||
|
response = json.loads(r.content)['runtime_services']
|
||||||
|
keys_to_preserve = {'left_seats', 'table_availabilities', 'closed', 'start_time', 'end_time', 'last_booking'}
|
||||||
|
response = map(lambda x: self.get_certain_keys(x, keys_to_preserve), response)
|
||||||
|
self.response = response
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commit_booking(self, payload):
|
||||||
|
url = f'{self.url}v1/pending_bookings/{payload}/commit'
|
||||||
|
r = requests.put(url, headers=self.get_common_headers())
|
||||||
|
self.response = json.loads(r.content)
|
||||||
|
if status.is_success(r.status_code) and self.response is None:
|
||||||
|
raise serializers.ValidationError(detail='Booking already committed.')
|
||||||
|
return status.is_success(r.status_code)
|
||||||
|
|
||||||
|
def update_booking(self, payload):
|
||||||
|
booking_id = payload.pop('pending_booking_id')
|
||||||
|
url = f'{self.url}v1/pending_bookings/{booking_id}'
|
||||||
|
payload['lastname'] = payload.pop('last_name')
|
||||||
|
payload['firstname'] = payload.pop('first_name')
|
||||||
|
payload['mobile_phone'] = payload.pop('phone')
|
||||||
|
headers = self.get_common_headers()
|
||||||
|
r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload}))
|
||||||
|
return status.is_success(r.status_code)
|
||||||
|
|
||||||
|
def create_booking(self, payload):
|
||||||
|
url = f'{self.url}v1/pending_bookings'
|
||||||
|
payload['hour'] = payload.pop('booking_time')
|
||||||
|
payload['persons'] = payload.pop('booked_persons_number')
|
||||||
|
payload['date'] = payload.pop('booking_date')
|
||||||
|
r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload))
|
||||||
|
return json.loads(r.content)['id'] if status.is_success(r.status_code) else False
|
||||||
|
|
||||||
|
def cancel_booking(self, payload):
|
||||||
|
url = f'{self.url}v1/pending_bookings/{payload}'
|
||||||
|
r = requests.delete(url, headers=self.get_common_headers())
|
||||||
|
return status.is_success(r.status_code)
|
||||||
|
|
||||||
|
def get_booking_details(self, payload):
|
||||||
|
url = f'{self.url}v1/bookings/{payload}'
|
||||||
|
r = requests.get(url, headers=self.get_common_headers())
|
||||||
|
return json.loads(r.content)
|
||||||
|
|
||||||
|
|
||||||
|
class LastableService(AbstractBookingService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(models.Booking.LASTABLE)
|
||||||
|
self.proxies = {
|
||||||
|
'http': settings.LASTABLE_PROXY,
|
||||||
|
'https': settings.LASTABLE_PROXY,
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_booking(self, payload):
|
||||||
|
url = f'{self.url}v1/partner/orders'
|
||||||
|
payload['places'] = payload.pop('booked_persons_number')
|
||||||
|
payload['hour'] = payload.pop('booking_time')
|
||||||
|
payload['firstName'] = payload.pop('first_name')
|
||||||
|
payload['lastName'] = payload.pop('last_name')
|
||||||
|
r = requests.post(url, headers=self.get_common_headers(), proxies=self.proxies, data=json.dumps(payload))
|
||||||
|
return json.loads(r.content)['data']['_id'] if status.is_success(r.status_code) else False
|
||||||
|
|
||||||
|
def get_common_headers(self):
|
||||||
|
return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json',
|
||||||
|
'Accept': 'application/json'}
|
||||||
|
|
||||||
|
def check_whether_booking_available(self, restaurant_id, date):
|
||||||
|
super().check_whether_booking_available(restaurant_id, date)
|
||||||
|
url = f'{self.url}v1/restaurant/{restaurant_id}/offers'
|
||||||
|
r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies)
|
||||||
|
response = json.loads(r.content).get('data')
|
||||||
|
if not status.is_success(r.status_code) or not response:
|
||||||
|
return False
|
||||||
|
self.response = response
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commit_booking(self, payload):
|
||||||
|
""" Lastable service has no pending booking to commit """
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_booking(self, payload):
|
||||||
|
""" Lastable service has no pending booking to update """
|
||||||
|
return False
|
||||||
|
|
||||||
|
def cancel_booking(self, payload):
|
||||||
|
url = f'{self.url}v1/partner/orders/{payload}'
|
||||||
|
r = requests.delete(url, headers=self.get_common_headers(), proxies=self.proxies)
|
||||||
|
return status.is_success(r.status_code)
|
||||||
|
|
||||||
|
def get_booking_details(self, payload):
|
||||||
|
url = f'{self.url}v1/partner/orders/{payload}'
|
||||||
|
r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies)
|
||||||
|
return json.loads(r.content)
|
||||||
0
apps/booking/serializers/__init__.py
Normal file
0
apps/booking/serializers/__init__.py
Normal file
64
apps/booking/serializers/web.py
Normal file
64
apps/booking/serializers/web.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from booking.models import models
|
||||||
|
|
||||||
|
|
||||||
|
class BookingSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Booking
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'type',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckBookingSerializer(serializers.ModelSerializer):
|
||||||
|
available = serializers.BooleanField()
|
||||||
|
type = serializers.ChoiceField(choices=models.Booking.AVAILABLE_SERVICES, allow_null=True)
|
||||||
|
details = serializers.DictField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Booking
|
||||||
|
fields = (
|
||||||
|
'available',
|
||||||
|
'type',
|
||||||
|
'details',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PendingBookingSerializer(serializers.ModelSerializer):
|
||||||
|
restaurant_id = serializers.CharField()
|
||||||
|
booking_id = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
|
id = serializers.ReadOnlyField()
|
||||||
|
user = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Booking
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'type',
|
||||||
|
'restaurant_id',
|
||||||
|
'booking_id',
|
||||||
|
'pending_booking_id',
|
||||||
|
'user',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateBookingSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Booking
|
||||||
|
fields = ('booking_id', 'id')
|
||||||
|
|
||||||
|
|
||||||
|
class GetBookingSerializer(serializers.ModelSerializer):
|
||||||
|
details = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_details(self, obj):
|
||||||
|
booking = self.instance
|
||||||
|
service = booking.get_service_by_type(booking.type)
|
||||||
|
return service.get_booking_details(booking.booking_id)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Booking
|
||||||
|
fields = '__all__'
|
||||||
14
apps/booking/urls.py
Normal file
14
apps/booking/urls.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Booking app urls."""
|
||||||
|
from django.urls import path
|
||||||
|
from booking import views
|
||||||
|
|
||||||
|
app = 'booking'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('<int:establishment_id>/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'),
|
||||||
|
path('<int:establishment_id>/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'),
|
||||||
|
path('<int:pk>/', views.UpdatePendingBooking.as_view(), name='update-pending-booking'),
|
||||||
|
path('<int:pk>/cancel/', views.CancelBooking.as_view(), name='cancel-existing-booking'),
|
||||||
|
path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'),
|
||||||
|
path('retrieve/<int:pk>/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'),
|
||||||
|
]
|
||||||
118
apps/booking/views.py
Normal file
118
apps/booking/views.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
from rest_framework import generics, permissions, status, serializers
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from booking.models.models import Booking, GuestonlineService, LastableService
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from booking.serializers.web import (PendingBookingSerializer,
|
||||||
|
UpdateBookingSerializer, GetBookingSerializer, CheckBookingSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
|
""" Checks which service to use if establishmend is managed by any """
|
||||||
|
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
serializer_class = CheckBookingSerializer
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
is_booking_available = False
|
||||||
|
establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
|
||||||
|
service = None
|
||||||
|
date = request.query_params.get('date')
|
||||||
|
g_service = GuestonlineService()
|
||||||
|
l_service = LastableService()
|
||||||
|
if (not establishment.lastable_id is None) and l_service \
|
||||||
|
.check_whether_booking_available(establishment.lastable_id, date):
|
||||||
|
is_booking_available = True
|
||||||
|
service = l_service
|
||||||
|
service.service_id = establishment.lastable_id
|
||||||
|
elif (not establishment.guestonline_id is None) and g_service \
|
||||||
|
.check_whether_booking_available(establishment.guestonline_id, date):
|
||||||
|
is_booking_available = True
|
||||||
|
service = g_service
|
||||||
|
service.service_id = establishment.guestonline_id
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'available': is_booking_available,
|
||||||
|
'type': service.service if service else None,
|
||||||
|
}
|
||||||
|
response.update({'details': service.response} if service and service.response else {})
|
||||||
|
return Response(data=response, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePendingBooking(generics.CreateAPIView):
|
||||||
|
""" Creates pending booking """
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
serializer_class = PendingBookingSerializer
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
data = request.data.copy()
|
||||||
|
if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None:
|
||||||
|
raise serializers.ValidationError(detail='Offer_id is required field for Lastable service')
|
||||||
|
establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
|
||||||
|
data['restaurant_id'] = Booking.get_booking_id_by_type(establishment, data.get('type'))
|
||||||
|
service = Booking.get_service_by_type(request.data.get('type'))
|
||||||
|
data['user'] = request.user.pk if request.user else None
|
||||||
|
service_to_keys = {
|
||||||
|
Booking.GUESTONLINE: {'restaurant_id', 'booking_time', 'booking_date', 'booked_persons_number', },
|
||||||
|
Booking.LASTABLE: {'booking_time', 'booked_persons_number', 'offer_id', 'email', 'phone',
|
||||||
|
'first_name', 'last_name', },
|
||||||
|
}
|
||||||
|
data['pending_booking_id'] = service.create_booking(
|
||||||
|
service.get_certain_keys(data.copy(), service_to_keys[data.get('type')]))
|
||||||
|
if not data['pending_booking_id']:
|
||||||
|
return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking')
|
||||||
|
data['booking_id'] = data['pending_booking_id'] if data.get('type') == Booking.LASTABLE else None
|
||||||
|
serializer = self.get_serializer(data=data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(status=status.HTTP_201_CREATED, data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePendingBooking(generics.UpdateAPIView):
|
||||||
|
""" Update pending booking with contacts """
|
||||||
|
queryset = Booking.objects.all()
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
serializer_class = UpdateBookingSerializer
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
data = request.data.copy()
|
||||||
|
service = Booking.get_service_by_type(instance.type)
|
||||||
|
data['pending_booking_id'] = instance.pending_booking_id
|
||||||
|
service.update_booking(service.get_certain_keys(data, {
|
||||||
|
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id',
|
||||||
|
}))
|
||||||
|
service.commit_booking(data['pending_booking_id'])
|
||||||
|
data = {
|
||||||
|
'booking_id': service.response.get('id'),
|
||||||
|
'id': instance.pk,
|
||||||
|
}
|
||||||
|
serializer = self.get_serializer(data=data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.update(instance, data)
|
||||||
|
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class CancelBooking(generics.DestroyAPIView):
|
||||||
|
""" Cancel existing booking """
|
||||||
|
queryset = Booking.objects.all()
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
||||||
|
|
||||||
|
class LastBooking(generics.RetrieveAPIView):
|
||||||
|
""" Get last booking by user credentials """
|
||||||
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
serializer_class = GetBookingSerializer
|
||||||
|
lookup_field = None
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return Booking.objects.by_user(self.request.user).latest('modified')
|
||||||
|
|
||||||
|
|
||||||
|
class GetBookingById(generics.RetrieveAPIView):
|
||||||
|
""" Returns booking by its id"""
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
serializer_class = GetBookingSerializer
|
||||||
|
queryset = Booking.objects.all()
|
||||||
|
|
@ -8,11 +8,6 @@ class CollectionAdmin(admin.ModelAdmin):
|
||||||
"""Collection admin."""
|
"""Collection admin."""
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.CollectionItem)
|
|
||||||
class CollectionItemAdmin(admin.ModelAdmin):
|
|
||||||
"""CollectionItem admin."""
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Guide)
|
@admin.register(models.Guide)
|
||||||
class GuideAdmin(admin.ModelAdmin):
|
class GuideAdmin(admin.ModelAdmin):
|
||||||
"""Guide admin."""
|
"""Guide admin."""
|
||||||
|
|
|
||||||
16
apps/collection/migrations/0009_delete_collectionitem.py
Normal file
16
apps/collection/migrations/0009_delete_collectionitem.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 08:55
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0008_auto_20190916_1158'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='CollectionItem',
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/collection/migrations/0010_collection_description.py
Normal file
19
apps/collection/migrations/0010_collection_description.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 09:27
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0009_delete_collectionitem'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='description',
|
||||||
|
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description'),
|
||||||
|
),
|
||||||
|
]
|
||||||
22
apps/collection/migrations/0011_auto_20190920_1059.py
Normal file
22
apps/collection/migrations/0011_auto_20190920_1059.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 10:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0010_collection_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='collection',
|
||||||
|
name='image',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='image_url',
|
||||||
|
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/collection/migrations/0012_auto_20190923_1340.py
Normal file
19
apps/collection/migrations/0012_auto_20190923_1340.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-23 13:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0011_auto_20190920_1059'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='guide',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='collection.Guide', verbose_name='parent'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/collection/migrations/0013_collection_slug.py
Normal file
18
apps/collection/migrations/0013_collection_slug.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-24 08:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0012_auto_20190923_1340'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(null=True, unique=True, verbose_name='Collection slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
23
apps/collection/migrations/0014_auto_20191022_1242.py
Normal file
23
apps/collection/migrations/0014_auto_20191022_1242.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-22 12:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0013_collection_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='collection',
|
||||||
|
name='end',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='guide',
|
||||||
|
name='end',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from django.contrib.postgres.fields import JSONField
|
|
||||||
from django.contrib.contenttypes.fields import ContentType
|
from django.contrib.contenttypes.fields import ContentType
|
||||||
from django.contrib.contenttypes import fields as generic
|
from django.contrib.postgres.fields import JSONField
|
||||||
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 _
|
||||||
|
|
||||||
from utils.models import ProjectBaseMixin, ImageMixin
|
from utils.models import ProjectBaseMixin, URLImageMixin
|
||||||
|
from utils.models import TJSONField
|
||||||
|
from utils.models import TranslatedFieldsMixin
|
||||||
|
from utils.querysets import RelatedObjectsCountMixin
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
# Mixins
|
||||||
|
|
@ -20,7 +22,8 @@ class CollectionNameMixin(models.Model):
|
||||||
class CollectionDateMixin(models.Model):
|
class CollectionDateMixin(models.Model):
|
||||||
"""CollectionDate mixin"""
|
"""CollectionDate mixin"""
|
||||||
start = models.DateTimeField(_('start'))
|
start = models.DateTimeField(_('start'))
|
||||||
end = models.DateTimeField(_('end'))
|
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('end'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
|
|
@ -28,7 +31,7 @@ class CollectionDateMixin(models.Model):
|
||||||
|
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
class CollectionQuerySet(models.QuerySet):
|
class CollectionQuerySet(RelatedObjectsCountMixin):
|
||||||
"""QuerySet for model Collection"""
|
"""QuerySet for model Collection"""
|
||||||
|
|
||||||
def by_country_code(self, code):
|
def by_country_code(self, code):
|
||||||
|
|
@ -40,7 +43,8 @@ class CollectionQuerySet(models.QuerySet):
|
||||||
return self.filter(is_publish=True)
|
return self.filter(is_publish=True)
|
||||||
|
|
||||||
|
|
||||||
class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin,
|
||||||
|
TranslatedFieldsMixin, URLImageMixin):
|
||||||
"""Collection model."""
|
"""Collection model."""
|
||||||
ORDINARY = 0 # Ordinary collection
|
ORDINARY = 0 # Ordinary collection
|
||||||
POP = 1 # POP collection
|
POP = 1 # POP collection
|
||||||
|
|
@ -53,9 +57,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES,
|
collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES,
|
||||||
default=ORDINARY,
|
default=ORDINARY,
|
||||||
verbose_name=_('Collection type'))
|
verbose_name=_('Collection type'))
|
||||||
image = models.ForeignKey(
|
|
||||||
'gallery.Image', null=True, blank=True, default=None,
|
|
||||||
verbose_name=_('Collection image'), on_delete=models.CASCADE)
|
|
||||||
is_publish = models.BooleanField(
|
is_publish = models.BooleanField(
|
||||||
default=False, verbose_name=_('Publish status'))
|
default=False, verbose_name=_('Publish status'))
|
||||||
on_top = models.BooleanField(
|
on_top = models.BooleanField(
|
||||||
|
|
@ -65,6 +66,11 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
block_size = JSONField(
|
block_size = JSONField(
|
||||||
_('collection block properties'), null=True, blank=True,
|
_('collection block properties'), null=True, blank=True,
|
||||||
default=None, help_text='{"width": "250px", "height":"250px"}')
|
default=None, help_text='{"width": "250px", "height":"250px"}')
|
||||||
|
description = TJSONField(
|
||||||
|
_('description'), null=True, blank=True,
|
||||||
|
default=None, help_text='{"en-GB":"some text"}')
|
||||||
|
slug = models.SlugField(max_length=50, unique=True,
|
||||||
|
verbose_name=_('Collection slug'), editable=True, null=True)
|
||||||
|
|
||||||
objects = CollectionQuerySet.as_manager()
|
objects = CollectionQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -78,26 +84,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
|
|
||||||
class CollectionItemQuerySet(models.QuerySet):
|
|
||||||
"""QuerySet for model CollectionItem."""
|
|
||||||
|
|
||||||
def by_collection(self, collection_id):
|
|
||||||
"""Filter by collection id"""
|
|
||||||
return self.filter(collection=collection_id)
|
|
||||||
|
|
||||||
|
|
||||||
class CollectionItem(ProjectBaseMixin):
|
|
||||||
"""CollectionItem model."""
|
|
||||||
collection = models.ForeignKey(
|
|
||||||
Collection, verbose_name=_('collection'), on_delete=models.CASCADE)
|
|
||||||
content_type = models.ForeignKey(ContentType, default=None,
|
|
||||||
null=True, blank=True, on_delete=models.CASCADE)
|
|
||||||
object_id = models.PositiveIntegerField(default=None, null=True, blank=True)
|
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
|
||||||
|
|
||||||
objects = CollectionItemQuerySet.as_manager()
|
|
||||||
|
|
||||||
|
|
||||||
class GuideQuerySet(models.QuerySet):
|
class GuideQuerySet(models.QuerySet):
|
||||||
"""QuerySet for Guide."""
|
"""QuerySet for Guide."""
|
||||||
|
|
||||||
|
|
@ -109,7 +95,9 @@ class GuideQuerySet(models.QuerySet):
|
||||||
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
"""Guide model."""
|
"""Guide model."""
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
'self', verbose_name=_('parent'), on_delete=models.CASCADE)
|
'self', verbose_name=_('parent'), on_delete=models.CASCADE,
|
||||||
|
null=True, blank=True, default=None
|
||||||
|
)
|
||||||
advertorials = JSONField(
|
advertorials = JSONField(
|
||||||
_('advertorials'), null=True, blank=True,
|
_('advertorials'), null=True, blank=True,
|
||||||
default=None, help_text='{"key":"value"}')
|
default=None, help_text='{"key":"value"}')
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,32 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
from gallery import models as gallery_models
|
|
||||||
from location import models as location_models
|
from location import models as location_models
|
||||||
|
|
||||||
|
|
||||||
class CollectionSerializer(serializers.ModelSerializer):
|
class CollectionBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Collection serializer"""
|
"""Collection base serializer"""
|
||||||
# RESPONSE
|
# RESPONSE
|
||||||
image_url = serializers.ImageField(source='image.image')
|
description_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Collection
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'description_translated',
|
||||||
|
'image_url',
|
||||||
|
'slug',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionSerializer(CollectionBaseSerializer):
|
||||||
|
"""Collection serializer"""
|
||||||
# COMMON
|
# COMMON
|
||||||
block_size = serializers.JSONField()
|
block_size = serializers.JSONField()
|
||||||
is_publish = serializers.BooleanField()
|
is_publish = serializers.BooleanField()
|
||||||
on_top = serializers.BooleanField()
|
on_top = serializers.BooleanField()
|
||||||
|
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
|
||||||
|
|
||||||
# REQUEST
|
# REQUEST
|
||||||
start = serializers.DateTimeField(write_only=True)
|
start = serializers.DateTimeField(write_only=True)
|
||||||
|
|
@ -21,19 +34,12 @@ class CollectionSerializer(serializers.ModelSerializer):
|
||||||
country = serializers.PrimaryKeyRelatedField(
|
country = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=location_models.Country.objects.all(),
|
queryset=location_models.Country.objects.all(),
|
||||||
write_only=True)
|
write_only=True)
|
||||||
image = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=gallery_models.Image.objects.all(),
|
|
||||||
write_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Collection
|
model = models.Collection
|
||||||
fields = [
|
fields = CollectionBaseSerializer.Meta.fields + [
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
'image',
|
|
||||||
'image_url',
|
|
||||||
'is_publish',
|
'is_publish',
|
||||||
'on_top',
|
'on_top',
|
||||||
'country',
|
'country',
|
||||||
|
|
@ -41,18 +47,6 @@ class CollectionSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CollectionItemSerializer(serializers.ModelSerializer):
|
|
||||||
"""CollectionItem serializer"""
|
|
||||||
class Meta:
|
|
||||||
model = models.CollectionItem
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'collection',
|
|
||||||
'content_type',
|
|
||||||
'object_id',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class GuideSerializer(serializers.ModelSerializer):
|
class GuideSerializer(serializers.ModelSerializer):
|
||||||
"""Guide serializer"""
|
"""Guide serializer"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,112 @@
|
||||||
from django.test import TestCase
|
import json
|
||||||
|
import pytz
|
||||||
|
from datetime import datetime
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
# Create your tests here.
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from collection.models import Collection, Guide
|
||||||
|
from establishment.models import Establishment, EstablishmentType
|
||||||
|
from location.models import Country
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.username = 'sedragurda'
|
||||||
|
self.password = 'sedragurdaredips19'
|
||||||
|
self.email = 'sedragurda@desoz.com'
|
||||||
|
self.newsletter = True
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username=self.username, email=self.email, password=self.password)
|
||||||
|
# get tokens
|
||||||
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('refresh_token'),
|
||||||
|
'country_code': 'en'})
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionListTests(BaseTestCase):
|
||||||
|
def test_collection_list_Read(self):
|
||||||
|
response = self.client.get('/api/web/collections/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionDetailTests(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# country = Country.objects.first()
|
||||||
|
# if not country:
|
||||||
|
country = Country.objects.create(
|
||||||
|
name=json.dumps({"en-GB": "Test country"}),
|
||||||
|
code="en"
|
||||||
|
)
|
||||||
|
country.save()
|
||||||
|
|
||||||
|
self.collection = Collection.objects.create(
|
||||||
|
name='Test collection',
|
||||||
|
is_publish=True,
|
||||||
|
start=datetime.now(pytz.utc),
|
||||||
|
end=datetime.now(pytz.utc),
|
||||||
|
country=country,
|
||||||
|
slug='test-collection-slug',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.collection.save()
|
||||||
|
|
||||||
|
def test_collection_detail_Read(self):
|
||||||
|
response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en',
|
||||||
|
format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionGuideTests(CollectionDetailTests):
|
||||||
|
|
||||||
|
def test_guide_list_Read(self):
|
||||||
|
response = self.client.get('/api/web/collections/guides/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionGuideDetailTests(CollectionDetailTests):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.guide = Guide.objects.create(
|
||||||
|
collection=self.collection,
|
||||||
|
start=datetime.now(pytz.utc),
|
||||||
|
end=datetime.now(pytz.utc)
|
||||||
|
)
|
||||||
|
self.guide.save()
|
||||||
|
|
||||||
|
def test_guide_detail_Read(self):
|
||||||
|
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionWebHomeTests(CollectionDetailTests):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
|
||||||
|
|
||||||
|
for i in range(1, 5):
|
||||||
|
setattr(self, f"establishment{i}",
|
||||||
|
Establishment.objects.create(
|
||||||
|
name=f"Test establishment {i}",
|
||||||
|
establishment_type_id=self.establishment_type.id,
|
||||||
|
is_publish=True,
|
||||||
|
slug=f"test-establishment-{i}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
getattr(self, f"establishment{i}").collections.add(self.collection)
|
||||||
|
|
||||||
|
def test_collection_list_filter(self):
|
||||||
|
response = self.client.get('/api/web/collections/?country_code=en', format='json')
|
||||||
|
data = response.json()
|
||||||
|
self.assertIn('count', data)
|
||||||
|
self.assertGreater(data['count'], 0)
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ from collection.views import common as views
|
||||||
app_name = 'collection'
|
app_name = 'collection'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.CollectionListView.as_view(), name='list'),
|
path('', views.CollectionHomePageView.as_view(), name='list'),
|
||||||
path('<int:pk>/', views.CollectionRetrieveView.as_view(), name='detail'),
|
path('<slug:slug>/', views.CollectionDetailView.as_view(), name='detail'),
|
||||||
|
path('<slug:slug>/establishments/', views.CollectionEstablishmentListView.as_view(),
|
||||||
path('items/', views.CollectionItemListView.as_view(), name='collection-items-list'),
|
name='detail'),
|
||||||
path('items/<int:pk>/', views.CollectionItemRetrieveView.as_view(),
|
|
||||||
name='collection-items-detail'),
|
|
||||||
|
|
||||||
path('guides/', views.GuideListView.as_view(), name='guides-list'),
|
path('guides/', views.GuideListView.as_view(), name='guides-list'),
|
||||||
path('guides/<int:pk>/', views.GuideRetrieveView.as_view(), name='guides-detail'),
|
path('guides/<int:pk>/', views.GuideRetrieveView.as_view(), name='guides-detail'),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ from rest_framework import generics
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
|
from utils.pagination import ProjectPageNumberPagination
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from establishment.serializers import EstablishmentBaseSerializer
|
||||||
from collection.serializers import common as serializers
|
from collection.serializers import common as serializers
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,13 +12,14 @@ from collection.serializers import common as serializers
|
||||||
class CollectionViewMixin(generics.GenericAPIView):
|
class CollectionViewMixin(generics.GenericAPIView):
|
||||||
"""Mixin for Collection view"""
|
"""Mixin for Collection view"""
|
||||||
model = models.Collection
|
model = models.Collection
|
||||||
queryset = models.Collection.objects.all()
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
serializer_class = serializers.CollectionSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
class CollectionItemViewMixin(generics.GenericAPIView):
|
"""Override get_queryset method."""
|
||||||
"""Mixin for CollectionItem view"""
|
return models.Collection.objects.published() \
|
||||||
model = models.CollectionItem
|
.by_country_code(code=self.request.country_code) \
|
||||||
queryset = models.CollectionItem.objects.all()
|
.order_by('-on_top', '-modified')
|
||||||
|
|
||||||
|
|
||||||
class GuideViewMixin(generics.GenericAPIView):
|
class GuideViewMixin(generics.GenericAPIView):
|
||||||
|
|
@ -27,35 +31,42 @@ class GuideViewMixin(generics.GenericAPIView):
|
||||||
# Views
|
# Views
|
||||||
# Collections
|
# Collections
|
||||||
class CollectionListView(CollectionViewMixin, generics.ListAPIView):
|
class CollectionListView(CollectionViewMixin, generics.ListAPIView):
|
||||||
"""List Collection view"""
|
"""List Collection view."""
|
||||||
pagination_class = None
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
|
||||||
serializer_class = serializers.CollectionSerializer
|
class CollectionHomePageView(CollectionListView):
|
||||||
|
"""Collection list view for home page."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset."""
|
||||||
return models.Collection.objects.published()\
|
return super(CollectionHomePageView, self).get_queryset() \
|
||||||
.by_country_code(code=self.request.country_code)\
|
.filter_all_related_gt(3)
|
||||||
.order_by('-on_top', '-created')
|
|
||||||
|
|
||||||
|
|
||||||
class CollectionRetrieveView(CollectionViewMixin, generics.RetrieveAPIView):
|
class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView):
|
||||||
"""Retrieve Collection view"""
|
"""Retrieve detail of Collection instance."""
|
||||||
permission_classes = (permissions.AllowAny,)
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.CollectionSerializer
|
serializer_class = serializers.CollectionBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
# CollectionItem
|
class CollectionEstablishmentListView(CollectionListView):
|
||||||
class CollectionItemListView(CollectionItemViewMixin, generics.ListAPIView):
|
"""Retrieve list of establishment for collection."""
|
||||||
"""List CollectionItem view"""
|
lookup_field = 'slug'
|
||||||
permission_classes = (permissions.AllowAny,)
|
pagination_class = ProjectPageNumberPagination
|
||||||
serializer_class = serializers.CollectionItemSerializer
|
serializer_class = EstablishmentBaseSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Override get_queryset method.
|
||||||
|
"""
|
||||||
|
queryset = super(CollectionEstablishmentListView, self).get_queryset()
|
||||||
|
# Perform the lookup filtering.
|
||||||
|
collection = get_object_or_404(queryset, slug=self.kwargs['slug'])
|
||||||
|
|
||||||
class CollectionItemRetrieveView(CollectionItemViewMixin, generics.RetrieveAPIView):
|
# May raise a permission denied
|
||||||
"""Retrieve CollectionItem view"""
|
self.check_object_permissions(self.request, collection)
|
||||||
permission_classes = (permissions.AllowAny,)
|
|
||||||
serializer_class = serializers.CollectionItemSerializer
|
return collection.establishments.all()
|
||||||
|
|
||||||
|
|
||||||
# Guide
|
# Guide
|
||||||
|
|
|
||||||
20
apps/comment/migrations/0002_comment_language.py
Normal file
20
apps/comment/migrations/0002_comment_language.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-10 11:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0003_auto_20190901_1032'),
|
||||||
|
('comment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comment',
|
||||||
|
name='language',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='translation.Language', verbose_name='Locale'),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
apps/comment/migrations/0003_auto_20191015_0704.py
Normal file
24
apps/comment/migrations/0003_auto_20191015_0704.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-15 07:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0012_data_migrate'),
|
||||||
|
('comment', '0002_comment_language'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='comment',
|
||||||
|
name='language',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comment',
|
||||||
|
name='country',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -6,6 +6,8 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from utils.models import ProjectBaseMixin
|
from utils.models import ProjectBaseMixin
|
||||||
from utils.querysets import ContentTypeQuerySetMixin
|
from utils.querysets import ContentTypeQuerySetMixin
|
||||||
|
from translation.models import Language
|
||||||
|
from location.models import Country
|
||||||
|
|
||||||
|
|
||||||
class CommentQuerySet(ContentTypeQuerySetMixin):
|
class CommentQuerySet(ContentTypeQuerySetMixin):
|
||||||
|
|
@ -41,6 +43,8 @@ class Comment(ProjectBaseMixin):
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
objects = CommentQuerySet.as_manager()
|
objects = CommentQuerySet.as_manager()
|
||||||
|
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||||
|
on_delete=models.SET_NULL, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
|
|
|
||||||
9
apps/comment/serializers/back.py
Normal file
9
apps/comment/serializers/back.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
"""Comment app common serializers."""
|
||||||
|
from comment import models
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Comment
|
||||||
|
fields = ('id', 'text', 'mark', 'user')
|
||||||
|
|
@ -1 +1,107 @@
|
||||||
# Create your tests here.
|
from utils.tests.tests_permissions import BasePermissionTests
|
||||||
|
from rest_framework import status
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
from account.models import Role, User, UserRole
|
||||||
|
from comment.models import Comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.role = Role.objects.create(
|
||||||
|
role=2,
|
||||||
|
country=self.country_ru
|
||||||
|
)
|
||||||
|
self.role.save()
|
||||||
|
|
||||||
|
self.moderator = User.objects.create_user(username='moderator',
|
||||||
|
email='moderator@mail.com',
|
||||||
|
password='passwordmoderator')
|
||||||
|
|
||||||
|
self.userRole = UserRole.objects.create(
|
||||||
|
user=self.moderator,
|
||||||
|
role=self.role
|
||||||
|
)
|
||||||
|
self.userRole.save()
|
||||||
|
|
||||||
|
content_type = ContentType.objects.get(app_label='location', model='country')
|
||||||
|
|
||||||
|
self.user_test = get_tokens_for_user()
|
||||||
|
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
||||||
|
user=self.user_test["user"],
|
||||||
|
object_id= self.country_ru.pk,
|
||||||
|
content_type_id=content_type.id,
|
||||||
|
country=self.country_ru
|
||||||
|
)
|
||||||
|
self.comment.save()
|
||||||
|
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
||||||
|
|
||||||
|
|
||||||
|
def test_put_moderator(self):
|
||||||
|
tokens = User.create_jwt_tokens(self.moderator)
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('access_token')})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"id": self.comment.id,
|
||||||
|
"text": "test text moderator",
|
||||||
|
"mark": 1,
|
||||||
|
"user": self.moderator.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
response = self.client.get(self.url, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
def test_put_other_user(self):
|
||||||
|
other_user = User.objects.create_user(username='test',
|
||||||
|
email='test@mail.com',
|
||||||
|
password='passwordtest')
|
||||||
|
|
||||||
|
tokens = User.create_jwt_tokens(other_user)
|
||||||
|
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('access_token')})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"id": self.comment.id,
|
||||||
|
"text": "test text moderator",
|
||||||
|
"mark": 1,
|
||||||
|
"user": other_user.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_put_super_user(self):
|
||||||
|
super_user = User.objects.create_user(username='super',
|
||||||
|
email='super@mail.com',
|
||||||
|
password='passwordtestsuper',
|
||||||
|
is_superuser=True)
|
||||||
|
|
||||||
|
tokens = User.create_jwt_tokens(super_user)
|
||||||
|
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('access_token')})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"id": self.comment.id,
|
||||||
|
"text": "test text moderator",
|
||||||
|
"mark": 1,
|
||||||
|
"user": super_user.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
11
apps/comment/urls/back.py
Normal file
11
apps/comment/urls/back.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""Back comment URLs"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from comment.views import back as views
|
||||||
|
|
||||||
|
app_name = 'comment'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.CommentLstView.as_view(), name='comment-list-create'),
|
||||||
|
path('<int:id>/', views.CommentRUDView.as_view(), name='comment-crud'),
|
||||||
|
]
|
||||||
19
apps/comment/views/back.py
Normal file
19
apps/comment/views/back.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
from comment.serializers import back as serializers
|
||||||
|
from comment import models
|
||||||
|
from utils.permissions import IsCommentModerator, IsCountryAdmin
|
||||||
|
|
||||||
|
|
||||||
|
class CommentLstView(generics.ListCreateAPIView):
|
||||||
|
"""Comment list create view."""
|
||||||
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
|
queryset = models.Comment.objects.all()
|
||||||
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
|
||||||
|
|
||||||
|
|
||||||
|
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Comment RUD view."""
|
||||||
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
|
queryset = models.Comment.objects.all()
|
||||||
|
permission_classes = [IsCountryAdmin|IsCommentModerator]
|
||||||
|
lookup_field = 'id'
|
||||||
|
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from main.models import Award, MetaDataContent
|
from main.models import Award
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,11 +24,6 @@ class AwardInline(GenericTabularInline):
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class MetaDataContentInline(GenericTabularInline):
|
|
||||||
model = MetaDataContent
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ContactPhoneInline(admin.TabularInline):
|
class ContactPhoneInline(admin.TabularInline):
|
||||||
"""Contact phone inline admin."""
|
"""Contact phone inline admin."""
|
||||||
model = models.ContactPhone
|
model = models.ContactPhone
|
||||||
|
|
@ -56,8 +51,7 @@ class EstablishmentAdmin(admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
list_display = ['id', '__str__', 'image_tag', ]
|
list_display = ['id', '__str__', 'image_tag', ]
|
||||||
inlines = [
|
inlines = [
|
||||||
AwardInline, MetaDataContentInline,
|
AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||||
ContactPhoneInline, ContactEmailInline,
|
|
||||||
ReviewInline, CommentInline]
|
ReviewInline, CommentInline]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
||||||
award_id = filters.NumberFilter(field_name='awards__id',)
|
award_id = filters.NumberFilter(field_name='awards__id',)
|
||||||
search = filters.CharFilter(method='search_text')
|
search = filters.CharFilter(method='search_text')
|
||||||
|
est_type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES,
|
||||||
|
method='by_type')
|
||||||
|
est_subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES,
|
||||||
|
method='by_subtype')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -19,6 +23,8 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
'tag_id',
|
'tag_id',
|
||||||
'award_id',
|
'award_id',
|
||||||
'search',
|
'search',
|
||||||
|
'est_type',
|
||||||
|
'est_subtype',
|
||||||
)
|
)
|
||||||
|
|
||||||
def search_text(self, queryset, name, value):
|
def search_text(self, queryset, name, value):
|
||||||
|
|
@ -26,3 +32,23 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
if value not in EMPTY_VALUES:
|
if value not in EMPTY_VALUES:
|
||||||
return queryset.search(value, locale=self.request.locale)
|
return queryset.search(value, locale=self.request.locale)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def by_type(self, queryset, name, value):
|
||||||
|
return queryset.by_type(value)
|
||||||
|
|
||||||
|
def by_subtype(self, queryset, name, value):
|
||||||
|
return queryset.by_subtype(value)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentTypeTagFilter(filters.FilterSet):
|
||||||
|
"""Establishment tag filter set."""
|
||||||
|
|
||||||
|
type_id = filters.NumberFilter(field_name='id')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.EstablishmentType
|
||||||
|
fields = (
|
||||||
|
'type_id',
|
||||||
|
)
|
||||||
|
|
|
||||||
23
apps/establishment/migrations/0020_auto_20190916_1532.py
Normal file
23
apps/establishment/migrations/0020_auto_20190916_1532.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-16 15:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0019_establishment_is_publish'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='guestonline_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='guestonline id'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='lastable_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='lastable id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 08:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('collection', '0009_delete_collectionitem'),
|
||||||
|
('establishment', '0023_merge_20190919_1136'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='collections',
|
||||||
|
field=models.ManyToManyField(related_name='establishments', to='collection.Collection', verbose_name='Collections'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0025_merge_20190920_1012.py
Normal file
14
apps/establishment/migrations/0025_merge_20190920_1012.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 10:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0024_establishment_collections'),
|
||||||
|
('establishment', '0024_merge_20190919_1456'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 10:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0025_merge_20190920_1012'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='preview_image_url',
|
||||||
|
field=models.URLField(blank=True, default=None, null=True, verbose_name='Preview image URL path'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0027_auto_20190920_1120.py
Normal file
18
apps/establishment/migrations/0027_auto_20190920_1120.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 11:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0026_establishment_preview_image_url'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='collections',
|
||||||
|
field=models.ManyToManyField(blank=True, default=None, null=True, related_name='establishments', to='collection.Collection', verbose_name='Collections'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0028_auto_20190920_1205.py
Normal file
18
apps/establishment/migrations/0028_auto_20190920_1205.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-20 12:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0027_auto_20190920_1120'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='collections',
|
||||||
|
field=models.ManyToManyField(blank=True, default=None, related_name='establishments', to='collection.Collection', verbose_name='Collections'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-23 09:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0028_auto_20190920_1205'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='name_transliterated',
|
||||||
|
field=models.CharField(default='', max_length=255, verbose_name='Transliterated name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0030_auto_20190923_1340.py
Normal file
18
apps/establishment/migrations/0030_auto_20190923_1340.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-23 13:40
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0029_establishment_name_transliterated'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='establishment',
|
||||||
|
old_name='name_transliterated',
|
||||||
|
new_name='name_translated',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0031_establishment_slug.py
Normal file
18
apps/establishment/migrations/0031_establishment_slug.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-09-24 08:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0030_auto_20190923_1340'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(null=True, unique=True, verbose_name='Establishment slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-09 07:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0031_establishment_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentTag',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'establishment tag',
|
||||||
|
'verbose_name_plural': 'establishment tags',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentTypeTagCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('establishment_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'establishment type tag categories',
|
||||||
|
'verbose_name_plural': 'establishment type tag categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0032_merge_20191001_1530.py
Normal file
14
apps/establishment/migrations/0032_merge_20191001_1530.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-01 15:30
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0020_auto_20190916_1532'),
|
||||||
|
('establishment', '0031_establishment_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-03 10:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
replaces = [('establishment', '0033_auto_20191003_0943'), ('establishment', '0034_auto_20191003_1036')]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0032_merge_20191001_1530'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='lastable_id',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='lastable_id',
|
||||||
|
field=models.TextField(blank=True, default=None, null=True, unique=True, verbose_name='lastable id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
30
apps/establishment/migrations/0033_auto_20191009_0715.py
Normal file
30
apps/establishment/migrations/0033_auto_20191009_0715.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-09 07:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0001_initial'),
|
||||||
|
('establishment', '0032_establishmenttag_establishmenttypetagcategory'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttypetagcategory',
|
||||||
|
name='tag_category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttag',
|
||||||
|
name='establishment',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='establishment.Establishment', verbose_name='establishment'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttag',
|
||||||
|
name='tag',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.Tag', verbose_name='tag'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0034_merge_20191009_1457.py
Normal file
14
apps/establishment/migrations/0034_merge_20191009_1457.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-09 14:57
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0033_auto_20191009_0715'),
|
||||||
|
('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-11 10:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0002_auto_20191009_1408'),
|
||||||
|
('establishment', '0034_merge_20191009_1457'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentSubTypeTagCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('establishment_subtype', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentSubType', verbose_name='establishment subtype')),
|
||||||
|
('tag_category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_subtype_tag_categories', to='tag.TagCategory', verbose_name='tag category')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'establishment subtype tag categories',
|
||||||
|
'verbose_name_plural': 'establishment subtype tag categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0036_auto_20191011_1356.py
Normal file
18
apps/establishment/migrations/0036_auto_20191011_1356.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-11 13:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0035_establishmentsubtypetagcategory'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='establishment_subtypes',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='subtype_establishment', to='establishment.EstablishmentSubType', verbose_name='subtype'),
|
||||||
|
),
|
||||||
|
]
|
||||||
54
apps/establishment/migrations/0037_auto_20191015_1404.py
Normal file
54
apps/establishment/migrations/0037_auto_20191015_1404.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-15 14:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0002_auto_20191009_1408'),
|
||||||
|
('establishment', '0036_auto_20191011_1356'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='establishmenttag',
|
||||||
|
name='establishment',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='establishmenttag',
|
||||||
|
name='tag',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='establishmenttypetagcategory',
|
||||||
|
name='establishment_type',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='establishmenttypetagcategory',
|
||||||
|
name='tag_category',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='tags',
|
||||||
|
field=models.ManyToManyField(related_name='establishments', to='tag.Tag', verbose_name='Tag'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='tag_categories',
|
||||||
|
field=models.ManyToManyField(related_name='establishment_subtypes', to='tag.TagCategory', verbose_name='Tag'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='tag_categories',
|
||||||
|
field=models.ManyToManyField(related_name='establishment_types', to='tag.TagCategory', verbose_name='Tag'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='EstablishmentSubTypeTagCategory',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='EstablishmentTag',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='EstablishmentTypeTagCategory',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-16 11:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_establishment_type(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
EstablishmentType = apps.get_model('establishment', 'EstablishmentType')
|
||||||
|
for n, et in enumerate(EstablishmentType.objects.all()):
|
||||||
|
et.index_name = f'Type {n}'
|
||||||
|
et.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0037_auto_20191015_1404'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_establishment_type, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(choices=[('restaurant', 'Restaurant'), ('artisan', 'Artisan'),
|
||||||
|
('producer', 'Producer')], db_index=True, max_length=50,
|
||||||
|
unique=True, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-18 13:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_establishment_subtype(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType')
|
||||||
|
for n, et in enumerate(EstablishmentSubType.objects.all()):
|
||||||
|
et.index_name = f'Type {n}'
|
||||||
|
et.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0038_establishmenttype_index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_establishment_subtype, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(choices=[('winery', 'Winery'), ], db_index=True, max_length=50,
|
||||||
|
unique=True, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
"""Establishment models."""
|
"""Establishment models."""
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from django.contrib.gis.db.models.functions import Distance
|
|
||||||
|
|
||||||
from django.contrib.gis.measure import Distance as DistanceMeasure
|
from django.conf import settings
|
||||||
from django.contrib.gis.geos import Point
|
|
||||||
from django.contrib.contenttypes import fields as generic
|
from django.contrib.contenttypes import fields as generic
|
||||||
|
from django.contrib.gis.db.models.functions import Distance
|
||||||
|
from django.contrib.gis.geos import Point
|
||||||
|
from django.contrib.gis.measure import Distance as DistanceMeasure
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
||||||
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
|
||||||
|
|
||||||
from location.models import Address
|
|
||||||
from collection.models import Collection
|
from collection.models import Collection
|
||||||
|
from location.models import Address
|
||||||
|
from main.models import Award
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes)
|
TranslatedFieldsMixin, BaseAttributes)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,9 +27,26 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
|
|
||||||
STR_FIELD_NAME = 'name'
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
RESTAURANT = 'restaurant'
|
||||||
|
ARTISAN = 'artisan'
|
||||||
|
PRODUCER = 'producer'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(RESTAURANT, _('Restaurant')),
|
||||||
|
(ARTISAN, _('Artisan')),
|
||||||
|
(PRODUCER, _('Producer')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
|
verbose_name=_('Index name'))
|
||||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
|
related_name='establishment_types',
|
||||||
|
verbose_name=_('Tag'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -48,11 +68,24 @@ class EstablishmentSubTypeManager(models.Manager):
|
||||||
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
"""Establishment type model."""
|
"""Establishment type model."""
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
WINERY = 'winery'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(WINERY, _('Winery')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
|
verbose_name=_('Index name'))
|
||||||
establishment_type = models.ForeignKey(EstablishmentType,
|
establishment_type = models.ForeignKey(EstablishmentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('Type'))
|
verbose_name=_('Type'))
|
||||||
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
|
related_name='establishment_subtypes',
|
||||||
|
verbose_name=_('Tag'))
|
||||||
|
|
||||||
objects = EstablishmentSubTypeManager()
|
objects = EstablishmentSubTypeManager()
|
||||||
|
|
||||||
|
|
@ -70,6 +103,20 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
class EstablishmentQuerySet(models.QuerySet):
|
class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""Extended queryset for Establishment model."""
|
"""Extended queryset for Establishment model."""
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
"""Return qs with related objects."""
|
||||||
|
return self.select_related('address', 'establishment_type').\
|
||||||
|
prefetch_related('tags')
|
||||||
|
|
||||||
|
def with_extended_related(self):
|
||||||
|
return self.select_related('establishment_type').\
|
||||||
|
prefetch_related('establishment_subtypes', 'awards', 'schedule',
|
||||||
|
'phones').\
|
||||||
|
prefetch_actual_employees()
|
||||||
|
|
||||||
|
def with_type_related(self):
|
||||||
|
return self.prefetch_related('establishment_subtypes')
|
||||||
|
|
||||||
def search(self, value, locale=None):
|
def search(self, value, locale=None):
|
||||||
"""Search text in JSON fields."""
|
"""Search text in JSON fields."""
|
||||||
if locale is not None:
|
if locale is not None:
|
||||||
|
|
@ -81,6 +128,16 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
|
# def es_search(self, value, locale=None):
|
||||||
|
# """Search text via ElasticSearch."""
|
||||||
|
# from search_indexes.documents import EstablishmentDocument
|
||||||
|
# search = EstablishmentDocument.search().filter(
|
||||||
|
# Elastic_Q('match', name=value) |
|
||||||
|
# Elastic_Q('match', **{f'description.{locale}': value})
|
||||||
|
# ).execute()
|
||||||
|
# ids = [result.meta.id for result in search]
|
||||||
|
# return self.filter(id__in=ids)
|
||||||
|
|
||||||
def by_country_code(self, code):
|
def by_country_code(self, code):
|
||||||
"""Return establishments by country code"""
|
"""Return establishments by country code"""
|
||||||
return self.filter(address__city__country__code=code)
|
return self.filter(address__city__country__code=code)
|
||||||
|
|
@ -91,29 +148,20 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""
|
"""
|
||||||
return self.filter(is_publish=True)
|
return self.filter(is_publish=True)
|
||||||
|
|
||||||
def annotate_distance(self, point: Point):
|
def has_published_reviews(self):
|
||||||
|
"""
|
||||||
|
Return QuerySet establishments with published reviews.
|
||||||
|
"""
|
||||||
|
return self.filter(reviews__status=Review.READY,)
|
||||||
|
|
||||||
|
def annotate_distance(self, point: Point = None):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with annotated field - distance
|
Return QuerySet with annotated field - distance
|
||||||
Description:
|
Description:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.annotate(distance=models.Value(
|
return self.annotate(distance=Distance('address__coordinates', point,
|
||||||
DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m,
|
srid=settings.GEO_DEFAULT_SRID))
|
||||||
output_field=models.FloatField()))
|
|
||||||
|
|
||||||
def annotate_distance_mark(self):
|
|
||||||
"""
|
|
||||||
Return QuerySet with annotated field - distance_mark.
|
|
||||||
Required fields: distance.
|
|
||||||
Description:
|
|
||||||
If the radius of the establishments in QuerySet does not exceed 500 meters,
|
|
||||||
then distance_mark is set to 0.6, otherwise 0.
|
|
||||||
"""
|
|
||||||
return self.annotate(distance_mark=models.Case(
|
|
||||||
models.When(distance__lte=500,
|
|
||||||
then=0.6),
|
|
||||||
default=0,
|
|
||||||
output_field=models.FloatField()))
|
|
||||||
|
|
||||||
def annotate_intermediate_public_mark(self):
|
def annotate_intermediate_public_mark(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -122,72 +170,66 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
If establishments in collection POP and its mark is null, then
|
If establishments in collection POP and its mark is null, then
|
||||||
intermediate_mark is set to 10;
|
intermediate_mark is set to 10;
|
||||||
"""
|
"""
|
||||||
return self.annotate(intermediate_public_mark=models.Case(
|
return self.annotate(intermediate_public_mark=Case(
|
||||||
models.When(
|
When(
|
||||||
collections__collection__collection_type=Collection.POP,
|
collections__collection_type=Collection.POP,
|
||||||
public_mark__isnull=True,
|
public_mark__isnull=True,
|
||||||
then=10
|
then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK
|
||||||
),
|
),
|
||||||
default='public_mark',
|
default='public_mark',
|
||||||
output_field=models.FloatField()))
|
output_field=models.FloatField()))
|
||||||
|
|
||||||
def annotate_additional_mark(self, public_mark: float):
|
def annotate_mark_similarity(self, mark):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with annotated field - additional_mark.
|
Return a QuerySet with annotated field - mark_similarity
|
||||||
Required fields: intermediate_public_mark
|
|
||||||
Description:
|
Description:
|
||||||
IF
|
Similarity mark determined by comparison with compared establishment mark
|
||||||
establishments public_mark + 3 > compared establishment public_mark
|
|
||||||
OR
|
|
||||||
establishments public_mark - 3 > compared establishment public_mark,
|
|
||||||
THEN
|
|
||||||
additional_mark is set to 0.4,
|
|
||||||
ELSE
|
|
||||||
set to 0.
|
|
||||||
"""
|
"""
|
||||||
return self.annotate(additional_mark=models.Case(
|
return self.annotate(mark_similarity=ExpressionWrapper(
|
||||||
models.When(
|
mark - F('intermediate_public_mark'),
|
||||||
models.Q(intermediate_public_mark__lte=public_mark + 3) |
|
output_field=models.FloatField()
|
||||||
models.Q(intermediate_public_mark__lte=public_mark - 3),
|
))
|
||||||
then=0.4),
|
|
||||||
default=0,
|
|
||||||
output_field=models.FloatField()))
|
|
||||||
|
|
||||||
def annotate_total_mark(self):
|
def similar(self, establishment_slug: str):
|
||||||
"""
|
|
||||||
Return QuerySet with annotated field - total_mark.
|
|
||||||
Required fields: distance_mark, additional_mark.
|
|
||||||
Fields
|
|
||||||
Description:
|
|
||||||
Annotated field is obtained by formula:
|
|
||||||
(distance + additional marks) * intermediate_public_mark.
|
|
||||||
"""
|
|
||||||
return self.annotate(
|
|
||||||
total_mark=(models.F('distance_mark') + models.F('additional_mark')) *
|
|
||||||
models.F('intermediate_public_mark'))
|
|
||||||
|
|
||||||
def similar(self, establishment_pk: int):
|
|
||||||
"""
|
"""
|
||||||
Return QuerySet with objects that similar to Establishment.
|
Return QuerySet with objects that similar to Establishment.
|
||||||
:param establishment_pk: integer
|
:param establishment_slug: str Establishment slug
|
||||||
"""
|
"""
|
||||||
establishment_qs = Establishment.objects.filter(pk=establishment_pk)
|
establishment_qs = self.filter(slug=establishment_slug,
|
||||||
|
public_mark__isnull=False)
|
||||||
if establishment_qs.exists():
|
if establishment_qs.exists():
|
||||||
establishment = establishment_qs.first()
|
establishment = establishment_qs.first()
|
||||||
return self.exclude(pk=establishment_pk) \
|
subquery_filter_by_distance = Subquery(
|
||||||
.filter(is_publish=True,
|
self.exclude(slug=establishment_slug)
|
||||||
image__isnull=False,
|
.filter(image_url__isnull=False, public_mark__gte=10)
|
||||||
reviews__isnull=False,
|
.has_published_reviews()
|
||||||
reviews__status=Review.READY,
|
.annotate_distance(point=establishment.location)
|
||||||
public_mark__gte=10) \
|
.order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
|
||||||
.annotate_distance(point=establishment.address.coordinates) \
|
.values('id')
|
||||||
.annotate_distance_mark() \
|
)
|
||||||
|
return self.filter(id__in=subquery_filter_by_distance) \
|
||||||
.annotate_intermediate_public_mark() \
|
.annotate_intermediate_public_mark() \
|
||||||
.annotate_additional_mark(public_mark=establishment.public_mark) \
|
.annotate_mark_similarity(mark=establishment.public_mark) \
|
||||||
.annotate_total_mark()
|
.order_by('mark_similarity') \
|
||||||
|
.distinct('mark_similarity', 'id')
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
|
def last_reviewed(self, point: Point):
|
||||||
|
"""
|
||||||
|
Return QuerySet with last reviewed establishments.
|
||||||
|
:param point: location Point object, needs to ordering
|
||||||
|
"""
|
||||||
|
subquery_filter_by_distance = Subquery(
|
||||||
|
self.filter(image_url__isnull=False, public_mark__gte=10)
|
||||||
|
.has_published_reviews()
|
||||||
|
.annotate_distance(point=point)
|
||||||
|
.order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
|
||||||
|
.values('id')
|
||||||
|
)
|
||||||
|
return self.filter(id__in=subquery_filter_by_distance) \
|
||||||
|
.order_by('-reviews__published_at')
|
||||||
|
|
||||||
def prefetch_actual_employees(self):
|
def prefetch_actual_employees(self):
|
||||||
"""Prefetch actual employees."""
|
"""Prefetch actual employees."""
|
||||||
return self.prefetch_related(
|
return self.prefetch_related(
|
||||||
|
|
@ -201,10 +243,10 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
favorite_establishments = []
|
favorite_establishments = []
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
favorite_establishments = user.favorites.by_content_type(app_label='establishment',
|
favorite_establishments = user.favorites.by_content_type(app_label='establishment',
|
||||||
model='establishment')\
|
model='establishment') \
|
||||||
.values_list('object_id', flat=True)
|
.values_list('object_id', flat=True)
|
||||||
return self.annotate(in_favorites=models.Case(
|
return self.annotate(in_favorites=Case(
|
||||||
models.When(
|
When(
|
||||||
id__in=favorite_establishments,
|
id__in=favorite_establishments,
|
||||||
then=True),
|
then=True),
|
||||||
default=False,
|
default=False,
|
||||||
|
|
@ -219,16 +261,41 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
:return: all establishments within the specified radius of specified point
|
:return: all establishments within the specified radius of specified point
|
||||||
:param unit: length unit e.g. m, km. Default is 'm'.
|
:param unit: length unit e.g. m, km. Default is 'm'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.gis.measure import Distance
|
|
||||||
kwargs = {unit: radius}
|
kwargs = {unit: radius}
|
||||||
return self.filter(address__coordinates__distance_lte=(center, Distance(**kwargs)))
|
return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
|
||||||
|
|
||||||
|
def artisans(self):
|
||||||
|
"""Return artisans."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN)
|
||||||
|
|
||||||
|
def producers(self):
|
||||||
|
"""Return producers."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER)
|
||||||
|
|
||||||
|
def restaurants(self):
|
||||||
|
"""Return restaurants."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT)
|
||||||
|
|
||||||
|
def wineries(self):
|
||||||
|
"""Return wineries."""
|
||||||
|
return self.producers().filter(
|
||||||
|
establishment_subtypes__index_name=EstablishmentSubType.WINERY)
|
||||||
|
|
||||||
|
def by_type(self, value):
|
||||||
|
"""Return QuerySet with type by value."""
|
||||||
|
return self.filter(establishment_type__index_name=value)
|
||||||
|
|
||||||
|
def by_subtype(self, value):
|
||||||
|
"""Return QuerySet with subtype by value."""
|
||||||
|
return self.filter(establishment_subtypes__index_name=value)
|
||||||
|
|
||||||
|
|
||||||
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=255, default='')
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
|
name_translated = models.CharField(_('Transliterated name'),
|
||||||
|
max_length=255, default='')
|
||||||
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"}')
|
||||||
|
|
@ -243,6 +310,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
verbose_name=_('type'))
|
verbose_name=_('type'))
|
||||||
establishment_subtypes = models.ManyToManyField(EstablishmentSubType,
|
establishment_subtypes = models.ManyToManyField(EstablishmentSubType,
|
||||||
|
blank=True,
|
||||||
related_name='subtype_establishment',
|
related_name='subtype_establishment',
|
||||||
verbose_name=_('subtype'))
|
verbose_name=_('subtype'))
|
||||||
address = models.ForeignKey(Address, blank=True, null=True, default=None,
|
address = models.ForeignKey(Address, blank=True, null=True, default=None,
|
||||||
|
|
@ -259,6 +327,10 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
verbose_name=_('Twitter URL'))
|
verbose_name=_('Twitter URL'))
|
||||||
lafourchette = models.URLField(blank=True, null=True, default=None,
|
lafourchette = models.URLField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Lafourchette URL'))
|
verbose_name=_('Lafourchette URL'))
|
||||||
|
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
|
||||||
|
null=True, default=None,)
|
||||||
|
lastable_id = models.TextField(blank=True, verbose_name=_('lastable id'), unique=True,
|
||||||
|
null=True, default=None,)
|
||||||
booking = models.URLField(blank=True, null=True, default=None,
|
booking = models.URLField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Booking URL'))
|
verbose_name=_('Booking URL'))
|
||||||
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
||||||
|
|
@ -269,13 +341,26 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
# help_text=_('Holidays closing date from'))
|
# help_text=_('Holidays closing date from'))
|
||||||
# holidays_to = models.DateTimeField(verbose_name=_('Holidays to'),
|
# holidays_to = models.DateTimeField(verbose_name=_('Holidays to'),
|
||||||
# help_text=_('Holidays closing date to'))
|
# help_text=_('Holidays closing date to'))
|
||||||
awards = generic.GenericRelation(to='main.Award')
|
|
||||||
tags = generic.GenericRelation(to='main.MetaDataContent')
|
|
||||||
reviews = generic.GenericRelation(to='review.Review')
|
|
||||||
comments = generic.GenericRelation(to='comment.Comment')
|
|
||||||
transportation = models.TextField(blank=True, null=True, default=None,
|
transportation = models.TextField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Transportation'))
|
verbose_name=_('Transportation'))
|
||||||
collections = generic.GenericRelation(to='collection.CollectionItem')
|
collections = models.ManyToManyField(to='collection.Collection',
|
||||||
|
related_name='establishments',
|
||||||
|
blank=True, default=None,
|
||||||
|
verbose_name=_('Collections'))
|
||||||
|
preview_image_url = models.URLField(verbose_name=_('Preview image URL path'),
|
||||||
|
blank=True, null=True, default=None)
|
||||||
|
slug = models.SlugField(unique=True, max_length=50, null=True,
|
||||||
|
verbose_name=_('Establishment slug'), editable=True)
|
||||||
|
|
||||||
|
awards = generic.GenericRelation(to='main.Award', related_query_name='establishment')
|
||||||
|
# todo: remove after data merge
|
||||||
|
# tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||||
|
tags = models.ManyToManyField('tag.Tag', related_name='establishments',
|
||||||
|
verbose_name=_('Tag'))
|
||||||
|
old_tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||||
|
reviews = generic.GenericRelation(to='review.Review')
|
||||||
|
comments = generic.GenericRelation(to='comment.Comment')
|
||||||
|
favorites = generic.GenericRelation(to='favorites.Favorites')
|
||||||
|
|
||||||
objects = EstablishmentQuerySet.as_manager()
|
objects = EstablishmentQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -303,12 +388,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
return country.low_price, country.high_price
|
return country.low_price, country.high_price
|
||||||
|
|
||||||
# todo: make via prefetch
|
# todo: make via prefetch
|
||||||
@property
|
# @property
|
||||||
def subtypes(self):
|
# def subtypes(self):
|
||||||
return EstablishmentSubType.objects.filter(
|
# return EstablishmentSubType.objects.filter(
|
||||||
subtype_establishment=self,
|
# subtype_establishment=self,
|
||||||
establishment_type=self.establishment_type,
|
# establishment_type=self.establishment_type,
|
||||||
establishment_type__use_subtypes=True)
|
# establishment_type__use_subtypes=True)
|
||||||
|
|
||||||
def set_establishment_type(self, establishment_type):
|
def set_establishment_type(self, establishment_type):
|
||||||
self.establishment_type = establishment_type
|
self.establishment_type = establishment_type
|
||||||
|
|
@ -320,6 +405,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
raise ValidationError('Establishment type of subtype does not match')
|
raise ValidationError('Establishment type of subtype does not match')
|
||||||
self.establishment_subtypes.add(establishment_subtype)
|
self.establishment_subtypes.add(establishment_subtype)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vintage_year(self):
|
||||||
|
last_review = self.reviews.by_status(Review.READY).last()
|
||||||
|
if last_review:
|
||||||
|
return last_review.vintage
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def best_price_menu(self):
|
def best_price_menu(self):
|
||||||
return 150
|
return 150
|
||||||
|
|
@ -333,6 +424,38 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
return [{'id': tag.metadata.id,
|
return [{'id': tag.metadata.id,
|
||||||
'label': tag.metadata.label} for tag in self.tags.all()]
|
'label': tag.metadata.label} for tag in self.tags.all()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_published_review(self):
|
||||||
|
"""Return last published review"""
|
||||||
|
return self.reviews.published()\
|
||||||
|
.order_by('-published_at').first()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self):
|
||||||
|
"""
|
||||||
|
Return Point object of establishment location
|
||||||
|
"""
|
||||||
|
return self.address.coordinates
|
||||||
|
|
||||||
|
@property
|
||||||
|
def the_most_recent_award(self):
|
||||||
|
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest(
|
||||||
|
field_name='vintage_year')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def country_id(self):
|
||||||
|
"""
|
||||||
|
Return Country id of establishment location
|
||||||
|
"""
|
||||||
|
return self.address.country_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def establishment_id(self):
|
||||||
|
"""
|
||||||
|
Return establishment id of establishment location
|
||||||
|
"""
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseAttributes, TranslatedFieldsMixin):
|
class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||||
"""Position model."""
|
"""Position model."""
|
||||||
|
|
@ -386,8 +509,8 @@ class Employee(BaseAttributes):
|
||||||
verbose_name=_('User'))
|
verbose_name=_('User'))
|
||||||
name = models.CharField(max_length=255, verbose_name=_('Last name'))
|
name = models.CharField(max_length=255, verbose_name=_('Last name'))
|
||||||
establishments = models.ManyToManyField(Establishment, related_name='employees',
|
establishments = models.ManyToManyField(Establishment, related_name='employees',
|
||||||
through=EstablishmentEmployee)
|
through=EstablishmentEmployee,)
|
||||||
awards = generic.GenericRelation(to='main.Award')
|
awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
|
||||||
tags = generic.GenericRelation(to='main.MetaDataContent')
|
tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -428,6 +551,7 @@ class ContactEmail(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.email}'
|
return f'{self.email}'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# class Wine(TranslatedFieldsMixin, models.Model):
|
# class Wine(TranslatedFieldsMixin, models.Model):
|
||||||
# """Wine model."""
|
# """Wine model."""
|
||||||
|
|
@ -466,6 +590,10 @@ class Plate(TranslatedFieldsMixin, models.Model):
|
||||||
menu = models.ForeignKey(
|
menu = models.ForeignKey(
|
||||||
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
|
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def establishment_id(self):
|
||||||
|
return self.menu.establishment.id
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('plate')
|
verbose_name = _('plate')
|
||||||
verbose_name_plural = _('plates')
|
verbose_name_plural = _('plates')
|
||||||
|
|
@ -501,3 +629,4 @@ class SocialNetwork(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import json
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from timetable.models import Timetable
|
|
||||||
from establishment.serializers import (
|
from establishment.serializers import (
|
||||||
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
|
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
|
||||||
ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer
|
ContactPhonesSerializer, SocialNetworkRelatedSerializers,
|
||||||
)
|
EstablishmentTypeBaseSerializer)
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
|
from utils.decorators import with_base_attributes
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
||||||
|
|
@ -20,6 +19,8 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
||||||
phones = ContactPhonesSerializer(read_only=True, many=True, )
|
phones = ContactPhonesSerializer(read_only=True, many=True, )
|
||||||
emails = ContactEmailsSerializer(read_only=True, many=True, )
|
emails = ContactEmailsSerializer(read_only=True, many=True, )
|
||||||
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
|
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
|
||||||
|
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50)
|
||||||
|
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
|
|
@ -34,7 +35,43 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
||||||
'type_id',
|
'type_id',
|
||||||
'type',
|
'type',
|
||||||
'socials',
|
'socials',
|
||||||
'image_url'
|
'image_url',
|
||||||
|
'slug',
|
||||||
|
# TODO: check in admin filters
|
||||||
|
'is_publish',
|
||||||
|
'guestonline_id',
|
||||||
|
'lastable_id',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
|
||||||
|
"""Establishment create serializer"""
|
||||||
|
|
||||||
|
type_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
source='establishment_type',
|
||||||
|
queryset=models.EstablishmentType.objects.all(), write_only=True
|
||||||
|
)
|
||||||
|
phones = ContactPhonesSerializer(read_only=False, many=True, )
|
||||||
|
emails = ContactEmailsSerializer(read_only=False, many=True, )
|
||||||
|
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
|
||||||
|
type = EstablishmentTypeBaseSerializer(source='establishment_type')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Establishment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'website',
|
||||||
|
'phones',
|
||||||
|
'emails',
|
||||||
|
'price_level',
|
||||||
|
'toque_number',
|
||||||
|
'type_id',
|
||||||
|
'type',
|
||||||
|
'socials',
|
||||||
|
'image_url',
|
||||||
|
# TODO: check in admin filters
|
||||||
|
'is_publish'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,13 +89,15 @@ class SocialNetworkSerializers(serializers.ModelSerializer):
|
||||||
|
|
||||||
class PlatesSerializers(PlateSerializer):
|
class PlatesSerializers(PlateSerializer):
|
||||||
"""Social network serializers."""
|
"""Social network serializers."""
|
||||||
name = serializers.JSONField()
|
|
||||||
currency_id = serializers.PrimaryKeyRelatedField(
|
currency_id = serializers.PrimaryKeyRelatedField(
|
||||||
source='currency',
|
source='currency',
|
||||||
queryset=Currency.objects.all(), write_only=True
|
queryset=Currency.objects.all(), write_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Plate
|
model = models.Plate
|
||||||
fields = PlateSerializer.Meta.fields + [
|
fields = PlateSerializer.Meta.fields + [
|
||||||
'name',
|
'name',
|
||||||
|
|
@ -89,7 +128,10 @@ class ContactEmailBackSerializers(PlateSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: test decorator
|
||||||
|
@with_base_attributes
|
||||||
class EmployeeBackSerializers(serializers.ModelSerializer):
|
class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
|
|
||||||
"""Social network serializers."""
|
"""Social network serializers."""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Employee
|
model = models.Employee
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
"""Establishment serializers."""
|
"""Establishment serializers."""
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from comment.serializers import common as comment_serializers
|
from comment.serializers import common as comment_serializers
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from favorites.models import Favorites
|
from favorites.models import Favorites
|
||||||
from location.serializers import AddressSerializer
|
from location.serializers import AddressBaseSerializer
|
||||||
from main.models import MetaDataContent
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
|
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
|
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 ProjectModelSerializer
|
||||||
|
from utils.serializers import TranslatedField
|
||||||
|
|
||||||
|
|
||||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -42,9 +45,9 @@ class SocialNetworkRelatedSerializers(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PlateSerializer(serializers.ModelSerializer):
|
class PlateSerializer(ProjectModelSerializer):
|
||||||
|
|
||||||
name_translated = serializers.CharField(allow_null=True, read_only=True)
|
name_translated = TranslatedField()
|
||||||
currency = CurrencySerializer(read_only=True)
|
currency = CurrencySerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -57,9 +60,8 @@ class PlateSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MenuSerializers(serializers.ModelSerializer):
|
class MenuSerializers(ProjectModelSerializer):
|
||||||
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
||||||
category = serializers.JSONField()
|
|
||||||
category_translated = serializers.CharField(read_only=True)
|
category_translated = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -73,9 +75,8 @@ class MenuSerializers(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MenuRUDSerializers(serializers.ModelSerializer):
|
class MenuRUDSerializers(ProjectModelSerializer):
|
||||||
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
||||||
category = serializers.JSONField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Menu
|
model = models.Menu
|
||||||
|
|
@ -87,30 +88,6 @@ class MenuRUDSerializers(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for EstablishmentType model."""
|
|
||||||
|
|
||||||
name_translated = serializers.CharField(allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
|
||||||
|
|
||||||
model = models.EstablishmentType
|
|
||||||
fields = ('id', 'name_translated')
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSubTypeSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for EstablishmentSubType models."""
|
|
||||||
|
|
||||||
name_translated = serializers.CharField(allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
|
||||||
|
|
||||||
model = models.EstablishmentSubType
|
|
||||||
fields = ('id', 'name_translated')
|
|
||||||
|
|
||||||
|
|
||||||
class ReviewSerializer(serializers.ModelSerializer):
|
class ReviewSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model Review."""
|
"""Serializer for model Review."""
|
||||||
text_translated = serializers.CharField(read_only=True)
|
text_translated = serializers.CharField(read_only=True)
|
||||||
|
|
@ -123,6 +100,45 @@ class ReviewSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for EstablishmentType model."""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.EstablishmentType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'name_translated',
|
||||||
|
'use_subtypes'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'name': {'write_only': True},
|
||||||
|
'use_subtypes': {'write_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for EstablishmentSubType models."""
|
||||||
|
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.EstablishmentSubType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'name_translated',
|
||||||
|
'establishment_type'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'name': {'write_only': True},
|
||||||
|
'establishment_type': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for actual employees."""
|
"""Serializer for actual employees."""
|
||||||
|
|
||||||
|
|
@ -139,13 +155,14 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
||||||
fields = ('id', 'name', 'position_translated', 'awards', 'priority')
|
fields = ('id', 'name', 'position_translated', 'awards', 'priority')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentBaseSerializer(serializers.ModelSerializer):
|
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
"""Base serializer for Establishment model."""
|
"""Base serializer for Establishment model."""
|
||||||
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
|
|
||||||
subtypes = EstablishmentSubTypeSerializer(many=True)
|
preview_image = serializers.URLField(source='preview_image_url')
|
||||||
address = AddressSerializer()
|
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
|
||||||
tags = MetaDataContentSerializer(many=True)
|
address = AddressBaseSerializer()
|
||||||
preview_image = serializers.SerializerMethodField()
|
in_favorites = serializers.BooleanField(allow_null=True)
|
||||||
|
tags = TagBaseSerializer(read_only=True, many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -154,62 +171,45 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
|
'name_translated',
|
||||||
'price_level',
|
'price_level',
|
||||||
'toque_number',
|
'toque_number',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
'type',
|
'slug',
|
||||||
'subtypes',
|
|
||||||
'preview_image',
|
'preview_image',
|
||||||
|
'in_favorites',
|
||||||
'address',
|
'address',
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_preview_image(self, obj):
|
|
||||||
"""Get preview image"""
|
|
||||||
return obj.get_full_image_url(request=self.context.get('request'),
|
|
||||||
thumbnail_key='establishment_preview')
|
|
||||||
|
|
||||||
|
class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
class EstablishmentListSerializer(EstablishmentBaseSerializer):
|
|
||||||
"""Serializer for Establishment model."""
|
"""Serializer for Establishment model."""
|
||||||
# Annotated fields
|
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
description_translated = TranslatedField()
|
||||||
"""Meta class."""
|
image = serializers.URLField(source='image_url')
|
||||||
|
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
model = models.Establishment
|
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||||
fields = EstablishmentBaseSerializer.Meta.fields + [
|
|
||||||
'in_favorites',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentDetailSerializer(EstablishmentListSerializer):
|
|
||||||
"""Serializer for Establishment model."""
|
|
||||||
description_translated = serializers.CharField(allow_null=True)
|
|
||||||
awards = AwardSerializer(many=True)
|
awards = AwardSerializer(many=True)
|
||||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
phones = ContactPhonesSerializer(read_only=True, many=True, )
|
phones = ContactPhonesSerializer(read_only=True, many=True)
|
||||||
emails = ContactEmailsSerializer(read_only=True, many=True, )
|
emails = ContactEmailsSerializer(read_only=True, many=True)
|
||||||
review = serializers.SerializerMethodField()
|
review = ReviewSerializer(source='last_published_review', allow_null=True)
|
||||||
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
||||||
many=True)
|
many=True)
|
||||||
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
|
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
|
||||||
preview_image = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
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)
|
||||||
|
vintage_year = serializers.ReadOnlyField()
|
||||||
|
|
||||||
in_favorites = serializers.SerializerMethodField()
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Establishment
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
fields = EstablishmentListSerializer.Meta.fields + [
|
|
||||||
'description_translated',
|
'description_translated',
|
||||||
'price_level',
|
|
||||||
'image',
|
'image',
|
||||||
|
'subtypes',
|
||||||
|
'type',
|
||||||
'awards',
|
'awards',
|
||||||
'schedule',
|
'schedule',
|
||||||
'website',
|
'website',
|
||||||
|
|
@ -225,23 +225,9 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer):
|
||||||
'best_price_menu',
|
'best_price_menu',
|
||||||
'best_price_carte',
|
'best_price_carte',
|
||||||
'transportation',
|
'transportation',
|
||||||
|
'vintage_year',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_review(self, obj):
|
|
||||||
"""Serializer method for getting last published review"""
|
|
||||||
return ReviewSerializer(obj.reviews.by_status(status=review_models.Review.READY)
|
|
||||||
.order_by('-published_at').first()).data
|
|
||||||
|
|
||||||
def get_in_favorites(self, obj):
|
|
||||||
"""Get in_favorites status flag"""
|
|
||||||
user = self.context.get('request').user
|
|
||||||
if user.is_authenticated:
|
|
||||||
return obj.id in user.favorites.by_content_type(app_label='establishment',
|
|
||||||
model='establishment')\
|
|
||||||
.values_list('object_id', flat=True)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
||||||
"""Create comment serializer"""
|
"""Create comment serializer"""
|
||||||
|
|
@ -262,10 +248,10 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
# Check establishment object
|
# Check establishment object
|
||||||
establishment_id = self.context.get('request').parser_context.get('kwargs').get('pk')
|
establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug')
|
||||||
establishment_qs = models.Establishment.objects.filter(id=establishment_id)
|
establishment_qs = models.Establishment.objects.filter(slug=establishment_slug)
|
||||||
if not establishment_qs.exists():
|
if not establishment_qs.exists():
|
||||||
return serializers.ValidationError()
|
raise serializers.ValidationError({'detail': _('Establishment not found.')})
|
||||||
attrs['establishment'] = establishment_qs.first()
|
attrs['establishment'] = establishment_qs.first()
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
@ -311,20 +297,22 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
# Check establishment object
|
# Check establishment object
|
||||||
establishment_id = self.context.get('request').parser_context.get('kwargs').get('pk')
|
establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug')
|
||||||
establishment_qs = models.Establishment.objects.filter(id=establishment_id)
|
establishment_qs = models.Establishment.objects.filter(slug=establishment_slug)
|
||||||
|
|
||||||
# Check establishment obj by pk from lookup_kwarg
|
# Check establishment obj by slug from lookup_kwarg
|
||||||
if not establishment_qs.exists():
|
if not establishment_qs.exists():
|
||||||
return serializers.ValidationError()
|
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||||
|
else:
|
||||||
|
establishment = establishment_qs.first()
|
||||||
|
|
||||||
# Check existence in favorites
|
# Check existence in favorites
|
||||||
if self.get_user().favorites.by_content_type(app_label='establishment',
|
if self.get_user().favorites.by_content_type(app_label='establishment',
|
||||||
model='establishment')\
|
model='establishment')\
|
||||||
.by_object_id(object_id=establishment_id).exists():
|
.by_object_id(object_id=establishment.id).exists():
|
||||||
raise utils_exceptions.FavoritesError()
|
raise utils_exceptions.FavoritesError()
|
||||||
|
|
||||||
attrs['establishment'] = establishment_qs.first()
|
attrs['establishment'] = establishment
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data, *args, **kwargs):
|
def create(self, validated_data, *args, **kwargs):
|
||||||
|
|
@ -335,17 +323,3 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
||||||
})
|
})
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTagListSerializer(serializers.ModelSerializer):
|
|
||||||
"""List establishment tag serializer."""
|
|
||||||
id = serializers.IntegerField(source='metadata.id')
|
|
||||||
label_translated = serializers.CharField(
|
|
||||||
source='metadata.label_translated', read_only=True, allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
|
||||||
model = MetaDataContent
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'label_translated',
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,106 @@
|
||||||
|
import json
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
from establishment.models import Employee
|
from main.models import Currency
|
||||||
|
from establishment.models import Establishment, EstablishmentType, Menu
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
from translation.models import Language
|
||||||
|
from account.models import Role, UserRole
|
||||||
|
from location.models import Country, Address, City, Region
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(APITestCase):
|
class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.username = 'sedragurda'
|
self.username = 'sedragurda'
|
||||||
self.password = 'sedragurdaredips19'
|
self.password = 'sedragurdaredips19'
|
||||||
self.email = 'sedragurda@desoz.com'
|
self.email = 'sedragurda@desoz.com'
|
||||||
self.newsletter = True
|
self.newsletter = True
|
||||||
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
|
self.user = User.objects.create_user(
|
||||||
|
username=self.username, email=self.email, password=self.password)
|
||||||
#get tokkens
|
#get tokkens
|
||||||
tokkens = User.create_jwt_tokens(self.user)
|
tokkens = User.create_jwt_tokens(self.user)
|
||||||
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokkens.get('access_token'),
|
||||||
'refresh_token': tokkens.get('refresh_token')})
|
'refresh_token': tokkens.get('refresh_token')})
|
||||||
|
|
||||||
|
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
|
||||||
|
|
||||||
|
# Create lang object
|
||||||
|
self.lang = Language.objects.get(
|
||||||
|
title='Russia',
|
||||||
|
locale='ru-RU'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.country_ru = Country.objects.get(
|
||||||
|
name={"en-GB": "Russian"}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.region = Region.objects.create(name='Moscow area', code='01',
|
||||||
|
country=self.country_ru)
|
||||||
|
self.region.save()
|
||||||
|
|
||||||
|
self.city = City.objects.create(name='Mosocow', code='01',
|
||||||
|
region=self.region, country=self.country_ru)
|
||||||
|
self.city.save()
|
||||||
|
|
||||||
|
self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya',
|
||||||
|
number=2, postal_code='010100')
|
||||||
|
self.address.save()
|
||||||
|
|
||||||
|
self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER)
|
||||||
|
self.role.save()
|
||||||
|
|
||||||
|
self.establishment = Establishment.objects.create(
|
||||||
|
name="Test establishment",
|
||||||
|
establishment_type_id=self.establishment_type.id,
|
||||||
|
is_publish=True,
|
||||||
|
slug="test",
|
||||||
|
address=self.address
|
||||||
|
)
|
||||||
|
|
||||||
|
self.establishment.save()
|
||||||
|
|
||||||
|
self.user_role = UserRole.objects.create(user=self.user, role=self.role,
|
||||||
|
establishment=self.establishment)
|
||||||
|
self.user_role.save()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentBTests(BaseTestCase):
|
||||||
|
def test_establishment_CRUD(self):
|
||||||
|
params = {'page': 1, 'page_size': 1,}
|
||||||
|
response = self.client.get('/api/back/establishments/', params, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': 'Test establishment',
|
||||||
|
'type_id': self.establishment_type.id,
|
||||||
|
'is_publish': True,
|
||||||
|
'slug': 'test-establishment-slug'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'name': 'Test new establishment'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/',
|
||||||
|
data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/',
|
||||||
|
format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class EmployeeTests(BaseTestCase):
|
class EmployeeTests(BaseTestCase):
|
||||||
def test_employee_CRD(self):
|
def test_employee_CRUD(self):
|
||||||
response = self.client.get('/api/back/establishments/employees/', format='json')
|
response = self.client.get('/api/back/establishments/employees/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
@ -36,9 +115,284 @@ class EmployeeTests(BaseTestCase):
|
||||||
response = self.client.get('/api/back/establishments/employees/1/', format='json')
|
response = self.client.get('/api/back/establishments/employees/1/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'name': 'Test new name'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch('/api/back/establishments/employees/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
response = self.client.delete('/api/back/establishments/employees/1/', format='json')
|
response = self.client.delete('/api/back/establishments/employees/1/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
# Class to test childs
|
||||||
|
class ChildTestCase(BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# Test childs
|
||||||
|
class EmailTests(ChildTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
response = self.client.get('/api/back/establishments/emails/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
data = {
|
||||||
|
'email': "test@test.com",
|
||||||
|
'establishment': self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/emails/', data=data)
|
||||||
|
self.id_email = response.json()['id']
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def test_get_by_pk(self):
|
||||||
|
self.test_post()
|
||||||
|
response = self.client.get(f'/api/back/establishments/emails/{self.id_email}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_patch(self):
|
||||||
|
self.test_post()
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'email': 'testnew@test.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/back/establishments/emails/{self.id_email}/',
|
||||||
|
data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_email_CRUD(self):
|
||||||
|
self.test_post()
|
||||||
|
response = self.client.delete(f'/api/back/establishments/emails/{self.id_email}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class PhoneTests(ChildTestCase):
|
||||||
|
def test_phone_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/establishments/phones/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'phone': "+79999999999",
|
||||||
|
'establishment': self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/phones/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get('/api/back/establishments/phones/1/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'phone': '+79999999998'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch('/api/back/establishments/phones/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete('/api/back/establishments/phones/1/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class SocialTests(ChildTestCase):
|
||||||
|
def test_social_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/establishments/socials/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'title': "Test social",
|
||||||
|
'url': 'https://testsocial.com',
|
||||||
|
'establishment': self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/socials/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get('/api/back/establishments/socials/1/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'title': 'Test new social'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch('/api/back/establishments/socials/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete('/api/back/establishments/socials/1/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class PlateTests(ChildTestCase):
|
||||||
|
def test_plate_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/establishments/plates/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
menu = Menu.objects.create(
|
||||||
|
category=json.dumps({"en-GB": "Test category"}),
|
||||||
|
establishment=self.establishment
|
||||||
|
)
|
||||||
|
currency = Currency.objects.create(name="Test currency")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': json.dumps({"en-GB": "Test plate"}),
|
||||||
|
'establishment': self.establishment.id,
|
||||||
|
'price': 10,
|
||||||
|
'menu': menu.id,
|
||||||
|
'currency_id': currency.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/plates/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get('/api/back/establishments/plates/1/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'name': json.dumps({"en-GB": "Test new plate"})
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch('/api/back/establishments/plates/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete('/api/back/establishments/plates/1/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class MenuTests(ChildTestCase):
|
||||||
|
def test_menu_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/establishments/menus/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'category': json.dumps({"en-GB": "Test category"}),
|
||||||
|
'establishment': self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/establishments/menus/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get('/api/back/establishments/menus/1/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'category': json.dumps({"en-GB": "Test new category"})
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch('/api/back/establishments/menus/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete('/api/back/establishments/menus/1/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentShedulerTests(ChildTestCase):
|
||||||
|
def test_shedule_CRUD(self):
|
||||||
|
data = {
|
||||||
|
'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/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'weekday': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/1/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
# Web tests
|
||||||
|
class EstablishmentWebTests(BaseTestCase):
|
||||||
|
|
||||||
|
def test_establishment_Read(self):
|
||||||
|
params = {'page': 1, 'page_size': 1,}
|
||||||
|
response = self.client.get('/api/web/establishments/', params, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWebTagTests(BaseTestCase):
|
||||||
|
|
||||||
|
def test_tag_Read(self):
|
||||||
|
response = self.client.get('/api/web/establishments/tags/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWebSlugTests(ChildTestCase):
|
||||||
|
|
||||||
|
def test_slug_Read(self):
|
||||||
|
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWebSimilarTests(ChildTestCase):
|
||||||
|
|
||||||
|
def test_similar_Read(self):
|
||||||
|
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/similar/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWebCommentsTests(ChildTestCase):
|
||||||
|
|
||||||
|
def test_comments_CRUD(self):
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'text': 'test',
|
||||||
|
'user': self.user.id,
|
||||||
|
'mark': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/comments/create/',
|
||||||
|
data=data)
|
||||||
|
|
||||||
|
comment = response.json()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
|
||||||
|
format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'text': 'Test new establishment'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
|
||||||
|
data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete(
|
||||||
|
f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/',
|
||||||
|
format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentWebFavoriteTests(ChildTestCase):
|
||||||
|
|
||||||
|
def test_favorite_CR(self):
|
||||||
|
data = {
|
||||||
|
"user": self.user.id,
|
||||||
|
"object_id": self.establishment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/',
|
||||||
|
data=data)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.delete(
|
||||||
|
f'/api/web/establishments/slug/{self.establishment.slug}/favorites/',
|
||||||
|
format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ app_name = 'establishment'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
||||||
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||||
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
|
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
|
||||||
name='schedule-rud'),
|
name='schedule-rud'),
|
||||||
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||||
|
|
@ -17,7 +17,7 @@ urlpatterns = [
|
||||||
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'),
|
||||||
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
||||||
path('plates/<int:pk>/', views.PlateListCreateView.as_view(), name='plate-rud'),
|
path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
|
||||||
path('socials/', views.SocialListCreateView.as_view(), name='socials'),
|
path('socials/', views.SocialListCreateView.as_view(), name='socials'),
|
||||||
path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'),
|
path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'),
|
||||||
path('phones/', views.PhonesListCreateView.as_view(), name='phones'),
|
path('phones/', views.PhonesListCreateView.as_view(), name='phones'),
|
||||||
|
|
@ -26,4 +26,8 @@ urlpatterns = [
|
||||||
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
|
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
|
||||||
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('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'),
|
||||||
|
path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'),
|
||||||
|
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
||||||
|
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
||||||
]
|
]
|
||||||
|
|
@ -7,14 +7,16 @@ app_name = 'establishment'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('tags/', views.EstablishmentTagListView.as_view(), name='tags'),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
name='recent-reviews'),
|
||||||
path('<int:pk>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
# path('wineries/', views.WineriesListView.as_view(), name='wineries-list'),
|
||||||
path('<int:pk>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
||||||
path('<int:pk>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
||||||
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
|
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||||
name='create-comment'),
|
name='create-comment'),
|
||||||
path('<int:pk>/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('<int:pk>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='add-favorites')
|
name='add-to-favorites')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,73 @@
|
||||||
"""Establishment app views."""
|
"""Establishment app views."""
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
from establishment import models
|
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||||
from establishment import serializers
|
from establishment import models, serializers
|
||||||
from establishment.views.common import EstablishmentMixin
|
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListCreateView(EstablishmentMixin, generics.ListCreateAPIView):
|
class EstablishmentMixinViews:
|
||||||
|
"""Establishment mixin."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overrided method 'get_queryset'."""
|
||||||
|
return models.Establishment.objects.published().with_base_related()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView):
|
||||||
"""Establishment list/create view."""
|
"""Establishment list/create view."""
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all()
|
||||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||||
|
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
queryset = models.Establishment.objects.all()
|
||||||
|
serializer_class = serializers.EstablishmentRUDSerializer
|
||||||
|
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Establishment schedule RUD view"""
|
||||||
|
serializer_class = ScheduleRUDSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""
|
||||||
|
Returns the object the view is displaying.
|
||||||
|
"""
|
||||||
|
establishment_pk = self.kwargs['pk']
|
||||||
|
schedule_id = self.kwargs['schedule_id']
|
||||||
|
|
||||||
|
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||||
|
pk=establishment_pk)
|
||||||
|
schedule = get_object_or_404(klass=establishment.schedule,
|
||||||
|
id=schedule_id)
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
self.check_object_permissions(self.request, schedule)
|
||||||
|
|
||||||
|
return schedule
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||||
|
"""Establishment schedule Create view"""
|
||||||
|
serializer_class = ScheduleCreateSerializer
|
||||||
|
|
||||||
|
|
||||||
class MenuListCreateView(generics.ListCreateAPIView):
|
class MenuListCreateView(generics.ListCreateAPIView):
|
||||||
"""Menu list create view."""
|
"""Menu list create view."""
|
||||||
serializer_class = serializers.MenuSerializers
|
serializer_class = serializers.MenuSerializers
|
||||||
queryset = models.Menu.objects.all()
|
queryset = models.Menu.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Menu RUD view."""
|
"""Menu RUD view."""
|
||||||
serializer_class = serializers.MenuRUDSerializers
|
serializer_class = serializers.MenuRUDSerializers
|
||||||
queryset = models.Menu.objects.all()
|
queryset = models.Menu.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class SocialListCreateView(generics.ListCreateAPIView):
|
class SocialListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -30,12 +75,14 @@ class SocialListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.SocialNetworkSerializers
|
serializer_class = serializers.SocialNetworkSerializers
|
||||||
queryset = models.SocialNetwork.objects.all()
|
queryset = models.SocialNetwork.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class SocialRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class SocialRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Social RUD view."""
|
"""Social RUD view."""
|
||||||
serializer_class = serializers.SocialNetworkSerializers
|
serializer_class = serializers.SocialNetworkSerializers
|
||||||
queryset = models.SocialNetwork.objects.all()
|
queryset = models.SocialNetwork.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PlateListCreateView(generics.ListCreateAPIView):
|
class PlateListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -43,12 +90,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.PlatesSerializers
|
serializer_class = serializers.PlatesSerializers
|
||||||
queryset = models.Plate.objects.all()
|
queryset = models.Plate.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Social RUD view."""
|
"""Social RUD view."""
|
||||||
serializer_class = serializers.PlatesSerializers
|
serializer_class = serializers.PlatesSerializers
|
||||||
queryset = models.Plate.objects.all()
|
queryset = models.Plate.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PhonesListCreateView(generics.ListCreateAPIView):
|
class PhonesListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -56,12 +105,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.ContactPhoneBackSerializers
|
serializer_class = serializers.ContactPhoneBackSerializers
|
||||||
queryset = models.ContactPhone.objects.all()
|
queryset = models.ContactPhone.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Social RUD view."""
|
"""Social RUD view."""
|
||||||
serializer_class = serializers.ContactPhoneBackSerializers
|
serializer_class = serializers.ContactPhoneBackSerializers
|
||||||
queryset = models.ContactPhone.objects.all()
|
queryset = models.ContactPhone.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmailListCreateView(generics.ListCreateAPIView):
|
class EmailListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -69,12 +120,14 @@ class EmailListCreateView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.ContactEmailBackSerializers
|
serializer_class = serializers.ContactEmailBackSerializers
|
||||||
queryset = models.ContactEmail.objects.all()
|
queryset = models.ContactEmail.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Social RUD view."""
|
"""Social RUD view."""
|
||||||
serializer_class = serializers.ContactEmailBackSerializers
|
serializer_class = serializers.ContactEmailBackSerializers
|
||||||
queryset = models.ContactEmail.objects.all()
|
queryset = models.ContactEmail.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class EmployeeListCreateView(generics.ListCreateAPIView):
|
class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -83,7 +136,34 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
queryset = models.Employee.objects.all()
|
queryset = models.Employee.objects.all()
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
class EmployeeRUDView(generics.RetrieveDestroyAPIView):
|
|
||||||
|
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Social RUD view."""
|
"""Social RUD view."""
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
queryset = models.Employee.objects.all()
|
queryset = models.Employee.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentTypeListCreateView(generics.ListCreateAPIView):
|
||||||
|
"""Establishment type list/create view."""
|
||||||
|
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||||
|
queryset = models.EstablishmentType.objects.all()
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Establishment type retrieve/update/destroy view."""
|
||||||
|
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||||
|
queryset = models.EstablishmentType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView):
|
||||||
|
"""Establishment subtype list/create view."""
|
||||||
|
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||||
|
queryset = models.EstablishmentSubType.objects.all()
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Establishment subtype retrieve/update/destroy view."""
|
||||||
|
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||||
|
queryset = models.EstablishmentSubType.objects.all()
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
"""Establishment app views."""
|
|
||||||
|
|
||||||
from rest_framework import permissions
|
|
||||||
|
|
||||||
from establishment import models
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentMixin:
|
|
||||||
"""Establishment mixin."""
|
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""Overrided method 'get_queryset'."""
|
|
||||||
return models.Establishment.objects.published() \
|
|
||||||
.prefetch_actual_employees()
|
|
||||||
|
|
@ -1,68 +1,113 @@
|
||||||
"""Establishment app views."""
|
"""Establishment app views."""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.gis.geos import Point
|
||||||
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
|
||||||
|
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from establishment import filters
|
from establishment import filters
|
||||||
from establishment import models, serializers
|
from establishment import models, serializers
|
||||||
from establishment.views import EstablishmentMixin
|
from main import methods
|
||||||
from main.models import MetaDataContent
|
from main.models import MetaDataContent
|
||||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
from utils.pagination import EstablishmentPortionPagination
|
||||||
|
from utils.permissions import IsCountryAdmin
|
||||||
|
|
||||||
|
class EstablishmentMixinView:
|
||||||
|
"""Establishment mixin."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden method 'get_queryset'."""
|
||||||
|
return models.Establishment.objects.published() \
|
||||||
|
.with_base_related() \
|
||||||
|
.annotate_in_favorites(self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListView(EstablishmentMixin, generics.ListAPIView):
|
class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||||
"""Resource for getting a list of establishments."""
|
"""Resource for getting a list of establishments."""
|
||||||
serializer_class = serializers.EstablishmentListSerializer
|
|
||||||
filter_class = filters.EstablishmentFilter
|
filter_class = filters.EstablishmentFilter
|
||||||
|
serializer_class = serializers.EstablishmentBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
qs = super(EstablishmentListView, self).get_queryset()
|
qs = super(EstablishmentListView, self).get_queryset()
|
||||||
return qs.by_country_code(code=self.request.country_code) \
|
if self.request.country_code:
|
||||||
.annotate_in_favorites(user=self.request.user)
|
qs = qs.by_country_code(self.request.country_code)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
||||||
|
"""Resource for getting a establishment."""
|
||||||
|
|
||||||
|
lookup_field = 'slug'
|
||||||
|
serializer_class = serializers.EstablishmentDetailSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().with_extended_related()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
|
"""List view for last reviewed establishments."""
|
||||||
|
|
||||||
|
pagination_class = EstablishmentPortionPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden method 'get_queryset'."""
|
||||||
|
qs = super().get_queryset()
|
||||||
|
user_ip = methods.get_user_ip(self.request)
|
||||||
|
query_params = self.request.query_params
|
||||||
|
if 'longitude' in query_params and 'latitude' in query_params:
|
||||||
|
longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
|
||||||
|
else:
|
||||||
|
longitude, latitude = methods.determine_coordinates(user_ip)
|
||||||
|
if not longitude or not latitude:
|
||||||
|
return qs.none()
|
||||||
|
point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
|
||||||
|
return qs.last_reviewed(point=point)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarListView(EstablishmentListView):
|
class EstablishmentSimilarListView(EstablishmentListView):
|
||||||
"""Resource for getting a list of establishments."""
|
"""Resource for getting a list of establishments."""
|
||||||
serializer_class = serializers.EstablishmentListSerializer
|
|
||||||
|
serializer_class = serializers.EstablishmentBaseSerializer
|
||||||
|
pagination_class = EstablishmentPortionPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
return qs.similar(establishment_pk=self.kwargs.get('pk'))\
|
return qs.similar(establishment_slug=self.kwargs.get('slug'))
|
||||||
.order_by('-total_mark')[:13]
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
|
|
||||||
"""Resource for getting a establishment."""
|
|
||||||
serializer_class = serializers.EstablishmentDetailSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeListView(generics.ListAPIView):
|
class EstablishmentTypeListView(generics.ListAPIView):
|
||||||
"""Resource for getting a list of establishment types."""
|
"""Resource for getting a list of establishment types."""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.EstablishmentTypeSerializer
|
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||||
queryset = models.EstablishmentType.objects.all()
|
queryset = models.EstablishmentType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateView(generics.CreateAPIView):
|
class EstablishmentCommentCreateView(generics.CreateAPIView):
|
||||||
"""View for create new comment."""
|
"""View for create new comment."""
|
||||||
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentCommentCreateSerializer
|
serializer_class = serializers.EstablishmentCommentCreateSerializer
|
||||||
queryset = comment_models.Comment.objects.all()
|
queryset = comment_models.Comment.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentListView(generics.ListAPIView):
|
class EstablishmentCommentListView(generics.ListAPIView):
|
||||||
"""View for return list of establishment comments."""
|
"""View for return list of establishment comments."""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.EstablishmentCommentCreateSerializer
|
serializer_class = serializers.EstablishmentCommentCreateSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
|
|
||||||
|
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
||||||
return comment_models.Comment.objects.by_content_type(app_label='establishment',
|
return comment_models.Comment.objects.by_content_type(app_label='establishment',
|
||||||
model='establishment')\
|
model='establishment')\
|
||||||
.by_object_id(object_id=self.kwargs.get('pk'))\
|
.by_object_id(object_id=establishment.pk)\
|
||||||
.order_by('-created')
|
.order_by('-created')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,17 +121,9 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
lookup_url_kwargs = ('pk', 'comment_id')
|
|
||||||
|
|
||||||
assert lookup_url_kwargs not in self.kwargs.keys(), (
|
|
||||||
'Expected view %s to be called with a URL keyword argument '
|
|
||||||
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
|
||||||
'attribute on the view correctly.' %
|
|
||||||
(self.__class__.__name__, lookup_url_kwargs)
|
|
||||||
)
|
|
||||||
|
|
||||||
establishment_obj = get_object_or_404(queryset,
|
establishment_obj = get_object_or_404(queryset,
|
||||||
pk=self.kwargs['pk'])
|
slug=self.kwargs['slug'])
|
||||||
comment_obj = get_object_or_404(establishment_obj.comments.by_user(self.request.user),
|
comment_obj = get_object_or_404(establishment_obj.comments.by_user(self.request.user),
|
||||||
pk=self.kwargs['comment_id'])
|
pk=self.kwargs['comment_id'])
|
||||||
|
|
||||||
|
|
@ -99,93 +136,52 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView):
|
class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView):
|
||||||
"""View for create/destroy establishment from favorites."""
|
"""View for create/destroy establishment from favorites."""
|
||||||
serializer_class = serializers.EstablishmentFavoritesCreateSerializer
|
serializer_class = serializers.EstablishmentFavoritesCreateSerializer
|
||||||
|
lookup_field = 'slug'
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
lookup_url_kwargs = ('pk',)
|
establishment_obj = get_object_or_404(models.Establishment,
|
||||||
assert lookup_url_kwargs not in self.kwargs.keys(), (
|
slug=self.kwargs['slug'])
|
||||||
'Expected view %s to be called with a URL keyword argument '
|
|
||||||
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
|
||||||
'attribute on the view correctly.' %
|
|
||||||
(self.__class__.__name__, lookup_url_kwargs)
|
|
||||||
)
|
|
||||||
|
|
||||||
obj = get_object_or_404(
|
obj = get_object_or_404(
|
||||||
self.request.user.favorites.by_user(user=self.request.user)
|
self.request.user.favorites.by_content_type(app_label='establishment',
|
||||||
.by_content_type(app_label='establishment',
|
|
||||||
model='establishment')
|
model='establishment')
|
||||||
.by_object_id(object_id=self.kwargs['pk']))
|
.by_object_id(object_id=establishment_obj.pk))
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, obj)
|
self.check_object_permissions(self.request, obj)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNearestRetrieveView(EstablishmentMixin, generics.ListAPIView):
|
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
|
||||||
"""Resource for getting list of nearest establishments."""
|
"""Resource for getting list of nearest establishments."""
|
||||||
serializer_class = serializers.EstablishmentListSerializer
|
|
||||||
|
serializer_class = serializers.EstablishmentBaseSerializer
|
||||||
filter_class = filters.EstablishmentFilter
|
filter_class = filters.EstablishmentFilter
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overrided method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
from django.contrib.gis.geos import Point
|
# todo: latitude and longitude
|
||||||
|
lat = self.request.query_params.get('lat')
|
||||||
|
lon = self.request.query_params.get('lon')
|
||||||
|
radius = self.request.query_params.get('radius')
|
||||||
|
unit = self.request.query_params.get('unit')
|
||||||
|
|
||||||
center = Point(float(self.request.query_params["lat"]), float(self.request.query_params["lon"]))
|
qs = super(EstablishmentNearestRetrieveView, self).get_queryset()
|
||||||
radius = float(self.request.query_params["radius"])
|
if lat and lon and radius and unit:
|
||||||
unit = self.request.query_params.get("unit", None)
|
center = Point(float(lat), float(lon))
|
||||||
by_distance_from_point_kwargs = {"center": center, "radius": radius, "unit": unit}
|
filter_kwargs = {'center': center, 'radius': float(radius), 'unit': unit}
|
||||||
return super(EstablishmentNearestRetrieveView, self).get_queryset() \
|
return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
|
||||||
.by_distance_from_point(**{k: v for k, v in by_distance_from_point_kwargs.items() if v is not None}) \
|
if v is not None})
|
||||||
.by_country_code(code=self.request.country_code) \
|
return qs
|
||||||
.annotate_in_favorites(user=self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTagListView(generics.ListAPIView):
|
# Wineries
|
||||||
"""List view for establishment tags."""
|
# todo: find out about difference between subtypes data
|
||||||
serializer_class = serializers.EstablishmentTagListSerializer
|
# class WineriesListView(EstablishmentListView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
# """Return list establishments with type Wineries"""
|
||||||
pagination_class = None
|
#
|
||||||
|
# def get_queryset(self):
|
||||||
def get_queryset(self):
|
# """Overridden get_queryset method."""
|
||||||
"""Override get_queryset method"""
|
# qs = super(WineriesListView, self).get_queryset()
|
||||||
return MetaDataContent.objects.by_content_type(app_label='establishment',
|
# return qs.with_type_related().wineries()
|
||||||
model='establishment')\
|
|
||||||
.distinct('metadata__label')
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""Establishment schedule RUD view"""
|
|
||||||
serializer_class = ScheduleRUDSerializer
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
"""
|
|
||||||
Returns the object the view is displaying.
|
|
||||||
"""
|
|
||||||
lookup_url_kwargs = ('pk', 'schedule_id')
|
|
||||||
|
|
||||||
assert lookup_url_kwargs not in self.kwargs.keys(), (
|
|
||||||
'Expected view %s to be called with a URL keyword argument '
|
|
||||||
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
|
||||||
'attribute on the view correctly.' %
|
|
||||||
(self.__class__.__name__, lookup_url_kwargs)
|
|
||||||
)
|
|
||||||
|
|
||||||
establishment_pk = self.kwargs['pk']
|
|
||||||
schedule_id = self.kwargs['schedule_id']
|
|
||||||
|
|
||||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
|
||||||
pk=establishment_pk)
|
|
||||||
schedule = get_object_or_404(klass=establishment.schedule,
|
|
||||||
id=schedule_id)
|
|
||||||
|
|
||||||
# May raise a permission denied
|
|
||||||
self.check_object_permissions(self.request, establishment)
|
|
||||||
self.check_object_permissions(self.request, schedule)
|
|
||||||
|
|
||||||
return schedule
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
|
||||||
"""Establishment schedule Create view"""
|
|
||||||
serializer_class = ScheduleCreateSerializer
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,3 @@
|
||||||
from .models import Favorites
|
from .models import Favorites
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from establishment.serializers import EstablishmentBaseSerializer
|
from establishment.serializers import EstablishmentBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class FavoritesEstablishmentListSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for model Favorites"""
|
|
||||||
detail = EstablishmentBaseSerializer(source='content_object')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class."""
|
|
||||||
model = Favorites
|
|
||||||
fields = (
|
|
||||||
'id',
|
|
||||||
'detail',
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,54 @@
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from favorites.models import Favorites
|
||||||
|
from establishment.models import Establishment, EstablishmentType, EstablishmentSubType
|
||||||
|
from news.models import NewsType, News
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.username = 'sedragurda'
|
||||||
|
self.password = 'sedragurdaredips19'
|
||||||
|
self.email = 'sedragurda@desoz.com'
|
||||||
|
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
|
||||||
|
tokkens = User.create_jwt_tokens(self.user)
|
||||||
|
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
|
||||||
|
'refresh_token': tokkens.get('refresh_token')})
|
||||||
|
|
||||||
|
self.test_news_type = NewsType.objects.create(name="Test news type")
|
||||||
|
self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"},
|
||||||
|
news_type=self.test_news_type,
|
||||||
|
description={"en-GB": "Description test news"},
|
||||||
|
playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00",
|
||||||
|
state=News.PUBLISHED, slug='test-news')
|
||||||
|
|
||||||
|
self.test_content_type = ContentType.objects.get(app_label="news", model="news")
|
||||||
|
|
||||||
|
self.test_favorites = Favorites.objects.create(user=self.user, content_type=self.test_content_type,
|
||||||
|
object_id=self.test_news.id)
|
||||||
|
|
||||||
|
self.test_establishment_type = EstablishmentType.objects.create(name={"en-GB": "test establishment type"},
|
||||||
|
use_subtypes=False)
|
||||||
|
|
||||||
|
self.test_establishment = Establishment.objects.create(name="test establishment",
|
||||||
|
description={"en-GB": "description of test establishment"},
|
||||||
|
establishment_type=self.test_establishment_type,
|
||||||
|
is_publish=True)
|
||||||
|
# value for GenericRelation(reverse side) field must be iterable
|
||||||
|
# value for GenericRelation(reverse side) field must be assigned through "set" method of field
|
||||||
|
self.test_establishment.favorites.set([self.test_favorites])
|
||||||
|
|
||||||
|
|
||||||
|
class FavoritesTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
response = self.client.get("/api/web/favorites/establishments/")
|
||||||
|
print(response.json())
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
@ -8,5 +8,4 @@ app_name = 'favorites'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
|
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
|
||||||
name='establishment-list'),
|
name='establishment-list'),
|
||||||
path('remove/<int:pk>/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
"""Views for app favorites."""
|
"""Views for app favorites."""
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from .serializers import FavoritesEstablishmentListSerializer
|
from establishment.models import Establishment
|
||||||
|
from establishment.serializers import EstablishmentBaseSerializer
|
||||||
from .models import Favorites
|
from .models import Favorites
|
||||||
|
|
||||||
|
|
||||||
class FavoritesBaseView(generics.GenericAPIView):
|
class FavoritesBaseView(generics.GenericAPIView):
|
||||||
"""Base view for Favorites."""
|
"""Base view for Favorites."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return Favorites.objects.by_user(self.request.user)
|
return Favorites.objects.by_user(self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class FavoritesEstablishmentListView(FavoritesBaseView, generics.ListAPIView):
|
class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
"""List views for favorites"""
|
"""List views for favorites"""
|
||||||
serializer_class = FavoritesEstablishmentListSerializer
|
|
||||||
|
serializer_class = EstablishmentBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return super().get_queryset().by_content_type(app_label='establishment',
|
return Establishment.objects.filter(favorites__user=self.request.user)\
|
||||||
model='establishment')
|
.order_by('-favorites')
|
||||||
|
|
||||||
|
|
||||||
class FavoritesDestroyView(FavoritesBaseView, generics.DestroyAPIView):
|
|
||||||
"""Destroy view for favorites"""
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
from utils.permissions import IsAuthenticatedAndTokenIsValid
|
|
||||||
from . import models, serializers
|
from . import models, serializers
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,4 +8,3 @@ class ImageUploadView(generics.CreateAPIView):
|
||||||
model = models.Image
|
model = models.Image
|
||||||
queryset = models.Image.objects.all()
|
queryset = models.Image.objects.all()
|
||||||
serializer_class = serializers.ImageSerializer
|
serializer_class = serializers.ImageSerializer
|
||||||
permission_classes = (IsAuthenticatedAndTokenIsValid, )
|
|
||||||
|
|
|
||||||
19
apps/location/migrations/0011_country_languages.py
Normal file
19
apps/location/migrations/0011_country_languages.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-10 12:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0003_auto_20190901_1032'),
|
||||||
|
('location', '0010_auto_20190904_0711'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='country',
|
||||||
|
name='languages',
|
||||||
|
field=models.ManyToManyField(to='translation.Language', verbose_name='Languages'),
|
||||||
|
),
|
||||||
|
]
|
||||||
25
apps/location/migrations/0012_data_migrate.py
Normal file
25
apps/location/migrations/0012_data_migrate.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from django.db import migrations, connection
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
# Check migration
|
||||||
|
def load_data_from_sql(apps, schema_editor):
|
||||||
|
file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql')
|
||||||
|
sql_statement = open(file_path).read()
|
||||||
|
with connection.cursor() as c:
|
||||||
|
c.execute(sql_statement)
|
||||||
|
|
||||||
|
def revert_data(apps, schema_editor):
|
||||||
|
file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql')
|
||||||
|
sql_statement = open(file_path).read()
|
||||||
|
with connection.cursor() as c:
|
||||||
|
c.execute(sql_statement)
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0011_country_languages'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(load_data_from_sql, revert_data),
|
||||||
|
]
|
||||||
382
apps/location/migrations/migrate_lang.sql
Normal file
382
apps/location/migrations/migrate_lang.sql
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
SET search_path TO gm, public;
|
||||||
|
|
||||||
|
CREATE TABLE codelang (
|
||||||
|
code varchar(100) NULL,
|
||||||
|
country varchar(10000) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('af','Afrikaans')
|
||||||
|
,('af-ZA','Afrikaans (South Africa)')
|
||||||
|
,('ar','Arabic')
|
||||||
|
,('ar-AE','Arabic (U.A.E.)')
|
||||||
|
,('ar-BH','Arabic (Bahrain)')
|
||||||
|
,('ar-DZ','Arabic (Algeria)')
|
||||||
|
,('ar-EG','Arabic (Egypt)')
|
||||||
|
,('ar-IQ','Arabic (Iraq)')
|
||||||
|
,('ar-JO','Arabic (Jordan)')
|
||||||
|
,('ar-KW','Arabic (Kuwait)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('ar-LB','Arabic (Lebanon)')
|
||||||
|
,('ar-LY','Arabic (Libya)')
|
||||||
|
,('ar-MA','Arabic (Morocco)')
|
||||||
|
,('ar-OM','Arabic (Oman)')
|
||||||
|
,('ar-QA','Arabic (Qatar)')
|
||||||
|
,('ar-SA','Arabic (Saudi Arabia)')
|
||||||
|
,('ar-SY','Arabic (Syria)')
|
||||||
|
,('ar-TN','Arabic (Tunisia)')
|
||||||
|
,('ar-YE','Arabic (Yemen)')
|
||||||
|
,('az','Azeri (Latin)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('az-AZ','Azeri (Latin) (Azerbaijan)')
|
||||||
|
,('az-AZ','Azeri (Cyrillic) (Azerbaijan)')
|
||||||
|
,('be','Belarusian')
|
||||||
|
,('be-BY','Belarusian (Belarus)')
|
||||||
|
,('bg','Bulgarian')
|
||||||
|
,('bg-BG','Bulgarian (Bulgaria)')
|
||||||
|
,('bs-BA','Bosnian (Bosnia and Herzegovina)')
|
||||||
|
,('ca','Catalan')
|
||||||
|
,('ca-ES','Catalan (Spain)')
|
||||||
|
,('cs','Czech')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('cs-CZ','Czech (Czech Republic)')
|
||||||
|
,('cy','Welsh')
|
||||||
|
,('cy-GB','Welsh (United Kingdom)')
|
||||||
|
,('da','Danish')
|
||||||
|
,('da-DK','Danish (Denmark)')
|
||||||
|
,('de','German')
|
||||||
|
,('de-AT','German (Austria)')
|
||||||
|
,('de-CH','German (Switzerland)')
|
||||||
|
,('de-DE','German (Germany)')
|
||||||
|
,('de-LI','German (Liechtenstein)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('de-LU','German (Luxembourg)')
|
||||||
|
,('dv','Divehi')
|
||||||
|
,('dv-MV','Divehi (Maldives)')
|
||||||
|
,('el','Greek')
|
||||||
|
,('el-GR','Greek (Greece)')
|
||||||
|
,('en','English')
|
||||||
|
,('en-AU','English (Australia)')
|
||||||
|
,('en-BZ','English (Belize)')
|
||||||
|
,('en-CA','English (Canada)')
|
||||||
|
,('en-CB','English (Caribbean)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('en-GB','English (United Kingdom)')
|
||||||
|
,('en-IE','English (Ireland)')
|
||||||
|
,('en-JM','English (Jamaica)')
|
||||||
|
,('en-NZ','English (New Zealand)')
|
||||||
|
,('en-PH','English (Republic of the Philippines)')
|
||||||
|
,('en-TT','English (Trinidad and Tobago)')
|
||||||
|
,('en-US','English (United States)')
|
||||||
|
,('en-ZA','English (South Africa)')
|
||||||
|
,('en-ZW','English (Zimbabwe)')
|
||||||
|
,('eo','Esperanto')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es','Spanish')
|
||||||
|
,('es-AR','Spanish (Argentina)')
|
||||||
|
,('es-BO','Spanish (Bolivia)')
|
||||||
|
,('es-CL','Spanish (Chile)')
|
||||||
|
,('es-CO','Spanish (Colombia)')
|
||||||
|
,('es-CR','Spanish (Costa Rica)')
|
||||||
|
,('es-DO','Spanish (Dominican Republic)')
|
||||||
|
,('es-EC','Spanish (Ecuador)')
|
||||||
|
,('es-ES','Spanish (Spain)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es-GT','Spanish (Guatemala)')
|
||||||
|
,('es-HN','Spanish (Honduras)')
|
||||||
|
,('es-MX','Spanish (Mexico)')
|
||||||
|
,('es-NI','Spanish (Nicaragua)')
|
||||||
|
,('es-PA','Spanish (Panama)')
|
||||||
|
,('es-PE','Spanish (Peru)')
|
||||||
|
,('es-PR','Spanish (Puerto Rico)')
|
||||||
|
,('es-PY','Spanish (Paraguay)')
|
||||||
|
,('es-SV','Spanish (El Salvador)')
|
||||||
|
,('es-UY','Spanish (Uruguay)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es-VE','Spanish (Venezuela)')
|
||||||
|
,('et','Estonian')
|
||||||
|
,('et-EE','Estonian (Estonia)')
|
||||||
|
,('eu','Basque')
|
||||||
|
,('eu-ES','Basque (Spain)')
|
||||||
|
,('fa','Farsi')
|
||||||
|
,('fa-IR','Farsi (Iran)')
|
||||||
|
,('fi','Finnish')
|
||||||
|
,('fi-FI','Finnish (Finland)')
|
||||||
|
,('fo','Faroese')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('fo-FO','Faroese (Faroe Islands)')
|
||||||
|
,('fr','French')
|
||||||
|
,('fr-BE','French (Belgium)')
|
||||||
|
,('fr-CA','French (Canada)')
|
||||||
|
,('fr-CH','French (Switzerland)')
|
||||||
|
,('fr-FR','French (France)')
|
||||||
|
,('fr-LU','French (Luxembourg)')
|
||||||
|
,('fr-MC','French (Principality of Monaco)')
|
||||||
|
,('gl','Galician')
|
||||||
|
,('gl-ES','Galician (Spain)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('gu','Gujarati')
|
||||||
|
,('gu-IN','Gujarati (India)')
|
||||||
|
,('he','Hebrew')
|
||||||
|
,('he-IL','Hebrew (Israel)')
|
||||||
|
,('hi','Hindi')
|
||||||
|
,('hi-IN','Hindi (India)')
|
||||||
|
,('hr','Croatian')
|
||||||
|
,('hr-BA','Croatian (Bosnia and Herzegovina)')
|
||||||
|
,('hr-HR','Croatian (Croatia)')
|
||||||
|
,('hu','Hungarian')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('hu-HU','Hungarian (Hungary)')
|
||||||
|
,('hy','Armenian')
|
||||||
|
,('hy-AM','Armenian (Armenia)')
|
||||||
|
,('id','Indonesian')
|
||||||
|
,('id-ID','Indonesian (Indonesia)')
|
||||||
|
,('is','Icelandic')
|
||||||
|
,('is-IS','Icelandic (Iceland)')
|
||||||
|
,('it','Italian')
|
||||||
|
,('it-CH','Italian (Switzerland)')
|
||||||
|
,('it-IT','Italian (Italy)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('ja','Japanese')
|
||||||
|
,('ja-JP','Japanese (Japan)')
|
||||||
|
,('ka','Georgian')
|
||||||
|
,('ka-GE','Georgian (Georgia)')
|
||||||
|
,('kk','Kazakh')
|
||||||
|
,('kk-KZ','Kazakh (Kazakhstan)')
|
||||||
|
,('kn','Kannada')
|
||||||
|
,('kn-IN','Kannada (India)')
|
||||||
|
,('ko','Korean')
|
||||||
|
,('ko-KR','Korean (Korea)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('kok','Konkani')
|
||||||
|
,('kok-IN','Konkani (India)')
|
||||||
|
,('ky','Kyrgyz')
|
||||||
|
,('ky-KG','Kyrgyz (Kyrgyzstan)')
|
||||||
|
,('lt','Lithuanian')
|
||||||
|
,('lt-LT','Lithuanian (Lithuania)')
|
||||||
|
,('lv','Latvian')
|
||||||
|
,('lv-LV','Latvian (Latvia)')
|
||||||
|
,('mi','Maori')
|
||||||
|
,('mi-NZ','Maori (New Zealand)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('mk','FYRO Macedonian')
|
||||||
|
,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)')
|
||||||
|
,('mn','Mongolian')
|
||||||
|
,('mn-MN','Mongolian (Mongolia)')
|
||||||
|
,('mr','Marathi')
|
||||||
|
,('mr-IN','Marathi (India)')
|
||||||
|
,('ms','Malay')
|
||||||
|
,('ms-BN','Malay (Brunei Darussalam)')
|
||||||
|
,('ms-MY','Malay (Malaysia)')
|
||||||
|
,('mt','Maltese')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('mt-MT','Maltese (Malta)')
|
||||||
|
,('nb','Norwegian (Bokm?l)')
|
||||||
|
,('nb-NO','Norwegian (Bokm?l) (Norway)')
|
||||||
|
,('nl','Dutch')
|
||||||
|
,('nl-BE','Dutch (Belgium)')
|
||||||
|
,('nl-NL','Dutch (Netherlands)')
|
||||||
|
,('nn-NO','Norwegian (Nynorsk) (Norway)')
|
||||||
|
,('ns','Northern Sotho')
|
||||||
|
,('ns-ZA','Northern Sotho (South Africa)')
|
||||||
|
,('pa','Punjabi')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('pa-IN','Punjabi (India)')
|
||||||
|
,('pl','Polish')
|
||||||
|
,('pl-PL','Polish (Poland)')
|
||||||
|
,('ps','Pashto')
|
||||||
|
,('ps-AR','Pashto (Afghanistan)')
|
||||||
|
,('pt','Portuguese')
|
||||||
|
,('pt-BR','Portuguese (Brazil)')
|
||||||
|
,('pt-PT','Portuguese (Portugal)')
|
||||||
|
,('qu','Quechua')
|
||||||
|
,('qu-BO','Quechua (Bolivia)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('qu-EC','Quechua (Ecuador)')
|
||||||
|
,('qu-PE','Quechua (Peru)')
|
||||||
|
,('ro','Romanian')
|
||||||
|
,('ro-RO','Romanian (Romania)')
|
||||||
|
,('ru','Russian')
|
||||||
|
,('ru-RU','Russian (Russia)')
|
||||||
|
,('sa','Sanskrit')
|
||||||
|
,('sa-IN','Sanskrit (India)')
|
||||||
|
,('se','Sami (Northern)')
|
||||||
|
,('se-FI','Sami (Northern) (Finland)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('se-FI','Sami (Skolt) (Finland)')
|
||||||
|
,('se-FI','Sami (Inari) (Finland)')
|
||||||
|
,('se-NO','Sami (Northern) (Norway)')
|
||||||
|
,('se-NO','Sami (Lule) (Norway)')
|
||||||
|
,('se-NO','Sami (Southern) (Norway)')
|
||||||
|
,('se-SE','Sami (Northern) (Sweden)')
|
||||||
|
,('se-SE','Sami (Lule) (Sweden)')
|
||||||
|
,('se-SE','Sami (Southern) (Sweden)')
|
||||||
|
,('sk','Slovak')
|
||||||
|
,('sk-SK','Slovak (Slovakia)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('sl','Slovenian')
|
||||||
|
,('sl-SI','Slovenian (Slovenia)')
|
||||||
|
,('sq','Albanian')
|
||||||
|
,('sq-AL','Albanian (Albania)')
|
||||||
|
,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)')
|
||||||
|
,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)')
|
||||||
|
,('sr-SP','Serbian (Latin) (Serbia and Montenegro)')
|
||||||
|
,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)')
|
||||||
|
,('sv','Swedish')
|
||||||
|
,('sv-FI','Swedish (Finland)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('sv-SE','Swedish (Sweden)')
|
||||||
|
,('sw','Swahili')
|
||||||
|
,('sw-KE','Swahili (Kenya)')
|
||||||
|
,('syr','Syriac')
|
||||||
|
,('syr-SY','Syriac (Syria)')
|
||||||
|
,('ta','Tamil')
|
||||||
|
,('ta-IN','Tamil (India)')
|
||||||
|
,('te','Telugu')
|
||||||
|
,('te-IN','Telugu (India)')
|
||||||
|
,('th','Thai')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('th-TH','Thai (Thailand)')
|
||||||
|
,('tl','Tagalog')
|
||||||
|
,('tl-PH','Tagalog (Philippines)')
|
||||||
|
,('tn','Tswana')
|
||||||
|
,('tn-ZA','Tswana (South Africa)')
|
||||||
|
,('tr','Turkish')
|
||||||
|
,('tr-TR','Turkish (Turkey)')
|
||||||
|
,('tt','Tatar')
|
||||||
|
,('tt-RU','Tatar (Russia)')
|
||||||
|
,('ts','Tsonga')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('uk','Ukrainian')
|
||||||
|
,('uk-UA','Ukrainian (Ukraine)')
|
||||||
|
,('ur','Urdu')
|
||||||
|
,('ur-PK','Urdu (Islamic Republic of Pakistan)')
|
||||||
|
,('uz','Uzbek (Latin)')
|
||||||
|
,('uz-UZ','Uzbek (Latin) (Uzbekistan)')
|
||||||
|
,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)')
|
||||||
|
,('vi','Vietnamese')
|
||||||
|
,('vi-VN','Vietnamese (Viet Nam)')
|
||||||
|
,('xh','Xhosa')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('xh-ZA','Xhosa (South Africa)')
|
||||||
|
,('zh','Chinese')
|
||||||
|
,('zh-CN','Chinese (S)')
|
||||||
|
,('zh-HK','Chinese (Hong Kong)')
|
||||||
|
,('zh-MO','Chinese (Macau)')
|
||||||
|
,('zh-SG','Chinese (Singapore)')
|
||||||
|
,('zh-TW','Chinese (T)')
|
||||||
|
,('zu','Zulu')
|
||||||
|
,('zu-ZA','Zulu (South Africa)')
|
||||||
|
;
|
||||||
|
/***************************/
|
||||||
|
-- Manual migrate
|
||||||
|
|
||||||
|
CREATE TABLE country_code (
|
||||||
|
code varchar(100) NULL,
|
||||||
|
country varchar(10000) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into country_code(code, country)
|
||||||
|
select distinct
|
||||||
|
t.code,
|
||||||
|
coalesce(
|
||||||
|
case when length(t.country_name2) = 1 then null else t.country_name2 end,
|
||||||
|
case when length(t.contry_name1) = 1 then null else t.contry_name1 end,
|
||||||
|
t.country
|
||||||
|
) as country
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select trim(c.code) as code,
|
||||||
|
substring(trim(c.country) from '\((.+)\)') as contry_name1,
|
||||||
|
substring(
|
||||||
|
substring(trim(c.country) from '\((.+)\)')
|
||||||
|
from '\((.*)$') as country_name2,
|
||||||
|
trim(c.country) as country
|
||||||
|
from codelang as c
|
||||||
|
) t;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
--delete from location_country as lc
|
||||||
|
|
||||||
|
INSERT INTO location_country
|
||||||
|
(code, "name", low_price, high_price, created, modified)
|
||||||
|
select distinct
|
||||||
|
lpad((row_number() over (order by t.country asc))::text, 3, '0') as code,
|
||||||
|
jsonb_build_object('en-GB', t.country),
|
||||||
|
0 as low_price,
|
||||||
|
100 as high_price,
|
||||||
|
now() as created,
|
||||||
|
now() as modified
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
distinct c.country
|
||||||
|
from country_code c
|
||||||
|
) t
|
||||||
|
;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
--delete from translation_language as tl;
|
||||||
|
|
||||||
|
INSERT INTO translation_language
|
||||||
|
(title, locale)
|
||||||
|
select
|
||||||
|
distinct
|
||||||
|
t.country as title,
|
||||||
|
t.code as locale
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
distinct c.country, c.code
|
||||||
|
from country_code c
|
||||||
|
) t
|
||||||
|
;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
--delete from location_country_languages
|
||||||
|
|
||||||
|
INSERT INTO location_country_languages
|
||||||
|
(country_id, language_id)
|
||||||
|
select lc.id as country_id,
|
||||||
|
l.id as language_id
|
||||||
|
from location_country as lc
|
||||||
|
join (
|
||||||
|
select tl.*, '"'||tl.title||'"' as country
|
||||||
|
from translation_language as tl
|
||||||
|
) l on l.country = (lc."name"::json->'en-GB')::text
|
||||||
|
;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
drop table country_code;
|
||||||
|
drop table codelang;
|
||||||
|
|
||||||
|
commit;
|
||||||
391
apps/location/migrations/remigrate_lang.sql
Normal file
391
apps/location/migrations/remigrate_lang.sql
Normal file
|
|
@ -0,0 +1,391 @@
|
||||||
|
SET search_path TO gm, public;
|
||||||
|
|
||||||
|
CREATE TABLE codelang (
|
||||||
|
code varchar(100) NULL,
|
||||||
|
country varchar(10000) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('af','Afrikaans')
|
||||||
|
,('af-ZA','Afrikaans (South Africa)')
|
||||||
|
,('ar','Arabic')
|
||||||
|
,('ar-AE','Arabic (U.A.E.)')
|
||||||
|
,('ar-BH','Arabic (Bahrain)')
|
||||||
|
,('ar-DZ','Arabic (Algeria)')
|
||||||
|
,('ar-EG','Arabic (Egypt)')
|
||||||
|
,('ar-IQ','Arabic (Iraq)')
|
||||||
|
,('ar-JO','Arabic (Jordan)')
|
||||||
|
,('ar-KW','Arabic (Kuwait)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('ar-LB','Arabic (Lebanon)')
|
||||||
|
,('ar-LY','Arabic (Libya)')
|
||||||
|
,('ar-MA','Arabic (Morocco)')
|
||||||
|
,('ar-OM','Arabic (Oman)')
|
||||||
|
,('ar-QA','Arabic (Qatar)')
|
||||||
|
,('ar-SA','Arabic (Saudi Arabia)')
|
||||||
|
,('ar-SY','Arabic (Syria)')
|
||||||
|
,('ar-TN','Arabic (Tunisia)')
|
||||||
|
,('ar-YE','Arabic (Yemen)')
|
||||||
|
,('az','Azeri (Latin)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('az-AZ','Azeri (Latin) (Azerbaijan)')
|
||||||
|
,('az-AZ','Azeri (Cyrillic) (Azerbaijan)')
|
||||||
|
,('be','Belarusian')
|
||||||
|
,('be-BY','Belarusian (Belarus)')
|
||||||
|
,('bg','Bulgarian')
|
||||||
|
,('bg-BG','Bulgarian (Bulgaria)')
|
||||||
|
,('bs-BA','Bosnian (Bosnia and Herzegovina)')
|
||||||
|
,('ca','Catalan')
|
||||||
|
,('ca-ES','Catalan (Spain)')
|
||||||
|
,('cs','Czech')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('cs-CZ','Czech (Czech Republic)')
|
||||||
|
,('cy','Welsh')
|
||||||
|
,('cy-GB','Welsh (United Kingdom)')
|
||||||
|
,('da','Danish')
|
||||||
|
,('da-DK','Danish (Denmark)')
|
||||||
|
,('de','German')
|
||||||
|
,('de-AT','German (Austria)')
|
||||||
|
,('de-CH','German (Switzerland)')
|
||||||
|
,('de-DE','German (Germany)')
|
||||||
|
,('de-LI','German (Liechtenstein)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('de-LU','German (Luxembourg)')
|
||||||
|
,('dv','Divehi')
|
||||||
|
,('dv-MV','Divehi (Maldives)')
|
||||||
|
,('el','Greek')
|
||||||
|
,('el-GR','Greek (Greece)')
|
||||||
|
,('en','English')
|
||||||
|
,('en-AU','English (Australia)')
|
||||||
|
,('en-BZ','English (Belize)')
|
||||||
|
,('en-CA','English (Canada)')
|
||||||
|
,('en-CB','English (Caribbean)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('en-GB','English (United Kingdom)')
|
||||||
|
,('en-IE','English (Ireland)')
|
||||||
|
,('en-JM','English (Jamaica)')
|
||||||
|
,('en-NZ','English (New Zealand)')
|
||||||
|
,('en-PH','English (Republic of the Philippines)')
|
||||||
|
,('en-TT','English (Trinidad and Tobago)')
|
||||||
|
,('en-US','English (United States)')
|
||||||
|
,('en-ZA','English (South Africa)')
|
||||||
|
,('en-ZW','English (Zimbabwe)')
|
||||||
|
,('eo','Esperanto')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es','Spanish')
|
||||||
|
,('es-AR','Spanish (Argentina)')
|
||||||
|
,('es-BO','Spanish (Bolivia)')
|
||||||
|
,('es-CL','Spanish (Chile)')
|
||||||
|
,('es-CO','Spanish (Colombia)')
|
||||||
|
,('es-CR','Spanish (Costa Rica)')
|
||||||
|
,('es-DO','Spanish (Dominican Republic)')
|
||||||
|
,('es-EC','Spanish (Ecuador)')
|
||||||
|
,('es-ES','Spanish (Castilian)')
|
||||||
|
,('es-ES','Spanish (Spain)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es-GT','Spanish (Guatemala)')
|
||||||
|
,('es-HN','Spanish (Honduras)')
|
||||||
|
,('es-MX','Spanish (Mexico)')
|
||||||
|
,('es-NI','Spanish (Nicaragua)')
|
||||||
|
,('es-PA','Spanish (Panama)')
|
||||||
|
,('es-PE','Spanish (Peru)')
|
||||||
|
,('es-PR','Spanish (Puerto Rico)')
|
||||||
|
,('es-PY','Spanish (Paraguay)')
|
||||||
|
,('es-SV','Spanish (El Salvador)')
|
||||||
|
,('es-UY','Spanish (Uruguay)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('es-VE','Spanish (Venezuela)')
|
||||||
|
,('et','Estonian')
|
||||||
|
,('et-EE','Estonian (Estonia)')
|
||||||
|
,('eu','Basque')
|
||||||
|
,('eu-ES','Basque (Spain)')
|
||||||
|
,('fa','Farsi')
|
||||||
|
,('fa-IR','Farsi (Iran)')
|
||||||
|
,('fi','Finnish')
|
||||||
|
,('fi-FI','Finnish (Finland)')
|
||||||
|
,('fo','Faroese')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('fo-FO','Faroese (Faroe Islands)')
|
||||||
|
,('fr','French')
|
||||||
|
,('fr-BE','French (Belgium)')
|
||||||
|
,('fr-CA','French (Canada)')
|
||||||
|
,('fr-CH','French (Switzerland)')
|
||||||
|
,('fr-FR','French (France)')
|
||||||
|
,('fr-LU','French (Luxembourg)')
|
||||||
|
,('fr-MC','French (Principality of Monaco)')
|
||||||
|
,('gl','Galician')
|
||||||
|
,('gl-ES','Galician (Spain)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('gu','Gujarati')
|
||||||
|
,('gu-IN','Gujarati (India)')
|
||||||
|
,('he','Hebrew')
|
||||||
|
,('he-IL','Hebrew (Israel)')
|
||||||
|
,('hi','Hindi')
|
||||||
|
,('hi-IN','Hindi (India)')
|
||||||
|
,('hr','Croatian')
|
||||||
|
,('hr-BA','Croatian (Bosnia and Herzegovina)')
|
||||||
|
,('hr-HR','Croatian (Croatia)')
|
||||||
|
,('hu','Hungarian')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('hu-HU','Hungarian (Hungary)')
|
||||||
|
,('hy','Armenian')
|
||||||
|
,('hy-AM','Armenian (Armenia)')
|
||||||
|
,('id','Indonesian')
|
||||||
|
,('id-ID','Indonesian (Indonesia)')
|
||||||
|
,('is','Icelandic')
|
||||||
|
,('is-IS','Icelandic (Iceland)')
|
||||||
|
,('it','Italian')
|
||||||
|
,('it-CH','Italian (Switzerland)')
|
||||||
|
,('it-IT','Italian (Italy)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('ja','Japanese')
|
||||||
|
,('ja-JP','Japanese (Japan)')
|
||||||
|
,('ka','Georgian')
|
||||||
|
,('ka-GE','Georgian (Georgia)')
|
||||||
|
,('kk','Kazakh')
|
||||||
|
,('kk-KZ','Kazakh (Kazakhstan)')
|
||||||
|
,('kn','Kannada')
|
||||||
|
,('kn-IN','Kannada (India)')
|
||||||
|
,('ko','Korean')
|
||||||
|
,('ko-KR','Korean (Korea)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('kok','Konkani')
|
||||||
|
,('kok-IN','Konkani (India)')
|
||||||
|
,('ky','Kyrgyz')
|
||||||
|
,('ky-KG','Kyrgyz (Kyrgyzstan)')
|
||||||
|
,('lt','Lithuanian')
|
||||||
|
,('lt-LT','Lithuanian (Lithuania)')
|
||||||
|
,('lv','Latvian')
|
||||||
|
,('lv-LV','Latvian (Latvia)')
|
||||||
|
,('mi','Maori')
|
||||||
|
,('mi-NZ','Maori (New Zealand)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('mk','FYRO Macedonian')
|
||||||
|
,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)')
|
||||||
|
,('mn','Mongolian')
|
||||||
|
,('mn-MN','Mongolian (Mongolia)')
|
||||||
|
,('mr','Marathi')
|
||||||
|
,('mr-IN','Marathi (India)')
|
||||||
|
,('ms','Malay')
|
||||||
|
,('ms-BN','Malay (Brunei Darussalam)')
|
||||||
|
,('ms-MY','Malay (Malaysia)')
|
||||||
|
,('mt','Maltese')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('mt-MT','Maltese (Malta)')
|
||||||
|
,('nb','Norwegian (Bokm?l)')
|
||||||
|
,('nb-NO','Norwegian (Bokm?l) (Norway)')
|
||||||
|
,('nl','Dutch')
|
||||||
|
,('nl-BE','Dutch (Belgium)')
|
||||||
|
,('nl-NL','Dutch (Netherlands)')
|
||||||
|
,('nn-NO','Norwegian (Nynorsk) (Norway)')
|
||||||
|
,('ns','Northern Sotho')
|
||||||
|
,('ns-ZA','Northern Sotho (South Africa)')
|
||||||
|
,('pa','Punjabi')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('pa-IN','Punjabi (India)')
|
||||||
|
,('pl','Polish')
|
||||||
|
,('pl-PL','Polish (Poland)')
|
||||||
|
,('ps','Pashto')
|
||||||
|
,('ps-AR','Pashto (Afghanistan)')
|
||||||
|
,('pt','Portuguese')
|
||||||
|
,('pt-BR','Portuguese (Brazil)')
|
||||||
|
,('pt-PT','Portuguese (Portugal)')
|
||||||
|
,('qu','Quechua')
|
||||||
|
,('qu-BO','Quechua (Bolivia)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('qu-EC','Quechua (Ecuador)')
|
||||||
|
,('qu-PE','Quechua (Peru)')
|
||||||
|
,('ro','Romanian')
|
||||||
|
,('ro-RO','Romanian (Romania)')
|
||||||
|
,('ru','Russian')
|
||||||
|
,('ru-RU','Russian (Russia)')
|
||||||
|
,('sa','Sanskrit')
|
||||||
|
,('sa-IN','Sanskrit (India)')
|
||||||
|
,('se','Sami (Northern)')
|
||||||
|
,('se-FI','Sami (Northern) (Finland)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('se-FI','Sami (Skolt) (Finland)')
|
||||||
|
,('se-FI','Sami (Inari) (Finland)')
|
||||||
|
,('se-NO','Sami (Northern) (Norway)')
|
||||||
|
,('se-NO','Sami (Lule) (Norway)')
|
||||||
|
,('se-NO','Sami (Southern) (Norway)')
|
||||||
|
,('se-SE','Sami (Northern) (Sweden)')
|
||||||
|
,('se-SE','Sami (Lule) (Sweden)')
|
||||||
|
,('se-SE','Sami (Southern) (Sweden)')
|
||||||
|
,('sk','Slovak')
|
||||||
|
,('sk-SK','Slovak (Slovakia)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('sl','Slovenian')
|
||||||
|
,('sl-SI','Slovenian (Slovenia)')
|
||||||
|
,('sq','Albanian')
|
||||||
|
,('sq-AL','Albanian (Albania)')
|
||||||
|
,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)')
|
||||||
|
,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)')
|
||||||
|
,('sr-SP','Serbian (Latin) (Serbia and Montenegro)')
|
||||||
|
,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)')
|
||||||
|
,('sv','Swedish')
|
||||||
|
,('sv-FI','Swedish (Finland)')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('sv-SE','Swedish (Sweden)')
|
||||||
|
,('sw','Swahili')
|
||||||
|
,('sw-KE','Swahili (Kenya)')
|
||||||
|
,('syr','Syriac')
|
||||||
|
,('syr-SY','Syriac (Syria)')
|
||||||
|
,('ta','Tamil')
|
||||||
|
,('ta-IN','Tamil (India)')
|
||||||
|
,('te','Telugu')
|
||||||
|
,('te-IN','Telugu (India)')
|
||||||
|
,('th','Thai')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('th-TH','Thai (Thailand)')
|
||||||
|
,('tl','Tagalog')
|
||||||
|
,('tl-PH','Tagalog (Philippines)')
|
||||||
|
,('tn','Tswana')
|
||||||
|
,('tn-ZA','Tswana (South Africa)')
|
||||||
|
,('tr','Turkish')
|
||||||
|
,('tr-TR','Turkish (Turkey)')
|
||||||
|
,('tt','Tatar')
|
||||||
|
,('tt-RU','Tatar (Russia)')
|
||||||
|
,('ts','Tsonga')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('uk','Ukrainian')
|
||||||
|
,('uk-UA','Ukrainian (Ukraine)')
|
||||||
|
,('ur','Urdu')
|
||||||
|
,('ur-PK','Urdu (Islamic Republic of Pakistan)')
|
||||||
|
,('uz','Uzbek (Latin)')
|
||||||
|
,('uz-UZ','Uzbek (Latin) (Uzbekistan)')
|
||||||
|
,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)')
|
||||||
|
,('vi','Vietnamese')
|
||||||
|
,('vi-VN','Vietnamese (Viet Nam)')
|
||||||
|
,('xh','Xhosa')
|
||||||
|
;
|
||||||
|
INSERT INTO codelang (code,country) VALUES
|
||||||
|
('xh-ZA','Xhosa (South Africa)')
|
||||||
|
,('zh','Chinese')
|
||||||
|
,('zh-CN','Chinese (S)')
|
||||||
|
,('zh-HK','Chinese (Hong Kong)')
|
||||||
|
,('zh-MO','Chinese (Macau)')
|
||||||
|
,('zh-SG','Chinese (Singapore)')
|
||||||
|
,('zh-TW','Chinese (T)')
|
||||||
|
,('zu','Zulu')
|
||||||
|
,('zu-ZA','Zulu (South Africa)')
|
||||||
|
;
|
||||||
|
/***************************/
|
||||||
|
-- Manual migrate
|
||||||
|
|
||||||
|
CREATE TABLE country_code (
|
||||||
|
code varchar(100) NULL,
|
||||||
|
country varchar(10000) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into country_code(code, country)
|
||||||
|
select distinct
|
||||||
|
t.code,
|
||||||
|
coalesce(
|
||||||
|
case when length(t.country_name2) = 1 then null else t.country_name2 end,
|
||||||
|
case when length(t.contry_name1) = 1 then null else t.contry_name1 end,
|
||||||
|
t.country
|
||||||
|
) as country
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select trim(c.code) as code,
|
||||||
|
substring(trim(c.country) from '\((.+)\)') as contry_name1,
|
||||||
|
substring(
|
||||||
|
substring(trim(c.country) from '\((.+)\)')
|
||||||
|
from '\((.*)$') as country_name2,
|
||||||
|
trim(c.country) as country
|
||||||
|
from codelang as c
|
||||||
|
) t;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
delete from location_country_languages as lcl
|
||||||
|
where lcl.country_id in
|
||||||
|
(
|
||||||
|
select
|
||||||
|
lc.id
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
lpad((row_number() over (order by t.country asc))::text, 3, '0') as code,
|
||||||
|
jsonb_build_object('en-GB', t.country) as "name"
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
distinct c.country
|
||||||
|
from country_code c
|
||||||
|
) t
|
||||||
|
) d
|
||||||
|
join location_country lc on lc.code = d.code and d."name"=lc."name"
|
||||||
|
)
|
||||||
|
;
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
delete from location_country as lcl
|
||||||
|
where lcl.id in
|
||||||
|
(
|
||||||
|
select
|
||||||
|
lc.id
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
lpad((row_number() over (order by t.country asc))::text, 3, '0') as code,
|
||||||
|
jsonb_build_object('en-GB', t.country) as "name"
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
distinct c.country
|
||||||
|
from country_code c
|
||||||
|
) t
|
||||||
|
) d
|
||||||
|
join location_country lc on lc.code = d.code and d."name"=lc."name"
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
delete from translation_language tl
|
||||||
|
where tl.id in
|
||||||
|
(
|
||||||
|
SELECT tl.id
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
select
|
||||||
|
distinct c.country, c.code
|
||||||
|
from country_code c
|
||||||
|
) t
|
||||||
|
JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country
|
||||||
|
);
|
||||||
|
|
||||||
|
commit;
|
||||||
|
|
||||||
|
drop table country_code;
|
||||||
|
drop table codelang;
|
||||||
|
|
||||||
|
commit;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user