Merge branch 'develop' into feature/product

This commit is contained in:
evgeniy-st 2019-10-22 15:58:51 +03:00
commit 45a3cd4b2b
222 changed files with 10382 additions and 1063 deletions

View File

@ -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``

View File

@ -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."""

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View 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 = [
]

View 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',
),
]

View 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 = [
]

View 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'),
),
]

View File

@ -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)

View 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'
]

View File

@ -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"""

View 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)

View 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)

View 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
View 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'),
]

View File

@ -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'),
] ]

View 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()

View File

@ -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)

View File

@ -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')

View File

@ -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')
) ),
] ]

View File

@ -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"""

View File

@ -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}')

View File

@ -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'))

View 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'))

View File

@ -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
View 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
View 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')

View File

@ -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',
},
),
]

View 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'),
),
]

View File

View 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')

View 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)

View File

View 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
View 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
View 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()

View File

@ -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."""

View 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',
),
]

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View File

@ -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"}')

View File

@ -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:

View File

@ -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)

View File

@ -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'),

View File

@ -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

View 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'),
),
]

View 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'),
),
]

View File

@ -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"""

View 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')

View File

@ -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
View 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'),
]

View 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'

View File

@ -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]

View File

@ -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',
)

View 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'),
),
]

View File

@ -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'),
),
]

View 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 = [
]

View File

@ -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'),
),
]

View 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'),
),
]

View 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'),
),
]

View File

@ -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'),
),
]

View 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',
),
]

View 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'),
),
]

View File

@ -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',
},
),
]

View 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 = [
]

View File

@ -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'),
),
]

View 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'),
),
]

View 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 = [
]

View File

@ -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',
},
),
]

View 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'),
),
]

View 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',
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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

View File

@ -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',
]

View File

@ -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(
'refresh_token': tokkens.get('refresh_token')}) {'access_token': tokkens.get('access_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)

View File

@ -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'),
] ]

View File

@ -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')
] ]

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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',
)

View File

@ -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)

View File

@ -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'),
] ]

View File

@ -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"""

View File

@ -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, )

View 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'),
),
]

View 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),
]

View 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;

View 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