From 7ac7df9ea36f6635e9c70970088c03e5fc3cb47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 16:48:29 +0300 Subject: [PATCH 01/62] Fix test news --- apps/news/urls/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 4ab11727..9cc3d94a 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -8,7 +8,7 @@ app_name = 'news' urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), - name='gallery-retrieve-update-destroy'), + name='retrieve-update-destroy'), path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), From 851ba7f9ddf43b7a26e15553480da25a09d2930b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 10:14:50 +0300 Subject: [PATCH 02/62] Test edit --- apps/comment/tests.py | 44 ++++++++++++++++++++++++++++++++++---- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 35 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 9b060f4e..87b7d32f 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -5,8 +5,9 @@ from django.urls import reverse from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie from account.models import Role, User, UserRole +from account.serializers.common import UserSerializer from comment.models import Comment - +import json class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): @@ -28,18 +29,53 @@ class CommentModeratorPermissionTests(BasePermissionTests): ) 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.comment = Comment.objects.create(text='Test comment', mark=1, user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, + object_id=self.country_ru.pk, + content_type_id=self.content_type.id, country=self.country_ru ) self.comment.save() 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_id": self.user_test["user"].id, + "object_id": self.country_ru.pk, + "content_type_id": 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) + json_user = json.dumps(self.moderator) + user = UserSerializer(data=self.moderator) + user.is_valid() + u_data = user.data + self.assertFalse(user.is_valid()) + # comment = { + # "text": "Test comment POST moder", + # "user": user, + # "object_id": self.country_ru.pk, + # "content_type_id": 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) + + # self.assertTrue(True) def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 2895fdbe..25c10a62 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,7 +8,7 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly,] + permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCommentModerator] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 45d978a0..aee2ab57 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -72,6 +72,20 @@ class IsStandardUser(IsGuest): Object-level permission to only allow owners of an object to edit it. 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): # Read permissions are allowed to any request rules = [ @@ -131,6 +145,27 @@ class IsCommentModerator(IsStandardUser): Object-level permission to only allow owners of an object to edit it. 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): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, From 046d0c5fe677ece42bce72efa32804e9f4c2287b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 10:59:31 +0300 Subject: [PATCH 03/62] Fix country and comment role --- apps/comment/serializers/back.py | 2 +- apps/comment/tests.py | 58 +++++++++++++-------------- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 20 +++++++++ apps/utils/tests/tests_permissions.py | 3 +- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/apps/comment/serializers/back.py b/apps/comment/serializers/back.py index d0cd47c8..325086c0 100644 --- a/apps/comment/serializers/back.py +++ b/apps/comment/serializers/back.py @@ -6,4 +6,4 @@ from rest_framework import serializers class CommentBaseSerializer(serializers.ModelSerializer): class Meta: model = models.Comment - fields = ('id', 'text', 'mark', 'user') \ No newline at end of file + fields = ('id', 'text', 'mark', 'user', 'object_id', 'content_type') \ No newline at end of file diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 87b7d32f..e91ee2f4 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -5,9 +5,8 @@ from django.urls import reverse from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie from account.models import Role, User, UserRole -from account.serializers.common import UserSerializer from comment.models import Comment -import json + class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): @@ -46,36 +45,30 @@ class CommentModeratorPermissionTests(BasePermissionTests): comment = { "text": "Test comment POST", - "user_id": self.user_test["user"].id, + "user": self.user_test["user"].id, "object_id": self.country_ru.pk, - "content_type_id": self.content_type.id, + "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) - json_user = json.dumps(self.moderator) - user = UserSerializer(data=self.moderator) - user.is_valid() - u_data = user.data - self.assertFalse(user.is_valid()) - # comment = { - # "text": "Test comment POST moder", - # "user": user, - # "object_id": self.country_ru.pk, - # "content_type_id": 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) - # self.assertTrue(True) + 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): tokens = User.create_jwt_tokens(self.moderator) @@ -87,7 +80,9 @@ class CommentModeratorPermissionTests(BasePermissionTests): "id": self.comment.id, "text": "test text moderator", "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') @@ -134,9 +129,10 @@ class CommentModeratorPermissionTests(BasePermissionTests): "id": self.comment.id, "text": "test text moderator", "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') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 25c10a62..8d836177 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,7 +8,7 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCommentModerator] + permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCountryAdmin|IsCommentModerator] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index aee2ab57..8ad1ae32 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -126,6 +126,26 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. 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): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COUNTRY_ADMIN, diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index edc1a5d7..3bba7b7d 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -9,10 +9,11 @@ class BasePermissionTests(APITestCase): title='Russia', locale='ru-RU' ) + self.lang.save() self.country_ru = Country.objects.get( name={"en-GB": "Russian"} ) - + self.country_ru.save() From 72713badff2a24556e9cb2089fca4ba4b3a07fc5 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 25 Oct 2019 11:04:40 +0300 Subject: [PATCH 04/62] remove print from tests --- apps/account/tests/tests_back.py | 21 ++++++++----- apps/account/tests/tests_common.py | 12 ++++---- .../tests/tests_authorization.py | 8 ++--- apps/comment/tests.py | 13 ++++---- apps/establishment/tests.py | 30 +++++++++++-------- apps/favorites/tests.py | 3 -- apps/location/tests.py | 12 ++------ apps/notification/tests.py | 8 ++--- apps/recipe/tests.py | 2 -- apps/utils/tests/tests_translated.py | 1 - 10 files changed, 55 insertions(+), 55 deletions(-) diff --git a/apps/account/tests/tests_back.py b/apps/account/tests/tests_back.py index 8adc6b35..d0178159 100644 --- a/apps/account/tests/tests_back.py +++ b/apps/account/tests/tests_back.py @@ -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 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 account.models import Role, User, UserRole + class RoleTests(APITestCase): def setUp(self): @@ -65,9 +68,11 @@ class UserRoleTests(APITestCase): ) self.role.save() - self.user_test = User.objects.create_user(username='test', - email='testemail@mail.com', - password='passwordtest') + self.user_test = User.objects.create_user( + username='test', + email='testemail@mail.com', + password='passwordtest' + ) def test_user_role_post(self): url = reverse('back:account:user-role-list-create') diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index dea807c4..67dfacda 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -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 account.models import User + 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): @@ -62,7 +64,7 @@ class AccountChangePasswordTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) -class AccountChangePasswordTests(APITestCase): +class AccountConfirmEmail(APITestCase): def setUp(self): self.data = get_tokens_for_user() diff --git a/apps/authorization/tests/tests_authorization.py b/apps/authorization/tests/tests_authorization.py index a6a49ea5..cc0881f5 100644 --- a/apps/authorization/tests/tests_authorization.py +++ b/apps/authorization/tests/tests_authorization.py @@ -1,7 +1,7 @@ -from rest_framework.test import APITestCase -from account.models import User 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( @@ -28,7 +28,7 @@ class AuthorizationTests(APITestCase): self.password = data["password"] def LoginTests(self): - data ={ + data = { 'username_or_email': self.username, 'password': self.password, 'remember': True diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 9b060f4e..bd22214a 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -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 django.contrib.contenttypes.models import ContentType +from django.urls import reverse +from rest_framework import status + from account.models import Role, User, UserRole +from authorization.tests.tests_authorization import get_tokens_for_user from comment.models import Comment +from utils.tests.tests_permissions import BasePermissionTests class CommentModeratorPermissionTests(BasePermissionTests): @@ -40,7 +42,6 @@ class CommentModeratorPermissionTests(BasePermissionTests): self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) self.client.cookies = SimpleCookie( diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 43aa62c6..cc9ed152 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -20,13 +20,14 @@ class BaseTestCase(APITestCase): self.newsletter = True self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - #get tokkens - tokkens = User.create_jwt_tokens(self.user) + # get tokens + tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + {'access_token': tokens.get('access_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 self.lang = Language.objects.get( @@ -42,12 +43,15 @@ class BaseTestCase(APITestCase): country=self.country_ru) self.region.save() - self.city = City.objects.create(name='Mosocow', code='01', - region=self.region, country=self.country_ru) + self.city = City.objects.create( + name='Mosocow', code='01', + region=self.region, + country=self.country_ru) self.city.save() - self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya', - number=2, postal_code='010100') + self.address = Address.objects.create( + city=self.city, street_name_1='Krasnaya', + number=2, postal_code='010100') self.address.save() self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER) @@ -63,8 +67,9 @@ class BaseTestCase(APITestCase): self.establishment.save() - self.user_role = UserRole.objects.create(user=self.user, role=self.role, - establishment=self.establishment) + self.user_role = UserRole.objects.create( + user=self.user, role=self.role, + establishment=self.establishment) self.user_role.save() @@ -128,12 +133,11 @@ class EmployeeTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -# Class to test childs class ChildTestCase(BaseTestCase): def setUp(self): super().setUp() -# Test childs + class EmailTests(ChildTestCase): def setUp(self): super().setUp() diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index cebd43c2..5f0a2fcd 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -61,9 +61,6 @@ class BaseTestCase(APITestCase): description={"en-GB": "description of test establishment"}, establishment_type=self.test_establishment_type, is_publish=True) - # value for GenericRelation(reverse side) field must be iterable - # value for GenericRelation(reverse side) field must be assigned through - # "set" method of field self.test_establishment.favorites.set([self.test_favorites]) diff --git a/apps/location/tests.py b/apps/location/tests.py index cb574036..4e192831 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -19,15 +19,10 @@ class BaseTestCase(APITestCase): self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - # get tokens - - # self.user.is_superuser = True - # self.user.save() - - tokkens = User.create_jwt_tokens(self.user) + tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) self.lang = Language.objects.get( title='Russia', @@ -102,7 +97,6 @@ class RegionTests(BaseTestCase): user_role.save() - def test_region_CRUD(self): response = self.client.get('/api/back/location/regions/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/notification/tests.py b/apps/notification/tests.py index d78c7fca..87264435 100644 --- a/apps/notification/tests.py +++ b/apps/notification/tests.py @@ -15,10 +15,10 @@ class BaseTestCase(APITestCase): self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - # get tokkens - tokkens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + # get tokens + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) class NotificationAnonSubscribeTestCase(APITestCase): diff --git a/apps/recipe/tests.py b/apps/recipe/tests.py index d0a17a36..68359be0 100644 --- a/apps/recipe/tests.py +++ b/apps/recipe/tests.py @@ -27,7 +27,5 @@ class BaseTestCase(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_recipe_detail(self): - print(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) diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 77b67d8a..4569ecf2 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -62,7 +62,6 @@ class TranslateFieldTests(BaseTestCase): response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) news_data = response.json() - print(news_data) self.assertIn("title_translated", news_data) self.assertIn("Test news item", news_data['title_translated']) From 7f4b46dbf83e989bad971831090d086f0896f3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 12:42:01 +0300 Subject: [PATCH 05/62] Fix country admin --- apps/comment/tests.py | 2 +- apps/comment/views/back.py | 5 +++-- apps/location/serializers/back.py | 1 + apps/location/tests.py | 5 ----- apps/location/views/back.py | 4 ++-- apps/utils/permissions.py | 28 +++++++++++++++++++++++----- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index e91ee2f4..786f68d3 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -90,7 +90,7 @@ class CommentModeratorPermissionTests(BasePermissionTests): def test_get(self): 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): other_user = User.objects.create_user(username='test', diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 8d836177..3b96cbd2 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,12 +8,13 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCountryAdmin|IsCommentModerator] + permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsCountryAdmin|IsCommentModerator] + + permission_classes = [IsCountryAdmin | IsCommentModerator] lookup_field = 'id' diff --git a/apps/location/serializers/back.py b/apps/location/serializers/back.py index f25aacf6..c178f7fd 100644 --- a/apps/location/serializers/back.py +++ b/apps/location/serializers/back.py @@ -16,4 +16,5 @@ class CountryBackSerializer(common.CountrySerializer): 'code', 'svg_image', 'name', + 'country_id' ] diff --git a/apps/location/tests.py b/apps/location/tests.py index cb574036..eed68071 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -19,11 +19,6 @@ class BaseTestCase(APITestCase): self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - # get tokens - - # self.user.is_superuser = True - # self.user.save() - tokkens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), diff --git a/apps/location/views/back.py b/apps/location/views/back.py index cb8246a4..1cdd91da 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics from location import models, serializers from location.views import common from utils.permissions import IsCountryAdmin - +from rest_framework.permissions import IsAuthenticatedOrReadOnly # Address class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" @@ -50,7 +50,7 @@ class CountryListCreateView(generics.ListCreateAPIView): queryset = models.Country.objects.all() serializer_class = serializers.CountryBackSerializer pagination_class = None - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 8ad1ae32..2a10200c 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -56,7 +56,15 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): Object-level permission to only allow owners of an object to edit it. """ def has_permission(self, request, view): - return request.user.is_authenticated + rules = [ + request.method in permissions.SAFE_METHODS + ] + # if hasattr(request, 'user.is_superuser'): + # rules = [ + # request.user.is_superuser, + # request.method in permissions.SAFE_METHODS + # ] + return any(rules) def has_object_permission(self, request, view, obj): @@ -131,7 +139,6 @@ class IsCountryAdmin(IsStandardUser): 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. @@ -153,9 +160,20 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' 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) From 91f6503a26fe1b22d7b9c60bd5d68822f2c03995 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Fri, 25 Oct 2019 10:54:01 +0000 Subject: [PATCH 06/62] Added country name to site settings --- apps/main/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index a0af662b..f939c448 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -63,6 +63,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer): # todo: remove this country_code = serializers.CharField(source='subdomain', read_only=True) + country_name = serializers.CharField(source='country.name_translated', read_only=True) + class Meta: """Meta class.""" @@ -78,7 +80,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer): 'config', 'ad_config', 'published_features', - 'currency' + 'currency', + 'country_name' ) From 7f23f0e891455324d1832a44e0053f3ffdadb489 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 07/62] Establishment each hour reindexing --- apps/establishment/models.py | 5 +++++ apps/establishment/tasks.py | 12 ++++++++++-- apps/search_indexes/documents/establishment.py | 2 +- project/settings/base.py | 10 +++++++++- requirements/base.txt | 3 ++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 304ea2a6..4a6a95e4 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -120,6 +120,11 @@ class EstablishmentQuerySet(models.QuerySet): def with_type_related(self): 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): """Search text in JSON fields.""" if locale is not None: diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index cf23a7e6..978f29ef 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -1,10 +1,13 @@ """Establishment app tasks.""" import logging + from celery import shared_task +from django.core import management +from django_elasticsearch_dsl.management.commands import search_index + from establishment import models from location.models import Country - logger = logging.getLogger(__name__) @@ -12,10 +15,15 @@ logger = logging.getLogger(__name__) def recalculate_price_levels_by_country(country_id): try: 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}') else: qs = models.Establishment.objects.filter(address__city__country=country) for establishment in qs: establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) + +@shared_task +def rebuild_establishment_indices(): + management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], + force=True) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d858321..665fc85d 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -106,4 +106,4 @@ class EstablishmentDocument(Document): ) def get_queryset(self): - return super().get_queryset().published() + return super().get_queryset().published().with_es_related() diff --git a/project/settings/base.py b/project/settings/base.py index f9524f94..8a65c1bb 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ import os import sys from datetime import timedelta +from celery.schedules import crontab # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -98,7 +99,8 @@ EXTERNAL_APPS = [ 'timezone_field', 'storages', 'sorl.thumbnail', - 'timezonefinder' + 'timezonefinder', + 'django_celery_beat', ] @@ -324,6 +326,12 @@ CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE +CELERY_BEAT_SCHEDULE = { + 'send-summary-every-hour': { + 'task': 'establishment.tasks.rebuild_establishment_indices', + 'schedule': crontab(hour=1), + }, +} # Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { diff --git a/requirements/base.txt b/requirements/base.txt index c95055de..19c29b7d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -44,4 +44,5 @@ sorl-thumbnail==12.5.0 # temp solution redis==3.2.0 amqp>=2.4.0 -celery==4.3.0rc2 \ No newline at end of file +celery==4.3.0rc2 +django-celery-beat==1.5.0 \ No newline at end of file From dd0b5951328fa09ea47ab1e221950b28b082110e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 08/62] Review fixes --- apps/establishment/tasks.py | 4 +++- project/settings/base.py | 8 -------- requirements/base.txt | 3 +-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index 978f29ef..b7a39d2d 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -2,6 +2,8 @@ import logging 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 @@ -23,7 +25,7 @@ def recalculate_price_levels_by_country(country_id): establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) -@shared_task +@periodic_task(run_every=crontab(minute=60)) def rebuild_establishment_indices(): management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], force=True) diff --git a/project/settings/base.py b/project/settings/base.py index 8a65c1bb..a8334839 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ import os import sys from datetime import timedelta -from celery.schedules import crontab # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -100,7 +99,6 @@ EXTERNAL_APPS = [ 'storages', 'sorl.thumbnail', 'timezonefinder', - 'django_celery_beat', ] @@ -326,12 +324,6 @@ CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE -CELERY_BEAT_SCHEDULE = { - 'send-summary-every-hour': { - 'task': 'establishment.tasks.rebuild_establishment_indices', - 'schedule': crontab(hour=1), - }, -} # Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { diff --git a/requirements/base.txt b/requirements/base.txt index 19c29b7d..c95055de 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -44,5 +44,4 @@ sorl-thumbnail==12.5.0 # temp solution redis==3.2.0 amqp>=2.4.0 -celery==4.3.0rc2 -django-celery-beat==1.5.0 \ No newline at end of file +celery==4.3.0rc2 \ No newline at end of file From 4eaa600fc1baee60176e9a37937670420d331e1a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 09/62] Fix all tests --- apps/establishment/tasks.py | 2 +- apps/utils/tests/tests_translated.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index b7a39d2d..fdc10933 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -25,7 +25,7 @@ def recalculate_price_levels_by_country(country_id): establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) -@periodic_task(run_every=crontab(minute=60)) +@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) diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 4569ecf2..557c8b5d 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -9,6 +9,7 @@ from account.models import User from news.models import News, NewsType from establishment.models import Establishment, EstablishmentType, Employee +from location.models import Country class BaseTestCase(APITestCase): @@ -39,7 +40,13 @@ class TranslateFieldTests(BaseTestCase): self.news_type = NewsType.objects.create(name="Test news type") self.news_type.save() + + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} + ) + self.news_item = News.objects.create( + id=8, created_by=self.user, modified_by=self.user, title={ @@ -52,6 +59,7 @@ class TranslateFieldTests(BaseTestCase): news_type=self.news_type, slug='test', state=News.PUBLISHED, + country=self.country_ru, ) self.news_item.save() From b7831b97393f3ace0a5b4375e593af11b3941a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 15:25:35 +0300 Subject: [PATCH 10/62] Fix IsContentPageManager --- apps/location/views/back.py | 19 ++++++++++++------- apps/news/tests.py | 16 ++++++++++++++++ apps/utils/permissions.py | 26 ++++++++++++++++++++------ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 1cdd91da..bb64ff72 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -6,42 +6,46 @@ from location.views import common from utils.permissions import IsCountryAdmin from rest_framework.permissions import IsAuthenticatedOrReadOnly # Address + + class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # City class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CitySerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # Region class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" serializer_class = serializers.RegionSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve view for model Region""" serializer_class = serializers.RegionSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # Country @@ -52,8 +56,9 @@ class CountryListCreateView(generics.ListCreateAPIView): pagination_class = None permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] queryset = models.Country.objects.all() \ No newline at end of file diff --git a/apps/news/tests.py b/apps/news/tests.py index 115763e5..77dbca8e 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -66,6 +66,22 @@ class NewsTestCase(BaseTestCase): def setUp(self): 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): response = self.client.get(reverse('web:news:list')) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2a10200c..7ee7811b 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -57,13 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ def has_permission(self, request, view): rules = [ + request.user.is_superuser, request.method in permissions.SAFE_METHODS ] - # if hasattr(request, 'user.is_superuser'): - # rules = [ - # request.user.is_superuser, - # request.method in permissions.SAFE_METHODS - # ] return any(rules) def has_object_permission(self, request, view, obj): @@ -114,6 +110,24 @@ class IsContentPageManager(IsStandardUser): Object-level permission to only allow owners of an object to edit it. 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): # Read permissions are allowed to any request. @@ -134,8 +148,8 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_permission(self, request, view): + rules = [ super().has_permission(request, view) ] From a38fed847a0f7601e320046629e2184f97689a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 15:51:07 +0300 Subject: [PATCH 11/62] Fix roles --- apps/utils/permissions.py | 87 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 7ee7811b..86a4be6f 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -20,8 +20,8 @@ class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): access_token = request.COOKIES.get('access_token') if user.is_authenticated and access_token: access_token = AccessToken(access_token) - valid_tokens = user.access_tokens.valid()\ - .by_jti(jti=access_token.payload.get('jti')) + valid_tokens = user.access_tokens.valid() \ + .by_jti(jti=access_token.payload.get('jti')) return valid_tokens.exists() else: return False @@ -31,13 +31,14 @@ class IsRefreshTokenValid(permissions.BasePermission): """ Check if user has a valid refresh token and authenticated """ + def has_permission(self, request, view): """Check permissions by refresh token and default REST permission IsAuthenticated""" refresh_token = request.COOKIES.get('refresh_token') if refresh_token: refresh_token = GMRefreshToken(refresh_token) - refresh_token_qs = JWTRefreshToken.objects.valid()\ - .by_jti(jti=refresh_token.payload.get('jti')) + refresh_token_qs = JWTRefreshToken.objects.valid() \ + .by_jti(jti=refresh_token.payload.get('jti')) return refresh_token_qs.exists() else: return False @@ -55,6 +56,7 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ + def has_permission(self, request, view): rules = [ request.user.is_superuser, @@ -63,7 +65,6 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): return any(rules) def has_object_permission(self, request, view, obj): - rules = [ request.user.is_superuser, request.method in permissions.SAFE_METHODS @@ -76,6 +77,7 @@ class IsStandardUser(IsGuest): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + def has_permission(self, request, view): rules = [ super().has_permission(request, view) @@ -118,7 +120,7 @@ class IsContentPageManager(IsStandardUser): # and request.user.email_confirmed, if hasattr(request, 'user'): role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=request.country_id)\ + country_id=request.country_id) \ .first() # 'Comments moderator' rules = [ @@ -132,7 +134,7 @@ class IsContentPageManager(IsStandardUser): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=obj.country_id)\ + country_id=obj.country_id) \ .first() # 'Comments moderator' rules = [ @@ -148,6 +150,7 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + def has_permission(self, request, view): rules = [ @@ -174,8 +177,8 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - 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 = [ @@ -221,7 +224,7 @@ class IsCommentModerator(IsStandardUser): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country_id=obj.country_id)\ + country_id=obj.country_id) \ .first() # 'Comments moderator' rules = [ @@ -234,10 +237,28 @@ class IsCommentModerator(IsStandardUser): class IsEstablishmentManager(IsStandardUser): - def has_object_permission(self, request, view, obj): - role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ + 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, 'establishment_id'): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ .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 = [ UserRole.objects.filter(user=request.user, role=role, establishment_id=obj.establishment_id @@ -250,11 +271,28 @@ class IsEstablishmentManager(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, - country_id=obj.country_id)\ - .first() + country_id=obj.country_id) \ + .first() rules = [ UserRole.objects.filter(user=request.user, role=role).exists(), @@ -266,8 +304,25 @@ class IsReviewerManager(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', model='establishment') From d4fe5ebd2ddb4376f2998201e271b93ec2fe761f Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 25 Oct 2019 16:06:52 +0300 Subject: [PATCH 12/62] search by type & subtype --- apps/news/serializers.py | 1 + .../search_indexes/documents/establishment.py | 21 +++++++++++++------ apps/search_indexes/serializers.py | 2 +- apps/search_indexes/views.py | 9 ++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 2b4e98b6..27d50977 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -97,6 +97,7 @@ class CropImageSerializer(serializers.Serializer): class NewsImageSerializer(serializers.ModelSerializer): """Serializer for returning crop images of news image.""" + orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) original_url = serializers.URLField(source='image.url') diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d858321..5d26ee59 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -10,6 +10,16 @@ EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1) +# todo: check & refactor +class ObjectField(fields.ObjectField): + + def get_value_from_instance(self, *args, **kwargs): + value = super(ObjectField, self).get_value_from_instance(*args, **kwargs) + if value == {}: + return None + return value + + @EstablishmentIndex.doc_type class EstablishmentDocument(Document): """Establishment document.""" @@ -22,14 +32,13 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), + 'index_name': fields.KeywordField(attr='index_name'), }) establishment_subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), - 'name': fields.ObjectField(attr='name_indexing', - properties={ - 'id': fields.IntegerField(), - }), + 'name': fields.ObjectField(attr='name_indexing'), + 'index_name': fields.KeywordField(attr='index_name'), }, multi=True) works_evening = fields.ListField(fields.IntegerField( @@ -54,7 +63,7 @@ class EstablishmentDocument(Document): 'closed_at': fields.KeywordField(attr='closed_at_str'), } )) - address = fields.ObjectField( + address = ObjectField( properties={ 'id': fields.IntegerField(), 'street_name_1': fields.TextField( @@ -82,7 +91,7 @@ class EstablishmentDocument(Document): ), } ), - } + }, ) # todo: need to fix # collections = fields.ObjectField( diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index d4ab2dbf..cb1fe735 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -75,7 +75,7 @@ class NewsDocumentSerializer(DocumentSerializer): class EstablishmentDocumentSerializer(DocumentSerializer): """Establishment document serializer.""" - address = AddressDocumentSerializer() + address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 25205bac..fd1c5b7a 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -124,6 +124,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'establishment_subtypes': { 'field': 'establishment_subtypes.id' }, + 'type': { + 'field': 'establishment_type.index_name' + }, + 'subtype': { + 'field': 'establishment_subtypes.index_name', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, 'works_noon': { 'field': 'works_noon', 'lookups': [ From 7d0acba51b04b4156bd07c4e45a0a956509b6377 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 25 Oct 2019 16:18:16 +0300 Subject: [PATCH 13/62] added to same_theme and should_read preview_image_url, added UniqueConstraint to NewsGallery model --- .../migrations/0029_auto_20191025_1241.py | 18 +++++++++++++++ apps/news/models.py | 2 +- apps/news/serializers.py | 22 +++++++++++-------- apps/news/views.py | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 apps/news/migrations/0029_auto_20191025_1241.py diff --git a/apps/news/migrations/0029_auto_20191025_1241.py b/apps/news/migrations/0029_auto_20191025_1241.py new file mode 100644 index 00000000..8e5bcba4 --- /dev/null +++ b/apps/news/migrations/0029_auto_20191025_1241.py @@ -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')}, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index dbb2f5bf..9b7190e4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -235,4 +235,4 @@ class NewsGallery(models.Model): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = ('news', 'is_main') + unique_together = (('news', 'is_main'), ('news', 'image')) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 27d50977..1389d20e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -150,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): """List serializer for News model.""" @@ -192,8 +203,8 @@ class NewsDetailSerializer(NewsBaseSerializer): class NewsDetailWebSerializer(NewsDetailSerializer): """News detail serializer for web users..""" - same_theme = NewsBaseSerializer(many=True, read_only=True) - should_read = NewsBaseSerializer(many=True, read_only=True) + same_theme = NewsSimilarListSerializer(many=True, read_only=True) + should_read = NewsSimilarListSerializer(many=True, read_only=True) agenda = AgendaSerializer() banner = NewsBannerSerializer() @@ -266,7 +277,6 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Override validate method.""" news_pk = self.get_request_kwargs().get('pk') image_id = self.get_request_kwargs().get('image_id') - is_main = attrs.get('is_main') news_qs = models.News.objects.filter(pk=news_pk) image_qs = Image.objects.filter(id=image_id) @@ -279,12 +289,6 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): news = news_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['image'] = image diff --git a/apps/news/views.py b/apps/news/views.py index 9cd5d969..9cbe6b53 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -119,7 +119,7 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, return gallery def create(self, request, *args, **kwargs): - """Override create method""" + """Overridden create method""" super().create(request, *args, **kwargs) return Response(status=status.HTTP_201_CREATED) From e1e0f1e997028a838eb4a965a84455a2a9fbdca7 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Fri, 25 Oct 2019 13:41:53 +0000 Subject: [PATCH 14/62] Added index_name in employee position --- .../migrations/0044_position_index_name.py | 18 ++++++++++++++++++ apps/establishment/models.py | 3 +++ apps/establishment/serializers/common.py | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 apps/establishment/migrations/0044_position_index_name.py diff --git a/apps/establishment/migrations/0044_position_index_name.py b/apps/establishment/migrations/0044_position_index_name.py new file mode 100644 index 00000000..0bf423d1 --- /dev/null +++ b/apps/establishment/migrations/0044_position_index_name.py @@ -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'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4a6a95e4..9648c6b0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -497,6 +497,9 @@ class Position(BaseAttributes, TranslatedFieldsMixin): 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: """Meta class.""" diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 2846c5c8..14be142a 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -147,12 +147,13 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): position_translated = serializers.CharField(source='position.name_translated') awards = AwardSerializer(source='employee.awards', many=True) priority = serializers.IntegerField(source='position.priority') + position_index_name = serializers.CharField(source='position.index_name') class Meta: """Meta class.""" model = models.Employee - fields = ('id', 'name', 'position_translated', 'awards', 'priority') + fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name') class EstablishmentBaseSerializer(ProjectModelSerializer): From e3616ee7f8c8ec4ce49e2dd115cdad819967284d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 25 Oct 2019 17:31:59 +0300 Subject: [PATCH 15/62] change news_preview geometry string on 300x2260 --- project/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/base.py b/project/settings/base.py index a8334839..fb542fce 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -360,7 +360,7 @@ THUMBNAIL_DEFAULT_OPTIONS = { THUMBNAIL_QUALITY = 85 THUMBNAIL_DEBUG = False 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_mobile': {'geometry_string': '375x260', 'crop': 'center'}, 'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'}, From 37d745eeaac036033d9a42e1bc2271aa62b296ff Mon Sep 17 00:00:00 2001 From: Anatoly Date: Sat, 26 Oct 2019 18:54:22 +0300 Subject: [PATCH 16/62] added endpoints, filters, serializers and extend model Product, ProductType, ProductSubtype --- apps/establishment/admin.py | 8 +++- apps/establishment/models.py | 5 ++ apps/product/admin.py | 18 +++++++ apps/product/filters.py | 34 +++++++++++++ apps/product/models.py | 71 ++++++++++++++++++++++++---- apps/product/serializers/__init__.py | 3 ++ apps/product/serializers/common.py | 54 +++++++++++++++++++++ apps/product/urls/common.py | 10 ++++ apps/product/urls/web.py | 7 +++ apps/product/views/__init__.py | 4 ++ apps/product/views/common.py | 20 ++++++++ project/settings/base.py | 1 + project/urls/web.py | 1 + 13 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 apps/product/admin.py create mode 100644 apps/product/filters.py diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 50c21b90..66f0aee1 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from comment.models import Comment from establishment import models from main.models import Award +from product.models import Product from review import models as review_models @@ -46,13 +47,18 @@ class CommentInline(GenericTabularInline): extra = 0 +class ProductInline(admin.TabularInline): + model = Product + extra = 0 + + @admin.register(models.Establishment) class EstablishmentAdmin(admin.ModelAdmin): """Establishment admin.""" list_display = ['id', '__str__', 'image_tag', ] inlines = [ AwardInline, ContactPhoneInline, ContactEmailInline, - ReviewInline, CommentInline] + ReviewInline, CommentInline, ProductInline] @admin.register(models.Position) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9648c6b0..0ca2463f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -486,6 +486,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ return self.id + @property + def wines(self): + """Return list products with type wine""" + return self.products.wines() + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/product/admin.py b/apps/product/admin.py new file mode 100644 index 00000000..b3dbc0cd --- /dev/null +++ b/apps/product/admin.py @@ -0,0 +1,18 @@ +"""Product admin conf.""" +from django.contrib import admin +from .models import Product, ProductType, ProductSubType + + +@admin.register(Product) +class ProductAdmin(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.""" diff --git a/apps/product/filters.py b/apps/product/filters.py new file mode 100644 index 00000000..9318b943 --- /dev/null +++ b/apps/product/filters.py @@ -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 ProductListFilterSet(filters.FilterSet): + """Product filter set.""" + + establishment_id = filters.NumberFilter() + type = filters.ChoiceFilter(method='by_type', + choices=models.ProductType.INDEX_NAME_TYPES) + subtype = filters.ChoiceFilter(method='by_subtype', + choices=models.ProductSubType.INDEX_NAME_TYPES) + + class Meta: + """Meta class.""" + model = models.Product + fields = [ + 'establishment_id', + 'type', + 'subtype', + ] + + def by_type(self, queryset, name, value): + if value not in EMPTY_VALUES: + return queryset.by_type(value) + return queryset + + def by_subtype(self, queryset, name, value): + if value not in EMPTY_VALUES: + return queryset.by_subtype(value) + return queryset diff --git a/apps/product/models.py b/apps/product/models.py index 41f0c7c6..d629d663 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,5 +1,6 @@ """Product app models.""" from django.db import models +from django.core.exceptions import ValidationError from django.contrib.postgres.fields import JSONField from django.utils.translation import gettext_lazy as _ from utils.models import (BaseAttributes, ProjectBaseMixin, @@ -9,9 +10,23 @@ from utils.models import (BaseAttributes, ProjectBaseMixin, class ProductType(TranslatedFieldsMixin, ProjectBaseMixin): """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, 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')) use_subtypes = models.BooleanField(_('Use subtypes'), default=True) @@ -25,19 +40,35 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin): class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin): """ProductSubtype model.""" + STR_FIELD_NAME = 'name' + + # INDEX NAME CHOICES + RUM = 'rum' + OTHER = 'other' + + INDEX_NAME_TYPES = ( + (RUM, _('Rum')), + (OTHER, _('Other')), + ) + product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE, related_name='subtypes', verbose_name=_('Product type')) name = TJSONField(blank=True, null=True, default=None, 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')) class Meta: """Meta class.""" - verbose_name = _('Product type') - verbose_name_plural = _('Product types') + verbose_name = _('Product subtype') + 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): @@ -47,16 +78,33 @@ class ProductManager(models.Manager): class ProductQuerySet(models.QuerySet): """Product queryset.""" + def with_base_related(self): + return self.select_related('country', 'product_type', 'establishment') \ + .prefetch_related('product_type__subtypes') + def common(self): return self.filter(category=self.model.COMMON) def online(self): return self.filter(category=self.model.ONLINE) + def wines(self): + return self.filter(type__index_name=ProductType.WINE) + + def by_type(self, type: str): + """Filter by type.""" + return self.filter(product_type__index_name=type) + + def by_subtype(self, subtype: str): + """Filter by subtype.""" + return self.filter(subtypes__index_name=subtype) + class Product(TranslatedFieldsMixin, BaseAttributes): """Product models.""" + STR_FIELD_NAME = 'name' + COMMON = 0 ONLINE = 1 @@ -75,10 +123,17 @@ class Product(TranslatedFieldsMixin, BaseAttributes): country = models.ForeignKey('location.Country', on_delete=models.PROTECT, verbose_name=_('Country')) available = models.BooleanField(_('Available'), default=True) - type = models.ForeignKey(ProductType, on_delete=models.PROTECT, - related_name='products', verbose_name=_('Type')) - subtypes = models.ManyToManyField(ProductSubType, related_name='products', + product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, + related_name='products', verbose_name=_('Type')) + subtypes = models.ManyToManyField(ProductSubType, blank=True, + related_name='products', 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'),) objects = ProductManager.from_queryset(ProductQuerySet)() @@ -93,7 +148,7 @@ class OnlineProductManager(ProductManager): """Extended manger for OnlineProduct model.""" def get_queryset(self): - """Overrided get_queryset method.""" + """Overridden get_queryset method.""" return super().get_queryset().online() diff --git a/apps/product/serializers/__init__.py b/apps/product/serializers/__init__.py index e69de29b..c564831e 100644 --- a/apps/product/serializers/__init__.py +++ b/apps/product/serializers/__init__.py @@ -0,0 +1,3 @@ +from .common import * +from .web import * +from .mobile import * diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index 236cd38b..f4f02b4d 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -1 +1,55 @@ +"""Product app serializers.""" from rest_framework import serializers +from utils.serializers import TranslatedField +from product.models import Product, ProductSubType, ProductType + + +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) + + class Meta: + """Meta class.""" + model = Product + fields = [ + 'id', + 'name_translated', + 'category_display', + 'description_translated', + 'available', + 'product_type', + 'subtypes', + 'public_mark', + ] diff --git a/apps/product/urls/common.py b/apps/product/urls/common.py index e69de29b..57abf4f0 100644 --- a/apps/product/urls/common.py +++ b/apps/product/urls/common.py @@ -0,0 +1,10 @@ +"""Product url patterns.""" +from django.urls import path + +from product import views + +app_name = 'product' + +urlpatterns = [ + path('', views.ProductListView.as_view(), name='list') +] diff --git a/apps/product/urls/web.py b/apps/product/urls/web.py index e69de29b..116c7b0b 100644 --- a/apps/product/urls/web.py +++ b/apps/product/urls/web.py @@ -0,0 +1,7 @@ +"""Product web url patterns.""" +from product.urls.common import urlpatterns as common_urlpatterns + +urlpatterns = [ +] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/product/views/__init__.py b/apps/product/views/__init__.py index e69de29b..6f4a8001 100644 --- a/apps/product/views/__init__.py +++ b/apps/product/views/__init__.py @@ -0,0 +1,4 @@ +from .back import * +from .common import * +from .mobile import * +from .web import * diff --git a/apps/product/views/common.py b/apps/product/views/common.py index e69de29b..403781e4 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -0,0 +1,20 @@ +"""Product app views.""" +from rest_framework import generics, permissions +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.ProductListFilterSet diff --git a/project/settings/base.py b/project/settings/base.py index fb542fce..85274993 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -75,6 +75,7 @@ PROJECT_APPS = [ 'favorites.apps.FavoritesConfig', 'rating.apps.RatingConfig', 'tag.apps.TagConfig', + 'product.apps.ProductConfig', ] EXTERNAL_APPS = [ diff --git a/project/urls/web.py b/project/urls/web.py index 77a06961..86f7eac2 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -35,4 +35,5 @@ urlpatterns = [ path('comments/', include('comment.urls.web')), path('favorites/', include('favorites.urls')), path('timetables/', include('timetable.urls.web')), + path('products/', include('product.urls.web')), ] From e41b539ed71cd85bfdde57be38319bf49581a5c5 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Sun, 27 Oct 2019 18:42:51 +0300 Subject: [PATCH 17/62] added wine region --- apps/location/admin.py | 10 +++++++ apps/location/models.py | 43 +++++++++++++++++++++++++++++ apps/location/serializers/common.py | 30 ++++++++++++++++++++ apps/product/models.py | 11 ++++++++ apps/product/serializers/common.py | 3 ++ 5 files changed, 97 insertions(+) diff --git a/apps/location/admin.py b/apps/location/admin.py index a7610a65..adb355ac 100644 --- a/apps/location/admin.py +++ b/apps/location/admin.py @@ -19,6 +19,16 @@ class CityAdmin(admin.ModelAdmin): """City admin.""" +@admin.register(models.WineRegion) +class WineRegionAdmin(admin.ModelAdmin): + """WineRegion admin.""" + + +@admin.register(models.WineAppellation) +class WineAppellationAdmin(admin.ModelAdmin): + """WineAppellation admin.""" + + @admin.register(models.Address) class AddressAdmin(admin.OSMGeoAdmin): """Address admin.""" diff --git a/apps/location/models.py b/apps/location/models.py index da645de6..ba171c27 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -131,6 +131,49 @@ class Address(models.Model): 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 @receiver(post_save, sender=Country) def run_recalculate_price_levels(sender, instance, **kwargs): diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 87d0df4e..cfc14355 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -148,3 +148,33 @@ class AddressDetailSerializer(AddressBaseSerializer): 'city_id', '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() + appellations = WineAppellationBaseSerializer(many=True) + + class Meta: + """Meta class.""" + model = models.WineRegion + fields = [ + 'id', + 'name_translated', + 'country', + 'appellations', + ] diff --git a/apps/product/models.py b/apps/product/models.py index d629d663..2f65c16a 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -134,6 +134,10 @@ class Product(TranslatedFieldsMixin, BaseAttributes): 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')) objects = ProductManager.from_queryset(ProductQuerySet)() @@ -143,6 +147,13 @@ class Product(TranslatedFieldsMixin, BaseAttributes): verbose_name = _('Product') 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.')) + class OnlineProductManager(ProductManager): """Extended manger for OnlineProduct model.""" diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index f4f02b4d..c0a8c6ed 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -2,6 +2,7 @@ from rest_framework import serializers from utils.serializers import TranslatedField from product.models import Product, ProductSubType, ProductType +from location.serializers import WineRegionBaseSerializer class ProductSubTypeBaseSerializer(serializers.ModelSerializer): @@ -39,6 +40,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): category_display = serializers.CharField(source='get_category_display') product_type = ProductTypeBaseSerializer() subtypes = ProductSubTypeBaseSerializer(many=True) + wine_region = WineRegionBaseSerializer(allow_null=True) class Meta: """Meta class.""" @@ -52,4 +54,5 @@ class ProductBaseSerializer(serializers.ModelSerializer): 'product_type', 'subtypes', 'public_mark', + 'wine_region', ] From d12df5c8e15e1cd720b31554b72e6e292ba32f32 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 28 Oct 2019 12:33:48 +0300 Subject: [PATCH 18/62] added WineAppellation model --- apps/location/admin.py | 6 ++++++ apps/location/serializers/common.py | 2 -- apps/product/models.py | 14 ++++++++++---- apps/product/serializers/common.py | 7 ++++++- apps/utils/models.py | 25 +++++++++++++------------ 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/apps/location/admin.py b/apps/location/admin.py index adb355ac..a52fa14e 100644 --- a/apps/location/admin.py +++ b/apps/location/admin.py @@ -19,9 +19,15 @@ class CityAdmin(admin.ModelAdmin): """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) diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index cfc14355..2a70c3b8 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -167,7 +167,6 @@ class WineRegionBaseSerializer(serializers.ModelSerializer): """Wine region serializer.""" name_translated = TranslatedField() country = CountrySerializer() - appellations = WineAppellationBaseSerializer(many=True) class Meta: """Meta class.""" @@ -176,5 +175,4 @@ class WineRegionBaseSerializer(serializers.ModelSerializer): 'id', 'name_translated', 'country', - 'appellations', ] diff --git a/apps/product/models.py b/apps/product/models.py index 2f65c16a..b0b1795e 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -79,8 +79,8 @@ class ProductQuerySet(models.QuerySet): """Product queryset.""" def with_base_related(self): - return self.select_related('country', 'product_type', 'establishment') \ - .prefetch_related('product_type__subtypes') + return self.select_related('product_type', 'establishment') \ + .prefetch_related('product_type__subtypes', 'country') def common(self): return self.filter(category=self.model.COMMON) @@ -120,8 +120,8 @@ class Product(TranslatedFieldsMixin, BaseAttributes): description = TJSONField(_('Description'), null=True, blank=True, default=None, help_text='{"en-GB":"some text"}') characteristics = JSONField(_('Characteristics')) - country = models.ForeignKey('location.Country', on_delete=models.PROTECT, - verbose_name=_('Country')) + country = models.ManyToManyField('location.Country', + verbose_name=_('Country')) available = models.BooleanField(_('Available'), default=True) product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, related_name='products', verbose_name=_('Type')) @@ -138,6 +138,9 @@ class Product(TranslatedFieldsMixin, BaseAttributes): 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')) objects = ProductManager.from_queryset(ProductQuerySet)() @@ -153,6 +156,9 @@ class Product(TranslatedFieldsMixin, BaseAttributes): 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): diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index c0a8c6ed..fb2f7aeb 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -2,7 +2,8 @@ from rest_framework import serializers from utils.serializers import TranslatedField from product.models import Product, ProductSubType, ProductType -from location.serializers import WineRegionBaseSerializer +from location.serializers import (WineRegionBaseSerializer, WineAppellationBaseSerializer, + CountrySimpleSerializer) class ProductSubTypeBaseSerializer(serializers.ModelSerializer): @@ -41,6 +42,8 @@ class ProductBaseSerializer(serializers.ModelSerializer): 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.""" @@ -55,4 +58,6 @@ class ProductBaseSerializer(serializers.ModelSerializer): 'subtypes', 'public_mark', 'wine_region', + 'wine_appellation', + 'available_countries', ] diff --git a/apps/utils/models.py b/apps/utils/models.py index fb1de17c..03330eb4 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -44,18 +44,19 @@ class TJSONField(JSONField): def to_locale(language): """Turn a language name (en-us) into a locale name (en_US).""" - language, _, country = language.lower().partition('-') - if not country: - return language - # A language with > 2 characters after the dash only has its first - # character after the dash capitalized; e.g. sr-latn becomes sr-Latn. - # A language with 2 characters after the dash has both characters - # capitalized; e.g. en-us becomes en-US. - country, _, tail = country.partition('-') - country = country.title() if len(country) > 2 else country.upper() - if tail: - country += '-' + tail - return language + '-' + country + if language: + language, _, country = language.lower().partition('-') + if not country: + return language + # A language with > 2 characters after the dash only has its first + # character after the dash capitalized; e.g. sr-latn becomes sr-Latn. + # A language with 2 characters after the dash has both characters + # capitalized; e.g. en-us becomes en-US. + country, _, tail = country.partition('-') + country = country.title() if len(country) > 2 else country.upper() + if tail: + country += '-' + tail + return language + '-' + country def translate_field(self, field_name): From 2ebeffcf3e255407fa94ac99a245117b21b604b1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 28 Oct 2019 12:34:37 +0300 Subject: [PATCH 19/62] Merge branch 'develop' of /home/a.feteleu/projects/gm-backend with conflicts. --- celerybeat-schedule | Bin 12845 -> 13471 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 3efe528a0f0c9bb6e563876fdc4c1a02541e62f7..b8d3e3de18a9fcae15f3b9986a0be9fe2dcfa5aa 100644 GIT binary patch literal 13471 zcmeI3O=}ZD7{@2sB%4ec+a@iFw#8dhp-XLxBK05?PoW}f#fadt*$nJrn)cb5h%E$i zD*6>X=uJF|UqG*3dhzHdm z81GqKAdb2Kj(N1%_Pm}|SC0mkoh4^Mwuqgcb;nTx%-VjV#hN|q`5)c${qAwFD3542 zy`aVXc4rGxLBLup-gX+y7xZ2bvSz2nZp)dsDWt1mDoh1UUoa7EFnW*yYn9sH?DNIx z8|3aI$OK#4VuyP7*HNNSI;RHfF^bsiiy1f zBcTieN-mu1!f0cz3u9dvuPd)-&$gnNaVW2Se9Hw?T$td=`s?cd4|00BxIt=lb%q(2 z^R*%#ZpbD_`1}cXV%F$Ht)_RR&5PR2D3HVd4HP<+o1RSXzZw=wZN*@qVG;xsT^Q*? zNr6y61dO>b&K(XFO<}@?Nq!2;9ulS%TE}@%MtKmOvR4S%zYf_!56VdovUefxLZOyE z!Gmb^09G+ff`AbhN|KX;H7XrPz_<(LvrK15s+r@MW;N6N3Q4g%2n{)r79n;8ZE^E0wcG!(ab%>V&51`zr?ly7DTVO-{g(7HzG{E3B9 zn{yPFu<-IiwQ>Vh@Nbq?{mj9~02N{f3b8S4meZ4Cl1MiUbvk7V7~3Ts%*q From 65e7c965e4f67511bec70f3918145b2743d2f8c3 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 28 Oct 2019 12:46:10 +0300 Subject: [PATCH 20/62] added migrations --- .../0013_wineappellation_wineregion.py | 41 ++++++++ apps/product/migrations/0001_initial.py | 94 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 apps/location/migrations/0013_wineappellation_wineregion.py create mode 100644 apps/product/migrations/0001_initial.py diff --git a/apps/location/migrations/0013_wineappellation_wineregion.py b/apps/location/migrations/0013_wineappellation_wineregion.py new file mode 100644 index 00000000..dee84e55 --- /dev/null +++ b/apps/location/migrations/0013_wineappellation_wineregion.py @@ -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), + ), + ] diff --git a/apps/product/migrations/0001_initial.py b/apps/product/migrations/0001_initial.py new file mode 100644 index 00000000..a6abbbb8 --- /dev/null +++ b/apps/product/migrations/0001_initial.py @@ -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',), + ), + ] From ed09ee78ccbee605163fb3837201823d1ea0e78d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 28 Oct 2019 13:11:07 +0300 Subject: [PATCH 21/62] small refactoring --- apps/product/filters.py | 20 ++++++++++---------- apps/product/models.py | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/product/filters.py b/apps/product/filters.py index 9318b943..7894a6e6 100644 --- a/apps/product/filters.py +++ b/apps/product/filters.py @@ -9,26 +9,26 @@ class ProductListFilterSet(filters.FilterSet): """Product filter set.""" establishment_id = filters.NumberFilter() - type = filters.ChoiceFilter(method='by_type', - choices=models.ProductType.INDEX_NAME_TYPES) - subtype = filters.ChoiceFilter(method='by_subtype', - choices=models.ProductSubType.INDEX_NAME_TYPES) + 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', - 'type', - 'subtype', + 'product_type', + 'product_subtype', ] - def by_type(self, queryset, name, value): + def by_product_type(self, queryset, name, value): if value not in EMPTY_VALUES: - return queryset.by_type(value) + return queryset.by_product_type(value) return queryset - def by_subtype(self, queryset, name, value): + def by_product_subtype(self, queryset, name, value): if value not in EMPTY_VALUES: - return queryset.by_subtype(value) + return queryset.by_product_subtype(value) return queryset diff --git a/apps/product/models.py b/apps/product/models.py index b0b1795e..40007f18 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -91,13 +91,13 @@ class ProductQuerySet(models.QuerySet): def wines(self): return self.filter(type__index_name=ProductType.WINE) - def by_type(self, type: str): + def by_product_type(self, product_type: str): """Filter by type.""" - return self.filter(product_type__index_name=type) + return self.filter(product_type__index_name=product_type) - def by_subtype(self, subtype: str): + def by_product_subtype(self, product_subtype: str): """Filter by subtype.""" - return self.filter(subtypes__index_name=subtype) + return self.filter(subtypes__index_name=product_subtype) class Product(TranslatedFieldsMixin, BaseAttributes): From 4e868c2d8d45f1c441ee640c50fae12f8542cbd5 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 08:25:03 +0300 Subject: [PATCH 22/62] transfer dishes --- apps/establishment/transfer_data.py | 27 ++++++++- apps/transfer/management/commands/transfer.py | 3 +- apps/transfer/models.py | 17 ++++++ apps/transfer/serializers/plate.py | 55 +++++++++++++++++++ apps/utils/constants.py | 26 +++++++++ project/settings/base.py | 2 +- project/settings/development.py | 4 +- 7 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 apps/transfer/serializers/plate.py create mode 100644 apps/utils/constants.py diff --git a/apps/establishment/transfer_data.py b/apps/establishment/transfer_data.py index 4670e6cf..3005e6c4 100644 --- a/apps/establishment/transfer_data.py +++ b/apps/establishment/transfer_data.py @@ -1,9 +1,10 @@ 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.plate import PlateSerializer def transfer_establishment(): @@ -77,6 +78,26 @@ def transfer_establishment(): 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}") + + data_types = { - "establishment": [transfer_establishment] + "establishment": [transfer_establishment], + "menu": [transfer_menu], } diff --git a/apps/transfer/management/commands/transfer.py b/apps/transfer/management/commands/transfer.py index 1c146018..2e4d5de5 100644 --- a/apps/transfer/management/commands/transfer.py +++ b/apps/transfer/management/commands/transfer.py @@ -18,7 +18,8 @@ class Command(BaseCommand): 'establishment', 'gallery', 'commercial', - 'tmp' + 'tmp', + 'menu', ] def handle(self, *args, **options): diff --git a/apps/transfer/models.py b/apps/transfer/models.py index ffd0664e..e9d7b1e6 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -461,6 +461,23 @@ class Descriptions(MigrateMixin): db_table = 'descriptions' +class Dishes(MigrateMixin): + using = 'legacy' + + name = models.CharField(max_length=255, blank=True, null=True) + price = models.FloatField(blank=True, null=True) + currency = models.CharField(max_length=255, blank=True, null=True) + dish_type = models.CharField(max_length=255, blank=True, null=True) + signature = models.IntegerField(blank=True, null=True) + establishment = models.ForeignKey('Establishments', models.DO_NOTHING, blank=True, null=True) + created_at = models.DateTimeField() + updated_at = models.DateTimeField() + + class Meta: + managed = False + db_table = 'dishes' + + # class EstablishmentAssets(MigrateMixin): # using = 'legacy' # diff --git a/apps/transfer/serializers/plate.py b/apps/transfer/serializers/plate.py new file mode 100644 index 00000000..7d5eb99b --- /dev/null +++ b/apps/transfer/serializers/plate.py @@ -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 diff --git a/apps/utils/constants.py b/apps/utils/constants.py new file mode 100644 index 00000000..fe24c008 --- /dev/null +++ b/apps/utils/constants.py @@ -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', # США +} diff --git a/project/settings/base.py b/project/settings/base.py index 2f05ca52..659a96ba 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -329,7 +329,7 @@ REDOC_SETTINGS = { # RabbitMQ # BROKER_URL = 'amqp://rabbitmq:5672' # Redis -BROKER_URL = 'redis://localhost:6379/1' +BROKER_URL = 'redis://redis:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL CELERY_ACCEPT_CONTENT = ['application/json'] diff --git a/project/settings/development.py b/project/settings/development.py index 6c643301..1cebcc87 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -19,8 +19,8 @@ DOMAIN_URI = 'gm.id-east.ru' # ELASTICSEARCH SETTINGS ELASTICSEARCH_DSL = { 'default': { - 'hosts': 'localhost:9200' - # 'hosts': 'elasticsearch:9200' + # 'hosts': 'localhost:9200' + 'hosts': 'elasticsearch:9200' } } From adcc43309bef723b575e6c34d4d5aebd87fc2530 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 08:26:52 +0300 Subject: [PATCH 23/62] fix settings --- project/settings/base.py | 3 ++- project/settings/development.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/project/settings/base.py b/project/settings/base.py index 659a96ba..3d3ec8ca 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -329,7 +329,8 @@ REDOC_SETTINGS = { # RabbitMQ # BROKER_URL = 'amqp://rabbitmq:5672' # Redis -BROKER_URL = 'redis://redis:6379/1' +BROKER_URL = 'redis://localhost:6379/1' +# BROKER_URL = 'redis://redis:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL CELERY_ACCEPT_CONTENT = ['application/json'] diff --git a/project/settings/development.py b/project/settings/development.py index 1cebcc87..6c643301 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -19,8 +19,8 @@ DOMAIN_URI = 'gm.id-east.ru' # ELASTICSEARCH SETTINGS ELASTICSEARCH_DSL = { 'default': { - # 'hosts': 'localhost:9200' - 'hosts': 'elasticsearch:9200' + 'hosts': 'localhost:9200' + # 'hosts': 'elasticsearch:9200' } } From dc11fb055f8fd3814f9388410ea86a4814920cdc Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 12:34:08 +0300 Subject: [PATCH 24/62] fix news and paris establishments --- .../commands/rm_old_establishments.py | 13 +++++++++++++ apps/establishment/transfer_data.py | 5 ++++- apps/news/admin.py | 1 + apps/news/management/__init__.py | 0 apps/news/management/commands/__init__.py | 0 apps/news/management/commands/rm_all_news.py | 13 +++++++++++++ apps/news/management/commands/rm_old_news.py | 13 +++++++++++++ apps/news/migrations/0030_news_old_id.py | 18 ++++++++++++++++++ .../migrations/0031_merge_20191029_0858.py | 14 ++++++++++++++ apps/news/models.py | 1 + apps/news/transfer_data.py | 1 + apps/transfer/models.py | 2 +- apps/transfer/serializers/news.py | 10 ++++++++++ 13 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 apps/establishment/management/commands/rm_old_establishments.py create mode 100644 apps/news/management/__init__.py create mode 100644 apps/news/management/commands/__init__.py create mode 100644 apps/news/management/commands/rm_all_news.py create mode 100644 apps/news/management/commands/rm_old_news.py create mode 100644 apps/news/migrations/0030_news_old_id.py create mode 100644 apps/news/migrations/0031_merge_20191029_0858.py diff --git a/apps/establishment/management/commands/rm_old_establishments.py b/apps/establishment/management/commands/rm_old_establishments.py new file mode 100644 index 00000000..ec9fbbaa --- /dev/null +++ b/apps/establishment/management/commands/rm_old_establishments.py @@ -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.')) diff --git a/apps/establishment/transfer_data.py b/apps/establishment/transfer_data.py index 3005e6c4..9911d8f5 100644 --- a/apps/establishment/transfer_data.py +++ b/apps/establishment/transfer_data.py @@ -10,7 +10,9 @@ from transfer.serializers.plate import PlateSerializer def transfer_establishment(): result = [] - old_establishments = Establishments.objects.exclude( + old_establishments = Establishments.objects.filter( + location__city__name__icontains='paris', + ).exclude( Q(type='Wineyard') | Q(location__timezone__isnull=True) ).prefetch_related( @@ -18,6 +20,7 @@ def transfer_establishment(): 'schedules_set', 'descriptions_set', ) + for item in old_establishments: data = { 'old_id': item.id, diff --git a/apps/news/admin.py b/apps/news/admin.py index 91fb284c..fa2aca8b 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -28,6 +28,7 @@ class NewsAdmin(admin.ModelAdmin): """News admin.""" raw_id_fields = ('address',) actions = [send_email_action] + raw_id_fields = ('news_type', 'address', 'country') @admin.register(models.NewsGallery) diff --git a/apps/news/management/__init__.py b/apps/news/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/news/management/commands/__init__.py b/apps/news/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/news/management/commands/rm_all_news.py b/apps/news/management/commands/rm_all_news.py new file mode 100644 index 00000000..6b27199e --- /dev/null +++ b/apps/news/management/commands/rm_all_news.py @@ -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.')) diff --git a/apps/news/management/commands/rm_old_news.py b/apps/news/management/commands/rm_old_news.py new file mode 100644 index 00000000..cbcf0f6a --- /dev/null +++ b/apps/news/management/commands/rm_old_news.py @@ -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.')) diff --git a/apps/news/migrations/0030_news_old_id.py b/apps/news/migrations/0030_news_old_id.py new file mode 100644 index 00000000..038e5e35 --- /dev/null +++ b/apps/news/migrations/0030_news_old_id.py @@ -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'), + ), + ] diff --git a/apps/news/migrations/0031_merge_20191029_0858.py b/apps/news/migrations/0031_merge_20191029_0858.py new file mode 100644 index 00000000..6f00fcf0 --- /dev/null +++ b/apps/news/migrations/0031_merge_20191029_0858.py @@ -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 = [ + ] diff --git a/apps/news/models.py b/apps/news/models.py index 306e4906..f0caa13c 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -126,6 +126,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): (PUBLISHED_EXCLUSIVE, _('Published exclusive')), ) + old_id = models.PositiveIntegerField(_('odl id'), blank=True, null=True, default=None) news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, diff --git a/apps/news/transfer_data.py b/apps/news/transfer_data.py index d46c7613..791b54ce 100644 --- a/apps/news/transfer_data.py +++ b/apps/news/transfer_data.py @@ -12,6 +12,7 @@ def transfer_news(): queryset = PageTexts.objects.filter(page__type="News").annotate( news_type=Value(news_type.id, output_field=IntegerField()), + country_code=F('page__site__country_code_2'), ) queryset = queryset.annotate(template=F('page__template')) diff --git a/apps/transfer/models.py b/apps/transfer/models.py index e9d7b1e6..c431c504 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -751,7 +751,7 @@ class Pages(MigrateMixin): using = 'legacy' 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) state = models.CharField(max_length=255, blank=True, null=True) template = models.CharField(max_length=255, blank=True, null=True) diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index e46c5b4a..c0ed6f79 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -1,5 +1,6 @@ from rest_framework import serializers +from location.models import Country from news.models import News from utils.legacy_parser import parse_legacy_news_content from utils.slug_generator import generate_unique_slug @@ -12,11 +13,13 @@ class NewsSerializer(serializers.ModelSerializer): title = serializers.CharField() template = serializers.CharField() state = serializers.CharField() + country_code = serializers.CharField(allow_null=True) created_at = serializers.DateTimeField(source='start', format='%m-%d-%Y %H:%M:%S') class Meta: model = News fields = ( + 'old_id', 'created_at', 'state', 'template', @@ -25,6 +28,7 @@ class NewsSerializer(serializers.ModelSerializer): 'slug', 'news_type', 'locale', + 'country_code', ) def validate(self, data): @@ -34,7 +38,9 @@ class NewsSerializer(serializers.ModelSerializer): 'template': self.get_template(data), 'title': self.get_title(data), 'description': self.get_description(data), + 'country': self.get_country(data), }) + data.pop('country_code') data.pop('body') data.pop('locale') return data @@ -42,6 +48,10 @@ class NewsSerializer(serializers.ModelSerializer): def create(self, validated_data): return News.objects.create(**validated_data) + @staticmethod + def get_country(data): + return Country.objects.filter(code__iexact=data['country_code']).first() + @staticmethod def get_template(data): templates = { From 251edda2ea876fb9fe73f6c9e9dc0a19c49137bb Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 29 Oct 2019 15:19:07 +0300 Subject: [PATCH 25/62] update Document ViewSet's pagination class --- apps/search_indexes/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index fd1c5b7a..42f4a87e 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -8,7 +8,7 @@ from django_elasticsearch_dsl_drf.filter_backends import ( from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet from search_indexes import serializers, filters from search_indexes.documents import EstablishmentDocument, NewsDocument -from utils.pagination import ProjectPageNumberPagination +from utils.pagination import ProjectMobilePagination class NewsDocumentViewSet(BaseDocumentViewSet): @@ -16,7 +16,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): document = NewsDocument lookup_field = 'slug' - pagination_class = ProjectPageNumberPagination + pagination_class = ProjectMobilePagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.NewsDocumentSerializer ordering = ('id',) @@ -53,7 +53,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): document = EstablishmentDocument lookup_field = 'slug' - pagination_class = ProjectPageNumberPagination + pagination_class = ProjectMobilePagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentDocumentSerializer ordering = ('id',) From 13eaa942617f697f012977da3b28d0f804ee3ed5 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 29 Oct 2019 15:23:26 +0300 Subject: [PATCH 26/62] added filter to FavoritesEstablishmentListView --- apps/favorites/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/favorites/views.py b/apps/favorites/views.py index 5d99ed4b..6b7bdab0 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -1,6 +1,7 @@ """Views for app favorites.""" from rest_framework import generics from establishment.models import Establishment +from establishment.filters import EstablishmentFilter from establishment.serializers import EstablishmentBaseSerializer from .models import Favorites @@ -17,6 +18,7 @@ class FavoritesEstablishmentListView(generics.ListAPIView): """List views for favorites""" serializer_class = EstablishmentBaseSerializer + filter_class = EstablishmentFilter def get_queryset(self): """Override get_queryset method""" From f929c096bc7acbc541bf9bd4b616183fcc4e95d1 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 16:42:43 +0300 Subject: [PATCH 27/62] add subtitle like a description --- apps/transfer/serializers/news.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index c0ed6f79..82853cad 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -38,6 +38,7 @@ class NewsSerializer(serializers.ModelSerializer): 'template': self.get_template(data), 'title': self.get_title(data), 'description': self.get_description(data), + 'subtitle': self.get_description(data), 'country': self.get_country(data), }) data.pop('country_code') From 03c75efeced62f941d30362481a921ae019fdca7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 29 Oct 2019 17:16:39 +0300 Subject: [PATCH 28/62] gm-259, gm-260, gm-261 --- apps/account/models.py | 14 ++++++++ apps/establishment/models.py | 8 ++--- apps/establishment/serializers/common.py | 34 +++++------------- apps/establishment/urls/common.py | 3 +- apps/establishment/views/web.py | 24 +++---------- apps/favorites/urls.py | 2 ++ apps/favorites/views.py | 17 ++++++++- apps/product/filters.py | 2 +- apps/product/migrations/0002_product_slug.py | 18 ++++++++++ apps/product/models.py | 4 +++ apps/product/serializers/common.py | 37 ++++++++++++++++++-- apps/product/urls/common.py | 4 ++- apps/product/views/common.py | 21 ++++++++++- apps/recipe/models.py | 8 ++--- apps/utils/serializers.py | 26 ++++++++++++++ 15 files changed, 160 insertions(+), 62 deletions(-) create mode 100644 apps/product/migrations/0002_product_slug.py diff --git a/apps/account/models.py b/apps/account/models.py index 2f8c97cc..cc80721a 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -241,6 +241,20 @@ class User(AbstractUser): template_name=settings.CHANGE_EMAIL_TEMPLATE, 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): """UserRole model.""" diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 0ca2463f..c31f3a60 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -248,14 +248,12 @@ class EstablishmentQuerySet(models.QuerySet): def annotate_in_favorites(self, user): """Annotate flag in_favorites""" - favorite_establishments = [] + favorite_establishment_ids = [] if user.is_authenticated: - favorite_establishments = user.favorites.by_content_type(app_label='establishment', - model='establishment') \ - .values_list('object_id', flat=True) + favorite_establishment_ids = user.favorite_establishment_ids return self.annotate(in_favorites=Case( When( - id__in=favorite_establishments, + id__in=favorite_establishment_ids, then=True), default=False, output_field=models.BooleanField(default=False))) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 14be142a..5e7c76d4 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -5,15 +5,14 @@ from rest_framework import serializers from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models -from favorites.models import Favorites from location.serializers import AddressBaseSerializer from main.serializers import AwardSerializer, CurrencySerializer from review import models as review_models from tag.serializers import TagBaseSerializer from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions -from utils.serializers import ProjectModelSerializer -from utils.serializers import TranslatedField +from utils.serializers import (ProjectModelSerializer, TranslatedField, + FavoritesCreateSerializer) class ContactPhonesSerializer(serializers.ModelSerializer): @@ -281,26 +280,13 @@ class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer): ] -class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): - """Create comment serializer""" - - 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 +class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer): + """Serializer to favorite object w/ model Establishment.""" def validate(self, attrs): - """Override validate method""" + """Overridden validate method""" # Check establishment object - establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug') - establishment_qs = models.Establishment.objects.filter(slug=establishment_slug) + establishment_qs = models.Establishment.objects.filter(slug=self.slug) # Check establishment obj by slug from lookup_kwarg if not establishment_qs.exists(): @@ -309,18 +295,16 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): establishment = establishment_qs.first() # Check existence in favorites - if self.get_user().favorites.by_content_type(app_label='establishment', - model='establishment')\ - .by_object_id(object_id=establishment.id).exists(): + if establishment.favorites.filter(user=self.user).exists(): raise utils_exceptions.FavoritesError() attrs['establishment'] = establishment return attrs def create(self, validated_data, *args, **kwargs): - """Override create method""" + """Overridden create method""" validated_data.update({ - 'user': self.get_user(), + 'user': self.user, 'content_object': validated_data.pop('establishment') }) return super().create(validated_data) diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 8d9453c1..49cd3631 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -9,7 +9,6 @@ urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), - # path('wineries/', views.WineriesListView.as_view(), name='wineries-list'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), path('slug//similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug//comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), @@ -18,5 +17,5 @@ urlpatterns = [ path('slug//comments//', views.EstablishmentCommentRUDView.as_view(), name='rud-comment'), path('slug//favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), - name='add-to-favorites') + name='create-destroy-favorites') ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 0699d9d0..6a1de00e 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -138,15 +138,12 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D """ Returns the object the view is displaying. """ - establishment_obj = get_object_or_404(models.Establishment, - slug=self.kwargs['slug']) - obj = get_object_or_404( - self.request.user.favorites.by_content_type(app_label='establishment', - model='establishment') - .by_object_id(object_id=establishment_obj.pk)) + establishment = get_object_or_404(models.Establishment, + slug=self.kwargs['slug']) + favorites = get_object_or_404(establishment.favorites.filter(user=self.request.user)) # May raise a permission denied - self.check_object_permissions(self.request, obj) - return obj + self.check_object_permissions(self.request, favorites) + return favorites class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView): @@ -170,14 +167,3 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() if v is not None}) 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() diff --git a/apps/favorites/urls.py b/apps/favorites/urls.py index bd0c1d16..ad4c6e9d 100644 --- a/apps/favorites/urls.py +++ b/apps/favorites/urls.py @@ -8,4 +8,6 @@ app_name = 'favorites' urlpatterns = [ path('establishments/', views.FavoritesEstablishmentListView.as_view(), name='establishment-list'), + path('products/', views.FavoritesProductListView.as_view(), + name='product-list'), ] diff --git a/apps/favorites/views.py b/apps/favorites/views.py index 6b7bdab0..d2973142 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -3,6 +3,9 @@ from rest_framework import generics from establishment.models import Establishment from establishment.filters import EstablishmentFilter from establishment.serializers import EstablishmentBaseSerializer +from product.models import Product +from product.serializers import ProductBaseSerializer +from product.filters import ProductFilterSet from .models import Favorites @@ -15,7 +18,7 @@ class FavoritesBaseView(generics.GenericAPIView): class FavoritesEstablishmentListView(generics.ListAPIView): - """List views for favorites""" + """List views for establishments in favorites.""" serializer_class = EstablishmentBaseSerializer filter_class = EstablishmentFilter @@ -24,3 +27,15 @@ class FavoritesEstablishmentListView(generics.ListAPIView): """Override get_queryset method""" return Establishment.objects.filter(favorites__user=self.request.user)\ .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') diff --git a/apps/product/filters.py b/apps/product/filters.py index 7894a6e6..a30147eb 100644 --- a/apps/product/filters.py +++ b/apps/product/filters.py @@ -5,7 +5,7 @@ from django_filters import rest_framework as filters from product import models -class ProductListFilterSet(filters.FilterSet): +class ProductFilterSet(filters.FilterSet): """Product filter set.""" establishment_id = filters.NumberFilter() diff --git a/apps/product/migrations/0002_product_slug.py b/apps/product/migrations/0002_product_slug.py new file mode 100644 index 00000000..a4605464 --- /dev/null +++ b/apps/product/migrations/0002_product_slug.py @@ -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'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index 40007f18..d4011fa0 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,5 +1,6 @@ """Product app 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.utils.translation import gettext_lazy as _ @@ -141,6 +142,9 @@ class Product(TranslatedFieldsMixin, BaseAttributes): 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)() diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index fb2f7aeb..7ebfaa28 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -1,9 +1,11 @@ """Product app serializers.""" from rest_framework import serializers -from utils.serializers import TranslatedField +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) + CountrySimpleSerializer) class ProductSubTypeBaseSerializer(serializers.ModelSerializer): @@ -50,6 +52,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): model = Product fields = [ 'id', + 'slug', 'name_translated', 'category_display', 'description_translated', @@ -61,3 +64,33 @@ class ProductBaseSerializer(serializers.ModelSerializer): '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) \ No newline at end of file diff --git a/apps/product/urls/common.py b/apps/product/urls/common.py index 57abf4f0..d0dbb8a9 100644 --- a/apps/product/urls/common.py +++ b/apps/product/urls/common.py @@ -6,5 +6,7 @@ from product import views app_name = 'product' urlpatterns = [ - path('', views.ProductListView.as_view(), name='list') + path('', views.ProductListView.as_view(), name='list'), + path('slug//favorites/', views.CreateFavoriteProductView.as_view(), + name='create-destroy-favorites') ] diff --git a/apps/product/views/common.py b/apps/product/views/common.py index 403781e4..63b9677f 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -1,5 +1,6 @@ """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 @@ -17,4 +18,22 @@ class ProductListView(ProductBaseView, generics.ListAPIView): """List view for model Product.""" permission_classes = (permissions.AllowAny, ) serializer_class = serializers.ProductBaseSerializer - filter_class = filters.ProductListFilterSet + 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 diff --git a/apps/recipe/models.py b/apps/recipe/models.py index 6df7adc2..349fed7b 100644 --- a/apps/recipe/models.py +++ b/apps/recipe/models.py @@ -15,14 +15,12 @@ class RecipeQuerySet(models.QuerySet): def annotate_in_favorites(self, user): """Annotate flag in_favorites""" - favorite_establishments = [] + favorite_recipe_ids = [] if user.is_authenticated: - favorite_establishments = user.favorites.by_content_type(app_label='recipe', - model='recipe') \ - .values_list('object_id', flat=True) + favorite_recipe_ids = user.favorite_recipe_ids return self.annotate(in_favorites=models.Case( models.When( - id__in=favorite_establishments, + id__in=favorite_recipe_ids, then=True), default=False, output_field=models.BooleanField(default=False))) diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index eeff1043..bd7e8b8e 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -4,6 +4,7 @@ from django.core import exceptions from rest_framework import serializers from utils import models from translation.models import Language +from favorites.models import Favorites class EmptySerializer(serializers.Serializer): @@ -72,3 +73,28 @@ class ProjectModelSerializer(serializers.ModelSerializer): """Overrided ModelSerializer.""" 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') From b41ec3eebd64a953eaf1920e0bab01888ee711d6 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 17:16:51 +0300 Subject: [PATCH 29/62] add is_publish --- .../management/commands/add_publish_data.py | 18 ++++++++++++++++++ apps/establishment/transfer_data.py | 1 + apps/transfer/serializers/establishment.py | 4 ++++ 3 files changed, 23 insertions(+) create mode 100644 apps/establishment/management/commands/add_publish_data.py diff --git a/apps/establishment/management/commands/add_publish_data.py b/apps/establishment/management/commands/add_publish_data.py new file mode 100644 index 00000000..1c5aae3e --- /dev/null +++ b/apps/establishment/management/commands/add_publish_data.py @@ -0,0 +1,18 @@ +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' + self.stdout.write(self.style.WARNING(f'Updated {count} objects.')) diff --git a/apps/establishment/transfer_data.py b/apps/establishment/transfer_data.py index 9911d8f5..b398bc3e 100644 --- a/apps/establishment/transfer_data.py +++ b/apps/establishment/transfer_data.py @@ -30,6 +30,7 @@ def transfer_establishment(): 'type': item.type, 'phone': item.phone, 'created': item.created_at, + 'state': item.state, 'description': {}, 'website': None, 'facebook': None, diff --git a/apps/transfer/serializers/establishment.py b/apps/transfer/serializers/establishment.py index 314705de..c9979430 100644 --- a/apps/transfer/serializers/establishment.py +++ b/apps/transfer/serializers/establishment.py @@ -24,6 +24,7 @@ class EstablishmentSerializer(serializers.ModelSerializer): facebook = 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) + state = serializers.CharField(allow_null=True) tz = serializers.CharField() created = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S') @@ -47,6 +48,7 @@ class EstablishmentSerializer(serializers.ModelSerializer): 'location', # + получить новые объекты Address по old_id 'email', # + создать объект для ContactEmail 'phone', # + создать объект для ContactPhone + 'state', # + создать объект для ContactPhone ) 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']), 'address_id': self.get_address(data['location']), 'establishment_type_id': self.get_type(data), + 'is_publish': data.get('state') == 'published', }) data.pop('location') data.pop('type') + data.pop('state') return data @transaction.atomic From c32b665b9218af1ed25375b3074ccc39704c863d Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 29 Oct 2019 17:17:24 +0300 Subject: [PATCH 30/62] enable elastic --- apps/search_indexes/__init__.py | 2 +- apps/search_indexes/signals.py | 142 ++++++++++++++++---------------- project/settings/development.py | 4 +- 3 files changed, 74 insertions(+), 74 deletions(-) diff --git a/apps/search_indexes/__init__.py b/apps/search_indexes/__init__.py index c45cbc38..6c812219 100644 --- a/apps/search_indexes/__init__.py +++ b/apps/search_indexes/__init__.py @@ -1 +1 @@ -# from search_indexes.signals import update_document +from search_indexes.signals import update_document diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index e38fa028..3da50fb8 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -4,74 +4,74 @@ from django.dispatch import receiver from django_elasticsearch_dsl.registries import registry -# @receiver(post_save) -# def update_document(sender, **kwargs): -# from establishment.models import Establishment -# app_label = sender._meta.app_label -# model_name = sender._meta.model_name -# instance = kwargs['instance'] -# -# if app_label == 'location': -# if model_name == 'country': -# establishments = Establishment.objects.filter( -# address__city__country=instance) -# for establishment in establishments: -# registry.update(establishment) -# if model_name == 'city': -# establishments = Establishment.objects.filter( -# address__city=instance) -# for establishment in establishments: -# registry.update(establishment) -# if model_name == 'address': -# establishments = Establishment.objects.filter( -# address=instance) -# for establishment in establishments: -# registry.update(establishment) -# -# if app_label == 'establishment': -# # todo: remove after migration -# from establishment import models as establishment_models -# if model_name == 'establishmenttype': -# if isinstance(instance, establishment_models.EstablishmentType): -# establishments = Establishment.objects.filter( -# establishment_type=instance) -# for establishment in establishments: -# registry.update(establishment) -# if model_name == 'establishmentsubtype': -# if isinstance(instance, establishment_models.EstablishmentSubType): -# establishments = Establishment.objects.filter( -# establishment_subtypes=instance) -# for establishment in establishments: -# registry.update(establishment) -# -# if app_label == 'tag': -# if model_name == 'tag': -# establishments = Establishment.objects.filter(tags=instance) -# for establishment in establishments: -# registry.update(establishment) -# -# -# @receiver(post_save) -# def update_news(sender, **kwargs): -# from news.models import News -# app_label = sender._meta.app_label -# model_name = sender._meta.model_name -# instance = kwargs['instance'] -# -# if app_label == 'location': -# if model_name == 'country': -# qs = News.objects.filter(country=instance) -# for news in qs: -# registry.update(news) -# -# if app_label == 'news': -# if model_name == 'newstype': -# qs = News.objects.filter(news_type=instance) -# for news in qs: -# registry.update(news) -# -# if app_label == 'tag': -# if model_name == 'tag': -# qs = News.objects.filter(tags=instance) -# for news in qs: -# registry.update(news) +@receiver(post_save) +def update_document(sender, **kwargs): + from establishment.models import Establishment + app_label = sender._meta.app_label + model_name = sender._meta.model_name + instance = kwargs['instance'] + + if app_label == 'location': + if model_name == 'country': + establishments = Establishment.objects.filter( + address__city__country=instance) + for establishment in establishments: + registry.update(establishment) + if model_name == 'city': + establishments = Establishment.objects.filter( + address__city=instance) + for establishment in establishments: + registry.update(establishment) + if model_name == 'address': + establishments = Establishment.objects.filter( + address=instance) + for establishment in establishments: + registry.update(establishment) + + if app_label == 'establishment': + # todo: remove after migration + from establishment import models as establishment_models + if model_name == 'establishmenttype': + if isinstance(instance, establishment_models.EstablishmentType): + establishments = Establishment.objects.filter( + establishment_type=instance) + for establishment in establishments: + registry.update(establishment) + if model_name == 'establishmentsubtype': + if isinstance(instance, establishment_models.EstablishmentSubType): + establishments = Establishment.objects.filter( + establishment_subtypes=instance) + for establishment in establishments: + registry.update(establishment) + + if app_label == 'tag': + if model_name == 'tag': + establishments = Establishment.objects.filter(tags=instance) + for establishment in establishments: + registry.update(establishment) + + +@receiver(post_save) +def update_news(sender, **kwargs): + from news.models import News + app_label = sender._meta.app_label + model_name = sender._meta.model_name + instance = kwargs['instance'] + + if app_label == 'location': + if model_name == 'country': + qs = News.objects.filter(country=instance) + for news in qs: + registry.update(news) + + if app_label == 'news': + if model_name == 'newstype': + qs = News.objects.filter(news_type=instance) + for news in qs: + registry.update(news) + + if app_label == 'tag': + if model_name == 'tag': + qs = News.objects.filter(tags=instance) + for news in qs: + registry.update(news) diff --git a/project/settings/development.py b/project/settings/development.py index 6c643301..a9cfe5be 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -26,8 +26,8 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { - # 'search_indexes.documents.news': 'development_news', # temporarily disabled - # 'search_indexes.documents.establishment': 'development_establishment', + 'search_indexes.documents.news': 'development_news', # temporarily disabled + 'search_indexes.documents.establishment': 'development_establishment', } From 5675f23faee8e497e1a712f1d05a5312ada54f82 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 29 Oct 2019 17:57:11 +0300 Subject: [PATCH 31/62] fixed BaseModelAdminMixin --- apps/establishment/admin.py | 12 ++++++------ apps/news/admin.py | 2 +- apps/review/admin.py | 2 +- apps/utils/admin.py | 3 +-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 0397c407..8acadc02 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -11,12 +11,12 @@ from review import models as review_models @admin.register(models.EstablishmentType) -class EstablishmentTypeAdmin(BaseModelAdminMixin): +class EstablishmentTypeAdmin(BaseModelAdminMixin, admin.ModelAdmin): """EstablishmentType admin.""" @admin.register(models.EstablishmentSubType) -class EstablishmentSubTypeAdmin(BaseModelAdminMixin): +class EstablishmentSubTypeAdmin(BaseModelAdminMixin, admin.ModelAdmin): """EstablishmentSubType admin.""" @@ -37,7 +37,7 @@ class ContactEmailInline(admin.TabularInline): extra = 0 -class ReviewInline(GenericTabularInline): +class ReviewInline(BaseModelAdminMixin, GenericTabularInline): model = review_models.Review extra = 0 @@ -48,7 +48,7 @@ class CommentInline(GenericTabularInline): @admin.register(models.Establishment) -class EstablishmentAdmin(BaseModelAdminMixin): +class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Establishment admin.""" list_display = ['id', '__str__', 'image_tag', ] inlines = [ @@ -59,7 +59,7 @@ class EstablishmentAdmin(BaseModelAdminMixin): @admin.register(models.Position) -class PositionAdmin(BaseModelAdminMixin): +class PositionAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Position admin.""" @@ -70,7 +70,7 @@ class PlateInline(admin.TabularInline): @admin.register(models.Menu) -class MenuAdmin(BaseModelAdminMixin): +class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Menu admin.""" list_display = ['id', 'category_translated'] inlines = [ diff --git a/apps/news/admin.py b/apps/news/admin.py index 90527ea4..cc48a887 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -25,7 +25,7 @@ send_email_action.short_description = "Send the selected news by email" @admin.register(models.News) -class NewsAdmin(BaseModelAdminMixin): +class NewsAdmin(BaseModelAdminMixin, admin.ModelAdmin): """News admin.""" raw_id_fields = ('address',) actions = [send_email_action] diff --git a/apps/review/admin.py b/apps/review/admin.py index 03c2419a..2a7326ae 100644 --- a/apps/review/admin.py +++ b/apps/review/admin.py @@ -5,5 +5,5 @@ from utils.admin import BaseModelAdminMixin @admin.register(models.Review) -class ReviewAdminModel(BaseModelAdminMixin): +class ReviewAdminModel(BaseModelAdminMixin, admin.ModelAdmin): """Admin model for model Review.""" diff --git a/apps/utils/admin.py b/apps/utils/admin.py index 43680e20..fd5e353f 100644 --- a/apps/utils/admin.py +++ b/apps/utils/admin.py @@ -1,9 +1,8 @@ """Mixins for admin models.""" -from django.contrib import admin from django.db.models import ForeignKey -class BaseModelAdminMixin(admin.ModelAdmin): +class BaseModelAdminMixin: """ Class that overridden ModelAdmin and adds to readonly_fields attr persisted fields like created_by, modified_by. From 3f233fd2460e724677f340bed0fc5af693f33936 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 29 Oct 2019 18:13:21 +0300 Subject: [PATCH 32/62] update establishment document index & establishment documentview --- .../search_indexes/documents/establishment.py | 19 +------------------ apps/search_indexes/serializers.py | 7 +++++++ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 88f7e44a..98138146 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -10,16 +10,6 @@ EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1) -# todo: check & refactor -class ObjectField(fields.ObjectField): - - def get_value_from_instance(self, *args, **kwargs): - value = super(ObjectField, self).get_value_from_instance(*args, **kwargs) - if value == {}: - return None - return value - - @EstablishmentIndex.doc_type class EstablishmentDocument(Document): """Establishment document.""" @@ -63,7 +53,7 @@ class EstablishmentDocument(Document): 'closed_at': fields.KeywordField(attr='closed_at_str'), } )) - address = ObjectField( + address = fields.ObjectField( properties={ 'id': fields.IntegerField(), 'street_name_1': fields.TextField( @@ -93,13 +83,6 @@ 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: diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index cb1fe735..ee2e2ee8 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -13,6 +13,8 @@ class TagsDocumentSerializer(serializers.Serializer): label_translated = serializers.SerializerMethodField() def get_label_translated(self, obj): + if isinstance(obj, dict): + return get_translated_value(obj.get('label')) return get_translated_value(obj.label) @@ -29,6 +31,11 @@ class AddressDocumentSerializer(serializers.Serializer): geo_lon = serializers.FloatField(allow_null=True, source='coordinates.lon') geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') + def to_representation(self, instance): + if len(instance) != 0: + return super().to_representation(instance) + return None + class ScheduleDocumentSerializer(serializers.Serializer): """Schedule serializer for ES Document""" From 9fecbfe98f7d34e271440c3d99e6da271035d5cc Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 29 Oct 2019 19:17:44 +0300 Subject: [PATCH 33/62] EstablishmentViewSet default ordering and filtration --- apps/search_indexes/documents/establishment.py | 3 ++- apps/search_indexes/serializers.py | 5 ++++- apps/search_indexes/views.py | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 98138146..7eac2d6c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -91,6 +91,7 @@ class EstablishmentDocument(Document): 'id', 'name', 'name_translated', + 'is_publish', 'price_level', 'toque_number', 'public_mark', @@ -98,4 +99,4 @@ class EstablishmentDocument(Document): ) def get_queryset(self): - return super().get_queryset().published().with_es_related() + return super().get_queryset().with_es_related() diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index ee2e2ee8..b9df01b7 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -1,5 +1,6 @@ """Search indexes serializers.""" from rest_framework import serializers +from elasticsearch_dsl import AttrDict from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument @@ -31,8 +32,10 @@ class AddressDocumentSerializer(serializers.Serializer): geo_lon = serializers.FloatField(allow_null=True, source='coordinates.lon') geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') + # todo: refator def to_representation(self, instance): - if len(instance) != 0: + if instance != AttrDict(d={}) or \ + (isinstance(instance, dict) and len(instance) != 0): return super().to_representation(instance) return None diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 42f4a87e..72ce2efa 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -3,7 +3,8 @@ from rest_framework import permissions from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf.filter_backends import ( FilteringFilterBackend, - GeoSpatialFilteringFilterBackend + GeoSpatialFilteringFilterBackend, + DefaultOrderingFilterBackend, ) from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet from search_indexes import serializers, filters @@ -56,12 +57,20 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): pagination_class = ProjectMobilePagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentDocumentSerializer - ordering = ('id',) + + def get_queryset(self): + qs = super(EstablishmentDocumentViewSet, self).get_queryset() + qs = qs.filter('match', is_publish=True) + if self.request.country_code: + qs = qs.filter('term', + **{'address.city.country.code': self.request.country_code}) + return qs filter_backends = [ FilteringFilterBackend, filters.CustomSearchFilterBackend, GeoSpatialFilteringFilterBackend, + DefaultOrderingFilterBackend, ] search_fields = { @@ -72,6 +81,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): translated_search_fields = ( 'description', ) + ordering = 'id' filter_fields = { 'slug': 'slug', 'tag': { From a0aa503cfa59211a1286d745f2a2c5bf9e99c024 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 34/62] Add index_name type field to geo --- apps/establishment/serializers/common.py | 25 ++++++++++++++++++++++++ apps/establishment/views/web.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 14be142a..12e217a9 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -118,6 +118,18 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer): '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): """Serializer for EstablishmentSubType models.""" @@ -185,6 +197,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): """Serializer for Establishment model.""" diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 0699d9d0..63033d4b 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -152,7 +152,7 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView): """Resource for getting list of nearest establishments.""" - serializer_class = serializers.EstablishmentBaseSerializer + serializer_class = serializers.EstablishmentGeoSerializer filter_class = filters.EstablishmentFilter def get_queryset(self): From 55bdc7771f62da0953f261d9d00a29f20135a4f5 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Oct 2019 21:54:03 +0300 Subject: [PATCH 35/62] add image to establishment --- .../commands/add_establishment_image.py | 30 +++++++++++++ apps/transfer/models.py | 42 ++++++++++--------- 2 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 apps/establishment/management/commands/add_establishment_image.py diff --git a/apps/establishment/management/commands/add_establishment_image.py b/apps/establishment/management/commands/add_establishment_image.py new file mode 100644 index 00000000..7806bfe4 --- /dev/null +++ b/apps/establishment/management/commands/add_establishment_image.py @@ -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.')) diff --git a/apps/transfer/models.py b/apps/transfer/models.py index c431c504..fbcea0d8 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -99,7 +99,7 @@ class Ezuser(MigrateMixin): password_hash = models.CharField(max_length=50, blank=True, null=True) password_hash_type = models.IntegerField() facebook_id = models.BigIntegerField() - #TODO: в legacy нету таблицы 'CadLevel' + # TODO: в legacy нету таблицы 'CadLevel' # level = models.ForeignKey('CadLevel', models.DO_NOTHING) points = models.IntegerField() @@ -478,25 +478,27 @@ class Dishes(MigrateMixin): db_table = 'dishes' -# class EstablishmentAssets(MigrateMixin): -# 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) -# -# class Meta: -# managed = False -# db_table = 'establishment_assets' +class EstablishmentAssets(MigrateMixin): + 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): using = 'legacy' From a3bf92177163c56ade4e3a1008516127e8b0a3b0 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 29 Oct 2019 21:54:59 +0300 Subject: [PATCH 36/62] fix timezone --- apps/establishment/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4eacdd0d..7aea9dc5 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -438,7 +438,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def works_now(self): """ 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() 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: From e524483ed01abdd30bb5fa35028333111927d600 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Oct 2019 08:03:30 +0300 Subject: [PATCH 37/62] fix ass_publish_data --- .../management/commands/add_publish_data.py | 1 + docker-compose.mysql.yml | 14 -------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/establishment/management/commands/add_publish_data.py b/apps/establishment/management/commands/add_publish_data.py index 1c5aae3e..81a52890 100644 --- a/apps/establishment/management/commands/add_publish_data.py +++ b/apps/establishment/management/commands/add_publish_data.py @@ -15,4 +15,5 @@ class Command(BaseCommand): if obj: count += 1 obj.is_publish = item.state == 'published' + obj.save() self.stdout.write(self.style.WARNING(f'Updated {count} objects.')) diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index 3c891dce..213dabe8 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -52,17 +52,9 @@ services: # Redis redis: image: redis:2.8.23 - ports: - - "6379:6379" networks: - redis_network - # RabbitMQ - #rabbitmq: - # image: rabbitmq:latest - # ports: - # - "5672:5672" - # Celery worker: build: . @@ -78,7 +70,6 @@ services: - .:/code links: - db -# - rabbitmq - redis networks: - redis_network @@ -97,7 +88,6 @@ services: - .:/code links: - db -# - rabbitmq - redis networks: - redis_network @@ -116,7 +106,6 @@ services: depends_on: - mysql_db - db -# - rabbitmq - redis - worker - worker_beat @@ -135,13 +124,10 @@ services: volumes: gm-mysql_db: name: gm-mysql_db - gm-db: name: gm-db - gm-media: name: gm-media - gm-esdata: networks: From 9e6634f1a6e1534966def39c29d8e146693f8236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 10:48:59 +0300 Subject: [PATCH 38/62] Fix fab --- fabfile.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fabfile.py b/fabfile.py index 9ad7f871..8bcff1c2 100644 --- a/fabfile.py +++ b/fabfile.py @@ -53,12 +53,13 @@ def collectstatic(): def deploy(branch=None): - fetch() - install_requirements() - migrate() - collectstatic() - touch() - kill_celery() + if env.roledefs[role]['branch'] !='develop': + fetch() + install_requirements() + migrate() + collectstatic() + touch() + kill_celery() def rev(): From 24ee90e8a09d8a28bd6b9d534c0e7111e770a6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 10:56:48 +0300 Subject: [PATCH 39/62] Fix fab --- fabfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fabfile.py b/fabfile.py index 8bcff1c2..595a0903 100644 --- a/fabfile.py +++ b/fabfile.py @@ -53,6 +53,7 @@ def collectstatic(): def deploy(branch=None): + role = env.roles[0] if env.roledefs[role]['branch'] !='develop': fetch() install_requirements() From 156a5479aa08dfe0b4287ee5bd830e8e7fc4a508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 11:42:23 +0300 Subject: [PATCH 40/62] Fix --- fabfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabfile.py b/fabfile.py index 595a0903..e8a52f85 100644 --- a/fabfile.py +++ b/fabfile.py @@ -54,7 +54,7 @@ def collectstatic(): def deploy(branch=None): role = env.roles[0] - if env.roledefs[role]['branch'] !='develop': + if env.roledefs[role]['branch'] != 'develop': fetch() install_requirements() migrate() From 00bcd6c2050abaefe8ce0df5c9cbbf3b9bc4437d Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 30 Oct 2019 11:59:19 +0300 Subject: [PATCH 41/62] added merge migrations --- .../migrations/0046_merge_20191030_0858.py | 14 ++++++++++++++ .../migrations/0019_merge_20191030_0858.py | 14 ++++++++++++++ apps/news/migrations/0032_merge_20191030_0858.py | 14 ++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 apps/establishment/migrations/0046_merge_20191030_0858.py create mode 100644 apps/location/migrations/0019_merge_20191030_0858.py create mode 100644 apps/news/migrations/0032_merge_20191030_0858.py diff --git a/apps/establishment/migrations/0046_merge_20191030_0858.py b/apps/establishment/migrations/0046_merge_20191030_0858.py new file mode 100644 index 00000000..39153492 --- /dev/null +++ b/apps/establishment/migrations/0046_merge_20191030_0858.py @@ -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 = [ + ] diff --git a/apps/location/migrations/0019_merge_20191030_0858.py b/apps/location/migrations/0019_merge_20191030_0858.py new file mode 100644 index 00000000..268dc08f --- /dev/null +++ b/apps/location/migrations/0019_merge_20191030_0858.py @@ -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 = [ + ] diff --git a/apps/news/migrations/0032_merge_20191030_0858.py b/apps/news/migrations/0032_merge_20191030_0858.py new file mode 100644 index 00000000..2936caef --- /dev/null +++ b/apps/news/migrations/0032_merge_20191030_0858.py @@ -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 = [ + ] From 7805a6305585d33e9ae10bdbd88b4b8e7e488a39 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 30 Oct 2019 12:15:40 +0300 Subject: [PATCH 42/62] remove establishment document filtering by country code --- apps/search_indexes/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 72ce2efa..2bcd2ea4 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -61,9 +61,6 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): def get_queryset(self): qs = super(EstablishmentDocumentViewSet, self).get_queryset() qs = qs.filter('match', is_publish=True) - if self.request.country_code: - qs = qs.filter('term', - **{'address.city.country.code': self.request.country_code}) return qs filter_backends = [ From c5218393894481ec4d6351d6cde847f43db3d763 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Oct 2019 13:53:12 +0300 Subject: [PATCH 43/62] news refactor --- apps/news/transfer_data.py | 34 ++++++--- apps/transfer/models.py | 4 +- apps/transfer/serializers/news.py | 116 ++++++++++++++++-------------- docker-compose.mysql.yml | 2 +- 4 files changed, 91 insertions(+), 65 deletions(-) diff --git a/apps/news/transfer_data.py b/apps/news/transfer_data.py index 791b54ce..7524a6cc 100644 --- a/apps/news/transfer_data.py +++ b/apps/news/transfer_data.py @@ -1,28 +1,46 @@ 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 transfer.models import PageTexts from transfer.serializers.news import NewsSerializer -def transfer_news(): - news_type, _ = NewsType.objects.get_or_create(name="News") +class GroupConcat(Aggregate): + function = 'GROUP_CONCAT' + template = '%(function)s(%(expressions)s)' - queryset = PageTexts.objects.filter(page__type="News").annotate( + def __init__(self, expression, **extra): + 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') + queryset = PageTexts.objects.filter( + page__type='News', + ).annotate( news_type=Value(news_type.id, output_field=IntegerField()), country_code=F('page__site__country_code_2'), - ) - queryset = queryset.annotate(template=F('page__template')) + news_title=F('page__root_title'), + image=F('page__attachment_suffix_url'), + template=F('page__template'), + tags=GroupConcat('page__tags__id'), + )[:100] serialized_data = NewsSerializer(data=list(queryset.values()), many=True) if serialized_data.is_valid(): serialized_data.save() else: - pprint(f"News serializer errors: {serialized_data.errors}") + pprint(f'News serializer errors: {serialized_data.errors}') data_types = { - "news": [transfer_news] + 'news': [transfer_news] } diff --git a/apps/transfer/models.py b/apps/transfer/models.py index fbcea0d8..0f7d3348 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -761,6 +761,7 @@ class Pages(MigrateMixin): attachment_content_type = models.CharField(max_length=255, 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) geometries = models.CharField(max_length=1024, blank=True, null=True) scheduled_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField() @@ -785,7 +786,6 @@ class PageTexts(MigrateMixin): locale = 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_id = models.IntegerField(blank=True, null=True) created_at = models.DateTimeField() updated_at = models.DateTimeField() summary = models.TextField(blank=True, null=True) @@ -814,7 +814,7 @@ class PageMetadata(MigrateMixin): key = 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() updated_at = models.DateTimeField() diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index 82853cad..cafbe994 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -2,64 +2,67 @@ from rest_framework import serializers from location.models import Country from news.models import News +from tag.models import TagCategory from utils.legacy_parser import parse_legacy_news_content from utils.slug_generator import generate_unique_slug -class NewsSerializer(serializers.ModelSerializer): - locale = serializers.CharField() - slug = serializers.CharField() - body = serializers.CharField(allow_null=True) +class NewsSerializer(serializers.Serializer): + old_id = serializers.IntegerField() + news_type = serializers.IntegerField() + news_title = serializers.CharField() title = serializers.CharField() - template = serializers.CharField() + body = serializers.CharField(allow_null=True) + created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S') + slug = serializers.CharField() state = serializers.CharField() + template = serializers.CharField() country_code = serializers.CharField(allow_null=True) - created_at = serializers.DateTimeField(source='start', format='%m-%d-%Y %H:%M:%S') - - class Meta: - model = News - fields = ( - 'old_id', - 'created_at', - 'state', - 'template', - 'title', - 'body', - 'slug', - 'news_type', - 'locale', - 'country_code', - ) - - 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), - 'subtitle': self.get_description(data), - 'country': self.get_country(data), - }) - data.pop('country_code') - data.pop('body') - data.pop('locale') - return data + locale = serializers.CharField() + image = serializers.CharField() + tags = serializers.CharField(allow_null=True) def create(self, validated_data): - return News.objects.create(**validated_data) - @staticmethod - def get_country(data): - return Country.objects.filter(code__iexact=data['country_code']).first() - - @staticmethod - def get_template(data): - templates = { - 'main': News.MAIN, - 'main.pdf.erb': News.MAIN_PDF_ERB, + payload = { + 'old_id': validated_data['old_id'], + 'news_type': validated_data['news_type'], + 'title': {validated_data['locale']: validated_data['news_title']}, + 'subtitle': {validated_data['locale']: validated_data['title']}, + '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), } - return templates.get(data['template'], News.MAIN) + obj = News.objects.create(**payload) + + tags = self.get_tags(validated_data) + + return obj + + # TODO: теги + # TODO: галерея с картинкой + + @staticmethod + def get_tags(data): + if not data['tags']: + return None + tag_cat, _ = TagCategory.objects.get_or_create( + public=True, + label={'en-GB': 'tag'}, + index_name='tag', + country='tag', + ) + + + @staticmethod + def get_description(data): + if data['body']: + content = parse_legacy_news_content(data['body']) + return {data['locale']: content} + return None @staticmethod def get_state(data): @@ -73,12 +76,17 @@ class NewsSerializer(serializers.ModelSerializer): return states.get(data['state'], News.WAITING) @staticmethod - def get_title(data): - return {data['locale']: data['title']} + 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_description(data): - content = None - if data['body']: - content = parse_legacy_news_content(data['body']) - return {data['locale']: content} + def get_country(data): + return Country.objects.filter(code__iexact=data['country_code']).first() + + @staticmethod + def get_title(data): + return {data['locale']: data['title']} diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index 213dabe8..4d4e553e 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -51,7 +51,7 @@ services: # Redis redis: - image: redis:2.8.23 + image: redis:alpine networks: - redis_network From 42c596b5920d384b8b61d13a1f0fedae44ebc84f Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 44/62] Weekday for mobile devices --- apps/timetable/serialziers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index babe33c1..37725e1d 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -27,6 +27,7 @@ class ScheduleRUDSerializer(serializers.ModelSerializer): fields = [ 'id', 'weekday_display', + 'weekday', 'lunch_start', 'lunch_end', 'dinner_start', From 6fcfb263d3548d4475f88c9eecd412e88bf176a8 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 30 Oct 2019 14:23:40 +0300 Subject: [PATCH 45/62] fix ProductModelAdmin --- apps/product/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/product/admin.py b/apps/product/admin.py index b3dbc0cd..b77f2cbc 100644 --- a/apps/product/admin.py +++ b/apps/product/admin.py @@ -1,10 +1,11 @@ """Product admin conf.""" from django.contrib import admin +from utils.admin import BaseModelAdminMixin from .models import Product, ProductType, ProductSubType @admin.register(Product) -class ProductAdmin(admin.ModelAdmin): +class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Admin page for model Product.""" From 8c3306628ea5cce3cf478fc187ce72fe7baa8bf1 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Oct 2019 15:48:32 +0300 Subject: [PATCH 46/62] news transfer --- apps/news/transfer_data.py | 10 ++++-- apps/transfer/serializers/news.py | 57 +++++++++++++++++++++++-------- project/settings/base.py | 4 +-- project/settings/development.py | 4 +-- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/apps/news/transfer_data.py b/apps/news/transfer_data.py index 7524a6cc..bdd1c513 100644 --- a/apps/news/transfer_data.py +++ b/apps/news/transfer_data.py @@ -4,6 +4,7 @@ from django.db.models import Aggregate, CharField, Value from django.db.models import IntegerField, F from news.models import NewsType +from tag.models import TagCategory from transfer.models import PageTexts from transfer.serializers.news import NewsSerializer @@ -23,16 +24,21 @@ class GroupConcat(Aggregate): 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( - news_type=Value(news_type.id, output_field=IntegerField()), + 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'), - )[:100] + ) serialized_data = NewsSerializer(data=list(queryset.values()), many=True) if serialized_data.is_valid(): diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index cafbe994..4159be67 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -1,15 +1,18 @@ from rest_framework import serializers +from gallery.models import Image from location.models import Country -from news.models import News -from tag.models import TagCategory +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.slug_generator import generate_unique_slug class NewsSerializer(serializers.Serializer): - old_id = serializers.IntegerField() - news_type = serializers.IntegerField() + id = serializers.IntegerField() + tag_cat_id = serializers.IntegerField() + news_type_id = serializers.IntegerField() news_title = serializers.CharField() title = serializers.CharField() body = serializers.CharField(allow_null=True) @@ -25,8 +28,8 @@ class NewsSerializer(serializers.Serializer): def create(self, validated_data): payload = { - 'old_id': validated_data['old_id'], - 'news_type': validated_data['news_type'], + 'old_id': validated_data['id'], + 'news_type_id': validated_data['news_type_id'], 'title': {validated_data['locale']: validated_data['news_title']}, 'subtitle': {validated_data['locale']: validated_data['title']}, 'description': self.get_description(validated_data), @@ -39,23 +42,47 @@ class NewsSerializer(serializers.Serializer): 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 - # TODO: теги - # TODO: галерея с картинкой + @staticmethod + def make_gallery(data, obj): + if not data['image'] or data['image'] == 'default/missing.png': + return + + img = Image.objects.create( + 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 None - tag_cat, _ = TagCategory.objects.get_or_create( - public=True, - label={'en-GB': 'tag'}, - index_name='tag', - country='tag', - ) + 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): diff --git a/project/settings/base.py b/project/settings/base.py index 3d3ec8ca..6979382e 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -329,8 +329,8 @@ REDOC_SETTINGS = { # RabbitMQ # BROKER_URL = 'amqp://rabbitmq:5672' # Redis -BROKER_URL = 'redis://localhost:6379/1' -# BROKER_URL = 'redis://redis:6379/1' +# BROKER_URL = 'redis://localhost:6379/1' +BROKER_URL = 'redis://redis:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL CELERY_ACCEPT_CONTENT = ['application/json'] diff --git a/project/settings/development.py b/project/settings/development.py index a9cfe5be..b9f03146 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -19,8 +19,8 @@ DOMAIN_URI = 'gm.id-east.ru' # ELASTICSEARCH SETTINGS ELASTICSEARCH_DSL = { 'default': { - 'hosts': 'localhost:9200' - # 'hosts': 'elasticsearch:9200' + # 'hosts': 'localhost:9200' + 'hosts': 'elasticsearch:9200' } } From b74b87d78da7c089a8fe8dfcaa5e69f8cb943905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 17:23:45 +0300 Subject: [PATCH 47/62] Add products model in transfer --- apps/product/management/__init__.py | 0 apps/product/management/commands/__init__.py | 0 .../management/commands/add_product.py | 0 apps/product/old_models.py | 0 apps/transfer/models.py | 42 +++++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 apps/product/management/__init__.py create mode 100644 apps/product/management/commands/__init__.py create mode 100644 apps/product/management/commands/add_product.py create mode 100644 apps/product/old_models.py diff --git a/apps/product/management/__init__.py b/apps/product/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/product/management/commands/__init__.py b/apps/product/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/product/management/commands/add_product.py b/apps/product/management/commands/add_product.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/product/old_models.py b/apps/product/old_models.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 0f7d3348..fecbc48b 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -841,3 +841,45 @@ class Ads(MigrateMixin): class Meta: managed = False db_table = 'ads' + +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' From f6151c0a14a4fdfd124656b84564c066b0775098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 17:24:40 +0300 Subject: [PATCH 48/62] delete okd_models from product --- apps/product/old_models.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/product/old_models.py diff --git a/apps/product/old_models.py b/apps/product/old_models.py deleted file mode 100644 index e69de29b..00000000 From c1c9f99fb56f09dc83a1e439e56e973062f72433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 30 Oct 2019 18:04:30 +0300 Subject: [PATCH 49/62] Add product migrate data --- .../management/commands/add_product.py | 54 +++++++++++++++++++ apps/product/models.py | 18 +++++++ apps/transfer/models.py | 1 + 3 files changed, 73 insertions(+) diff --git a/apps/product/management/commands/add_product.py b/apps/product/management/commands/add_product.py index e69de29b..981487ec 100644 --- a/apps/product/management/commands/add_product.py +++ b/apps/product/management/commands/add_product.py @@ -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) + + + + diff --git a/apps/product/models.py b/apps/product/models.py index d4011fa0..332a92f6 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -46,10 +46,28 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin): # 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, diff --git a/apps/transfer/models.py b/apps/transfer/models.py index fecbc48b..1a978596 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -842,6 +842,7 @@ class Ads(MigrateMixin): managed = False db_table = 'ads' + 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) From 58be2491a55151db539df2252940683af17bfee3 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 30 Oct 2019 18:47:13 +0300 Subject: [PATCH 50/62] create command for establishment tags --- .../migrations/0032_auto_20191030_1149.py | 18 ++++++ apps/news/models.py | 2 +- apps/tag/management/__init__.py | 0 apps/tag/management/commands/__init__.py | 0 apps/tag/management/commands/add_tags.py | 62 +++++++++++++++++++ .../tag/migrations/0006_auto_20191030_1151.py | 23 +++++++ .../tag/migrations/0007_auto_20191030_1514.py | 18 ++++++ apps/tag/models.py | 17 ++++- apps/transfer/models.py | 54 ++++++++++++++++ apps/transfer/serializers/news.py | 11 +++- 10 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 apps/news/migrations/0032_auto_20191030_1149.py create mode 100644 apps/tag/management/__init__.py create mode 100644 apps/tag/management/commands/__init__.py create mode 100644 apps/tag/management/commands/add_tags.py create mode 100644 apps/tag/migrations/0006_auto_20191030_1151.py create mode 100644 apps/tag/migrations/0007_auto_20191030_1514.py diff --git a/apps/news/migrations/0032_auto_20191030_1149.py b/apps/news/migrations/0032_auto_20191030_1149.py new file mode 100644 index 00000000..75408f46 --- /dev/null +++ b/apps/news/migrations/0032_auto_20191030_1149.py @@ -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'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index f0caa13c..6508215d 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -126,7 +126,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): (PUBLISHED_EXCLUSIVE, _('Published exclusive')), ) - old_id = models.PositiveIntegerField(_('odl id'), blank=True, null=True, default=None) + old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, diff --git a/apps/tag/management/__init__.py b/apps/tag/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/management/commands/__init__.py b/apps/tag/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/management/commands/add_tags.py b/apps/tag/management/commands/add_tags.py new file mode 100644 index 00000000..5fc721b7 --- /dev/null +++ b/apps/tag/management/commands/add_tags.py @@ -0,0 +1,62 @@ +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): + + categories = legacy.KeyValueMetadata.objects.all() + existing_establishment = Establishment.objects.filter(old_id__isnull=False) + for category in categories: + 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}, + 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}, + value=tag.value, + category=tag_category, + ) + est = existing_establishment.get(old_id=tag.establishment_id) + est.tags.add(new_tag) + est.save() + diff --git a/apps/tag/migrations/0006_auto_20191030_1151.py b/apps/tag/migrations/0006_auto_20191030_1151.py new file mode 100644 index 00000000..b11a4c20 --- /dev/null +++ b/apps/tag/migrations/0006_auto_20191030_1151.py @@ -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'), + ), + ] diff --git a/apps/tag/migrations/0007_auto_20191030_1514.py b/apps/tag/migrations/0007_auto_20191030_1514.py new file mode 100644 index 00000000..1bcb3deb --- /dev/null +++ b/apps/tag/migrations/0007_auto_20191030_1514.py @@ -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'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index 26079849..e3967f0f 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -19,6 +19,8 @@ class Tag(TranslatedFieldsMixin, models.Model): label = TJSONField(blank=True, null=True, default=None, verbose_name=_('label'), 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, null=True, related_name='tags', verbose_name=_('Category')) @@ -72,6 +74,16 @@ class TagCategoryQuerySet(models.QuerySet): class TagCategory(TranslatedFieldsMixin, models.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, verbose_name=_('label'), help_text='{"en-GB":"some text"}') @@ -80,7 +92,10 @@ class TagCategory(TranslatedFieldsMixin, models.Model): default=None) public = models.BooleanField(default=False) 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() diff --git a/apps/transfer/models.py b/apps/transfer/models.py index fbcea0d8..99dfd0ac 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -841,3 +841,57 @@ class Ads(MigrateMixin): class Meta: managed = False 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' diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index 82853cad..6842f248 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -15,6 +15,7 @@ class NewsSerializer(serializers.ModelSerializer): state = serializers.CharField() country_code = serializers.CharField(allow_null=True) created_at = serializers.DateTimeField(source='start', format='%m-%d-%Y %H:%M:%S') + summary = serializers.CharField(allow_null=True, allow_blank=True) class Meta: model = News @@ -29,6 +30,7 @@ class NewsSerializer(serializers.ModelSerializer): 'news_type', 'locale', 'country_code', + 'summary' ) def validate(self, data): @@ -38,7 +40,7 @@ class NewsSerializer(serializers.ModelSerializer): 'template': self.get_template(data), 'title': self.get_title(data), 'description': self.get_description(data), - 'subtitle': self.get_description(data), + 'subtitle': self.get_subtitle(data), 'country': self.get_country(data), }) data.pop('country_code') @@ -82,3 +84,10 @@ class NewsSerializer(serializers.ModelSerializer): if data['body']: content = parse_legacy_news_content(data['body']) return {data['locale']: content} + + @staticmethod + def get_subtitle(data): + if 'summary' in data: + content = data.pop('summary') + if content: + return {data['locale']: content} From 523e3a1ca0cf5b3198fbd96ed0a0df59bc3fd507 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 30 Oct 2019 18:59:16 +0300 Subject: [PATCH 51/62] merge work in subtitle --- apps/transfer/serializers/news.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index 49b64476..311cee6a 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -28,7 +28,7 @@ class NewsSerializer(serializers.Serializer): 'old_id': validated_data['old_id'], 'news_type': validated_data['news_type'], 'title': {validated_data['locale']: validated_data['news_title']}, - 'subtitle': {validated_data['locale']: validated_data['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']), @@ -91,16 +91,10 @@ class NewsSerializer(serializers.Serializer): def get_title(data): return {data['locale']: data['title']} - @staticmethod - def get_description(data): - content = None - if data['body']: - content = parse_legacy_news_content(data['body']) - return {data['locale']: content} - @staticmethod def get_subtitle(data): if 'summary' in data: content = data.pop('summary') - if content: - return {data['locale']: content} + if not content: + content = {data['locale']: data['title']} + return {data['locale']: content} From 887bc92b850c8afce9383cf92a5cb38cb704f779 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 30 Oct 2019 19:17:50 +0300 Subject: [PATCH 52/62] fix merge --- project/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/base.py b/project/settings/base.py index 3a23122a..5a52f212 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -75,7 +75,7 @@ PROJECT_APPS = [ 'favorites.apps.FavoritesConfig', 'rating.apps.RatingConfig', 'transfer.apps.TransferConfig', - 'tag.apps.TagConfig' + 'tag.apps.TagConfig', 'product.apps.ProductConfig', ] From d31bb9b495629ecb780ffdc7079e3839d494469d Mon Sep 17 00:00:00 2001 From: "a.feteleu" Date: Wed, 30 Oct 2019 19:19:40 +0300 Subject: [PATCH 53/62] merge migrations --- .../migrations/0046_merge_20191030_1618.py | 14 ++++++++++++++ .../migrations/0019_merge_20191030_1618.py | 14 ++++++++++++++ apps/news/migrations/0033_merge_20191030_1618.py | 14 ++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 apps/establishment/migrations/0046_merge_20191030_1618.py create mode 100644 apps/location/migrations/0019_merge_20191030_1618.py create mode 100644 apps/news/migrations/0033_merge_20191030_1618.py diff --git a/apps/establishment/migrations/0046_merge_20191030_1618.py b/apps/establishment/migrations/0046_merge_20191030_1618.py new file mode 100644 index 00000000..853b25eb --- /dev/null +++ b/apps/establishment/migrations/0046_merge_20191030_1618.py @@ -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 = [ + ] diff --git a/apps/location/migrations/0019_merge_20191030_1618.py b/apps/location/migrations/0019_merge_20191030_1618.py new file mode 100644 index 00000000..74d64c63 --- /dev/null +++ b/apps/location/migrations/0019_merge_20191030_1618.py @@ -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 = [ + ] diff --git a/apps/news/migrations/0033_merge_20191030_1618.py b/apps/news/migrations/0033_merge_20191030_1618.py new file mode 100644 index 00000000..05811754 --- /dev/null +++ b/apps/news/migrations/0033_merge_20191030_1618.py @@ -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 = [ + ] From aa2759d7e063e924fc1c6800a692be6f6e732acc Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 30 Oct 2019 19:51:33 +0300 Subject: [PATCH 54/62] fix estab tags --- apps/tag/management/commands/add_tags.py | 79 ++++++++++++------------ 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/apps/tag/management/commands/add_tags.py b/apps/tag/management/commands/add_tags.py index 5fc721b7..bf6d4f3c 100644 --- a/apps/tag/management/commands/add_tags.py +++ b/apps/tag/management/commands/add_tags.py @@ -10,53 +10,52 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): - categories = legacy.KeyValueMetadata.objects.all() existing_establishment = Establishment.objects.filter(old_id__isnull=False) - for category in categories: - ESTABLISHMENT = 1 - SHOP = 2 - RESTAURANT = 3 - WINEYARD = 4 + ESTABLISHMENT = 1 + SHOP = 2 + RESTAURANT = 3 + WINEYARD = 4 - MAPPER = { - RESTAURANT: EstablishmentType.RESTAURANT, - WINEYARD: EstablishmentType.PRODUCER, - } + 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') + 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) + est_type, _ = EstablishmentType.objects.get_or_create(index_name=value) - key_value_metadata = legacy.KeyValueMetadata.objects.filter(id__in=values_meta_id_list) + 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, + # 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}, + tag_category.value_type = key_value.value_type + tag_category.save() + est_type.tag_categories.add( + tag_category ) - if created: - tag_category.label = {'en-GB': 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))): - # 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}, - value=tag.value, - category=tag_category, - ) - est = existing_establishment.get(old_id=tag.establishment_id) - est.tags.add(new_tag) - est.save() + new_tag, _ = Tag.objects.get_or_create( + label={'en-GB': tag.value}, + value=tag.value, + category=tag_category, + ) + est = existing_establishment.get(old_id=tag.establishment_id) + est.tags.add(new_tag) + est.save() From 776b500d195b0dad9aef888a9ea497d455eeb290 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 30 Oct 2019 19:59:12 +0300 Subject: [PATCH 55/62] modified NewsImageSerializer, in future needs refactoring --- apps/news/serializers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 1389d20e..ee6d5d33 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -100,7 +100,9 @@ class NewsImageSerializer(serializers.ModelSerializer): orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) - original_url = serializers.URLField(source='image.url') + # todo: refactor + # original_url = serializers.URLField(source='image.url') + original_url = serializers.SerializerMethodField() auto_crop_images = CropImageSerializer(source='image', allow_null=True) class Meta: @@ -116,6 +118,10 @@ class NewsImageSerializer(serializers.ModelSerializer): 'orientation': {'write_only': True} } + def get_original_url(self, obj): + """Get absolute image url.""" + return obj.image.__str__() + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" From 414ad72ed3c3397522c6d69c286a3d0ce1674bc3 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 30 Oct 2019 20:01:19 +0300 Subject: [PATCH 56/62] Revert "modified NewsImageSerializer, in future needs refactoring" This reverts commit 776b500d --- apps/news/serializers.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index ee6d5d33..1389d20e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -100,9 +100,7 @@ class NewsImageSerializer(serializers.ModelSerializer): orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) - # todo: refactor - # original_url = serializers.URLField(source='image.url') - original_url = serializers.SerializerMethodField() + original_url = serializers.URLField(source='image.url') auto_crop_images = CropImageSerializer(source='image', allow_null=True) class Meta: @@ -118,10 +116,6 @@ class NewsImageSerializer(serializers.ModelSerializer): 'orientation': {'write_only': True} } - def get_original_url(self, obj): - """Get absolute image url.""" - return obj.image.__str__() - class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" From 7e503f617e6e5e5fb5b2c799ce353d704d328496 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Oct 2019 20:15:19 +0300 Subject: [PATCH 57/62] merge migrations --- .../migrations/0047_merge_20191030_1714.py | 14 ++++ .../migrations/0020_merge_20191030_1714.py | 14 ++++ .../migrations/0034_merge_20191030_1714.py | 14 ++++ apps/transfer/models.py | 82 +++++++++---------- 4 files changed, 83 insertions(+), 41 deletions(-) create mode 100644 apps/establishment/migrations/0047_merge_20191030_1714.py create mode 100644 apps/location/migrations/0020_merge_20191030_1714.py create mode 100644 apps/news/migrations/0034_merge_20191030_1714.py diff --git a/apps/establishment/migrations/0047_merge_20191030_1714.py b/apps/establishment/migrations/0047_merge_20191030_1714.py new file mode 100644 index 00000000..845a472e --- /dev/null +++ b/apps/establishment/migrations/0047_merge_20191030_1714.py @@ -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 = [ + ] diff --git a/apps/location/migrations/0020_merge_20191030_1714.py b/apps/location/migrations/0020_merge_20191030_1714.py new file mode 100644 index 00000000..f0b28e5a --- /dev/null +++ b/apps/location/migrations/0020_merge_20191030_1714.py @@ -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 = [ + ] diff --git a/apps/news/migrations/0034_merge_20191030_1714.py b/apps/news/migrations/0034_merge_20191030_1714.py new file mode 100644 index 00000000..5ac187ab --- /dev/null +++ b/apps/news/migrations/0034_merge_20191030_1714.py @@ -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 = [ + ] diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 4033f3b7..ccda8cd6 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -897,44 +897,44 @@ class KeyValueMetadatumKeyValueMetadatumEstablishments(MigrateMixin): 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' +# 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' From e75229b4d3e1d613adb946e351afca7b2e5ce22c Mon Sep 17 00:00:00 2001 From: "a.feteleu" Date: Wed, 30 Oct 2019 20:35:15 +0300 Subject: [PATCH 58/62] fix elastic settings --- project/settings/development.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/settings/development.py b/project/settings/development.py index b9f03146..a9cfe5be 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -19,8 +19,8 @@ DOMAIN_URI = 'gm.id-east.ru' # ELASTICSEARCH SETTINGS ELASTICSEARCH_DSL = { 'default': { - # 'hosts': 'localhost:9200' - 'hosts': 'elasticsearch:9200' + 'hosts': 'localhost:9200' + # 'hosts': 'elasticsearch:9200' } } From e74c32f0cb8e24b082786c671c0e279df72e12b4 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 30 Oct 2019 23:15:40 +0300 Subject: [PATCH 59/62] fix summary --- apps/transfer/serializers/news.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index 34052e0a..021aefe0 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -15,6 +15,7 @@ class NewsSerializer(serializers.Serializer): news_type_id = serializers.IntegerField() news_title = serializers.CharField() title = 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() From 1bbe481a390c6bca898a6d2fc973f6c615f44994 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 31 Oct 2019 00:16:33 +0300 Subject: [PATCH 60/62] add settings for phone --- project/settings/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/settings/base.py b/project/settings/base.py index 18f835bd..5b096020 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -485,3 +485,6 @@ STATICFILES_DIRS = ( # MEDIA MEDIA_LOCATION = 'media' + +PHONENUMBER_DB_FORMAT = 'NATIONAL' +PHONENUMBER_DEFAULT_REGION = "FR" From e653e6c7da388e8d29da4695fee6e24dc2e50579 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 31 Oct 2019 00:53:01 +0300 Subject: [PATCH 61/62] fix tag command --- apps/tag/management/commands/add_tags.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/tag/management/commands/add_tags.py b/apps/tag/management/commands/add_tags.py index bf6d4f3c..e1e97f33 100644 --- a/apps/tag/management/commands/add_tags.py +++ b/apps/tag/management/commands/add_tags.py @@ -39,7 +39,11 @@ class Command(BaseCommand): ) if created: - tag_category.label = {'en-GB': key_value.key_name}, + 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( From a4c9c8f0cc1a4f6a1a6bb7119de13ff981051e08 Mon Sep 17 00:00:00 2001 From: "a.feteleu" Date: Thu, 31 Oct 2019 02:16:29 +0300 Subject: [PATCH 62/62] fix tags command --- apps/tag/management/commands/add_tags.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/tag/management/commands/add_tags.py b/apps/tag/management/commands/add_tags.py index e1e97f33..89ee89a1 100644 --- a/apps/tag/management/commands/add_tags.py +++ b/apps/tag/management/commands/add_tags.py @@ -55,7 +55,11 @@ class Command(BaseCommand): establishment__id__in=list(existing_establishment.values_list('old_id', flat=True))): new_tag, _ = Tag.objects.get_or_create( - label={'en-GB': tag.value}, + label={ + 'en-GB': tag.value, + 'fr-FR': tag.value, + 'ru-RU': tag.value, + }, value=tag.value, category=tag_category, )