Merge branch 'develop' into origin/feature/migrate-etablishment
# Conflicts: # apps/establishment/transfer_data.py # apps/transfer/management/commands/transfer.py
This commit is contained in:
commit
8f18320171
|
|
@ -243,6 +243,20 @@ class User(AbstractUser):
|
||||||
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
template_name=settings.CHANGE_EMAIL_TEMPLATE,
|
||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def favorite_establishment_ids(self):
|
||||||
|
"""Return establishment IDs that in favorites for current user."""
|
||||||
|
return self.favorites.by_content_type(app_label='establishment',
|
||||||
|
model='establishment')\
|
||||||
|
.values_list('object_id', flat=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def favorite_recipe_ids(self):
|
||||||
|
"""Return recipe IDs that in favorites for current user."""
|
||||||
|
return self.favorites.by_content_type(app_label='recipe',
|
||||||
|
model='recipe')\
|
||||||
|
.values_list('object_id', flat=True)
|
||||||
|
|
||||||
|
|
||||||
class UserRole(ProjectBaseMixin):
|
class UserRole(ProjectBaseMixin):
|
||||||
"""UserRole model."""
|
"""UserRole model."""
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
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 http.cookies import SimpleCookie
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import Role, User
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from account.models import Role, User, UserRole
|
|
||||||
|
|
||||||
class RoleTests(APITestCase):
|
class RoleTests(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -65,9 +68,11 @@ class UserRoleTests(APITestCase):
|
||||||
)
|
)
|
||||||
self.role.save()
|
self.role.save()
|
||||||
|
|
||||||
self.user_test = User.objects.create_user(username='test',
|
self.user_test = User.objects.create_user(
|
||||||
email='testemail@mail.com',
|
username='test',
|
||||||
password='passwordtest')
|
email='testemail@mail.com',
|
||||||
|
password='passwordtest'
|
||||||
|
)
|
||||||
|
|
||||||
def test_user_role_post(self):
|
def test_user_role_post(self):
|
||||||
url = reverse('back:account:user-role-list-create')
|
url = reverse('back:account:user-role-list-create')
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
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 http.cookies import SimpleCookie
|
||||||
from account.models import User
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
|
|
||||||
|
|
||||||
class AccountUserTests(APITestCase):
|
class AccountUserTests(APITestCase):
|
||||||
|
|
@ -62,7 +64,7 @@ class AccountChangePasswordTests(APITestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class AccountChangePasswordTests(APITestCase):
|
class AccountConfirmEmail(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.data = get_tokens_for_user()
|
self.data = get_tokens_for_user()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
from account.models import User
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
# Create your tests here.
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
def get_tokens_for_user(
|
def get_tokens_for_user(
|
||||||
|
|
@ -28,7 +28,7 @@ class AuthorizationTests(APITestCase):
|
||||||
self.password = data["password"]
|
self.password = data["password"]
|
||||||
|
|
||||||
def LoginTests(self):
|
def LoginTests(self):
|
||||||
data ={
|
data = {
|
||||||
'username_or_email': self.username,
|
'username_or_email': self.username,
|
||||||
'password': self.password,
|
'password': self.password,
|
||||||
'remember': True
|
'remember': True
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ from rest_framework import serializers
|
||||||
class CommentBaseSerializer(serializers.ModelSerializer):
|
class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Comment
|
model = models.Comment
|
||||||
fields = ('id', 'text', 'mark', 'user')
|
fields = ('id', 'text', 'mark', 'user', 'object_id', 'content_type')
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
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 http.cookies import SimpleCookie
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from account.models import Role, User, UserRole
|
from account.models import Role, User, UserRole
|
||||||
|
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
|
from utils.tests.tests_permissions import BasePermissionTests
|
||||||
|
|
||||||
|
|
||||||
class CommentModeratorPermissionTests(BasePermissionTests):
|
class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
|
|
@ -28,18 +30,47 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
)
|
)
|
||||||
self.userRole.save()
|
self.userRole.save()
|
||||||
|
|
||||||
content_type = ContentType.objects.get(app_label='location', model='country')
|
self.content_type = ContentType.objects.get(app_label='location', model='country')
|
||||||
|
|
||||||
self.user_test = get_tokens_for_user()
|
self.user_test = get_tokens_for_user()
|
||||||
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
||||||
user=self.user_test["user"],
|
user=self.user_test["user"],
|
||||||
object_id= self.country_ru.pk,
|
object_id=self.country_ru.pk,
|
||||||
content_type_id=content_type.id,
|
content_type_id=self.content_type.id,
|
||||||
country=self.country_ru
|
country=self.country_ru
|
||||||
)
|
)
|
||||||
self.comment.save()
|
self.comment.save()
|
||||||
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
self.url = reverse('back:comment:comment-list-create')
|
||||||
|
|
||||||
|
comment = {
|
||||||
|
"text": "Test comment POST",
|
||||||
|
"user": self.user_test["user"].id,
|
||||||
|
"object_id": self.country_ru.pk,
|
||||||
|
"content_type": self.content_type.id,
|
||||||
|
"country_id": self.country_ru.id
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(self.url, format='json', data=comment)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
comment = {
|
||||||
|
"text": "Test comment POST moder",
|
||||||
|
"user": self.moderator.id,
|
||||||
|
"object_id": self.country_ru.id,
|
||||||
|
"content_type": self.content_type.id,
|
||||||
|
"country_id": self.country_ru.id
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens = User.create_jwt_tokens(self.moderator)
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('access_token')})
|
||||||
|
|
||||||
|
response = self.client.post(self.url, format='json', data=comment)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def test_put_moderator(self):
|
def test_put_moderator(self):
|
||||||
tokens = User.create_jwt_tokens(self.moderator)
|
tokens = User.create_jwt_tokens(self.moderator)
|
||||||
|
|
@ -51,7 +82,9 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
"id": self.comment.id,
|
"id": self.comment.id,
|
||||||
"text": "test text moderator",
|
"text": "test text moderator",
|
||||||
"mark": 1,
|
"mark": 1,
|
||||||
"user": self.moderator.id
|
"user": self.moderator.id,
|
||||||
|
"object_id": self.comment.country_id,
|
||||||
|
"content_type": self.content_type.id
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.put(self.url, data=data, format='json')
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
|
|
@ -59,7 +92,7 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
response = self.client.get(self.url, format='json')
|
response = self.client.get(self.url, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_put_other_user(self):
|
def test_put_other_user(self):
|
||||||
other_user = User.objects.create_user(username='test',
|
other_user = User.objects.create_user(username='test',
|
||||||
|
|
@ -98,9 +131,10 @@ class CommentModeratorPermissionTests(BasePermissionTests):
|
||||||
"id": self.comment.id,
|
"id": self.comment.id,
|
||||||
"text": "test text moderator",
|
"text": "test text moderator",
|
||||||
"mark": 1,
|
"mark": 1,
|
||||||
"user": super_user.id
|
"user": super_user.id,
|
||||||
|
"object_id": self.country_ru.id,
|
||||||
|
"content_type": self.content_type.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.put(self.url, data=data, format='json')
|
response = self.client.put(self.url, data=data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ class CommentLstView(generics.ListCreateAPIView):
|
||||||
"""Comment list create view."""
|
"""Comment list create view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Comment RUD view."""
|
"""Comment RUD view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = serializers.CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
permission_classes = [IsCountryAdmin|IsCommentModerator]
|
|
||||||
|
permission_classes = [IsCountryAdmin | IsCommentModerator]
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from comment.models import Comment
|
||||||
from utils.admin import BaseModelAdminMixin
|
from utils.admin import BaseModelAdminMixin
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from main.models import Award
|
from main.models import Award
|
||||||
|
from product.models import Product
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,13 +48,18 @@ class CommentInline(GenericTabularInline):
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ProductInline(admin.TabularInline):
|
||||||
|
model = Product
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Establishment)
|
@admin.register(models.Establishment)
|
||||||
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
list_display = ['id', '__str__', 'image_tag', ]
|
list_display = ['id', '__str__', 'image_tag', ]
|
||||||
inlines = [
|
inlines = [
|
||||||
AwardInline, ContactPhoneInline, ContactEmailInline,
|
AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||||
ReviewInline, CommentInline]
|
ReviewInline, CommentInline, ProductInline]
|
||||||
raw_id_fields = ('address',)
|
raw_id_fields = ('address',)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from transfer.models import EstablishmentAssets
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add preview_image_url values from old db to new db'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
count = 0
|
||||||
|
url = 'https://1dc3f33f6d-3.optimicdn.com/gaultmillau.com/'
|
||||||
|
raw_qs = EstablishmentAssets.objects.raw(
|
||||||
|
'''SELECT establishment_assets.id, establishment_id, attachment_suffix_url
|
||||||
|
FROM establishment_assets
|
||||||
|
WHERE attachment_file_name IS NOT NULL
|
||||||
|
AND attachment_suffix_url IS NOT NULL
|
||||||
|
AND scope = 'public'
|
||||||
|
AND type = 'Photo'
|
||||||
|
AND menu_id IS NULL
|
||||||
|
GROUP BY establishment_id;'''
|
||||||
|
)
|
||||||
|
queryset = [vars(query) for query in raw_qs]
|
||||||
|
for obj in queryset:
|
||||||
|
establishment = Establishment.objects.filter(old_id=obj['establishment_id']).first()
|
||||||
|
if establishment:
|
||||||
|
establishment.preview_image_url = url + obj['attachment_suffix_url']
|
||||||
|
establishment.save()
|
||||||
|
count += 1
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated {count} objects.'))
|
||||||
19
apps/establishment/management/commands/add_publish_data.py
Normal file
19
apps/establishment/management/commands/add_publish_data.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from transfer.models import Establishments
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add publish values from old db to new db'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
old_establishments = Establishments.objects.all()
|
||||||
|
count = 0
|
||||||
|
for item in old_establishments:
|
||||||
|
obj = Establishment.objects.filter(old_id=item.id).first()
|
||||||
|
if obj:
|
||||||
|
count += 1
|
||||||
|
obj.is_publish = item.state == 'published'
|
||||||
|
obj.save()
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated {count} objects.'))
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Remove old establishments from new bd'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
old_establishments = Establishment.objects.exclude(old_id__isnull=True)
|
||||||
|
count = old_establishments.count()
|
||||||
|
old_establishments.delete()
|
||||||
|
self.stdout.write(self.style.WARNING(f'Deleted {count} objects.'))
|
||||||
18
apps/establishment/migrations/0044_position_index_name.py
Normal file
18
apps/establishment/migrations/0044_position_index_name.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-24 14:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0043_establishment_currency'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='position',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(db_index=True, max_length=255, null=True, unique=True, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0046_merge_20191030_0858.py
Normal file
14
apps/establishment/migrations/0046_merge_20191030_0858.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 08:58
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0045_auto_20191029_0719'),
|
||||||
|
('establishment', '0044_position_index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0046_merge_20191030_1618.py
Normal file
14
apps/establishment/migrations/0046_merge_20191030_1618.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 16:18
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0045_auto_20191029_0719'),
|
||||||
|
('establishment', '0044_position_index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0047_merge_20191030_1714.py
Normal file
14
apps/establishment/migrations/0047_merge_20191030_1714.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 17:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0046_merge_20191030_1618'),
|
||||||
|
('establishment', '0046_merge_20191030_0858'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -121,6 +121,11 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
def with_type_related(self):
|
def with_type_related(self):
|
||||||
return self.prefetch_related('establishment_subtypes')
|
return self.prefetch_related('establishment_subtypes')
|
||||||
|
|
||||||
|
def with_es_related(self):
|
||||||
|
"""Return qs with related for ES indexing objects."""
|
||||||
|
return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country').\
|
||||||
|
prefetch_related('tags', 'schedule')
|
||||||
|
|
||||||
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:
|
||||||
|
|
@ -244,14 +249,12 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def annotate_in_favorites(self, user):
|
def annotate_in_favorites(self, user):
|
||||||
"""Annotate flag in_favorites"""
|
"""Annotate flag in_favorites"""
|
||||||
favorite_establishments = []
|
favorite_establishment_ids = []
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
favorite_establishments = user.favorites.by_content_type(app_label='establishment',
|
favorite_establishment_ids = user.favorite_establishment_ids
|
||||||
model='establishment') \
|
|
||||||
.values_list('object_id', flat=True)
|
|
||||||
return self.annotate(in_favorites=Case(
|
return self.annotate(in_favorites=Case(
|
||||||
When(
|
When(
|
||||||
id__in=favorite_establishments,
|
id__in=favorite_establishment_ids,
|
||||||
then=True),
|
then=True),
|
||||||
default=False,
|
default=False,
|
||||||
output_field=models.BooleanField(default=False)))
|
output_field=models.BooleanField(default=False)))
|
||||||
|
|
@ -438,7 +441,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
@property
|
@property
|
||||||
def works_now(self):
|
def works_now(self):
|
||||||
""" Is establishment working now """
|
""" Is establishment working now """
|
||||||
now_at_est_tz = datetime.now(tz=ptz(self.tz))
|
now_at_est_tz = datetime.now(tz=self.tz)
|
||||||
current_week = now_at_est_tz.weekday()
|
current_week = now_at_est_tz.weekday()
|
||||||
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
||||||
if schedule_for_today is None or schedule_for_today.closed_at is None or schedule_for_today.opening_at is None:
|
if schedule_for_today is None or schedule_for_today.closed_at is None or schedule_for_today.opening_at is None:
|
||||||
|
|
@ -483,6 +486,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
"""
|
"""
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wines(self):
|
||||||
|
"""Return list products with type wine"""
|
||||||
|
return self.products.wines()
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseAttributes, TranslatedFieldsMixin):
|
class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||||
"""Position model."""
|
"""Position model."""
|
||||||
|
|
@ -494,6 +502,9 @@ class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||||
|
|
||||||
priority = models.IntegerField(unique=True, null=True, default=None)
|
priority = models.IntegerField(unique=True, null=True, default=None)
|
||||||
|
|
||||||
|
index_name = models.CharField(max_length=255, db_index=True, unique=True,
|
||||||
|
null=True, verbose_name=_('Index name'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,14 @@ 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 location.serializers import AddressBaseSerializer
|
from location.serializers import AddressBaseSerializer
|
||||||
from main.serializers import AwardSerializer, CurrencySerializer
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from timetable.serialziers import ScheduleRUDSerializer
|
from timetable.serialziers import ScheduleRUDSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import ProjectModelSerializer
|
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||||
from utils.serializers import TranslatedField
|
FavoritesCreateSerializer)
|
||||||
|
|
||||||
|
|
||||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -118,6 +117,18 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'use_subtypes': {'write_only': True},
|
'use_subtypes': {'write_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EstablishmentTypeGeoSerializer(EstablishmentTypeBaseSerializer):
|
||||||
|
"""Serializer for EstablishmentType model w/ index_name."""
|
||||||
|
|
||||||
|
class Meta(EstablishmentTypeBaseSerializer.Meta):
|
||||||
|
fields = EstablishmentTypeBaseSerializer.Meta.fields + [
|
||||||
|
'index_name'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
**EstablishmentTypeBaseSerializer.Meta.extra_kwargs,
|
||||||
|
'index_name': {'read_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for EstablishmentSubType models."""
|
"""Serializer for EstablishmentSubType models."""
|
||||||
|
|
@ -147,12 +158,13 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
||||||
position_translated = serializers.CharField(source='position.name_translated')
|
position_translated = serializers.CharField(source='position.name_translated')
|
||||||
awards = AwardSerializer(source='employee.awards', many=True)
|
awards = AwardSerializer(source='employee.awards', many=True)
|
||||||
priority = serializers.IntegerField(source='position.priority')
|
priority = serializers.IntegerField(source='position.priority')
|
||||||
|
position_index_name = serializers.CharField(source='position.index_name')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Employee
|
model = models.Employee
|
||||||
fields = ('id', 'name', 'position_translated', 'awards', 'priority')
|
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
|
|
@ -184,6 +196,19 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentGeoSerializer(EstablishmentBaseSerializer):
|
||||||
|
"""Serializer for Geo view."""
|
||||||
|
|
||||||
|
type = EstablishmentTypeGeoSerializer(source='establishment_type', read_only=True)
|
||||||
|
|
||||||
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
|
'type'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
"""Serializer for Establishment model."""
|
"""Serializer for Establishment model."""
|
||||||
|
|
||||||
|
|
@ -280,26 +305,13 @@ class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
"""Create comment serializer"""
|
"""Serializer to favorite object w/ model Establishment."""
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Serializer for model Comment"""
|
|
||||||
model = Favorites
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'created',
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_user(self):
|
|
||||||
"""Get user from request"""
|
|
||||||
return self.context.get('request').user
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Overridden validate method"""
|
||||||
# Check establishment object
|
# Check establishment object
|
||||||
establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug')
|
establishment_qs = models.Establishment.objects.filter(slug=self.slug)
|
||||||
establishment_qs = models.Establishment.objects.filter(slug=establishment_slug)
|
|
||||||
|
|
||||||
# Check establishment obj by slug from lookup_kwarg
|
# Check establishment obj by slug from lookup_kwarg
|
||||||
if not establishment_qs.exists():
|
if not establishment_qs.exists():
|
||||||
|
|
@ -308,18 +320,16 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
||||||
establishment = establishment_qs.first()
|
establishment = establishment_qs.first()
|
||||||
|
|
||||||
# Check existence in favorites
|
# Check existence in favorites
|
||||||
if self.get_user().favorites.by_content_type(app_label='establishment',
|
if establishment.favorites.filter(user=self.user).exists():
|
||||||
model='establishment')\
|
|
||||||
.by_object_id(object_id=establishment.id).exists():
|
|
||||||
raise utils_exceptions.FavoritesError()
|
raise utils_exceptions.FavoritesError()
|
||||||
|
|
||||||
attrs['establishment'] = establishment
|
attrs['establishment'] = establishment
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data, *args, **kwargs):
|
def create(self, validated_data, *args, **kwargs):
|
||||||
"""Override create method"""
|
"""Overridden create method"""
|
||||||
validated_data.update({
|
validated_data.update({
|
||||||
'user': self.get_user(),
|
'user': self.user,
|
||||||
'content_object': validated_data.pop('establishment')
|
'content_object': validated_data.pop('establishment')
|
||||||
})
|
})
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
"""Establishment app tasks."""
|
"""Establishment app tasks."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from celery.schedules import crontab
|
||||||
|
from celery.task import periodic_task
|
||||||
|
from django.core import management
|
||||||
|
from django_elasticsearch_dsl.management.commands import search_index
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,10 +17,15 @@ logger = logging.getLogger(__name__)
|
||||||
def recalculate_price_levels_by_country(country_id):
|
def recalculate_price_levels_by_country(country_id):
|
||||||
try:
|
try:
|
||||||
country = Country.objects.get(pk=country_id)
|
country = Country.objects.get(pk=country_id)
|
||||||
except Country.DoesNotExist as ex:
|
except Country.DoesNotExist as _:
|
||||||
logger.error(f'ESTABLISHMENT. Country does not exist. ID {country_id}')
|
logger.error(f'ESTABLISHMENT. Country does not exist. ID {country_id}')
|
||||||
else:
|
else:
|
||||||
qs = models.Establishment.objects.filter(address__city__country=country)
|
qs = models.Establishment.objects.filter(address__city__country=country)
|
||||||
for establishment in qs:
|
for establishment in qs:
|
||||||
establishment.recalculate_price_level(low_price=country.low_price,
|
establishment.recalculate_price_level(low_price=country.low_price,
|
||||||
high_price=country.high_price)
|
high_price=country.high_price)
|
||||||
|
|
||||||
|
@periodic_task(run_every=crontab(minute=59))
|
||||||
|
def rebuild_establishment_indices():
|
||||||
|
management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__],
|
||||||
|
force=True)
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@ class BaseTestCase(APITestCase):
|
||||||
self.newsletter = True
|
self.newsletter = True
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
username=self.username, email=self.email, password=self.password)
|
username=self.username, email=self.email, password=self.password)
|
||||||
#get tokkens
|
# get tokens
|
||||||
tokkens = User.create_jwt_tokens(self.user)
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
self.client.cookies = SimpleCookie(
|
self.client.cookies = SimpleCookie(
|
||||||
{'access_token': tokkens.get('access_token'),
|
{'access_token': tokens.get('access_token'),
|
||||||
'refresh_token': tokkens.get('refresh_token')})
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
|
self.establishment_type = EstablishmentType.objects.create(
|
||||||
|
name="Test establishment type")
|
||||||
|
|
||||||
# Create lang object
|
# Create lang object
|
||||||
self.lang = Language.objects.get(
|
self.lang = Language.objects.get(
|
||||||
|
|
@ -42,12 +43,15 @@ class BaseTestCase(APITestCase):
|
||||||
country=self.country_ru)
|
country=self.country_ru)
|
||||||
self.region.save()
|
self.region.save()
|
||||||
|
|
||||||
self.city = City.objects.create(name='Mosocow', code='01',
|
self.city = City.objects.create(
|
||||||
region=self.region, country=self.country_ru)
|
name='Mosocow', code='01',
|
||||||
|
region=self.region,
|
||||||
|
country=self.country_ru)
|
||||||
self.city.save()
|
self.city.save()
|
||||||
|
|
||||||
self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya',
|
self.address = Address.objects.create(
|
||||||
number=2, postal_code='010100')
|
city=self.city, street_name_1='Krasnaya',
|
||||||
|
number=2, postal_code='010100')
|
||||||
self.address.save()
|
self.address.save()
|
||||||
|
|
||||||
self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER)
|
self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER)
|
||||||
|
|
@ -63,8 +67,9 @@ class BaseTestCase(APITestCase):
|
||||||
|
|
||||||
self.establishment.save()
|
self.establishment.save()
|
||||||
|
|
||||||
self.user_role = UserRole.objects.create(user=self.user, role=self.role,
|
self.user_role = UserRole.objects.create(
|
||||||
establishment=self.establishment)
|
user=self.user, role=self.role,
|
||||||
|
establishment=self.establishment)
|
||||||
self.user_role.save()
|
self.user_role.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,12 +133,11 @@ class EmployeeTests(BaseTestCase):
|
||||||
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):
|
class ChildTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
# Test childs
|
|
||||||
class EmailTests(ChildTestCase):
|
class EmailTests(ChildTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q, F
|
||||||
|
|
||||||
from transfer.models import Establishments
|
from transfer.models import Establishments, Dishes
|
||||||
from transfer.serializers.establishment import EstablishmentSerializer
|
from transfer.serializers.establishment import EstablishmentSerializer
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
|
from transfer.serializers.plate import PlateSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_establishment():
|
def transfer_establishment():
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
old_establishments = Establishments.objects.exclude(
|
old_establishments = Establishments.objects.filter(
|
||||||
|
location__city__name__icontains='paris',
|
||||||
|
).exclude(
|
||||||
Q(type='Wineyard') |
|
Q(type='Wineyard') |
|
||||||
Q(location__timezone__isnull=True)
|
Q(location__timezone__isnull=True)
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
|
|
@ -19,6 +22,7 @@ def transfer_establishment():
|
||||||
'schedules_set',
|
'schedules_set',
|
||||||
'descriptions_set',
|
'descriptions_set',
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in old_establishments:
|
for item in old_establishments:
|
||||||
data = {
|
data = {
|
||||||
'old_id': item.id,
|
'old_id': item.id,
|
||||||
|
|
@ -28,6 +32,7 @@ def transfer_establishment():
|
||||||
'type': item.type,
|
'type': item.type,
|
||||||
'phone': item.phone,
|
'phone': item.phone,
|
||||||
'created': item.created_at,
|
'created': item.created_at,
|
||||||
|
'state': item.state,
|
||||||
'description': {},
|
'description': {},
|
||||||
'website': None,
|
'website': None,
|
||||||
'facebook': None,
|
'facebook': None,
|
||||||
|
|
@ -79,6 +84,25 @@ def transfer_establishment():
|
||||||
pprint(f"Establishment serializer errors: {serialized_data.errors}")
|
pprint(f"Establishment serializer errors: {serialized_data.errors}")
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_menu():
|
||||||
|
dishes = Dishes.objects.exclude(
|
||||||
|
Q(establishment_id__isnull=True) |
|
||||||
|
Q(dish_type__isnull=True) |
|
||||||
|
Q(price__isnull=True) |
|
||||||
|
Q(currency__isnull=True) |
|
||||||
|
Q(name__isnull=True) |
|
||||||
|
Q(name__exact='')
|
||||||
|
).annotate(
|
||||||
|
country_code=F('establishment__location__country_code'),
|
||||||
|
)
|
||||||
|
|
||||||
|
serialized_data = PlateSerializer(data=list(dishes.values()), many=True)
|
||||||
|
if serialized_data.is_valid():
|
||||||
|
serialized_data.save()
|
||||||
|
else:
|
||||||
|
pprint(f"Menu serializer errors: {serialized_data.errors}")
|
||||||
|
|
||||||
|
|
||||||
def transfer_establishment_addresses():
|
def transfer_establishment_addresses():
|
||||||
old_establishments = Establishments.objects.only("id", "location_id").exclude(
|
old_establishments = Establishments.objects.only("id", "location_id").exclude(
|
||||||
Q(type='Wineyard') |
|
Q(type='Wineyard') |
|
||||||
|
|
@ -105,4 +129,5 @@ data_types = {
|
||||||
transfer_establishment,
|
transfer_establishment,
|
||||||
transfer_establishment_addresses
|
transfer_establishment_addresses
|
||||||
]
|
]
|
||||||
|
"menu": [transfer_menu],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
name='recent-reviews'),
|
name='recent-reviews'),
|
||||||
# path('wineries/', views.WineriesListView.as_view(), name='wineries-list'),
|
|
||||||
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
||||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
||||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
|
|
@ -18,5 +17,5 @@ urlpatterns = [
|
||||||
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
|
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
|
||||||
name='rud-comment'),
|
name='rud-comment'),
|
||||||
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='add-to-favorites')
|
name='create-destroy-favorites')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -138,21 +138,18 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
establishment_obj = get_object_or_404(models.Establishment,
|
establishment = get_object_or_404(models.Establishment,
|
||||||
slug=self.kwargs['slug'])
|
slug=self.kwargs['slug'])
|
||||||
obj = get_object_or_404(
|
favorites = get_object_or_404(establishment.favorites.filter(user=self.request.user))
|
||||||
self.request.user.favorites.by_content_type(app_label='establishment',
|
|
||||||
model='establishment')
|
|
||||||
.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, favorites)
|
||||||
return obj
|
return favorites
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
|
class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
|
||||||
"""Resource for getting list of nearest establishments."""
|
"""Resource for getting list of nearest establishments."""
|
||||||
|
|
||||||
serializer_class = serializers.EstablishmentBaseSerializer
|
serializer_class = serializers.EstablishmentGeoSerializer
|
||||||
filter_class = filters.EstablishmentFilter
|
filter_class = filters.EstablishmentFilter
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
@ -170,14 +167,3 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi
|
||||||
return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
|
return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
|
||||||
if v is not None})
|
if v is not None})
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
# Wineries
|
|
||||||
# todo: find out about difference between subtypes data
|
|
||||||
# class WineriesListView(EstablishmentListView):
|
|
||||||
# """Return list establishments with type Wineries"""
|
|
||||||
#
|
|
||||||
# def get_queryset(self):
|
|
||||||
# """Overridden get_queryset method."""
|
|
||||||
# qs = super(WineriesListView, self).get_queryset()
|
|
||||||
# return qs.with_type_related().wineries()
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,6 @@ class BaseTestCase(APITestCase):
|
||||||
description={"en-GB": "description of test establishment"},
|
description={"en-GB": "description of test establishment"},
|
||||||
establishment_type=self.test_establishment_type,
|
establishment_type=self.test_establishment_type,
|
||||||
is_publish=True)
|
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])
|
self.test_establishment.favorites.set([self.test_favorites])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,6 @@ app_name = 'favorites'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
|
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
|
||||||
name='establishment-list'),
|
name='establishment-list'),
|
||||||
|
path('products/', views.FavoritesProductListView.as_view(),
|
||||||
|
name='product-list'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
"""Views for app favorites."""
|
"""Views for app favorites."""
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
|
from establishment.filters import EstablishmentFilter
|
||||||
from establishment.serializers import EstablishmentBaseSerializer
|
from establishment.serializers import EstablishmentBaseSerializer
|
||||||
|
from product.models import Product
|
||||||
|
from product.serializers import ProductBaseSerializer
|
||||||
|
from product.filters import ProductFilterSet
|
||||||
from .models import Favorites
|
from .models import Favorites
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -14,11 +18,24 @@ class FavoritesBaseView(generics.GenericAPIView):
|
||||||
|
|
||||||
|
|
||||||
class FavoritesEstablishmentListView(generics.ListAPIView):
|
class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
"""List views for favorites"""
|
"""List views for establishments in favorites."""
|
||||||
|
|
||||||
serializer_class = EstablishmentBaseSerializer
|
serializer_class = EstablishmentBaseSerializer
|
||||||
|
filter_class = EstablishmentFilter
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return Establishment.objects.filter(favorites__user=self.request.user)\
|
return Establishment.objects.filter(favorites__user=self.request.user)\
|
||||||
.order_by('-favorites')
|
.order_by('-favorites')
|
||||||
|
|
||||||
|
|
||||||
|
class FavoritesProductListView(generics.ListAPIView):
|
||||||
|
"""List views for products in favorites."""
|
||||||
|
|
||||||
|
serializer_class = ProductBaseSerializer
|
||||||
|
filter_class = ProductFilterSet
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method"""
|
||||||
|
return Product.objects.filter(favorites__user=self.request.user)\
|
||||||
|
.order_by('-favorites')
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,22 @@ class CityAdmin(admin.ModelAdmin):
|
||||||
"""City admin."""
|
"""City admin."""
|
||||||
|
|
||||||
|
|
||||||
|
class WineAppellationInline(admin.TabularInline):
|
||||||
|
model = models.WineAppellation
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.WineRegion)
|
||||||
|
class WineRegionAdmin(admin.ModelAdmin):
|
||||||
|
"""WineRegion admin."""
|
||||||
|
inlines = [WineAppellationInline, ]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.WineAppellation)
|
||||||
|
class WineAppellationAdmin(admin.ModelAdmin):
|
||||||
|
"""WineAppellation admin."""
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Address)
|
@admin.register(models.Address)
|
||||||
class AddressAdmin(admin.OSMGeoAdmin):
|
class AddressAdmin(admin.OSMGeoAdmin):
|
||||||
"""Address admin."""
|
"""Address admin."""
|
||||||
|
|
|
||||||
41
apps/location/migrations/0013_wineappellation_wineregion.py
Normal file
41
apps/location/migrations/0013_wineappellation_wineregion.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-28 07:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0012_data_migrate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WineRegion',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', utils.models.TJSONField(help_text='{"en-GB":"some text"}', verbose_name='Name')),
|
||||||
|
('country', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='country')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'wine region',
|
||||||
|
'verbose_name_plural': 'wine regions',
|
||||||
|
},
|
||||||
|
bases=(utils.models.TranslatedFieldsMixin, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WineAppellation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', utils.models.TJSONField(help_text='{"en-GB":"some text"}', verbose_name='Name')),
|
||||||
|
('wine_region', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='appellations', to='location.WineRegion', verbose_name='wine region')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'wine appellation',
|
||||||
|
'verbose_name_plural': 'wine appellations',
|
||||||
|
},
|
||||||
|
bases=(utils.models.TranslatedFieldsMixin, models.Model),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/location/migrations/0019_merge_20191030_0858.py
Normal file
14
apps/location/migrations/0019_merge_20191030_0858.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 08:58
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0018_address_old_id'),
|
||||||
|
('location', '0013_wineappellation_wineregion'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/location/migrations/0019_merge_20191030_1618.py
Normal file
14
apps/location/migrations/0019_merge_20191030_1618.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 16:18
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0018_address_old_id'),
|
||||||
|
('location', '0013_wineappellation_wineregion'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/location/migrations/0020_merge_20191030_1714.py
Normal file
14
apps/location/migrations/0020_merge_20191030_1714.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 17:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0019_merge_20191030_0858'),
|
||||||
|
('location', '0019_merge_20191030_1618'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -134,6 +134,49 @@ class Address(models.Model):
|
||||||
return self.city.country_id
|
return self.city.country_id
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegionQuerySet(models.QuerySet):
|
||||||
|
"""Wine region queryset."""
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegion(TranslatedFieldsMixin, models.Model):
|
||||||
|
"""Wine region model."""
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
name = TJSONField(verbose_name=_('Name'),
|
||||||
|
help_text='{"en-GB":"some text"}')
|
||||||
|
country = models.ForeignKey(Country, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_('country'))
|
||||||
|
|
||||||
|
objects = WineRegionQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name_plural = _('wine regions')
|
||||||
|
verbose_name = _('wine region')
|
||||||
|
|
||||||
|
|
||||||
|
class WineAppellationQuerySet(models.QuerySet):
|
||||||
|
"""Wine appellation queryset."""
|
||||||
|
|
||||||
|
|
||||||
|
class WineAppellation(TranslatedFieldsMixin, models.Model):
|
||||||
|
"""Wine appellation model."""
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
name = TJSONField(verbose_name=_('Name'),
|
||||||
|
help_text='{"en-GB":"some text"}')
|
||||||
|
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
|
||||||
|
related_name='appellations',
|
||||||
|
verbose_name=_('wine region'))
|
||||||
|
|
||||||
|
objects = WineAppellationQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name_plural = _('wine appellations')
|
||||||
|
verbose_name = _('wine appellation')
|
||||||
|
|
||||||
|
|
||||||
# todo: Make recalculate price levels
|
# todo: Make recalculate price levels
|
||||||
@receiver(post_save, sender=Country)
|
@receiver(post_save, sender=Country)
|
||||||
def run_recalculate_price_levels(sender, instance, **kwargs):
|
def run_recalculate_price_levels(sender, instance, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,5 @@ class CountryBackSerializer(common.CountrySerializer):
|
||||||
'code',
|
'code',
|
||||||
'svg_image',
|
'svg_image',
|
||||||
'name',
|
'name',
|
||||||
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -148,3 +148,31 @@ class AddressDetailSerializer(AddressBaseSerializer):
|
||||||
'city_id',
|
'city_id',
|
||||||
'city',
|
'city',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WineAppellationBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Wine appellations."""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.WineAppellation
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegionBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Wine region serializer."""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
country = CountrySerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.WineRegion
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'country',
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,11 @@ class BaseTestCase(APITestCase):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
username=self.username, email=self.email, password=self.password)
|
username=self.username, email=self.email, password=self.password)
|
||||||
|
|
||||||
# get tokens
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
|
|
||||||
# self.user.is_superuser = True
|
|
||||||
# self.user.save()
|
|
||||||
|
|
||||||
tokkens = User.create_jwt_tokens(self.user)
|
|
||||||
self.client.cookies = SimpleCookie(
|
self.client.cookies = SimpleCookie(
|
||||||
{'access_token': tokkens.get('access_token'),
|
{'access_token': tokens.get('access_token'),
|
||||||
'refresh_token': tokkens.get('refresh_token')})
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
self.lang = Language.objects.get(
|
self.lang = Language.objects.get(
|
||||||
title='Russia',
|
title='Russia',
|
||||||
|
|
@ -102,7 +98,6 @@ class RegionTests(BaseTestCase):
|
||||||
|
|
||||||
user_role.save()
|
user_role.save()
|
||||||
|
|
||||||
|
|
||||||
def test_region_CRUD(self):
|
def test_region_CRUD(self):
|
||||||
response = self.client.get('/api/back/location/regions/', format='json')
|
response = self.client.get('/api/back/location/regions/', format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,48 @@ from rest_framework import generics
|
||||||
from location import models, serializers
|
from location import models, serializers
|
||||||
from location.views import common
|
from location.views import common
|
||||||
from utils.permissions import IsCountryAdmin
|
from utils.permissions import IsCountryAdmin
|
||||||
|
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
|
|
||||||
class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView):
|
class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model Address."""
|
"""Create view for model Address."""
|
||||||
serializer_class = serializers.AddressDetailSerializer
|
serializer_class = serializers.AddressDetailSerializer
|
||||||
queryset = models.Address.objects.all()
|
queryset = models.Address.objects.all()
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model Address."""
|
"""RUD view for model Address."""
|
||||||
serializer_class = serializers.AddressDetailSerializer
|
serializer_class = serializers.AddressDetailSerializer
|
||||||
queryset = models.Address.objects.all()
|
queryset = models.Address.objects.all()
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
# City
|
# City
|
||||||
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model City."""
|
"""RUD view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CitySerializer
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
# Region
|
# Region
|
||||||
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model Region"""
|
"""Create view for model Region"""
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Retrieve view for model Region"""
|
"""Retrieve view for model Region"""
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
# Country
|
# Country
|
||||||
|
|
@ -50,10 +54,11 @@ class CountryListCreateView(generics.ListCreateAPIView):
|
||||||
queryset = models.Country.objects.all()
|
queryset = models.Country.objects.all()
|
||||||
serializer_class = serializers.CountryBackSerializer
|
serializer_class = serializers.CountryBackSerializer
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model Country."""
|
"""RUD view for model Country."""
|
||||||
serializer_class = serializers.CountryBackSerializer
|
serializer_class = serializers.CountryBackSerializer
|
||||||
permission_classes = [IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
queryset = models.Country.objects.all()
|
queryset = models.Country.objects.all()
|
||||||
|
|
@ -63,6 +63,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
# todo: remove this
|
# todo: remove this
|
||||||
country_code = serializers.CharField(source='subdomain', read_only=True)
|
country_code = serializers.CharField(source='subdomain', read_only=True)
|
||||||
|
|
||||||
|
country_name = serializers.CharField(source='country.name_translated', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
@ -78,7 +80,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
'config',
|
'config',
|
||||||
'ad_config',
|
'ad_config',
|
||||||
'published_features',
|
'published_features',
|
||||||
'currency'
|
'currency',
|
||||||
|
'country_name'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class NewsAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""News admin."""
|
"""News admin."""
|
||||||
raw_id_fields = ('address',)
|
raw_id_fields = ('address',)
|
||||||
actions = [send_email_action]
|
actions = [send_email_action]
|
||||||
|
raw_id_fields = ('news_type', 'address', 'country')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.NewsGallery)
|
@admin.register(models.NewsGallery)
|
||||||
|
|
|
||||||
0
apps/news/management/__init__.py
Normal file
0
apps/news/management/__init__.py
Normal file
0
apps/news/management/commands/__init__.py
Normal file
0
apps/news/management/commands/__init__.py
Normal file
13
apps/news/management/commands/rm_all_news.py
Normal file
13
apps/news/management/commands/rm_all_news.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from news.models import News
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Remove all news from new bd'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
old_news = News.objects.all()
|
||||||
|
count = old_news.count()
|
||||||
|
old_news.delete()
|
||||||
|
self.stdout.write(self.style.WARNING(f'Deleted {count} objects.'))
|
||||||
13
apps/news/management/commands/rm_old_news.py
Normal file
13
apps/news/management/commands/rm_old_news.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from news.models import News
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Remove old news from new bd'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
old_news = News.objects.exclude(old_id__isnull=True)
|
||||||
|
count = old_news.count()
|
||||||
|
old_news.delete()
|
||||||
|
self.stdout.write(self.style.WARNING(f'Deleted {count} objects.'))
|
||||||
18
apps/news/migrations/0029_auto_20191025_1241.py
Normal file
18
apps/news/migrations/0029_auto_20191025_1241.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-25 12:41
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0003_auto_20191003_1228'),
|
||||||
|
('news', '0028_auto_20191024_1649'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='newsgallery',
|
||||||
|
unique_together={('news', 'image'), ('news', 'is_main')},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/news/migrations/0030_news_old_id.py
Normal file
18
apps/news/migrations/0030_news_old_id.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-29 08:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0029_merge_20191025_0906'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='old_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='odl id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/news/migrations/0031_merge_20191029_0858.py
Normal file
14
apps/news/migrations/0031_merge_20191029_0858.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-29 08:58
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0030_news_old_id'),
|
||||||
|
('news', '0030_merge_20191028_0725'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
18
apps/news/migrations/0032_auto_20191030_1149.py
Normal file
18
apps/news/migrations/0032_auto_20191030_1149.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 11:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0031_merge_20191029_0858'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='old_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/news/migrations/0032_merge_20191030_0858.py
Normal file
14
apps/news/migrations/0032_merge_20191030_0858.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 08:58
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0029_auto_20191025_1241'),
|
||||||
|
('news', '0031_merge_20191029_0858'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/news/migrations/0033_merge_20191030_1618.py
Normal file
14
apps/news/migrations/0033_merge_20191030_1618.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 16:18
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0032_auto_20191030_1149'),
|
||||||
|
('news', '0029_auto_20191025_1241'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/news/migrations/0034_merge_20191030_1714.py
Normal file
14
apps/news/migrations/0034_merge_20191030_1714.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 17:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0033_merge_20191030_1618'),
|
||||||
|
('news', '0032_merge_20191030_0858'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
|
|
@ -126,6 +126,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
|
||||||
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
|
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
|
news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
|
||||||
verbose_name=_('news type'))
|
verbose_name=_('news type'))
|
||||||
title = TJSONField(blank=True, null=True, default=None,
|
title = TJSONField(blank=True, null=True, default=None,
|
||||||
|
|
@ -235,4 +236,4 @@ class NewsGallery(models.Model):
|
||||||
"""NewsGallery meta class."""
|
"""NewsGallery meta class."""
|
||||||
verbose_name = _('news gallery')
|
verbose_name = _('news gallery')
|
||||||
verbose_name_plural = _('news galleries')
|
verbose_name_plural = _('news galleries')
|
||||||
unique_together = ('news', 'is_main')
|
unique_together = (('news', 'is_main'), ('news', 'image'))
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ class CropImageSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class NewsImageSerializer(serializers.ModelSerializer):
|
class NewsImageSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for returning crop images of news image."""
|
"""Serializer for returning crop images of news image."""
|
||||||
|
|
||||||
orientation_display = serializers.CharField(source='get_orientation_display',
|
orientation_display = serializers.CharField(source='get_orientation_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
original_url = serializers.URLField(source='image.url')
|
original_url = serializers.URLField(source='image.url')
|
||||||
|
|
@ -149,6 +150,17 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NewsSimilarListSerializer(NewsBaseSerializer):
|
||||||
|
"""List serializer for News model."""
|
||||||
|
preview_image_url = serializers.URLField()
|
||||||
|
|
||||||
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = NewsBaseSerializer.Meta.fields + (
|
||||||
|
'preview_image_url',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewsListSerializer(NewsBaseSerializer):
|
class NewsListSerializer(NewsBaseSerializer):
|
||||||
"""List serializer for News model."""
|
"""List serializer for News model."""
|
||||||
|
|
||||||
|
|
@ -191,8 +203,8 @@ class NewsDetailSerializer(NewsBaseSerializer):
|
||||||
class NewsDetailWebSerializer(NewsDetailSerializer):
|
class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
"""News detail serializer for web users.."""
|
"""News detail serializer for web users.."""
|
||||||
|
|
||||||
same_theme = NewsBaseSerializer(many=True, read_only=True)
|
same_theme = NewsSimilarListSerializer(many=True, read_only=True)
|
||||||
should_read = NewsBaseSerializer(many=True, read_only=True)
|
should_read = NewsSimilarListSerializer(many=True, read_only=True)
|
||||||
agenda = AgendaSerializer()
|
agenda = AgendaSerializer()
|
||||||
banner = NewsBannerSerializer()
|
banner = NewsBannerSerializer()
|
||||||
|
|
||||||
|
|
@ -265,7 +277,6 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
"""Override validate method."""
|
"""Override validate method."""
|
||||||
news_pk = self.get_request_kwargs().get('pk')
|
news_pk = self.get_request_kwargs().get('pk')
|
||||||
image_id = self.get_request_kwargs().get('image_id')
|
image_id = self.get_request_kwargs().get('image_id')
|
||||||
is_main = attrs.get('is_main')
|
|
||||||
|
|
||||||
news_qs = models.News.objects.filter(pk=news_pk)
|
news_qs = models.News.objects.filter(pk=news_pk)
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
@ -278,12 +289,6 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
news = news_qs.first()
|
news = news_qs.first()
|
||||||
image = image_qs.first()
|
image = image_qs.first()
|
||||||
|
|
||||||
if news.news_gallery.filter(image=image).exists():
|
|
||||||
raise serializers.ValidationError({'detail': _('Image is already added')})
|
|
||||||
|
|
||||||
if is_main and news.news_gallery.main_image().exists():
|
|
||||||
raise serializers.ValidationError({'detail': _('Main image is already added')})
|
|
||||||
|
|
||||||
attrs['news'] = news
|
attrs['news'] = news
|
||||||
attrs['image'] = image
|
attrs['image'] = image
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,22 @@ class NewsTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
|
def test_news_post(self):
|
||||||
|
test_news ={
|
||||||
|
"title": {"en-GB": "Test news POST"},
|
||||||
|
"news_type_id": self.test_news_type.id,
|
||||||
|
"description": {"en-GB": "Description test news"},
|
||||||
|
"start": datetime.now() + timedelta(hours=-2),
|
||||||
|
"end": datetime.now() + timedelta(hours=2),
|
||||||
|
"state": News.PUBLISHED,
|
||||||
|
"slug": 'test-news-slug_post',
|
||||||
|
"country_id": self.country_ru.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
url = reverse("back:news:list-create")
|
||||||
|
response = self.client.post(url, data=test_news, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def test_web_news(self):
|
def test_web_news(self):
|
||||||
response = self.client.get(reverse('web:news:list'))
|
response = self.client.get(reverse('web:news:list'))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,52 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from django.db.models import Value, IntegerField, F
|
from django.db.models import Aggregate, CharField, Value
|
||||||
|
from django.db.models import IntegerField, F
|
||||||
|
|
||||||
from news.models import NewsType
|
from news.models import NewsType
|
||||||
|
from tag.models import TagCategory
|
||||||
from transfer.models import PageTexts
|
from transfer.models import PageTexts
|
||||||
from transfer.serializers.news import NewsSerializer
|
from transfer.serializers.news import NewsSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_news():
|
class GroupConcat(Aggregate):
|
||||||
news_type, _ = NewsType.objects.get_or_create(name="News")
|
function = 'GROUP_CONCAT'
|
||||||
|
template = '%(function)s(%(expressions)s)'
|
||||||
|
|
||||||
queryset = PageTexts.objects.filter(page__type="News").annotate(
|
def __init__(self, expression, **extra):
|
||||||
news_type=Value(news_type.id, output_field=IntegerField()),
|
output_field = extra.pop('output_field', CharField())
|
||||||
|
super().__init__(expression, output_field=output_field, **extra)
|
||||||
|
|
||||||
|
def as_postgresql(self, compiler, connection):
|
||||||
|
self.function = 'STRING_AGG'
|
||||||
|
return super().as_sql(compiler, connection)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_news():
|
||||||
|
news_type, _ = NewsType.objects.get_or_create(name='News')
|
||||||
|
tag_cat, _ = TagCategory.objects.get_or_create(index_name='tag', public=True)
|
||||||
|
news_type.tag_categories.add(tag_cat)
|
||||||
|
news_type.save()
|
||||||
|
|
||||||
|
queryset = PageTexts.objects.filter(
|
||||||
|
page__type='News',
|
||||||
|
).annotate(
|
||||||
|
tag_cat_id=Value(tag_cat.id, output_field=IntegerField()),
|
||||||
|
news_type_id=Value(news_type.id, output_field=IntegerField()),
|
||||||
|
country_code=F('page__site__country_code_2'),
|
||||||
|
news_title=F('page__root_title'),
|
||||||
|
image=F('page__attachment_suffix_url'),
|
||||||
|
template=F('page__template'),
|
||||||
|
tags=GroupConcat('page__tags__id'),
|
||||||
)
|
)
|
||||||
queryset = queryset.annotate(template=F('page__template'))
|
|
||||||
|
|
||||||
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"News serializer errors: {serialized_data.errors}")
|
pprint(f'News serializer errors: {serialized_data.errors}')
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"news": [transfer_news]
|
'news': [transfer_news]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
return gallery
|
return gallery
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""Override create method"""
|
"""Overridden create method"""
|
||||||
super().create(request, *args, **kwargs)
|
super().create(request, *args, **kwargs)
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ class BaseTestCase(APITestCase):
|
||||||
self.password = 'sedragurdaredips19'
|
self.password = 'sedragurdaredips19'
|
||||||
self.email = 'sedragurda@desoz.com'
|
self.email = 'sedragurda@desoz.com'
|
||||||
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 tokens
|
||||||
tokkens = User.create_jwt_tokens(self.user)
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
|
self.client.cookies = SimpleCookie({'access_token': tokens.get('access_token'),
|
||||||
'refresh_token': tokkens.get('refresh_token')})
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
|
|
||||||
class NotificationAnonSubscribeTestCase(APITestCase):
|
class NotificationAnonSubscribeTestCase(APITestCase):
|
||||||
|
|
|
||||||
19
apps/product/admin.py
Normal file
19
apps/product/admin.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""Product admin conf."""
|
||||||
|
from django.contrib import admin
|
||||||
|
from utils.admin import BaseModelAdminMixin
|
||||||
|
from .models import Product, ProductType, ProductSubType
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Product)
|
||||||
|
class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
|
"""Admin page for model Product."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductType)
|
||||||
|
class ProductTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin page for model ProductType."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductSubType)
|
||||||
|
class ProductSubTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin page for model ProductSubType."""
|
||||||
34
apps/product/filters.py
Normal file
34
apps/product/filters.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""Filters for app Product."""
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from product import models
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterSet(filters.FilterSet):
|
||||||
|
"""Product filter set."""
|
||||||
|
|
||||||
|
establishment_id = filters.NumberFilter()
|
||||||
|
product_type = filters.ChoiceFilter(method='by_product_type',
|
||||||
|
choices=models.ProductType.INDEX_NAME_TYPES)
|
||||||
|
product_subtype = filters.ChoiceFilter(method='by_product_subtype',
|
||||||
|
choices=models.ProductSubType.INDEX_NAME_TYPES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Product
|
||||||
|
fields = [
|
||||||
|
'establishment_id',
|
||||||
|
'product_type',
|
||||||
|
'product_subtype',
|
||||||
|
]
|
||||||
|
|
||||||
|
def by_product_type(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_product_type(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def by_product_subtype(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_product_subtype(value)
|
||||||
|
return queryset
|
||||||
0
apps/product/management/__init__.py
Normal file
0
apps/product/management/__init__.py
Normal file
0
apps/product/management/commands/__init__.py
Normal file
0
apps/product/management/commands/__init__.py
Normal file
54
apps/product/management/commands/add_product.py
Normal file
54
apps/product/management/commands/add_product.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from product.models import ProductType, ProductSubType
|
||||||
|
|
||||||
|
|
||||||
|
def add_type():
|
||||||
|
product_type = ProductType.objects.create(
|
||||||
|
name={'"en-GB"': "Wine"},
|
||||||
|
index_name=ProductType.WINE
|
||||||
|
)
|
||||||
|
return product_type.save()
|
||||||
|
|
||||||
|
|
||||||
|
def add_subtype(id_type):
|
||||||
|
subtypes = ProductSubType.objects.bulk_create([
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.EXTRA_BRUT},
|
||||||
|
index_name=ProductSubType.EXTRA_BRUT),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.BRUT},
|
||||||
|
index_name=ProductSubType.BRUT),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.BRUT_NATURE},
|
||||||
|
index_name=ProductSubType.BRUT_NATURE),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.DEMI_SEC},
|
||||||
|
index_name=ProductSubType.DEMI_SEC),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.EXTRA_DRY},
|
||||||
|
index_name=ProductSubType.EXTRA_DRY),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.DOSAGE_ZERO},
|
||||||
|
index_name=ProductSubType.DOSAGE_ZERO),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.SEC},
|
||||||
|
index_name=ProductSubType.SEC),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.SEC},
|
||||||
|
index_name=ProductSubType.SEC),
|
||||||
|
ProductSubType(product_type=id_type,
|
||||||
|
name={"en-GB", ProductSubType.MOELLEUX},
|
||||||
|
index_name=ProductSubType.MOELLEUX),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add product data'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
product_type = add_type()
|
||||||
|
add_subtype(product_type.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
94
apps/product/migrations/0001_initial.py
Normal file
94
apps/product/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-28 07:27
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('location', '0013_wineappellation_wineregion'),
|
||||||
|
('establishment', '0044_position_index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProductType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Name')),
|
||||||
|
('index_name', models.CharField(choices=[('food', 'Food'), ('wine', 'Wine'), ('liquor', 'Liquor')], db_index=True, max_length=50, unique=True, verbose_name='Index name')),
|
||||||
|
('use_subtypes', models.BooleanField(default=True, verbose_name='Use subtypes')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Product type',
|
||||||
|
'verbose_name_plural': 'Product types',
|
||||||
|
},
|
||||||
|
bases=(utils.models.TranslatedFieldsMixin, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProductSubType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Name')),
|
||||||
|
('index_name', models.CharField(choices=[('rum', 'Rum'), ('other', 'Other')], db_index=True, max_length=50, unique=True, verbose_name='Index name')),
|
||||||
|
('product_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subtypes', to='product.ProductType', verbose_name='Product type')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Product subtype',
|
||||||
|
'verbose_name_plural': 'Product subtypes',
|
||||||
|
},
|
||||||
|
bases=(utils.models.TranslatedFieldsMixin, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Product',
|
||||||
|
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')),
|
||||||
|
('category', models.PositiveIntegerField(choices=[(0, 'Common'), (1, 'Online')], default=0)),
|
||||||
|
('name', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Name')),
|
||||||
|
('description', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Description')),
|
||||||
|
('characteristics', django.contrib.postgres.fields.jsonb.JSONField(verbose_name='Characteristics')),
|
||||||
|
('available', models.BooleanField(default=True, verbose_name='Available')),
|
||||||
|
('public_mark', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='public mark')),
|
||||||
|
('country', models.ManyToManyField(to='location.Country', verbose_name='Country')),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment')),
|
||||||
|
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
|
||||||
|
('product_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='product.ProductType', verbose_name='Type')),
|
||||||
|
('subtypes', models.ManyToManyField(blank=True, related_name='products', to='product.ProductSubType', verbose_name='Subtypes')),
|
||||||
|
('wine_appellation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.WineAppellation', verbose_name='wine appellation')),
|
||||||
|
('wine_region', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='wines', to='location.WineRegion', verbose_name='wine region')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Product',
|
||||||
|
'verbose_name_plural': 'Products',
|
||||||
|
},
|
||||||
|
bases=(utils.models.TranslatedFieldsMixin, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OnlineProduct',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Online product',
|
||||||
|
'verbose_name_plural': 'Online products',
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('product.product',),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/product/migrations/0002_product_slug.py
Normal file
18
apps/product/migrations/0002_product_slug.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-29 14:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Establishment slug'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
"""Product app models."""
|
"""Product app models."""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.contenttypes import fields as generic
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from utils.models import (BaseAttributes, ProjectBaseMixin,
|
from utils.models import (BaseAttributes, ProjectBaseMixin,
|
||||||
|
|
@ -9,9 +11,23 @@ from utils.models import (BaseAttributes, ProjectBaseMixin,
|
||||||
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
"""ProductType model."""
|
"""ProductType model."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
FOOD = 'food'
|
||||||
|
WINE = 'wine'
|
||||||
|
LIQUOR = 'liquor'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(FOOD, _('Food')),
|
||||||
|
(WINE, _('Wine')),
|
||||||
|
(LIQUOR, _('Liquor')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
name = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
verbose_name=_('Index name'))
|
verbose_name=_('Index name'))
|
||||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
|
|
||||||
|
|
@ -25,19 +41,53 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
"""ProductSubtype model."""
|
"""ProductSubtype model."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
RUM = 'rum'
|
||||||
|
OTHER = 'other'
|
||||||
|
EXTRA_BRUT = 'extra brut'
|
||||||
|
BRUT = 'brut'
|
||||||
|
BRUT_NATURE = 'brut nature'
|
||||||
|
DEMI_SEC = 'demi-sec'
|
||||||
|
EXTRA_DRY = 'Extra Dry'
|
||||||
|
DOSAGE_ZERO = 'dosage zero'
|
||||||
|
SEC = 'sec'
|
||||||
|
DOUX = 'doux'
|
||||||
|
MOELLEUX= 'moelleux'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(RUM, _('Rum')),
|
||||||
|
(OTHER, _('Other')),
|
||||||
|
(EXTRA_BRUT, _('extra brut')),
|
||||||
|
(BRUT, _('brut')),
|
||||||
|
(BRUT_NATURE, _('brut nature')),
|
||||||
|
(DEMI_SEC, _('demi-sec')),
|
||||||
|
(EXTRA_DRY, _('Extra Dry')),
|
||||||
|
(DOSAGE_ZERO, _('dosage zero')),
|
||||||
|
(SEC, _('sec')),
|
||||||
|
(DOUX, _('doux')),
|
||||||
|
(MOELLEUX, _('moelleux'))
|
||||||
|
)
|
||||||
|
|
||||||
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
|
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
|
||||||
related_name='subtypes',
|
related_name='subtypes',
|
||||||
verbose_name=_('Product type'))
|
verbose_name=_('Product type'))
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
name = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
verbose_name=_('Index name'))
|
verbose_name=_('Index name'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
verbose_name = _('Product type')
|
verbose_name = _('Product subtype')
|
||||||
verbose_name_plural = _('Product types')
|
verbose_name_plural = _('Product subtypes')
|
||||||
|
|
||||||
|
def clean_fields(self, exclude=None):
|
||||||
|
if not self.product_type.use_subtypes:
|
||||||
|
raise ValidationError(_('Product type is not use subtypes.'))
|
||||||
|
|
||||||
|
|
||||||
class ProductManager(models.Manager):
|
class ProductManager(models.Manager):
|
||||||
|
|
@ -47,16 +97,33 @@ class ProductManager(models.Manager):
|
||||||
class ProductQuerySet(models.QuerySet):
|
class ProductQuerySet(models.QuerySet):
|
||||||
"""Product queryset."""
|
"""Product queryset."""
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
return self.select_related('product_type', 'establishment') \
|
||||||
|
.prefetch_related('product_type__subtypes', 'country')
|
||||||
|
|
||||||
def common(self):
|
def common(self):
|
||||||
return self.filter(category=self.model.COMMON)
|
return self.filter(category=self.model.COMMON)
|
||||||
|
|
||||||
def online(self):
|
def online(self):
|
||||||
return self.filter(category=self.model.ONLINE)
|
return self.filter(category=self.model.ONLINE)
|
||||||
|
|
||||||
|
def wines(self):
|
||||||
|
return self.filter(type__index_name=ProductType.WINE)
|
||||||
|
|
||||||
|
def by_product_type(self, product_type: str):
|
||||||
|
"""Filter by type."""
|
||||||
|
return self.filter(product_type__index_name=product_type)
|
||||||
|
|
||||||
|
def by_product_subtype(self, product_subtype: str):
|
||||||
|
"""Filter by subtype."""
|
||||||
|
return self.filter(subtypes__index_name=product_subtype)
|
||||||
|
|
||||||
|
|
||||||
class Product(TranslatedFieldsMixin, BaseAttributes):
|
class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
"""Product models."""
|
"""Product models."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
COMMON = 0
|
COMMON = 0
|
||||||
ONLINE = 1
|
ONLINE = 1
|
||||||
|
|
||||||
|
|
@ -72,13 +139,30 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
description = TJSONField(_('Description'), null=True, blank=True,
|
description = TJSONField(_('Description'), null=True, blank=True,
|
||||||
default=None, help_text='{"en-GB":"some text"}')
|
default=None, help_text='{"en-GB":"some text"}')
|
||||||
characteristics = JSONField(_('Characteristics'))
|
characteristics = JSONField(_('Characteristics'))
|
||||||
country = models.ForeignKey('location.Country', on_delete=models.PROTECT,
|
country = models.ManyToManyField('location.Country',
|
||||||
verbose_name=_('Country'))
|
verbose_name=_('Country'))
|
||||||
available = models.BooleanField(_('Available'), default=True)
|
available = models.BooleanField(_('Available'), default=True)
|
||||||
type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
|
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
|
||||||
related_name='products', verbose_name=_('Type'))
|
related_name='products', verbose_name=_('Type'))
|
||||||
subtypes = models.ManyToManyField(ProductSubType, related_name='products',
|
subtypes = models.ManyToManyField(ProductSubType, blank=True,
|
||||||
|
related_name='products',
|
||||||
verbose_name=_('Subtypes'))
|
verbose_name=_('Subtypes'))
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='products',
|
||||||
|
verbose_name=_('establishment'))
|
||||||
|
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('public mark'),)
|
||||||
|
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
|
||||||
|
related_name='wines',
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('wine region'))
|
||||||
|
wine_appellation = models.ForeignKey('location.WineAppellation', on_delete=models.PROTECT,
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('wine appellation'))
|
||||||
|
slug = models.SlugField(unique=True, max_length=255, null=True,
|
||||||
|
verbose_name=_('Establishment slug'))
|
||||||
|
favorites = generic.GenericRelation(to='favorites.Favorites')
|
||||||
|
|
||||||
objects = ProductManager.from_queryset(ProductQuerySet)()
|
objects = ProductManager.from_queryset(ProductQuerySet)()
|
||||||
|
|
||||||
|
|
@ -88,12 +172,22 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
verbose_name = _('Product')
|
verbose_name = _('Product')
|
||||||
verbose_name_plural = _('Products')
|
verbose_name_plural = _('Products')
|
||||||
|
|
||||||
|
def clean_fields(self, exclude=None):
|
||||||
|
super().clean_fields(exclude=exclude)
|
||||||
|
if self.product_type.index_name == ProductType.WINE and not self.wine_region:
|
||||||
|
raise ValidationError(_('wine_region field must be specified.'))
|
||||||
|
if not self.product_type.index_name == ProductType.WINE and self.wine_region:
|
||||||
|
raise ValidationError(_('wine_region field must not be specified.'))
|
||||||
|
if (self.wine_region and self.wine_appellation) and \
|
||||||
|
self.wine_appellation not in self.wine_region.appellations.all():
|
||||||
|
raise ValidationError(_('Wine appellation not exists in wine region.'))
|
||||||
|
|
||||||
|
|
||||||
class OnlineProductManager(ProductManager):
|
class OnlineProductManager(ProductManager):
|
||||||
"""Extended manger for OnlineProduct model."""
|
"""Extended manger for OnlineProduct model."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overrided get_queryset method."""
|
"""Overridden get_queryset method."""
|
||||||
return super().get_queryset().online()
|
return super().get_queryset().online()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .common import *
|
||||||
|
from .web import *
|
||||||
|
from .mobile import *
|
||||||
|
|
@ -1 +1,96 @@
|
||||||
|
"""Product app serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from utils.serializers import TranslatedField, FavoritesCreateSerializer
|
||||||
|
from product.models import Product, ProductSubType, ProductType
|
||||||
|
from utils import exceptions as utils_exceptions
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from location.serializers import (WineRegionBaseSerializer, WineAppellationBaseSerializer,
|
||||||
|
CountrySimpleSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""ProductSubType base serializer"""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
index_name_display = serializers.CharField(source='get_index_name_display')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductSubType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'index_name_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""ProductType base serializer"""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
index_name_display = serializers.CharField(source='get_index_name_display')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name_translated',
|
||||||
|
'index_name_display',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Product base serializer."""
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
description_translated = TranslatedField()
|
||||||
|
category_display = serializers.CharField(source='get_category_display')
|
||||||
|
product_type = ProductTypeBaseSerializer()
|
||||||
|
subtypes = ProductSubTypeBaseSerializer(many=True)
|
||||||
|
wine_region = WineRegionBaseSerializer(allow_null=True)
|
||||||
|
wine_appellation = WineAppellationBaseSerializer(allow_null=True)
|
||||||
|
available_countries = CountrySimpleSerializer(source='country', many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = Product
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'slug',
|
||||||
|
'name_translated',
|
||||||
|
'category_display',
|
||||||
|
'description_translated',
|
||||||
|
'available',
|
||||||
|
'product_type',
|
||||||
|
'subtypes',
|
||||||
|
'public_mark',
|
||||||
|
'wine_region',
|
||||||
|
'wine_appellation',
|
||||||
|
'available_countries',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
|
"""Serializer to create favorite object w/ model Product."""
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Overridden validate method"""
|
||||||
|
# Check establishment object
|
||||||
|
product_qs = Product.objects.filter(slug=self.slug)
|
||||||
|
|
||||||
|
# Check establishment obj by slug from lookup_kwarg
|
||||||
|
if not product_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||||
|
else:
|
||||||
|
product = product_qs.first()
|
||||||
|
|
||||||
|
# Check existence in favorites
|
||||||
|
if product.favorites.filter(user=self.user).exists():
|
||||||
|
raise utils_exceptions.FavoritesError()
|
||||||
|
|
||||||
|
attrs['product'] = product
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data, *args, **kwargs):
|
||||||
|
"""Overridden create method"""
|
||||||
|
validated_data.update({
|
||||||
|
'user': self.user,
|
||||||
|
'content_object': validated_data.pop('product')
|
||||||
|
})
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""Product url patterns."""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from product import views
|
||||||
|
|
||||||
|
app_name = 'product'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.ProductListView.as_view(), name='list'),
|
||||||
|
path('slug/<slug:slug>/favorites/', views.CreateFavoriteProductView.as_view(),
|
||||||
|
name='create-destroy-favorites')
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""Product web url patterns."""
|
||||||
|
from product.urls.common import urlpatterns as common_urlpatterns
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns.extend(common_urlpatterns)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .back import *
|
||||||
|
from .common import *
|
||||||
|
from .mobile import *
|
||||||
|
from .web import *
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
"""Product app views."""
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from product.models import Product
|
||||||
|
from product import serializers
|
||||||
|
from product import filters
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBaseView(generics.GenericAPIView):
|
||||||
|
"""Product base view"""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method."""
|
||||||
|
return Product.objects.with_base_related()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListView(ProductBaseView, generics.ListAPIView):
|
||||||
|
"""List view for model Product."""
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
serializer_class = serializers.ProductBaseSerializer
|
||||||
|
filter_class = filters.ProductFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class CreateFavoriteProductView(generics.CreateAPIView,
|
||||||
|
generics.DestroyAPIView):
|
||||||
|
"""View for create/destroy product in favorites."""
|
||||||
|
serializer_class = serializers.ProductFavoritesCreateSerializer
|
||||||
|
lookup_field = 'slug'
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""
|
||||||
|
Returns the object the view is displaying.
|
||||||
|
"""
|
||||||
|
product = get_object_or_404(Product, slug=self.kwargs['slug'])
|
||||||
|
favorites = get_object_or_404(product.favorites.filter(user=self.request.user))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, favorites)
|
||||||
|
return favorites
|
||||||
|
|
@ -15,14 +15,12 @@ class RecipeQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def annotate_in_favorites(self, user):
|
def annotate_in_favorites(self, user):
|
||||||
"""Annotate flag in_favorites"""
|
"""Annotate flag in_favorites"""
|
||||||
favorite_establishments = []
|
favorite_recipe_ids = []
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
favorite_establishments = user.favorites.by_content_type(app_label='recipe',
|
favorite_recipe_ids = user.favorite_recipe_ids
|
||||||
model='recipe') \
|
|
||||||
.values_list('object_id', flat=True)
|
|
||||||
return self.annotate(in_favorites=models.Case(
|
return self.annotate(in_favorites=models.Case(
|
||||||
models.When(
|
models.When(
|
||||||
id__in=favorite_establishments,
|
id__in=favorite_recipe_ids,
|
||||||
then=True),
|
then=True),
|
||||||
default=False,
|
default=False,
|
||||||
output_field=models.BooleanField(default=False)))
|
output_field=models.BooleanField(default=False)))
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,5 @@ class BaseTestCase(APITestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_recipe_detail(self):
|
def test_recipe_detail(self):
|
||||||
print(self.test_recipe.id)
|
|
||||||
response = self.client.get(f"/api/web/recipes/{self.test_recipe.id}/")
|
response = self.client.get(f"/api/web/recipes/{self.test_recipe.id}/")
|
||||||
print(response.json())
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
# from search_indexes.signals import update_document
|
from search_indexes.signals import update_document
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,13 @@ class EstablishmentDocument(Document):
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.ObjectField(attr='name_indexing',
|
'name': fields.ObjectField(attr='name_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES),
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'index_name': fields.KeywordField(attr='index_name'),
|
||||||
})
|
})
|
||||||
establishment_subtypes = fields.ObjectField(
|
establishment_subtypes = fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.ObjectField(attr='name_indexing',
|
'name': fields.ObjectField(attr='name_indexing'),
|
||||||
properties={
|
'index_name': fields.KeywordField(attr='index_name'),
|
||||||
'id': fields.IntegerField(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
works_evening = fields.ListField(fields.IntegerField(
|
works_evening = fields.ListField(fields.IntegerField(
|
||||||
|
|
@ -82,15 +81,8 @@ class EstablishmentDocument(Document):
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
# todo: need to fix
|
|
||||||
# collections = fields.ObjectField(
|
|
||||||
# properties={
|
|
||||||
# 'id': fields.IntegerField(attr='collection.id'),
|
|
||||||
# 'collection_type': fields.IntegerField(attr='collection.collection_type'),
|
|
||||||
# },
|
|
||||||
# multi=True)
|
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
|
|
@ -99,6 +91,7 @@ class EstablishmentDocument(Document):
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'name_translated',
|
'name_translated',
|
||||||
|
'is_publish',
|
||||||
'price_level',
|
'price_level',
|
||||||
'toque_number',
|
'toque_number',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
|
|
@ -106,4 +99,4 @@ class EstablishmentDocument(Document):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().published()
|
return super().get_queryset().with_es_related()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Search indexes serializers."""
|
"""Search indexes serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from elasticsearch_dsl import AttrDict
|
||||||
from django_elasticsearch_dsl_drf.serializers import DocumentSerializer
|
from django_elasticsearch_dsl_drf.serializers import DocumentSerializer
|
||||||
from news.serializers import NewsTypeSerializer
|
from news.serializers import NewsTypeSerializer
|
||||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||||
|
|
@ -13,6 +14,8 @@ class TagsDocumentSerializer(serializers.Serializer):
|
||||||
label_translated = serializers.SerializerMethodField()
|
label_translated = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_label_translated(self, obj):
|
def get_label_translated(self, obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return get_translated_value(obj.get('label'))
|
||||||
return get_translated_value(obj.label)
|
return get_translated_value(obj.label)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,6 +32,13 @@ class AddressDocumentSerializer(serializers.Serializer):
|
||||||
geo_lon = serializers.FloatField(allow_null=True, source='coordinates.lon')
|
geo_lon = serializers.FloatField(allow_null=True, source='coordinates.lon')
|
||||||
geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat')
|
geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat')
|
||||||
|
|
||||||
|
# todo: refator
|
||||||
|
def to_representation(self, instance):
|
||||||
|
if instance != AttrDict(d={}) or \
|
||||||
|
(isinstance(instance, dict) and len(instance) != 0):
|
||||||
|
return super().to_representation(instance)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ScheduleDocumentSerializer(serializers.Serializer):
|
class ScheduleDocumentSerializer(serializers.Serializer):
|
||||||
"""Schedule serializer for ES Document"""
|
"""Schedule serializer for ES Document"""
|
||||||
|
|
@ -75,7 +85,7 @@ class NewsDocumentSerializer(DocumentSerializer):
|
||||||
class EstablishmentDocumentSerializer(DocumentSerializer):
|
class EstablishmentDocumentSerializer(DocumentSerializer):
|
||||||
"""Establishment document serializer."""
|
"""Establishment document serializer."""
|
||||||
|
|
||||||
address = AddressDocumentSerializer()
|
address = AddressDocumentSerializer(allow_null=True)
|
||||||
tags = TagsDocumentSerializer(many=True)
|
tags = TagsDocumentSerializer(many=True)
|
||||||
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,74 +4,74 @@ from django.dispatch import receiver
|
||||||
from django_elasticsearch_dsl.registries import registry
|
from django_elasticsearch_dsl.registries import registry
|
||||||
|
|
||||||
|
|
||||||
# @receiver(post_save)
|
@receiver(post_save)
|
||||||
# def update_document(sender, **kwargs):
|
def update_document(sender, **kwargs):
|
||||||
# from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
# app_label = sender._meta.app_label
|
app_label = sender._meta.app_label
|
||||||
# model_name = sender._meta.model_name
|
model_name = sender._meta.model_name
|
||||||
# instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
#
|
|
||||||
# if app_label == 'location':
|
if app_label == 'location':
|
||||||
# if model_name == 'country':
|
if model_name == 'country':
|
||||||
# establishments = Establishment.objects.filter(
|
establishments = Establishment.objects.filter(
|
||||||
# address__city__country=instance)
|
address__city__country=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
# if model_name == 'city':
|
if model_name == 'city':
|
||||||
# establishments = Establishment.objects.filter(
|
establishments = Establishment.objects.filter(
|
||||||
# address__city=instance)
|
address__city=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
# if model_name == 'address':
|
if model_name == 'address':
|
||||||
# establishments = Establishment.objects.filter(
|
establishments = Establishment.objects.filter(
|
||||||
# address=instance)
|
address=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
#
|
|
||||||
# if app_label == 'establishment':
|
if app_label == 'establishment':
|
||||||
# # todo: remove after migration
|
# todo: remove after migration
|
||||||
# from establishment import models as establishment_models
|
from establishment import models as establishment_models
|
||||||
# if model_name == 'establishmenttype':
|
if model_name == 'establishmenttype':
|
||||||
# if isinstance(instance, establishment_models.EstablishmentType):
|
if isinstance(instance, establishment_models.EstablishmentType):
|
||||||
# establishments = Establishment.objects.filter(
|
establishments = Establishment.objects.filter(
|
||||||
# establishment_type=instance)
|
establishment_type=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
# if model_name == 'establishmentsubtype':
|
if model_name == 'establishmentsubtype':
|
||||||
# if isinstance(instance, establishment_models.EstablishmentSubType):
|
if isinstance(instance, establishment_models.EstablishmentSubType):
|
||||||
# establishments = Establishment.objects.filter(
|
establishments = Establishment.objects.filter(
|
||||||
# establishment_subtypes=instance)
|
establishment_subtypes=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
#
|
|
||||||
# if app_label == 'tag':
|
if app_label == 'tag':
|
||||||
# if model_name == 'tag':
|
if model_name == 'tag':
|
||||||
# establishments = Establishment.objects.filter(tags=instance)
|
establishments = Establishment.objects.filter(tags=instance)
|
||||||
# for establishment in establishments:
|
for establishment in establishments:
|
||||||
# registry.update(establishment)
|
registry.update(establishment)
|
||||||
#
|
|
||||||
#
|
|
||||||
# @receiver(post_save)
|
@receiver(post_save)
|
||||||
# def update_news(sender, **kwargs):
|
def update_news(sender, **kwargs):
|
||||||
# from news.models import News
|
from news.models import News
|
||||||
# app_label = sender._meta.app_label
|
app_label = sender._meta.app_label
|
||||||
# model_name = sender._meta.model_name
|
model_name = sender._meta.model_name
|
||||||
# instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
#
|
|
||||||
# if app_label == 'location':
|
if app_label == 'location':
|
||||||
# if model_name == 'country':
|
if model_name == 'country':
|
||||||
# qs = News.objects.filter(country=instance)
|
qs = News.objects.filter(country=instance)
|
||||||
# for news in qs:
|
for news in qs:
|
||||||
# registry.update(news)
|
registry.update(news)
|
||||||
#
|
|
||||||
# if app_label == 'news':
|
if app_label == 'news':
|
||||||
# if model_name == 'newstype':
|
if model_name == 'newstype':
|
||||||
# qs = News.objects.filter(news_type=instance)
|
qs = News.objects.filter(news_type=instance)
|
||||||
# for news in qs:
|
for news in qs:
|
||||||
# registry.update(news)
|
registry.update(news)
|
||||||
#
|
|
||||||
# if app_label == 'tag':
|
if app_label == 'tag':
|
||||||
# if model_name == 'tag':
|
if model_name == 'tag':
|
||||||
# qs = News.objects.filter(tags=instance)
|
qs = News.objects.filter(tags=instance)
|
||||||
# for news in qs:
|
for news in qs:
|
||||||
# registry.update(news)
|
registry.update(news)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ from rest_framework import permissions
|
||||||
from django_elasticsearch_dsl_drf import constants
|
from django_elasticsearch_dsl_drf import constants
|
||||||
from django_elasticsearch_dsl_drf.filter_backends import (
|
from django_elasticsearch_dsl_drf.filter_backends import (
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend
|
GeoSpatialFilteringFilterBackend,
|
||||||
|
DefaultOrderingFilterBackend,
|
||||||
)
|
)
|
||||||
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
|
||||||
from search_indexes import serializers, filters
|
from search_indexes import serializers, filters
|
||||||
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
from search_indexes.documents import EstablishmentDocument, NewsDocument
|
||||||
from utils.pagination import ProjectPageNumberPagination
|
from utils.pagination import ProjectMobilePagination
|
||||||
|
|
||||||
|
|
||||||
class NewsDocumentViewSet(BaseDocumentViewSet):
|
class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
|
|
@ -16,7 +17,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
|
|
||||||
document = NewsDocument
|
document = NewsDocument
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
pagination_class = ProjectPageNumberPagination
|
pagination_class = ProjectMobilePagination
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.NewsDocumentSerializer
|
serializer_class = serializers.NewsDocumentSerializer
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|
@ -53,15 +54,20 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
|
|
||||||
document = EstablishmentDocument
|
document = EstablishmentDocument
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
pagination_class = ProjectPageNumberPagination
|
pagination_class = ProjectMobilePagination
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.EstablishmentDocumentSerializer
|
serializer_class = serializers.EstablishmentDocumentSerializer
|
||||||
ordering = ('id',)
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super(EstablishmentDocumentViewSet, self).get_queryset()
|
||||||
|
qs = qs.filter('match', is_publish=True)
|
||||||
|
return qs
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
GeoSpatialFilteringFilterBackend,
|
||||||
|
DefaultOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = {
|
search_fields = {
|
||||||
|
|
@ -72,6 +78,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
translated_search_fields = (
|
translated_search_fields = (
|
||||||
'description',
|
'description',
|
||||||
)
|
)
|
||||||
|
ordering = 'id'
|
||||||
filter_fields = {
|
filter_fields = {
|
||||||
'slug': 'slug',
|
'slug': 'slug',
|
||||||
'tag': {
|
'tag': {
|
||||||
|
|
@ -124,6 +131,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
'establishment_subtypes': {
|
'establishment_subtypes': {
|
||||||
'field': 'establishment_subtypes.id'
|
'field': 'establishment_subtypes.id'
|
||||||
},
|
},
|
||||||
|
'type': {
|
||||||
|
'field': 'establishment_type.index_name'
|
||||||
|
},
|
||||||
|
'subtype': {
|
||||||
|
'field': 'establishment_subtypes.index_name',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
],
|
||||||
|
},
|
||||||
'works_noon': {
|
'works_noon': {
|
||||||
'field': 'works_noon',
|
'field': 'works_noon',
|
||||||
'lookups': [
|
'lookups': [
|
||||||
|
|
|
||||||
0
apps/tag/management/__init__.py
Normal file
0
apps/tag/management/__init__.py
Normal file
0
apps/tag/management/commands/__init__.py
Normal file
0
apps/tag/management/commands/__init__.py
Normal file
69
apps/tag/management/commands/add_tags.py
Normal file
69
apps/tag/management/commands/add_tags.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import Establishment, EstablishmentType
|
||||||
|
from transfer import models as legacy
|
||||||
|
from tag.models import Tag, TagCategory
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add tags values from old db to new db'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
|
||||||
|
existing_establishment = Establishment.objects.filter(old_id__isnull=False)
|
||||||
|
ESTABLISHMENT = 1
|
||||||
|
SHOP = 2
|
||||||
|
RESTAURANT = 3
|
||||||
|
WINEYARD = 4
|
||||||
|
|
||||||
|
MAPPER = {
|
||||||
|
RESTAURANT: EstablishmentType.RESTAURANT,
|
||||||
|
WINEYARD: EstablishmentType.PRODUCER,
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper_values_meta = legacy.KeyValueMetadatumKeyValueMetadatumEstablishments.objects.all()
|
||||||
|
for key, value in MAPPER.items():
|
||||||
|
values_meta_id_list = mapper_values_meta.filter(
|
||||||
|
key_value_metadatum_establishment_id=key
|
||||||
|
).values_list('key_value_metadatum_id')
|
||||||
|
|
||||||
|
est_type, _ = EstablishmentType.objects.get_or_create(index_name=value)
|
||||||
|
|
||||||
|
key_value_metadata = legacy.KeyValueMetadata.objects.filter(
|
||||||
|
id__in=values_meta_id_list)
|
||||||
|
|
||||||
|
# create TagCategory
|
||||||
|
for key_value in key_value_metadata:
|
||||||
|
tag_category, created = TagCategory.objects.get_or_create(
|
||||||
|
index_name=key_value.key_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
tag_category.label = {
|
||||||
|
'en-GB': key_value.key_name,
|
||||||
|
'fr-FR': key_value.key_name,
|
||||||
|
'ru-RU': key_value.key_name,
|
||||||
|
},
|
||||||
|
tag_category.value_type = key_value.value_type
|
||||||
|
tag_category.save()
|
||||||
|
est_type.tag_categories.add(
|
||||||
|
tag_category
|
||||||
|
)
|
||||||
|
|
||||||
|
# create Tag
|
||||||
|
for tag in key_value.metadata_set.filter(
|
||||||
|
establishment__id__in=list(existing_establishment.values_list('old_id', flat=True))):
|
||||||
|
|
||||||
|
new_tag, _ = Tag.objects.get_or_create(
|
||||||
|
label={
|
||||||
|
'en-GB': tag.value,
|
||||||
|
'fr-FR': tag.value,
|
||||||
|
'ru-RU': tag.value,
|
||||||
|
},
|
||||||
|
value=tag.value,
|
||||||
|
category=tag_category,
|
||||||
|
)
|
||||||
|
est = existing_establishment.get(old_id=tag.establishment_id)
|
||||||
|
est.tags.add(new_tag)
|
||||||
|
est.save()
|
||||||
|
|
||||||
23
apps/tag/migrations/0006_auto_20191030_1151.py
Normal file
23
apps/tag/migrations/0006_auto_20191030_1151.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 11:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0005_tagcategory_name_indexing'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=255, null=True, unique=True, verbose_name='indexing name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tagcategory',
|
||||||
|
name='value_type',
|
||||||
|
field=models.CharField(choices=[('string', 'string'), ('list', 'list'), ('integer', 'integer')], default='list', max_length=255, verbose_name='value type'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0007_auto_20191030_1514.py
Normal file
18
apps/tag/migrations/0007_auto_20191030_1514.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-30 15:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0006_auto_20191030_1151'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='indexing name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -19,6 +19,8 @@ class Tag(TranslatedFieldsMixin, models.Model):
|
||||||
label = TJSONField(blank=True, null=True, default=None,
|
label = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('label'),
|
verbose_name=_('label'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
value = models.CharField(_('indexing name'), max_length=255, blank=True,
|
||||||
|
null=True, default=None)
|
||||||
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
|
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
|
||||||
null=True, related_name='tags',
|
null=True, related_name='tags',
|
||||||
verbose_name=_('Category'))
|
verbose_name=_('Category'))
|
||||||
|
|
@ -72,6 +74,16 @@ class TagCategoryQuerySet(models.QuerySet):
|
||||||
class TagCategory(TranslatedFieldsMixin, models.Model):
|
class TagCategory(TranslatedFieldsMixin, models.Model):
|
||||||
"""Tag base category model."""
|
"""Tag base category model."""
|
||||||
|
|
||||||
|
STRING = 'string'
|
||||||
|
LIST = 'list'
|
||||||
|
INTEGER = 'integer'
|
||||||
|
|
||||||
|
VALUE_TYPE_CHOICES = (
|
||||||
|
(STRING, _('string')),
|
||||||
|
(LIST, _('list')),
|
||||||
|
(INTEGER, _('integer')),
|
||||||
|
)
|
||||||
|
|
||||||
label = TJSONField(blank=True, null=True, default=None,
|
label = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('label'),
|
verbose_name=_('label'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
|
@ -80,7 +92,10 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
|
||||||
default=None)
|
default=None)
|
||||||
public = models.BooleanField(default=False)
|
public = models.BooleanField(default=False)
|
||||||
index_name = models.CharField(max_length=255, blank=True, null=True,
|
index_name = models.CharField(max_length=255, blank=True, null=True,
|
||||||
verbose_name=_('indexing name'), unique=True)
|
verbose_name=_('indexing name'), unique=True)
|
||||||
|
|
||||||
|
value_type = models.CharField(_('value type'), max_length=255,
|
||||||
|
choices=VALUE_TYPE_CHOICES, default=LIST, )
|
||||||
|
|
||||||
objects = TagCategoryQuerySet.as_manager()
|
objects = TagCategoryQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'weekday_display',
|
'weekday_display',
|
||||||
|
'weekday',
|
||||||
'lunch_start',
|
'lunch_start',
|
||||||
'lunch_end',
|
'lunch_end',
|
||||||
'dinner_start',
|
'dinner_start',
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ class Command(BaseCommand):
|
||||||
'gallery',
|
'gallery',
|
||||||
'commercial',
|
'commercial',
|
||||||
'overlook',
|
'overlook',
|
||||||
'tmp'
|
'tmp',
|
||||||
|
'menu'
|
||||||
]
|
]
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class Ezuser(MigrateMixin):
|
||||||
password_hash = models.CharField(max_length=50, blank=True, null=True)
|
password_hash = models.CharField(max_length=50, blank=True, null=True)
|
||||||
password_hash_type = models.IntegerField()
|
password_hash_type = models.IntegerField()
|
||||||
facebook_id = models.BigIntegerField()
|
facebook_id = models.BigIntegerField()
|
||||||
#TODO: в legacy нету таблицы 'CadLevel'
|
# TODO: в legacy нету таблицы 'CadLevel'
|
||||||
|
|
||||||
# level = models.ForeignKey('CadLevel', models.DO_NOTHING)
|
# level = models.ForeignKey('CadLevel', models.DO_NOTHING)
|
||||||
points = models.IntegerField()
|
points = models.IntegerField()
|
||||||
|
|
@ -461,25 +461,44 @@ class Descriptions(MigrateMixin):
|
||||||
db_table = 'descriptions'
|
db_table = 'descriptions'
|
||||||
|
|
||||||
|
|
||||||
# class EstablishmentAssets(MigrateMixin):
|
class Dishes(MigrateMixin):
|
||||||
# using = 'legacy'
|
using = 'legacy'
|
||||||
#
|
|
||||||
# establishment = models.ForeignKey('Establishments', models.DO_NOTHING)
|
name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
# account = models.ForeignKey(Accounts, models.DO_NOTHING, blank=True, null=True)
|
price = models.FloatField(blank=True, null=True)
|
||||||
# menu_id = models.IntegerField(blank=True, null=True)
|
currency = models.CharField(max_length=255, blank=True, null=True)
|
||||||
# type = models.CharField(max_length=64)
|
dish_type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
# scope = models.CharField(max_length=32)
|
signature = models.IntegerField(blank=True, null=True)
|
||||||
# created_at = models.DateTimeField()
|
establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True)
|
||||||
# updated_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
# attachment_file_name = models.CharField(max_length=255, blank=True, null=True)
|
updated_at = models.DateTimeField()
|
||||||
# attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
|
|
||||||
# geometries = models.CharField(max_length=1024, blank=True, null=True)
|
class Meta:
|
||||||
# attachment_file_size = models.IntegerField(blank=True, null=True)
|
managed = False
|
||||||
# attachment_updated_at = models.DateTimeField(blank=True, null=True)
|
db_table = 'dishes'
|
||||||
#
|
|
||||||
# class Meta:
|
|
||||||
# managed = False
|
class EstablishmentAssets(MigrateMixin):
|
||||||
# db_table = 'establishment_assets'
|
using = 'legacy'
|
||||||
|
|
||||||
|
establishment = models.ForeignKey('Establishments', models.DO_NOTHING)
|
||||||
|
account = models.ForeignKey(Accounts, models.DO_NOTHING, blank=True, null=True)
|
||||||
|
menu_id = models.IntegerField(blank=True, null=True)
|
||||||
|
type = models.CharField(max_length=64)
|
||||||
|
scope = models.CharField(max_length=32)
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
updated_at = models.DateTimeField()
|
||||||
|
attachment_file_name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
geometries = models.CharField(max_length=1024, blank=True, null=True)
|
||||||
|
attachment_file_size = models.IntegerField(blank=True, null=True)
|
||||||
|
attachment_updated_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
attachment_suffix_url = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'establishment_assets'
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentBacklinks(MigrateMixin):
|
class EstablishmentBacklinks(MigrateMixin):
|
||||||
using = 'legacy'
|
using = 'legacy'
|
||||||
|
|
@ -734,7 +753,7 @@ class Pages(MigrateMixin):
|
||||||
using = 'legacy'
|
using = 'legacy'
|
||||||
|
|
||||||
root_title = models.CharField(max_length=255, blank=True, null=True)
|
root_title = models.CharField(max_length=255, blank=True, null=True)
|
||||||
site_id = models.IntegerField()
|
site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True)
|
||||||
account_id = models.IntegerField(blank=True, null=True)
|
account_id = models.IntegerField(blank=True, null=True)
|
||||||
state = models.CharField(max_length=255, blank=True, null=True)
|
state = models.CharField(max_length=255, blank=True, null=True)
|
||||||
template = models.CharField(max_length=255, blank=True, null=True)
|
template = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
|
@ -742,6 +761,7 @@ class Pages(MigrateMixin):
|
||||||
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
|
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
attachment_file_size = models.IntegerField(blank=True, null=True)
|
attachment_file_size = models.IntegerField(blank=True, null=True)
|
||||||
attachment_updated_at = models.DateTimeField(blank=True, null=True)
|
attachment_updated_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
attachment_suffix_url = models.TextField(blank=True, null=True)
|
||||||
geometries = models.CharField(max_length=1024, blank=True, null=True)
|
geometries = models.CharField(max_length=1024, blank=True, null=True)
|
||||||
scheduled_at = models.DateTimeField(blank=True, null=True)
|
scheduled_at = models.DateTimeField(blank=True, null=True)
|
||||||
created_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
|
|
@ -766,7 +786,6 @@ class PageTexts(MigrateMixin):
|
||||||
locale = models.CharField(max_length=255, blank=True, null=True)
|
locale = models.CharField(max_length=255, blank=True, null=True)
|
||||||
state = models.CharField(max_length=255, blank=True, null=True)
|
state = models.CharField(max_length=255, blank=True, null=True)
|
||||||
page = models.ForeignKey(Pages, models.DO_NOTHING, blank=True, null=True)
|
page = models.ForeignKey(Pages, models.DO_NOTHING, blank=True, null=True)
|
||||||
# page_id = models.IntegerField(blank=True, null=True)
|
|
||||||
created_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
updated_at = models.DateTimeField()
|
updated_at = models.DateTimeField()
|
||||||
summary = models.TextField(blank=True, null=True)
|
summary = models.TextField(blank=True, null=True)
|
||||||
|
|
@ -795,7 +814,7 @@ class PageMetadata(MigrateMixin):
|
||||||
|
|
||||||
key = models.CharField(max_length=255, blank=True, null=True)
|
key = models.CharField(max_length=255, blank=True, null=True)
|
||||||
value = models.CharField(max_length=255, blank=True, null=True)
|
value = models.CharField(max_length=255, blank=True, null=True)
|
||||||
page_id = models.IntegerField(blank=True, null=True)
|
page = models.ForeignKey('Pages', models.DO_NOTHING, blank=True, null=True, related_name='tags')
|
||||||
created_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
updated_at = models.DateTimeField()
|
updated_at = models.DateTimeField()
|
||||||
|
|
||||||
|
|
@ -822,3 +841,100 @@ class Ads(MigrateMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
db_table = 'ads'
|
db_table = 'ads'
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueMetadata(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
key_name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
value_type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
value_list = models.TextField(blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
updated_at = models.DateTimeField()
|
||||||
|
public = models.IntegerField(blank=True, null=True)
|
||||||
|
site_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'key_value_metadata'
|
||||||
|
|
||||||
|
|
||||||
|
class Metadata(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
key = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
value = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
establishment = models.ForeignKey('transfer.Establishments', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
updated_at = models.DateTimeField()
|
||||||
|
key_value_metadatum = models.ForeignKey('transfer.KeyValueMetadata', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'metadata'
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueMetadatumEstablishments(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
updated_at = models.DateTimeField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'key_value_metadatum_establishments'
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueMetadatumKeyValueMetadatumEstablishments(MigrateMixin):
|
||||||
|
using = 'legacy'
|
||||||
|
|
||||||
|
key_value_metadatum_id = models.IntegerField(blank=True, null=True)
|
||||||
|
key_value_metadatum_establishment_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'key_value_metadatum_key_value_metadatum_establishments'
|
||||||
|
|
||||||
|
|
||||||
|
# class Products(models.Model):
|
||||||
|
# establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# brand = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# vintage = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# type = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# unique_key = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# price = models.FloatField(blank=True, null=True)
|
||||||
|
# average_price_in_shops = models.FloatField(blank=True, null=True)
|
||||||
|
# fra_encima_id = models.IntegerField(blank=True, null=True)
|
||||||
|
# wine_sub_region_id = models.IntegerField(blank=True, null=True)
|
||||||
|
# classification = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# wine_region = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# wine_type = models.ForeignKey('WineTypes', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# wine_color = models.ForeignKey('WineColors', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# appellation = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# created_at = models.DateTimeField()
|
||||||
|
# updated_at = models.DateTimeField()
|
||||||
|
# state = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# wine_style = models.ForeignKey('WineStyles', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# village = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# vineyard = models.ForeignKey('WineLocations', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# yard_classification = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# wine_quality = models.ForeignKey('WineClassifications', models.DO_NOTHING, blank=True, null=True)
|
||||||
|
# bottles_produced = models.CharField(max_length=3000, blank=True, null=True)
|
||||||
|
# deu_import_id = models.IntegerField(blank=True, null=True)
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
# managed = False
|
||||||
|
# db_table = 'products'
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class WineTypes(models.Model):
|
||||||
|
# name = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
# fra_encima_id = models.IntegerField(blank=True, null=True)
|
||||||
|
# created_at = models.DateTimeField()
|
||||||
|
# updated_at = models.DateTimeField()
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
# managed = False
|
||||||
|
# db_table = 'wine_types'
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
facebook = serializers.CharField(allow_null=True, allow_blank=True)
|
facebook = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
twitter = serializers.CharField(allow_null=True, allow_blank=True)
|
twitter = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
booking = serializers.CharField(allow_null=True, allow_blank=True)
|
booking = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
|
state = serializers.CharField(allow_null=True)
|
||||||
tz = serializers.CharField()
|
tz = serializers.CharField()
|
||||||
created = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
|
created = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
|
||||||
|
|
||||||
|
|
@ -47,6 +48,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
'location', # + получить новые объекты Address по old_id
|
'location', # + получить новые объекты Address по old_id
|
||||||
'email', # + создать объект для ContactEmail
|
'email', # + создать объект для ContactEmail
|
||||||
'phone', # + создать объект для ContactPhone
|
'phone', # + создать объект для ContactPhone
|
||||||
|
'state', # + создать объект для ContactPhone
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
|
@ -54,9 +56,11 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']),
|
'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']),
|
||||||
'address_id': self.get_address(data['location']),
|
'address_id': self.get_address(data['location']),
|
||||||
'establishment_type_id': self.get_type(data),
|
'establishment_type_id': self.get_type(data),
|
||||||
|
'is_publish': data.get('state') == 'published',
|
||||||
})
|
})
|
||||||
data.pop('location')
|
data.pop('location')
|
||||||
data.pop('type')
|
data.pop('type')
|
||||||
|
data.pop('state')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,96 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from news.models import News
|
from gallery.models import Image
|
||||||
|
from location.models import Country
|
||||||
|
from news.models import News, NewsGallery
|
||||||
|
from tag.models import Tag
|
||||||
|
from transfer.models import PageMetadata
|
||||||
from utils.legacy_parser import parse_legacy_news_content
|
from utils.legacy_parser import parse_legacy_news_content
|
||||||
from utils.slug_generator import generate_unique_slug
|
from utils.slug_generator import generate_unique_slug
|
||||||
|
|
||||||
|
|
||||||
class NewsSerializer(serializers.ModelSerializer):
|
class NewsSerializer(serializers.Serializer):
|
||||||
locale = serializers.CharField()
|
id = serializers.IntegerField()
|
||||||
slug = serializers.CharField()
|
tag_cat_id = serializers.IntegerField()
|
||||||
body = serializers.CharField(allow_null=True)
|
news_type_id = serializers.IntegerField()
|
||||||
|
news_title = serializers.CharField()
|
||||||
title = serializers.CharField()
|
title = serializers.CharField()
|
||||||
template = serializers.CharField()
|
summary = serializers.CharField(allow_null=True)
|
||||||
|
body = serializers.CharField(allow_null=True)
|
||||||
|
created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
|
||||||
|
slug = serializers.CharField()
|
||||||
state = serializers.CharField()
|
state = serializers.CharField()
|
||||||
created_at = serializers.DateTimeField(source='start', format='%m-%d-%Y %H:%M:%S')
|
template = serializers.CharField()
|
||||||
|
country_code = serializers.CharField(allow_null=True)
|
||||||
class Meta:
|
locale = serializers.CharField()
|
||||||
model = News
|
image = serializers.CharField()
|
||||||
fields = (
|
tags = serializers.CharField(allow_null=True)
|
||||||
'created_at',
|
|
||||||
'state',
|
|
||||||
'template',
|
|
||||||
'title',
|
|
||||||
'body',
|
|
||||||
'slug',
|
|
||||||
'news_type',
|
|
||||||
'locale',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
data.update({
|
|
||||||
'slug': generate_unique_slug(News, data['slug']),
|
|
||||||
'state': self.get_state(data),
|
|
||||||
'template': self.get_template(data),
|
|
||||||
'title': self.get_title(data),
|
|
||||||
'description': self.get_description(data),
|
|
||||||
})
|
|
||||||
data.pop('body')
|
|
||||||
data.pop('locale')
|
|
||||||
return data
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
return News.objects.create(**validated_data)
|
|
||||||
|
payload = {
|
||||||
|
'old_id': validated_data['id'],
|
||||||
|
'news_type_id': validated_data['news_type_id'],
|
||||||
|
'title': {validated_data['locale']: validated_data['news_title']},
|
||||||
|
'subtitle': self.get_subtitle(validated_data),
|
||||||
|
'description': self.get_description(validated_data),
|
||||||
|
'start': validated_data['created_at'],
|
||||||
|
'slug': generate_unique_slug(News, validated_data['slug']),
|
||||||
|
'state': self.get_state(validated_data),
|
||||||
|
'template': self.get_template(validated_data),
|
||||||
|
'country': self.get_country(validated_data),
|
||||||
|
}
|
||||||
|
obj = News.objects.create(**payload)
|
||||||
|
|
||||||
|
tags = self.get_tags(validated_data)
|
||||||
|
for tag in tags:
|
||||||
|
obj.tags.add(tag)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
self.make_gallery(validated_data, obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_template(data):
|
def make_gallery(data, obj):
|
||||||
templates = {
|
if not data['image'] or data['image'] == 'default/missing.png':
|
||||||
'main': News.MAIN,
|
return
|
||||||
'main.pdf.erb': News.MAIN_PDF_ERB,
|
|
||||||
}
|
img = Image.objects.create(
|
||||||
return templates.get(data['template'], News.MAIN)
|
image=data['image'],
|
||||||
|
title=data['news_title'],
|
||||||
|
)
|
||||||
|
NewsGallery.objects.create(
|
||||||
|
news=obj,
|
||||||
|
image=img,
|
||||||
|
is_main=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tags(data):
|
||||||
|
results = []
|
||||||
|
if not data['tags']:
|
||||||
|
return results
|
||||||
|
|
||||||
|
meta_ids = (int(_id) for _id in data['tags'].split(','))
|
||||||
|
tags = PageMetadata.objects.filter(
|
||||||
|
id__in=meta_ids,
|
||||||
|
key='tag',
|
||||||
|
value__isnull=False,
|
||||||
|
)
|
||||||
|
for old_tag in tags:
|
||||||
|
tag, _ = Tag.objects.get_or_create(
|
||||||
|
category_id=data['tag_cat_id'],
|
||||||
|
label={data['locale']: old_tag.value},
|
||||||
|
)
|
||||||
|
results.append(tag)
|
||||||
|
return results
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_description(data):
|
||||||
|
if data['body']:
|
||||||
|
content = parse_legacy_news_content(data['body'])
|
||||||
|
return {data['locale']: content}
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_state(data):
|
def get_state(data):
|
||||||
|
|
@ -61,13 +103,26 @@ class NewsSerializer(serializers.ModelSerializer):
|
||||||
}
|
}
|
||||||
return states.get(data['state'], News.WAITING)
|
return states.get(data['state'], News.WAITING)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_template(data):
|
||||||
|
templates = {
|
||||||
|
'main': News.MAIN,
|
||||||
|
'main.pdf.erb': News.MAIN_PDF_ERB,
|
||||||
|
}
|
||||||
|
return templates.get(data['template'], News.MAIN)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_country(data):
|
||||||
|
return Country.objects.filter(code__iexact=data['country_code']).first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_title(data):
|
def get_title(data):
|
||||||
return {data['locale']: data['title']}
|
return {data['locale']: data['title']}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_description(data):
|
def get_subtitle(data):
|
||||||
content = None
|
if 'summary' in data:
|
||||||
if data['body']:
|
content = data.pop('summary')
|
||||||
content = parse_legacy_news_content(data['body'])
|
if not content:
|
||||||
return {data['locale']: content}
|
content = {data['locale']: data['title']}
|
||||||
|
return {data['locale']: content}
|
||||||
|
|
|
||||||
55
apps/transfer/serializers/plate.py
Normal file
55
apps/transfer/serializers/plate.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from establishment.models import Menu, Plate, Establishment
|
||||||
|
from main.models import Currency
|
||||||
|
from utils.constants import CODE_LOCALES
|
||||||
|
|
||||||
|
|
||||||
|
class PlateSerializer(serializers.Serializer):
|
||||||
|
name = serializers.CharField()
|
||||||
|
price = serializers.DecimalField(decimal_places=2, max_digits=10)
|
||||||
|
currency = serializers.CharField()
|
||||||
|
dish_type = serializers.CharField()
|
||||||
|
country_code = serializers.CharField()
|
||||||
|
establishment_id = serializers.IntegerField()
|
||||||
|
signature = serializers.IntegerField(allow_null=True)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
establishment = Establishment.objects.filter(old_id=validated_data['establishment_id']).first()
|
||||||
|
if not establishment:
|
||||||
|
return
|
||||||
|
return Plate.objects.create(**self.get_plate_data(validated_data, establishment.id))
|
||||||
|
|
||||||
|
def get_plate_data(self, validated_data, est_id):
|
||||||
|
locale = CODE_LOCALES.get(validated_data['country_code'], 'en-GB')
|
||||||
|
payload = {
|
||||||
|
'name': {locale: validated_data['name']},
|
||||||
|
'price': validated_data['price'],
|
||||||
|
'currency_id': self.get_currency(validated_data),
|
||||||
|
'menu_id': self.get_menu(validated_data, est_id),
|
||||||
|
'is_signature_plate': bool(validated_data['signature']),
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_menu(validated_data, est_id):
|
||||||
|
payload = {
|
||||||
|
'establishment_id': est_id,
|
||||||
|
'category': {'en-GB': validated_data['dish_type']},
|
||||||
|
}
|
||||||
|
menu, _ = Menu.objects.get_or_create(**payload)
|
||||||
|
return menu.id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_currency(validated_data):
|
||||||
|
try:
|
||||||
|
currency = Currency.objects.get(
|
||||||
|
slug=validated_data['currency'],
|
||||||
|
sign='$',
|
||||||
|
)
|
||||||
|
except Currency.DoesNotExist:
|
||||||
|
currency = Currency.objects.create(
|
||||||
|
slug=validated_data['currency'],
|
||||||
|
sign='$',
|
||||||
|
)
|
||||||
|
return currency.id
|
||||||
26
apps/utils/constants.py
Normal file
26
apps/utils/constants.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
CODE_LOCALES = {
|
||||||
|
'AAA': 'en-GB',
|
||||||
|
'AUS': 'en-AU', # Австралия
|
||||||
|
'AUT': 'de-AT', # Австрия
|
||||||
|
'BEL': 'de-BE', # Бельгия
|
||||||
|
'BRA': 'pt-BR', # Бразилия
|
||||||
|
'CAN': 'fr-CA', # Канада
|
||||||
|
'DEU': 'da-DE', # Германия
|
||||||
|
'FRA': 'fr-FR', # Франция
|
||||||
|
'GEO': 'ka', # Грузия
|
||||||
|
'GRC': 'el-GR', # Греция
|
||||||
|
'HRV': 'hr-HR', # Хорватия
|
||||||
|
'HUN': 'hu-HU', # Венгрия
|
||||||
|
'ISR': 'en-IL', # Израиль
|
||||||
|
'ITA': 'it-IT', # Италия
|
||||||
|
'JPN': 'ja', # Япония
|
||||||
|
'LUX': 'fr-LU', # Люксембург
|
||||||
|
'MAR': 'ar-MA', # Марокко
|
||||||
|
'MDV': 'dv', # Мальдивы
|
||||||
|
'NLD': 'nl-NL', # Нидерланды
|
||||||
|
'POL': 'pl', # Польша
|
||||||
|
'ROU': 'ro', # Румыния
|
||||||
|
'RUS': 'ru-RU', # Россия
|
||||||
|
'SVN': 'hu-SI', # Словения
|
||||||
|
'USA': 'en-US', # США
|
||||||
|
}
|
||||||
|
|
@ -44,18 +44,19 @@ class TJSONField(JSONField):
|
||||||
|
|
||||||
def to_locale(language):
|
def to_locale(language):
|
||||||
"""Turn a language name (en-us) into a locale name (en_US)."""
|
"""Turn a language name (en-us) into a locale name (en_US)."""
|
||||||
language, _, country = language.lower().partition('-')
|
if language:
|
||||||
if not country:
|
language, _, country = language.lower().partition('-')
|
||||||
return language
|
if not country:
|
||||||
# A language with > 2 characters after the dash only has its first
|
return language
|
||||||
# character after the dash capitalized; e.g. sr-latn becomes sr-Latn.
|
# A language with > 2 characters after the dash only has its first
|
||||||
# A language with 2 characters after the dash has both characters
|
# character after the dash capitalized; e.g. sr-latn becomes sr-Latn.
|
||||||
# capitalized; e.g. en-us becomes en-US.
|
# A language with 2 characters after the dash has both characters
|
||||||
country, _, tail = country.partition('-')
|
# capitalized; e.g. en-us becomes en-US.
|
||||||
country = country.title() if len(country) > 2 else country.upper()
|
country, _, tail = country.partition('-')
|
||||||
if tail:
|
country = country.title() if len(country) > 2 else country.upper()
|
||||||
country += '-' + tail
|
if tail:
|
||||||
return language + '-' + country
|
country += '-' + tail
|
||||||
|
return language + '-' + country
|
||||||
|
|
||||||
|
|
||||||
def translate_field(self, field_name):
|
def translate_field(self, field_name):
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ class IsAuthenticatedAndTokenIsValid(permissions.BasePermission):
|
||||||
access_token = request.COOKIES.get('access_token')
|
access_token = request.COOKIES.get('access_token')
|
||||||
if user.is_authenticated and access_token:
|
if user.is_authenticated and access_token:
|
||||||
access_token = AccessToken(access_token)
|
access_token = AccessToken(access_token)
|
||||||
valid_tokens = user.access_tokens.valid()\
|
valid_tokens = user.access_tokens.valid() \
|
||||||
.by_jti(jti=access_token.payload.get('jti'))
|
.by_jti(jti=access_token.payload.get('jti'))
|
||||||
return valid_tokens.exists()
|
return valid_tokens.exists()
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
@ -31,13 +31,14 @@ class IsRefreshTokenValid(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
Check if user has a valid refresh token and authenticated
|
Check if user has a valid refresh token and authenticated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
"""Check permissions by refresh token and default REST permission IsAuthenticated"""
|
"""Check permissions by refresh token and default REST permission IsAuthenticated"""
|
||||||
refresh_token = request.COOKIES.get('refresh_token')
|
refresh_token = request.COOKIES.get('refresh_token')
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
refresh_token = GMRefreshToken(refresh_token)
|
refresh_token = GMRefreshToken(refresh_token)
|
||||||
refresh_token_qs = JWTRefreshToken.objects.valid()\
|
refresh_token_qs = JWTRefreshToken.objects.valid() \
|
||||||
.by_jti(jti=refresh_token.payload.get('jti'))
|
.by_jti(jti=refresh_token.payload.get('jti'))
|
||||||
return refresh_token_qs.exists()
|
return refresh_token_qs.exists()
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
@ -55,11 +56,15 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly):
|
||||||
"""
|
"""
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return request.user.is_authenticated
|
rules = [
|
||||||
|
request.user.is_superuser,
|
||||||
|
request.method in permissions.SAFE_METHODS
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
request.user.is_superuser,
|
request.user.is_superuser,
|
||||||
request.method in permissions.SAFE_METHODS
|
request.method in permissions.SAFE_METHODS
|
||||||
|
|
@ -72,6 +77,21 @@ class IsStandardUser(IsGuest):
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
Assumes the model instance has an `owner` attribute.
|
Assumes the model instance has an `owner` attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request, 'user'):
|
||||||
|
rules = [
|
||||||
|
request.user.is_authenticated,
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
# Read permissions are allowed to any request
|
# Read permissions are allowed to any request
|
||||||
rules = [
|
rules = [
|
||||||
|
|
@ -92,11 +112,29 @@ class IsContentPageManager(IsStandardUser):
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
Assumes the model instance has an `owner` attribute.
|
Assumes the model instance has an `owner` attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request, 'user'):
|
||||||
|
role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER,
|
||||||
|
country_id=request.country_id) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role).exists(),
|
||||||
|
# and obj.user != request.user,
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
# Read permissions are allowed to any request.
|
# Read permissions are allowed to any request.
|
||||||
|
|
||||||
role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER,
|
role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER,
|
||||||
country_id=obj.country_id)\
|
country_id=obj.country_id) \
|
||||||
.first() # 'Comments moderator'
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
|
|
@ -112,6 +150,26 @@ class IsCountryAdmin(IsStandardUser):
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
Assumes the model instance has an `owner` attribute.
|
Assumes the model instance has an `owner` attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'):
|
||||||
|
# Read permissions are allowed to any request.
|
||||||
|
|
||||||
|
role = Role.objects.filter(role=Role.COUNTRY_ADMIN,
|
||||||
|
country_id=request.data.country_id) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role).exists(),
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
# Read permissions are allowed to any request.
|
# Read permissions are allowed to any request.
|
||||||
role = Role.objects.filter(role=Role.COUNTRY_ADMIN,
|
role = Role.objects.filter(role=Role.COUNTRY_ADMIN,
|
||||||
|
|
@ -119,9 +177,20 @@ class IsCountryAdmin(IsStandardUser):
|
||||||
.first() # 'Comments moderator'
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
UserRole.objects.filter(user=request.user, role=role).exists(),
|
super().has_object_permission(request, view, obj)
|
||||||
super().has_object_permission(request, view, obj),
|
|
||||||
]
|
]
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request, 'user') and request.user.is_authenticated:
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role).exists(),
|
||||||
|
super().has_object_permission(request, view, obj),
|
||||||
|
]
|
||||||
|
|
||||||
|
if hasattr(request.data, 'user'):
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.data.user, role=role).exists(),
|
||||||
|
super().has_object_permission(request, view, obj),
|
||||||
|
]
|
||||||
|
|
||||||
return any(rules)
|
return any(rules)
|
||||||
|
|
||||||
|
|
@ -131,10 +200,31 @@ class IsCommentModerator(IsStandardUser):
|
||||||
Object-level permission to only allow owners of an object to edit it.
|
Object-level permission to only allow owners of an object to edit it.
|
||||||
Assumes the model instance has an `owner` attribute.
|
Assumes the model instance has an `owner` attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'):
|
||||||
|
# Read permissions are allowed to any request.
|
||||||
|
|
||||||
|
role = Role.objects.filter(role=Role.COMMENTS_MODERATOR,
|
||||||
|
country_id=request.data.country_id) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role).exists(),
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
# Read permissions are allowed to any request.
|
# Read permissions are allowed to any request.
|
||||||
role = Role.objects.filter(role=Role.COMMENTS_MODERATOR,
|
role = Role.objects.filter(role=Role.COMMENTS_MODERATOR,
|
||||||
country_id=obj.country_id)\
|
country_id=obj.country_id) \
|
||||||
.first() # 'Comments moderator'
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
|
|
@ -147,10 +237,28 @@ class IsCommentModerator(IsStandardUser):
|
||||||
|
|
||||||
class IsEstablishmentManager(IsStandardUser):
|
class IsEstablishmentManager(IsStandardUser):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_permission(self, request, view):
|
||||||
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request.data, 'user') and hasattr(request.data, 'establishment_id'):
|
||||||
|
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \
|
||||||
.first() # 'Comments moderator'
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
|
establishment_id=request.data.establishment_id
|
||||||
|
).exists(),
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
UserRole.objects.filter(user=request.user, role=role,
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
establishment_id=obj.establishment_id
|
establishment_id=obj.establishment_id
|
||||||
|
|
@ -163,11 +271,28 @@ class IsEstablishmentManager(IsStandardUser):
|
||||||
|
|
||||||
class IsReviewerManager(IsStandardUser):
|
class IsReviewerManager(IsStandardUser):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'):
|
||||||
|
role = Role.objects.filter(role=Role.REVIEWER_MANGER) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
|
establishment_id=request.data.country_id
|
||||||
|
).exists(),
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
role = Role.objects.filter(role=Role.REVIEWER_MANGER,
|
role = Role.objects.filter(role=Role.REVIEWER_MANGER,
|
||||||
country_id=obj.country_id)\
|
country_id=obj.country_id) \
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
UserRole.objects.filter(user=request.user, role=role).exists(),
|
UserRole.objects.filter(user=request.user, role=role).exists(),
|
||||||
|
|
@ -179,8 +304,25 @@ class IsReviewerManager(IsStandardUser):
|
||||||
|
|
||||||
class IsRestaurantReviewer(IsStandardUser):
|
class IsRestaurantReviewer(IsStandardUser):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_permission(self, request, view):
|
||||||
|
rules = [
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
|
||||||
|
# and request.user.email_confirmed,
|
||||||
|
if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'):
|
||||||
|
role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \
|
||||||
|
.first() # 'Comments moderator'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
UserRole.objects.filter(user=request.user, role=role,
|
||||||
|
establishment_id=request.data.object_id
|
||||||
|
).exists(),
|
||||||
|
super().has_permission(request, view)
|
||||||
|
]
|
||||||
|
return any(rules)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
content_type = ContentType.objects.get(app_lable='establishment',
|
content_type = ContentType.objects.get(app_lable='establishment',
|
||||||
model='establishment')
|
model='establishment')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from django.core import exceptions
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from utils import models
|
from utils import models
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
|
from favorites.models import Favorites
|
||||||
|
|
||||||
|
|
||||||
class EmptySerializer(serializers.Serializer):
|
class EmptySerializer(serializers.Serializer):
|
||||||
|
|
@ -72,3 +73,28 @@ class ProjectModelSerializer(serializers.ModelSerializer):
|
||||||
"""Overrided ModelSerializer."""
|
"""Overrided ModelSerializer."""
|
||||||
|
|
||||||
serializers.ModelSerializer.serializer_field_mapping[models.TJSONField] = TJSONField
|
serializers.ModelSerializer.serializer_field_mapping[models.TJSONField] = TJSONField
|
||||||
|
|
||||||
|
|
||||||
|
class FavoritesCreateSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer to favorite object."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Serializer for model Comment."""
|
||||||
|
model = Favorites
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request(self):
|
||||||
|
return self.context.get('request')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
"""Get user from request"""
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slug(self):
|
||||||
|
return self.request.parser_context.get('kwargs').get('slug')
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ class BasePermissionTests(APITestCase):
|
||||||
title='Russia',
|
title='Russia',
|
||||||
locale='ru-RU'
|
locale='ru-RU'
|
||||||
)
|
)
|
||||||
|
self.lang.save()
|
||||||
|
|
||||||
self.country_ru = Country.objects.get(
|
self.country_ru = Country.objects.get(
|
||||||
name={"en-GB": "Russian"}
|
name={"en-GB": "Russian"}
|
||||||
)
|
)
|
||||||
|
self.country_ru.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from account.models import User
|
||||||
from news.models import News, NewsType
|
from news.models import News, NewsType
|
||||||
|
|
||||||
from establishment.models import Establishment, EstablishmentType, Employee
|
from establishment.models import Establishment, EstablishmentType, Employee
|
||||||
|
from location.models import Country
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(APITestCase):
|
class BaseTestCase(APITestCase):
|
||||||
|
|
@ -39,7 +40,13 @@ class TranslateFieldTests(BaseTestCase):
|
||||||
self.news_type = NewsType.objects.create(name="Test news type")
|
self.news_type = NewsType.objects.create(name="Test news type")
|
||||||
self.news_type.save()
|
self.news_type.save()
|
||||||
|
|
||||||
|
|
||||||
|
self.country_ru = Country.objects.get(
|
||||||
|
name={"en-GB": "Russian"}
|
||||||
|
)
|
||||||
|
|
||||||
self.news_item = News.objects.create(
|
self.news_item = News.objects.create(
|
||||||
|
id=8,
|
||||||
created_by=self.user,
|
created_by=self.user,
|
||||||
modified_by=self.user,
|
modified_by=self.user,
|
||||||
title={
|
title={
|
||||||
|
|
@ -52,6 +59,7 @@ class TranslateFieldTests(BaseTestCase):
|
||||||
news_type=self.news_type,
|
news_type=self.news_type,
|
||||||
slug='test',
|
slug='test',
|
||||||
state=News.PUBLISHED,
|
state=News.PUBLISHED,
|
||||||
|
country=self.country_ru,
|
||||||
)
|
)
|
||||||
self.news_item.save()
|
self.news_item.save()
|
||||||
|
|
||||||
|
|
@ -62,7 +70,6 @@ class TranslateFieldTests(BaseTestCase):
|
||||||
response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json')
|
response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
news_data = response.json()
|
news_data = response.json()
|
||||||
print(news_data)
|
|
||||||
self.assertIn("title_translated", news_data)
|
self.assertIn("title_translated", news_data)
|
||||||
|
|
||||||
self.assertIn("Test news item", news_data['title_translated'])
|
self.assertIn("Test news item", news_data['title_translated'])
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,10 @@ services:
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
redis:
|
redis:
|
||||||
image: redis:2.8.23
|
image: redis:alpine
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
networks:
|
networks:
|
||||||
- redis_network
|
- redis_network
|
||||||
|
|
||||||
# RabbitMQ
|
|
||||||
#rabbitmq:
|
|
||||||
# image: rabbitmq:latest
|
|
||||||
# ports:
|
|
||||||
# - "5672:5672"
|
|
||||||
|
|
||||||
# Celery
|
# Celery
|
||||||
worker:
|
worker:
|
||||||
build: .
|
build: .
|
||||||
|
|
@ -78,7 +70,6 @@ services:
|
||||||
- .:/code
|
- .:/code
|
||||||
links:
|
links:
|
||||||
- db
|
- db
|
||||||
# - rabbitmq
|
|
||||||
- redis
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- redis_network
|
- redis_network
|
||||||
|
|
@ -97,7 +88,6 @@ services:
|
||||||
- .:/code
|
- .:/code
|
||||||
links:
|
links:
|
||||||
- db
|
- db
|
||||||
# - rabbitmq
|
|
||||||
- redis
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- redis_network
|
- redis_network
|
||||||
|
|
@ -116,7 +106,6 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql_db
|
- mysql_db
|
||||||
- db
|
- db
|
||||||
# - rabbitmq
|
|
||||||
- redis
|
- redis
|
||||||
- worker
|
- worker
|
||||||
- worker_beat
|
- worker_beat
|
||||||
|
|
@ -135,13 +124,10 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
gm-mysql_db:
|
gm-mysql_db:
|
||||||
name: gm-mysql_db
|
name: gm-mysql_db
|
||||||
|
|
||||||
gm-db:
|
gm-db:
|
||||||
name: gm-db
|
name: gm-db
|
||||||
|
|
||||||
gm-media:
|
gm-media:
|
||||||
name: gm-media
|
name: gm-media
|
||||||
|
|
||||||
gm-esdata:
|
gm-esdata:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|
|
||||||
14
fabfile.py
vendored
14
fabfile.py
vendored
|
|
@ -53,12 +53,14 @@ def collectstatic():
|
||||||
|
|
||||||
|
|
||||||
def deploy(branch=None):
|
def deploy(branch=None):
|
||||||
fetch()
|
role = env.roles[0]
|
||||||
install_requirements()
|
if env.roledefs[role]['branch'] != 'develop':
|
||||||
migrate()
|
fetch()
|
||||||
collectstatic()
|
install_requirements()
|
||||||
touch()
|
migrate()
|
||||||
kill_celery()
|
collectstatic()
|
||||||
|
touch()
|
||||||
|
kill_celery()
|
||||||
|
|
||||||
|
|
||||||
def rev():
|
def rev():
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ PROJECT_APPS = [
|
||||||
'favorites.apps.FavoritesConfig',
|
'favorites.apps.FavoritesConfig',
|
||||||
'rating.apps.RatingConfig',
|
'rating.apps.RatingConfig',
|
||||||
'transfer.apps.TransferConfig',
|
'transfer.apps.TransferConfig',
|
||||||
'tag.apps.TagConfig'
|
'tag.apps.TagConfig',
|
||||||
|
'product.apps.ProductConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTERNAL_APPS = [
|
EXTERNAL_APPS = [
|
||||||
|
|
@ -100,7 +100,7 @@ EXTERNAL_APPS = [
|
||||||
'timezone_field',
|
'timezone_field',
|
||||||
'storages',
|
'storages',
|
||||||
'sorl.thumbnail',
|
'sorl.thumbnail',
|
||||||
'timezonefinder'
|
'timezonefinder',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -329,7 +329,8 @@ REDOC_SETTINGS = {
|
||||||
# RabbitMQ
|
# RabbitMQ
|
||||||
# BROKER_URL = 'amqp://rabbitmq:5672'
|
# BROKER_URL = 'amqp://rabbitmq:5672'
|
||||||
# Redis
|
# Redis
|
||||||
BROKER_URL = 'redis://localhost:6379/1'
|
# BROKER_URL = 'redis://localhost:6379/1'
|
||||||
|
BROKER_URL = 'redis://redis:6379/1'
|
||||||
CELERY_RESULT_BACKEND = BROKER_URL
|
CELERY_RESULT_BACKEND = BROKER_URL
|
||||||
CELERY_BROKER_URL = BROKER_URL
|
CELERY_BROKER_URL = BROKER_URL
|
||||||
CELERY_ACCEPT_CONTENT = ['application/json']
|
CELERY_ACCEPT_CONTENT = ['application/json']
|
||||||
|
|
@ -372,7 +373,7 @@ THUMBNAIL_DEFAULT_OPTIONS = {
|
||||||
THUMBNAIL_QUALITY = 85
|
THUMBNAIL_QUALITY = 85
|
||||||
THUMBNAIL_DEBUG = False
|
THUMBNAIL_DEBUG = False
|
||||||
SORL_THUMBNAIL_ALIASES = {
|
SORL_THUMBNAIL_ALIASES = {
|
||||||
'news_preview': {'geometry_string': '100x100', 'crop': 'center'},
|
'news_preview': {'geometry_string': '300x260', 'crop': 'center'},
|
||||||
'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'},
|
'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'},
|
||||||
'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'},
|
'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'},
|
||||||
'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'},
|
'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'},
|
||||||
|
|
@ -484,3 +485,6 @@ STATICFILES_DIRS = (
|
||||||
|
|
||||||
# MEDIA
|
# MEDIA
|
||||||
MEDIA_LOCATION = 'media'
|
MEDIA_LOCATION = 'media'
|
||||||
|
|
||||||
|
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||||
|
PHONENUMBER_DEFAULT_REGION = "FR"
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ ELASTICSEARCH_DSL = {
|
||||||
|
|
||||||
|
|
||||||
ELASTICSEARCH_INDEX_NAMES = {
|
ELASTICSEARCH_INDEX_NAMES = {
|
||||||
# 'search_indexes.documents.news': 'development_news', # temporarily disabled
|
'search_indexes.documents.news': 'development_news', # temporarily disabled
|
||||||
# 'search_indexes.documents.establishment': 'development_establishment',
|
'search_indexes.documents.establishment': 'development_establishment',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,5 @@ urlpatterns = [
|
||||||
path('comments/', include('comment.urls.web')),
|
path('comments/', include('comment.urls.web')),
|
||||||
path('favorites/', include('favorites.urls')),
|
path('favorites/', include('favorites.urls')),
|
||||||
path('timetables/', include('timetable.urls.web')),
|
path('timetables/', include('timetable.urls.web')),
|
||||||
|
path('products/', include('product.urls.web')),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user