From 1687e804929ee22e218ce98b33e9a72e82f99f3e 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, 21 Nov 2019 13:13:19 +0300 Subject: [PATCH 01/44] Check serial number for product --- .../management/commands/add_product_tag.py | 11 +++++++++++ .../migrations/0019_product_serial_number.py | 18 ++++++++++++++++++ apps/product/models.py | 4 ++++ 3 files changed, 33 insertions(+) create mode 100644 apps/product/migrations/0019_product_serial_number.py diff --git a/apps/product/management/commands/add_product_tag.py b/apps/product/management/commands/add_product_tag.py index 6377fcac..9ee09bdd 100644 --- a/apps/product/management/commands/add_product_tag.py +++ b/apps/product/management/commands/add_product_tag.py @@ -149,6 +149,17 @@ class Command(BaseCommand): tag.label = new_label tag.save() + + def check_serail_number(self): + category = TagCategory.objects.get(index_name='serial_number') + tags = Tag.objects.filter(category=category, products__isnull=False) + for tag in tqdm(tags, desc='Update serial number for product'): + tag.products.all().update(serial_number=tag.value) + + self.stdout.write(self.style.WARNING(f'Check serial number product end.')) + + + def handle(self, *args, **kwargs): self.remove_tags_product() self.remove_tags() diff --git a/apps/product/migrations/0019_product_serial_number.py b/apps/product/migrations/0019_product_serial_number.py new file mode 100644 index 00000000..eb8bef34 --- /dev/null +++ b/apps/product/migrations/0019_product_serial_number.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-11-21 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0018_purchasedproduct'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='serial_number', + field=models.CharField(default=None, max_length=255, null=True, verbose_name='Serial number'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index f499afee..3d27559f 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -219,6 +219,10 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, HasTagsM comments = generic.GenericRelation(to='comment.Comment') awards = generic.GenericRelation(to='main.Award', related_query_name='product') + serial_number = models.CharField(max_length=255, + default=None, null=True, + verbose_name=_('Serial number')) + objects = ProductManager.from_queryset(ProductQuerySet)() class Meta: From 938b9436eb4e7a142a3cd78e3a11da2a0a873f9d 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, 21 Nov 2019 13:25:31 +0300 Subject: [PATCH 02/44] Fix name def --- apps/product/management/commands/add_product_tag.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/product/management/commands/add_product_tag.py b/apps/product/management/commands/add_product_tag.py index 9ee09bdd..1ff823b0 100644 --- a/apps/product/management/commands/add_product_tag.py +++ b/apps/product/management/commands/add_product_tag.py @@ -149,8 +149,7 @@ class Command(BaseCommand): tag.label = new_label tag.save() - - def check_serail_number(self): + def check_serial_number(self): category = TagCategory.objects.get(index_name='serial_number') tags = Tag.objects.filter(category=category, products__isnull=False) for tag in tqdm(tags, desc='Update serial number for product'): @@ -158,8 +157,6 @@ class Command(BaseCommand): self.stdout.write(self.style.WARNING(f'Check serial number product end.')) - - def handle(self, *args, **kwargs): self.remove_tags_product() self.remove_tags() @@ -168,3 +165,4 @@ class Command(BaseCommand): self.add_tag() self.check_tag() self.add_product_tag() + self.check_serial_number() From 9a2f7fec39b8e9d170e417dde53e49c3312e8098 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, 21 Nov 2019 14:33:27 +0300 Subject: [PATCH 03/44] fix --- apps/product/management/commands/add_product_tag.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/product/management/commands/add_product_tag.py b/apps/product/management/commands/add_product_tag.py index 1ff823b0..e430166d 100644 --- a/apps/product/management/commands/add_product_tag.py +++ b/apps/product/management/commands/add_product_tag.py @@ -101,14 +101,12 @@ class Command(BaseCommand): p.tags.clear() print('End clear tags product') - def remove_tags(self): print('Begin delete many tags') Tag.objects.\ filter(news__isnull=True, establishments__isnull=True).delete() print('End delete many tags') - def product_sql(self): with connections['legacy'].cursor() as cursor: cursor.execute(''' From a2087213a4265b82eddb36e1d80cd8643bb794f4 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, 21 Nov 2019 15:22:22 +0300 Subject: [PATCH 04/44] Check serial number command --- .../management/commands/add_product_tag.py | 11 +--------- .../commands/check_serial_number.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 apps/product/management/commands/check_serial_number.py diff --git a/apps/product/management/commands/add_product_tag.py b/apps/product/management/commands/add_product_tag.py index e430166d..01f5d7f7 100644 --- a/apps/product/management/commands/add_product_tag.py +++ b/apps/product/management/commands/add_product_tag.py @@ -7,7 +7,7 @@ from tqdm import tqdm class Command(BaseCommand): - help = '''Add add product tags networks from old db to new db. + help = '''Add product tags networks from old db to new db. Run after add_product!!!''' def category_sql(self): @@ -147,14 +147,6 @@ class Command(BaseCommand): tag.label = new_label tag.save() - def check_serial_number(self): - category = TagCategory.objects.get(index_name='serial_number') - tags = Tag.objects.filter(category=category, products__isnull=False) - for tag in tqdm(tags, desc='Update serial number for product'): - tag.products.all().update(serial_number=tag.value) - - self.stdout.write(self.style.WARNING(f'Check serial number product end.')) - def handle(self, *args, **kwargs): self.remove_tags_product() self.remove_tags() @@ -163,4 +155,3 @@ class Command(BaseCommand): self.add_tag() self.check_tag() self.add_product_tag() - self.check_serial_number() diff --git a/apps/product/management/commands/check_serial_number.py b/apps/product/management/commands/check_serial_number.py new file mode 100644 index 00000000..ae2e43a3 --- /dev/null +++ b/apps/product/management/commands/check_serial_number.py @@ -0,0 +1,22 @@ +from django.core.management.base import BaseCommand +from django.db import connections +from establishment.management.commands.add_position import namedtuplefetchall +from tag.models import Tag, TagCategory +from product.models import Product, ProductType +from tqdm import tqdm + + +class Command(BaseCommand): + help = '''Check product serial number from old db to new db. + Run after add_product_tag!!!''' + + def check_serial_number(self): + category = TagCategory.objects.get(index_name='serial_number') + tags = Tag.objects.filter(category=category, products__isnull=False) + for tag in tqdm(tags, desc='Update serial number for product'): + tag.products.all().update(serial_number=tag.value) + + self.stdout.write(self.style.WARNING(f'Check serial number product end.')) + + def handle(self, *args, **kwargs): + self.check_serial_number() From 5236bca874fe03d14c550ae9496c0037ce4cebcf 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, 21 Nov 2019 15:22:43 +0300 Subject: [PATCH 05/44] Check serial number command --- apps/product/management/commands/check_serial_number.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/product/management/commands/check_serial_number.py b/apps/product/management/commands/check_serial_number.py index ae2e43a3..75d1a693 100644 --- a/apps/product/management/commands/check_serial_number.py +++ b/apps/product/management/commands/check_serial_number.py @@ -1,8 +1,5 @@ from django.core.management.base import BaseCommand -from django.db import connections -from establishment.management.commands.add_position import namedtuplefetchall from tag.models import Tag, TagCategory -from product.models import Product, ProductType from tqdm import tqdm From 26b8dcd082f997883cc56628a84e254c647b26b0 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, 21 Nov 2019 15:26:43 +0300 Subject: [PATCH 06/44] Check serial number command --- apps/product/management/commands/check_serial_number.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/product/management/commands/check_serial_number.py b/apps/product/management/commands/check_serial_number.py index 75d1a693..d9caf69a 100644 --- a/apps/product/management/commands/check_serial_number.py +++ b/apps/product/management/commands/check_serial_number.py @@ -12,6 +12,7 @@ class Command(BaseCommand): tags = Tag.objects.filter(category=category, products__isnull=False) for tag in tqdm(tags, desc='Update serial number for product'): tag.products.all().update(serial_number=tag.value) + tag.products.clear() self.stdout.write(self.style.WARNING(f'Check serial number product end.')) From c2e111f75b0e72e6ed398009c8c452f69720446a Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 27 Nov 2019 18:12:22 +0300 Subject: [PATCH 07/44] Fix queryset --- apps/partner/views/common.py | 2 +- docker-compose.mysql.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/partner/views/common.py b/apps/partner/views/common.py index a5d0338c..3c45fb6b 100644 --- a/apps/partner/views/common.py +++ b/apps/partner/views/common.py @@ -8,7 +8,7 @@ from partner.serializers import common as serializers # Mixins class PartnerViewMixin(generics.GenericAPIView): """View mixin for Partner views""" - queryset = models.Partner.objects.all() + queryset = models.Partner.objects.distinct("name") # Views diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index bd81ecb2..106cabb3 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -13,6 +13,9 @@ services: MYSQL_ROOT_PASSWORD: rootPassword volumes: - gm-mysql_db:/var/lib/mysql + - .:/code + + # PostgreSQL database From 93056cf524994e5a4d3a7a33d11cd01a28fa84b3 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 3 Dec 2019 13:35:52 +0300 Subject: [PATCH 08/44] Add filter Add queryset Add settings --- _dockerfiles/db/Dockerfile | 2 +- apps/location/filters.py | 24 ++++++++++++++++++++++++ apps/location/models.py | 15 +++++++++++++++ apps/location/views/back.py | 4 ++++ docker-compose.mysql.yml | 1 + project/settings/local.py | 14 +++++++++++++- 6 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 apps/location/filters.py diff --git a/_dockerfiles/db/Dockerfile b/_dockerfiles/db/Dockerfile index 45c707d9..e8a9ded3 100644 --- a/_dockerfiles/db/Dockerfile +++ b/_dockerfiles/db/Dockerfile @@ -1,3 +1,3 @@ -FROM mdillon/postgis:9.5 +FROM mdillon/postgis:latest RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8 ENV LANG ru_RU.utf8 diff --git a/apps/location/filters.py b/apps/location/filters.py new file mode 100644 index 00000000..5e95db44 --- /dev/null +++ b/apps/location/filters.py @@ -0,0 +1,24 @@ +from django.core.validators import EMPTY_VALUES +from django_filters import rest_framework as filters + +from location import models + + +class CityBackFilter(filters.FilterSet): + """Employee filter set.""" + + search = filters.CharFilter(method='search_by_name') + + class Meta: + """Meta class.""" + + model = models.City + fields = ( + 'search', + ) + + def search_by_name(self, queryset, name, value): + """Search by name or last name.""" + if value not in EMPTY_VALUES: + return queryset.search_by_name(value) + return queryset diff --git a/apps/location/models.py b/apps/location/models.py index 3f104644..32a6cf4f 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -5,6 +5,9 @@ from django.db.models.signals import post_save from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ +from functools import reduce +from typing import List + from translation.models import Language from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField, @@ -92,6 +95,18 @@ class Region(models.Model): class CityQuerySet(models.QuerySet): """Extended queryset for City model.""" + def _generic_search(self, value, filter_fields_names: List[str]): + """Generic method for searching value in specified fields""" + filters = [ + {f'{field}__icontains': value} + for field in filter_fields_names + ] + return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters])) + + def search_by_name(self, value): + """Search by name or last_name.""" + return self._generic_search(value, ['name', 'code', 'postal_code']) + def by_country_code(self, code): """Return establishments by country code""" return self.filter(country__code=code) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 4d420154..8406dee3 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -9,6 +9,8 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from django.shortcuts import get_object_or_404 from utils.serializers import ImageBaseSerializer +from location import filters + # Address @@ -31,6 +33,8 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CitySerializer permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + queryset = models.City.objects.all() + filter_class = filters.CityBackFilter class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index a8900226..800a9644 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -29,6 +29,7 @@ services: - "5436:5432" volumes: - gm-db:/var/lib/postgresql/data/ + - .:/code elasticsearch: diff --git a/project/settings/local.py b/project/settings/local.py index 6a592a46..989af4ef 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -41,7 +41,19 @@ DATABASES.update({ 'PORT': 3306, 'NAME': 'dev', 'USER': 'dev', - 'PASSWORD': 'octosecret123'}}) + 'PASSWORD': 'octosecret123'}, + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': os.environ.get('DB_NAME'), + 'USER': os.environ.get('DB_USERNAME'), + 'PASSWORD': os.environ.get('DB_PASSWORD'), + 'HOST': os.environ.get('DB_HOSTNAME'), + 'PORT': os.environ.get('DB_PORT'), + 'OPTIONS': { + 'options': '-c search_path=gm' + }, + }, +}) # LOGGING From a81f3f079ae873da6795c75a7fd49b91e3be6635 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 4 Dec 2019 13:37:49 +0300 Subject: [PATCH 09/44] Changed url id to slug in back-office --- apps/establishment/serializers/back.py | 6 +++- apps/establishment/serializers/common.py | 4 ++- apps/establishment/tests.py | 36 ++++++++++++------------ apps/establishment/urls/back.py | 20 ++++++------- apps/establishment/views/back.py | 25 ++++++++++------ apps/timetable/serialziers.py | 8 +++++- apps/utils/serializers.py | 4 +++ apps/utils/views.py | 6 +++- 8 files changed, 69 insertions(+), 40 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index dd16e861..a1b6cc4a 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -232,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer): def validate(self, attrs): """Override validate method.""" establishment_pk = self.get_request_kwargs().get('pk') + establishment_slug = self.get_request_kwargs().get('slug') + + search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug} + image_id = self.get_request_kwargs().get('image_id') - establishment_qs = models.Establishment.objects.filter(pk=establishment_pk) + establishment_qs = models.Establishment.objects.filter(**search_kwargs) image_qs = Image.objects.filter(id=image_id) if not establishment_qs.exists(): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7364d02c..e8a68cc0 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -486,7 +486,9 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer): """Serializer to carousel object w/ model News.""" def validate(self, attrs): - establishment = models.Establishment.objects.filter(pk=self.pk).first() + search_kwargs = {'pk': self.pk} if self.pk else {'slug': self.slug} + + establishment = models.Establishment.objects.filter(**search_kwargs).first() if not establishment: raise serializers.ValidationError({'detail': _('Object not found.')}) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index bd96b052..6bc23ccc 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -104,18 +104,18 @@ class EstablishmentBTests(BaseTestCase): response = self.client.post('/api/back/establishments/', data=data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') + response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) update_data = { 'name': 'Test new establishment' } - response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', + response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', + response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -372,22 +372,22 @@ class EstablishmentShedulerTests(ChildTestCase): 'weekday': 1 } - response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data) + response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) schedule = response.data - response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') + response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/') self.assertEqual(response.status_code, status.HTTP_200_OK) update_data = { 'weekday': 2 } - response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/', + response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') + response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -410,21 +410,21 @@ class EstablishmentWebTagTests(BaseTestCase): class EstablishmentWebSlugTests(ChildTestCase): def test_slug_Read(self): - response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/', format='json') + response = self.client.get(f'/api/web/establishments/{self.establishment.id}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) class EstablishmentWebSimilarTests(ChildTestCase): def test_similar_Read(self): - response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/similar/', format='json') + response = self.client.get(f'/api/web/establishments/{self.establishment.id}/similar/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) class EstablishmentWebCommentsTests(ChildTestCase): def test_comments_CRUD(self): - response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json') + response = self.client.get(f'/api/web/establishments/{self.establishment.id}/comments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) data = { @@ -433,13 +433,13 @@ class EstablishmentWebCommentsTests(ChildTestCase): 'mark': 4 } - response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/comments/create/', + response = self.client.post(f'/api/web/establishments/{self.establishment.id}/comments/create/', data=data) comment = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + response = self.client.get(f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -448,12 +448,12 @@ class EstablishmentWebCommentsTests(ChildTestCase): } response = self.client.patch( - f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.delete( - f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -466,13 +466,13 @@ class EstablishmentWebFavoriteTests(ChildTestCase): "object_id": self.establishment.id } - response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', + response = self.client.post(f'/api/web/establishments/{self.establishment.id}/favorites/', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.delete( - f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', + f'/api/web/establishments/{self.establishment.id}/favorites/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -484,8 +484,8 @@ class EstablishmentCarouselTests(ChildTestCase): "object_id": self.establishment.id } - response = self.client.post(f'/api/back/establishments/{self.establishment.id}/carousels/', data=data) + response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/carousels/') + response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index d9b2fbd7..ce1c7b27 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -8,25 +8,25 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListCreateView.as_view(), name='list'), - path('/', views.EstablishmentRUDView.as_view(), name='detail'), - path('/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(), + path('slug//', views.EstablishmentRUDView.as_view(), name='detail'), + path('slug//carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'), - path('/schedule//', views.EstablishmentScheduleRUDView.as_view(), + path('slug//schedule//', views.EstablishmentScheduleRUDView.as_view(), name='schedule-rud'), - path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), + path('slug//schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/gallery/', views.EstablishmentGalleryListView.as_view(), + path('slug//gallery/', views.EstablishmentGalleryListView.as_view(), name='gallery-list'), - path('/gallery//', + path('slug//gallery//', views.EstablishmentGalleryCreateDestroyView.as_view(), name='gallery-create-destroy'), - path('/companies/', views.EstablishmentCompanyListCreateView.as_view(), + path('slug//companies/', views.EstablishmentCompanyListCreateView.as_view(), name='company-list-create'), - path('/companies//', views.EstablishmentCompanyRUDView.as_view(), + path('slug//companies//', views.EstablishmentCompanyRUDView.as_view(), name='company-rud'), - path('/notes/', views.EstablishmentNoteListCreateView.as_view(), + path('slug//notes/', views.EstablishmentNoteListCreateView.as_view(), name='note-list-create'), - path('/notes//', views.EstablishmentNoteRUDView.as_view(), + path('slug//notes//', views.EstablishmentNoteRUDView.as_view(), name='note-rud'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index d3afbf2e..b92929c1 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -31,6 +31,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): + lookup_field = 'slug' queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer permission_classes = [IsCountryAdmin | IsEstablishmentManager] @@ -38,6 +39,7 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" + lookup_field = 'slug' serializer_class = ScheduleRUDSerializer permission_classes = [IsEstablishmentManager] @@ -45,11 +47,11 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """ Returns the object the view is displaying. """ - establishment_pk = self.kwargs['pk'] + establishment_slug = self.kwargs['slug'] schedule_id = self.kwargs['schedule_id'] establishment = get_object_or_404(klass=models.Establishment.objects.all(), - pk=establishment_pk) + slug=establishment_slug) schedule = get_object_or_404(klass=establishment.schedule, id=schedule_id) @@ -62,6 +64,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleCreateView(generics.CreateAPIView): """Establishment schedule Create view""" + lookup_field = 'slug' serializer_class = ScheduleCreateSerializer queryset = Timetable.objects.all() permission_classes = [IsEstablishmentManager] @@ -210,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, CreateDestroyGalleryViewMixin): """Resource for a create|destroy gallery for establishment for back-office users.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentBackOfficeGallerySerializer def get_object(self): @@ -218,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, """ establishment_qs = self.filter_queryset(self.get_queryset()) - establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(establishment_qs, slug=self.kwargs.get('slug')) gallery = get_object_or_404(establishment.establishment_gallery, image_id=self.kwargs.get('image_id')) @@ -231,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, class EstablishmentGalleryListView(EstablishmentMixinViews, generics.ListAPIView): """Resource for returning gallery for establishment for back-office users.""" + lookup_field = 'slug' serializer_class = serializers.ImageBaseSerializer def get_object(self): """Override get_object method.""" qs = super(EstablishmentGalleryListView, self).get_queryset() - establishment = get_object_or_404(qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(qs, slug=self.kwargs.get('slug')) # May raise a permission denied self.check_object_permissions(self.request, establishment) @@ -252,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView): """List|Create establishment company view.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentCompanyListCreateSerializer def get_object(self): @@ -259,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews, establishment_qs = models.Establishment.objects.all() filtered_ad_qs = self.filter_queryset(establishment_qs) - establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug')) # May raise a permission denied self.check_object_permissions(self.request, establishment) @@ -275,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView): """Create|Retrieve|Update|Destroy establishment company view.""" + lookup_field = 'slug' serializer_class = serializers.CompanyBaseSerializer def get_object(self): @@ -282,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews, establishment_qs = models.Establishment.objects.all() filtered_ad_qs = self.filter_queryset(establishment_qs) - establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug')) company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk')) # May raise a permission denied @@ -295,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView): """Retrieve|Update|Destroy establishment note view.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentNoteListCreateSerializer def get_object(self): @@ -302,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews, establishment_qs = models.Establishment.objects.all() filtered_establishment_qs = self.filter_queryset(establishment_qs) - establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug')) # May raise a permission denied self.check_object_permissions(self.request, establishment) @@ -318,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView): """Create|Retrieve|Update|Destroy establishment note view.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentNoteBaseSerializer def get_object(self): @@ -325,7 +334,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews, establishment_qs = models.Establishment.objects.all() filtered_establishment_qs = self.filter_queryset(establishment_qs) - establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) + establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug')) note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk']) # May raise a permission denied diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index 48c8374d..305be0ec 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -45,8 +45,14 @@ class ScheduleRUDSerializer(serializers.ModelSerializer): .parser_context.get('view')\ .kwargs.get('pk') + establishment_slug = self.context.get('request')\ + .parser_context.get('view')\ + .kwargs.get('slug') + + search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug} + # Check if establishment exists. - establishment_qs = Establishment.objects.filter(pk=establishment_pk) + establishment_qs = Establishment.objects.filter(**search_kwargs) if not establishment_qs.exists(): raise serializers.ValidationError({'detail': _('Establishment not found.')}) attrs['establishment'] = establishment_qs.first() diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index f55b69bc..15a58f7b 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -118,6 +118,10 @@ class CarouselCreateSerializer(serializers.ModelSerializer): def pk(self): return self.request.parser_context.get('kwargs').get('pk') + @property + def slug(self): + return self.request.parser_context.get('kwargs').get('slug') + class RecursiveFieldSerializer(serializers.Serializer): def to_representation(self, value): diff --git a/apps/utils/views.py b/apps/utils/views.py index 478a3cb2..db10dbfd 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -158,7 +158,11 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView): lookup_field = 'id' def get_base_object(self): - return get_object_or_404(self._model, id=self.kwargs['pk']) + search_kwargs = { + 'id': self.kwargs.get('pk'), + 'slug': self.kwargs.get('slug'), + } + return get_object_or_404(self._model, **search_kwargs) def get_object(self): """ From 4202eb679b5a938d0a454cfcffa11b429cd05578 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 4 Dec 2019 15:48:54 +0300 Subject: [PATCH 10/44] Corrections for tests --- apps/establishment/tests.py | 18 +++++++++--------- apps/establishment/views/web.py | 1 + apps/tag/serializers.py | 1 + apps/utils/views.py | 11 +++++++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 6bc23ccc..f46cccc3 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -410,21 +410,21 @@ class EstablishmentWebTagTests(BaseTestCase): class EstablishmentWebSlugTests(ChildTestCase): def test_slug_Read(self): - response = self.client.get(f'/api/web/establishments/{self.establishment.id}/', format='json') + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) class EstablishmentWebSimilarTests(ChildTestCase): def test_similar_Read(self): - response = self.client.get(f'/api/web/establishments/{self.establishment.id}/similar/', format='json') + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/similar/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) class EstablishmentWebCommentsTests(ChildTestCase): def test_comments_CRUD(self): - response = self.client.get(f'/api/web/establishments/{self.establishment.id}/comments/', format='json') + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) data = { @@ -433,13 +433,13 @@ class EstablishmentWebCommentsTests(ChildTestCase): 'mark': 4 } - response = self.client.post(f'/api/web/establishments/{self.establishment.id}/comments/create/', + response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/comments/create/', data=data) comment = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get(f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -448,12 +448,12 @@ class EstablishmentWebCommentsTests(ChildTestCase): } response = self.client.patch( - f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', + f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.delete( - f'/api/web/establishments/{self.establishment.id}/comments/{comment["id"]}/', + f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -466,13 +466,13 @@ class EstablishmentWebFavoriteTests(ChildTestCase): "object_id": self.establishment.id } - response = self.client.post(f'/api/web/establishments/{self.establishment.id}/favorites/', + response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.delete( - f'/api/web/establishments/{self.establishment.id}/favorites/', + f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index bd826e4e..65a56a5b 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -142,6 +142,7 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView): class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView): """View for create/destroy establishment from carousel.""" + lookup_field = 'slug' _model = models.Establishment serializer_class = serializers.EstablishmentCarouselCreateSerializer diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 2cc818a9..27590b18 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -37,6 +37,7 @@ class TagBackOfficeSerializer(TagBaseSerializer): 'category' ) + class TagCategoryProductSerializer(serializers.ModelSerializer): """SHORT Serializer for TagCategory""" diff --git a/apps/utils/views.py b/apps/utils/views.py index db10dbfd..e158357e 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -158,10 +158,10 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView): lookup_field = 'id' def get_base_object(self): - search_kwargs = { - 'id': self.kwargs.get('pk'), - 'slug': self.kwargs.get('slug'), - } + establishment_pk = self.kwargs.get('pk') + establishment_slug = self.kwargs.get('slug') + + search_kwargs = {'id': establishment_pk} if establishment_pk else {'slug': establishment_slug} return get_object_or_404(self._model, **search_kwargs) def get_object(self): @@ -175,6 +175,9 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView): # self.check_object_permissions(self.request, carousels) return carousels + def perform_destroy(self, instance): + instance.delete() + self.es_update_base_object() # BackOffice user`s views & viewsets class BindObjectMixin: From 72d52e5c19747094183e4c115e3f34d40c967dc9 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 4 Dec 2019 15:51:03 +0300 Subject: [PATCH 11/44] Removed unusable destroy function --- apps/utils/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/utils/views.py b/apps/utils/views.py index e158357e..9ac8ca60 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -175,9 +175,6 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView): # self.check_object_permissions(self.request, carousels) return carousels - def perform_destroy(self, instance): - instance.delete() - self.es_update_base_object() # BackOffice user`s views & viewsets class BindObjectMixin: From 88b190d76db506d76f47a8259e5dc1501b101816 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, 6 Dec 2019 09:45:38 +0300 Subject: [PATCH 12/44] winery choice --- apps/account/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index 205171d0..53163d95 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -33,7 +33,7 @@ class Role(ProjectBaseMixin): REVIEWER_MANGER = 6 RESTAURANT_REVIEWER = 7 SALES_MAN = 8 - WINERY_REVIEWER = 9 + WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 ROLE_CHOICES = ( From 8c8acd3e191ccd93a4cdbf3dcc722775d81e0f4c 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, 6 Dec 2019 11:13:07 +0300 Subject: [PATCH 13/44] Models --- .../0024_role_establishment_subtype.py | 20 +++++++++++++++++++ apps/account/models.py | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 apps/account/migrations/0024_role_establishment_subtype.py diff --git a/apps/account/migrations/0024_role_establishment_subtype.py b/apps/account/migrations/0024_role_establishment_subtype.py new file mode 100644 index 00000000..3b9062c5 --- /dev/null +++ b/apps/account/migrations/0024_role_establishment_subtype.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.7 on 2019-12-06 06:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0067_auto_20191122_1244'), + ('account', '0023_auto_20191204_0916'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='establishment_subtype', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.EstablishmentSubType', verbose_name='Establishment subtype'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 53163d95..8f6c6233 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -1,5 +1,6 @@ """Account models""" from datetime import datetime +from tabnanny import verbose from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager @@ -15,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from authorization.models import Application -from establishment.models import Establishment +from establishment.models import Establishment, EstablishmentSubType from location.models import Country from main.models import SiteSettings from utils.models import GMTokenGenerator @@ -54,6 +55,9 @@ class Role(ProjectBaseMixin): null=True, blank=True, on_delete=models.SET_NULL) site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'), null=True, blank=True, on_delete=models.SET_NULL) + establishment_subtype = models.ForeignKey(EstablishmentSubType, + verbose_name=_('Establishment subtype'), + null=True, blank=True, on_delete=models.SET_NULL) class UserManager(BaseUserManager): From 7635eafb0906291896a3b426b99b2f0c56ac039d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 13:11:29 +0300 Subject: [PATCH 14/44] booking urls for mobile (cherry picked from commit 0df3425) --- apps/booking/urls/__init__.py | 0 apps/booking/{urls.py => urls/common.py} | 0 apps/booking/urls/mobile.py | 8 ++++++++ apps/booking/urls/web.py | 8 ++++++++ project/urls/mobile.py | 1 + project/urls/web.py | 2 +- 6 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 apps/booking/urls/__init__.py rename apps/booking/{urls.py => urls/common.py} (100%) create mode 100644 apps/booking/urls/mobile.py create mode 100644 apps/booking/urls/web.py diff --git a/apps/booking/urls/__init__.py b/apps/booking/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/urls.py b/apps/booking/urls/common.py similarity index 100% rename from apps/booking/urls.py rename to apps/booking/urls/common.py diff --git a/apps/booking/urls/mobile.py b/apps/booking/urls/mobile.py new file mode 100644 index 00000000..57e58c8f --- /dev/null +++ b/apps/booking/urls/mobile.py @@ -0,0 +1,8 @@ +from booking.urls import common as common_views +app = 'booking' + + +urlpatterns_api = [] + +urlpatterns = urlpatterns_api + \ + common_views.urlpatterns diff --git a/apps/booking/urls/web.py b/apps/booking/urls/web.py new file mode 100644 index 00000000..57e58c8f --- /dev/null +++ b/apps/booking/urls/web.py @@ -0,0 +1,8 @@ +from booking.urls import common as common_views +app = 'booking' + + +urlpatterns_api = [] + +urlpatterns = urlpatterns_api + \ + common_views.urlpatterns diff --git a/project/urls/mobile.py b/project/urls/mobile.py index 4fa53ad9..4368189e 100644 --- a/project/urls/mobile.py +++ b/project/urls/mobile.py @@ -3,6 +3,7 @@ from django.urls import path, include app_name = 'mobile' urlpatterns = [ + path('booking/', include('booking.urls.web')), path('establishments/', include('establishment.urls.mobile')), path('location/', include('location.urls.mobile')), path('main/', include('main.urls.mobile')), diff --git a/project/urls/web.py b/project/urls/web.py index c5a609e2..99c12937 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -19,7 +19,7 @@ app_name = 'web' urlpatterns = [ path('account/', include('account.urls.web')), - path('booking/', include('booking.urls')), + path('booking/', include('booking.urls.web')), path('re_blocks/', include('advertisement.urls.web')), path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), From c272dc1255e1e51b90aa9973a0196aaa9a8a8686 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 6 Dec 2019 13:15:27 +0300 Subject: [PATCH 15/44] increase JWT token lifetime --- project/settings/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project/settings/base.py b/project/settings/base.py index 6648f0af..1d2c5744 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -414,10 +414,10 @@ SORL_THUMBNAIL_ALIASES = { SIMPLE_JWT = { # Increase access token lifetime b.c. front-end dev's cant send multiple # requests to API in one HTTP request. - 'ACCESS_TOKEN_LIFETIME': timedelta(days=30), - 'ACCESS_TOKEN_LIFETIME_SECONDS': 21600, # 6 hours in seconds - 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), - 'REFRESH_TOKEN_LIFETIME_SECONDS': 2592000, # 30 days in seconds + 'ACCESS_TOKEN_LIFETIME': timedelta(days=182), + 'ACCESS_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months + 'REFRESH_TOKEN_LIFETIME': timedelta(days=182), + 'REFRESH_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, @@ -453,7 +453,7 @@ NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html' # COOKIES -COOKIES_MAX_AGE = 2628000 # 30 days +COOKIES_MAX_AGE = 15730000 # 6 months SESSION_COOKIE_SAMESITE = None From 91b528feb9cf846aa7ad13f29d0686be1fa57829 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 14:03:06 +0300 Subject: [PATCH 16/44] tags for establishments --- apps/establishment/models.py | 8 +++++++- apps/establishment/serializers/common.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 3cdef691..fb401ddd 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -457,9 +457,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, def visible_tags(self): return super().visible_tags \ .exclude(category__index_name__in=['guide', 'collection', 'purchased_item', - 'business_tag', 'business_tags_de', 'tag']) + 'business_tag', 'business_tags_de']) \ + .exclude(value__in=['rss', 'rss_selection']) # todo: recalculate toque_number + @property + def visible_tags_detail(self): + """Removes some tags from detail Establishment representation""" + return self.visible_tags.exclude(category__index_name__in=['tag']) + def recalculate_toque_number(self): toque_number = 0 if self.address and self.public_mark: diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 23fc24df..4025eb96 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -369,7 +369,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer): employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', many=True) address = AddressDetailSerializer(read_only=True) - + tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags_detail') menu = MenuSerializers(source='menu_set', many=True, read_only=True) best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) From e08296c52d1eef433da9494204e5de87b1ed5c0c Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 6 Dec 2019 14:12:26 +0300 Subject: [PATCH 17/44] address to product detail web api --- apps/establishment/serializers/common.py | 26 +++++++++++++++++++++++- apps/product/serializers/common.py | 12 +++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 23fc24df..0b2cd6be 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -17,7 +17,7 @@ from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer from utils.serializers import (ProjectModelSerializer, TranslatedField, FavoritesCreateSerializer) from location.serializers import EstablishmentWineRegionBaseSerializer, \ - EstablishmentWineOriginBaseSerializer + EstablishmentWineOriginBaseSerializer class ContactPhonesSerializer(serializers.ModelSerializer): @@ -239,6 +239,30 @@ class EstablishmentShortSerializer(serializers.ModelSerializer): ] +class _EstablishmentAddressShortSerializer(serializers.ModelSerializer): + """Short serializer for establishment.""" + city = CitySerializer(source='address.city', allow_null=True) + establishment_type = EstablishmentTypeGeoSerializer() + establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True) + currency = CurrencySerializer(read_only=True) + address = AddressBaseSerializer(read_only=True) + + class Meta: + """Meta class.""" + model = models.Establishment + fields = [ + 'id', + 'name', + 'index_name', + 'slug', + 'city', + 'establishment_type', + 'establishment_subtypes', + 'currency', + 'address', + ] + + class EstablishmentProductShortSerializer(serializers.ModelSerializer): """SHORT Serializer for displaying info about an establishment on product page.""" establishment_type = EstablishmentTypeGeoSerializer() diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index 983cc2ab..d794ca93 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -4,15 +4,15 @@ from rest_framework import serializers from comment.models import Comment from comment.serializers import CommentSerializer -from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer, EstablishmentProductShortSerializer -from gallery.models import Image +from establishment.serializers import EstablishmentProductShortSerializer +from establishment.serializers.common import _EstablishmentAddressShortSerializer +from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer +from main.serializers import AwardSerializer from product import models from review.serializers import ReviewShortSerializer +from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer from utils import exceptions as utils_exceptions from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer -from main.serializers import AwardSerializer -from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer -from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer class ProductTagSerializer(TagBaseSerializer): @@ -119,7 +119,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): class ProductDetailSerializer(ProductBaseSerializer): """Product detail serializer.""" description_translated = TranslatedField() - establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True) + establishment_detail = _EstablishmentAddressShortSerializer(source='establishment', read_only=True) review = ReviewShortSerializer(source='last_published_review', read_only=True) awards = AwardSerializer(many=True, read_only=True) classifications = ProductClassificationBaseSerializer(many=True, read_only=True) From 1372538acf7e722a97ca4064dea3eaf00bb51654 Mon Sep 17 00:00:00 2001 From: dormantman Date: Thu, 5 Dec 2019 18:10:24 +0300 Subject: [PATCH 18/44] Merge branch 'fix/dublicate-products' of /home/dormantman/PycharmProjects/gm-backend with conflicts. --- .gitignore | 6 ++---- apps/product/views/common.py | 2 ++ project/settings/local.py | 36 +++++++++++++----------------------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 90e4f23f..ea6151b0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,10 +21,8 @@ logs/ /geoip_db/ # dev -./docker-compose.override.yml +docker-compose.override.yml + celerybeat-schedule local_files celerybeat.pid -/gm_viktor.dump -/docker-compose.dump.yml -/gm_production_20191029.sql diff --git a/apps/product/views/common.py b/apps/product/views/common.py index f984a87b..764a0c97 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -26,6 +26,8 @@ class ProductListView(ProductBaseView, generics.ListAPIView): filter_class = filters.ProductFilterSet def get_queryset(self): + print(super().get_queryset()) + qs = super().get_queryset().with_extended_related() \ .by_country_code(self.request.country_code) return qs diff --git a/project/settings/local.py b/project/settings/local.py index c56f9042..fe521d92 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -30,31 +30,18 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) THUMBNAIL_DEBUG = True # ADDED TRANSFER APP -INSTALLED_APPS.append('transfer.apps.TransferConfig') +# INSTALLED_APPS.append('transfer.apps.TransferConfig') # DATABASES -DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': os.environ.get('DB_NAME'), - 'USER': os.environ.get('DB_USERNAME'), - 'PASSWORD': os.environ.get('DB_PASSWORD'), - 'HOST': os.environ.get('DB_HOSTNAME'), - 'PORT': os.environ.get('DB_PORT'), - 'OPTIONS': { - 'options': '-c search_path=gm' - }, - }, - 'legacy': { - 'ENGINE': 'django.db.backends.mysql', - # 'HOST': '172.22.0.1', - 'HOST': 'mysql_db', - 'PORT': 3306, - 'NAME': 'dev', - 'USER': 'dev', - 'PASSWORD': 'octosecret123' - }, -} +# DATABASES.update({ +# 'legacy': { +# 'ENGINE': 'django.db.backends.mysql', +# # 'HOST': '172.22.0.1', +# 'HOST': 'mysql_db', +# 'PORT': 3306, +# 'NAME': 'dev', +# 'USER': 'dev', +# 'PASSWORD': 'octosecret123'}}) # LOGGING @@ -113,3 +100,6 @@ TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} ELASTICSEARCH_DSL_AUTOSYNC = False + +# INSTALLED APPS +INSTALLED_APPS.append('transfer.apps.TransferConfig') \ No newline at end of file From 0c5140d35944328b2916b6eabaa04ec8677b23a5 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 6 Dec 2019 15:03:18 +0300 Subject: [PATCH 19/44] Added current product exclude --- apps/product/filters.py | 6 ++++++ apps/product/models.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/apps/product/filters.py b/apps/product/filters.py index c7e87dda..1d226356 100644 --- a/apps/product/filters.py +++ b/apps/product/filters.py @@ -9,6 +9,7 @@ class ProductFilterSet(filters.FilterSet): """Product filter set.""" establishment_id = filters.NumberFilter() + current_product = filters.CharFilter(method='without_current_product') product_type = filters.CharFilter(method='by_product_type') product_subtype = filters.CharFilter(method='by_product_subtype') @@ -21,6 +22,11 @@ class ProductFilterSet(filters.FilterSet): 'product_subtype', ] + def without_current_product(self, queryset, name, value): + if value not in EMPTY_VALUES: + return queryset.without_current_product(value) + return queryset + def by_product_type(self, queryset, name, value): if value not in EMPTY_VALUES: return queryset.by_product_type(value) diff --git a/apps/product/models.py b/apps/product/models.py index a6129c48..6d3e4954 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -102,6 +102,11 @@ class ProductQuerySet(models.QuerySet): def wines(self): return self.filter(type__index_name__icontains=ProductType.WINE) + def without_current_product(self, current_product: str): + """Exclude by current product.""" + kwargs = {'pk': int(current_product)} if current_product.isdigit() else {'slug': current_product} + return self.exclude(**kwargs) + def by_product_type(self, product_type: str): """Filter by type.""" return self.filter(product_type__index_name__icontains=product_type) From 6a9356aaa6b9efc7f5a41ff32a58c2b37196bf76 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 15:10:48 +0300 Subject: [PATCH 20/44] fix merge conflicts --- .gitignore | 6 ++++-- apps/product/views/common.py | 2 -- project/settings/local.py | 36 +++++++++++++++++++++++------------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ea6151b0..90e4f23f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,10 @@ logs/ /geoip_db/ # dev -docker-compose.override.yml - +./docker-compose.override.yml celerybeat-schedule local_files celerybeat.pid +/gm_viktor.dump +/docker-compose.dump.yml +/gm_production_20191029.sql diff --git a/apps/product/views/common.py b/apps/product/views/common.py index 764a0c97..f984a87b 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -26,8 +26,6 @@ class ProductListView(ProductBaseView, generics.ListAPIView): filter_class = filters.ProductFilterSet def get_queryset(self): - print(super().get_queryset()) - qs = super().get_queryset().with_extended_related() \ .by_country_code(self.request.country_code) return qs diff --git a/project/settings/local.py b/project/settings/local.py index fe521d92..c56f9042 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -30,18 +30,31 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) THUMBNAIL_DEBUG = True # ADDED TRANSFER APP -# INSTALLED_APPS.append('transfer.apps.TransferConfig') +INSTALLED_APPS.append('transfer.apps.TransferConfig') # DATABASES -# DATABASES.update({ -# 'legacy': { -# 'ENGINE': 'django.db.backends.mysql', -# # 'HOST': '172.22.0.1', -# 'HOST': 'mysql_db', -# 'PORT': 3306, -# 'NAME': 'dev', -# 'USER': 'dev', -# 'PASSWORD': 'octosecret123'}}) +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': os.environ.get('DB_NAME'), + 'USER': os.environ.get('DB_USERNAME'), + 'PASSWORD': os.environ.get('DB_PASSWORD'), + 'HOST': os.environ.get('DB_HOSTNAME'), + 'PORT': os.environ.get('DB_PORT'), + 'OPTIONS': { + 'options': '-c search_path=gm' + }, + }, + 'legacy': { + 'ENGINE': 'django.db.backends.mysql', + # 'HOST': '172.22.0.1', + 'HOST': 'mysql_db', + 'PORT': 3306, + 'NAME': 'dev', + 'USER': 'dev', + 'PASSWORD': 'octosecret123' + }, +} # LOGGING @@ -100,6 +113,3 @@ TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} ELASTICSEARCH_DSL_AUTOSYNC = False - -# INSTALLED APPS -INSTALLED_APPS.append('transfer.apps.TransferConfig') \ No newline at end of file From 351d4920121049e0b00c3563abd6d48091969da0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 15:45:46 +0300 Subject: [PATCH 21/44] command to remove relative news description images --- .../news/management/commands/rm_empty_images.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/news/management/commands/rm_empty_images.py diff --git a/apps/news/management/commands/rm_empty_images.py b/apps/news/management/commands/rm_empty_images.py new file mode 100644 index 00000000..3a48e2e4 --- /dev/null +++ b/apps/news/management/commands/rm_empty_images.py @@ -0,0 +1,17 @@ +from django.core.management.base import BaseCommand + +from news.models import News +import re + +class Command(BaseCommand): + help = 'Removes empty img html tags from news description' + + relative_img_regex = re.compile(r'\', re.I) + + def handle(self, *args, **kwargs): + for news in News.objects.all(): + if isinstance(news.description, dict): + news.description = {locale: self.relative_img_regex.sub('', rich_text) + for locale, rich_text in news.description.items()} + self.stdout.write(self.style.WARNING(f'Replaced {news} empty img html tags...\n')) + news.save() \ No newline at end of file From bdb82d0fb010632fcfb64b9a056e3e163f4407cc Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 16:40:39 +0300 Subject: [PATCH 22/44] add migration command --- make_data_migration.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make_data_migration.sh b/make_data_migration.sh index 70e27ccb..e6cec3e1 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -12,4 +12,5 @@ ./manage.py transfer --wine_characteristics ./manage.py transfer --inquiries ./manage.py transfer --assemblage -./manage.py transfer --purchased_plaques \ No newline at end of file +./manage.py transfer --purchased_plaques +./manage.py rm_empty_images \ No newline at end of file From e824b5ee65cc85e626e64073eb3a9f26998b94b0 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, 6 Dec 2019 16:51:57 +0300 Subject: [PATCH 23/44] winery permission --- apps/establishment/models.py | 19 ----------- apps/establishment/tests.py | 57 ++++++++++++++++++++++++++++++-- apps/establishment/views/back.py | 28 ++++++++-------- apps/utils/permissions.py | 52 ++++++++++++++++++++++++++--- 4 files changed, 116 insertions(+), 40 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 3cdef691..20db3710 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -824,25 +824,6 @@ class ContactEmail(models.Model): return f'{self.email}' -# -# class Wine(TranslatedFieldsMixin, models.Model): -# """Wine model.""" -# establishment = models.ForeignKey( -# 'establishment.Establishment', verbose_name=_('establishment'), -# on_delete=models.CASCADE) -# bottles = models.IntegerField(_('bottles')) -# price_min = models.DecimalField( -# _('price min'), max_digits=14, decimal_places=2) -# price_max = models.DecimalField( -# _('price max'), max_digits=14, decimal_places=2) -# by_glass = models.BooleanField(_('by glass')) -# price_glass_min = models.DecimalField( -# _('price min'), max_digits=14, decimal_places=2) -# price_glass_max = models.DecimalField( -# _('price max'), max_digits=14, decimal_places=2) -# - - class Plate(TranslatedFieldsMixin, models.Model): """Plate model.""" STR_FIELD_NAME = 'name' diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index bd96b052..86606d6b 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -4,7 +4,8 @@ from account.models import User from rest_framework import status from http.cookies import SimpleCookie from main.models import Currency -from establishment.models import Establishment, EstablishmentType, Menu, SocialChoice, SocialNetwork +from establishment.models import Establishment, EstablishmentType, EstablishmentSubType,\ + Menu, SocialChoice, SocialNetwork # Create your tests here. from translation.models import Language from account.models import Role, UserRole @@ -87,7 +88,59 @@ class BaseTestCase(APITestCase): ) -class EstablishmentBTests(BaseTestCase): +class WineryBackTests(BaseTestCase): + def setUp(self): + super().setUp() + self.user_role.delete() + self.role.delete() + + def test_establishment_CRUD(self): + params = {'page': 1, 'page_size': 1, } + response = self.client.get('/api/back/establishments/', params, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.establishment_subtype = EstablishmentSubType.objects.create( + name={"en-GB":"some text"}, + index_name='Index name', + establishment_type_id=self.establishment_type.id + ) + + self.establishment_subtype.save() + self.role = Role.objects.create(role=Role.WINERY_REVIEWER, + establishment_subtype_id=self.establishment_subtype.id) + self.role.save() + self.establishment.add_establishment_subtype(self.establishment_subtype) + + data = { + 'name': 'Test establishment', + 'type_id': self.establishment_type.id, + 'is_publish': True, + 'slug': 'test-establishment-slug', + 'tz': py_tz('Europe/Moscow').zone, + 'address_id': self.address.id + } + + response = self.client.post('/api/back/establishments/', data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'name': 'Test new establishment' + } + + response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', + data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', + format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + + +class EstablishmentBackTests(BaseTestCase): def test_establishment_CRUD(self): params = {'page': 1, 'page_size': 1, } response = self.client.get('/api/back/establishments/', params, format='json') diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 6fa4d821..e7cd8f73 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -3,10 +3,9 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions, status -from utils.permissions import IsCountryAdmin, IsEstablishmentManager from establishment import filters, models, serializers from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer -from utils.permissions import IsCountryAdmin, IsEstablishmentManager +from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer from utils.views import CreateDestroyGalleryViewMixin from timetable.models import Timetable from rest_framework import status @@ -25,7 +24,8 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" filter_class = filters.EstablishmentFilter - permission_classes = [IsCountryAdmin | IsEstablishmentManager] + + permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer @@ -33,13 +33,13 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [IsCountryAdmin | IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" serializer_class = ScheduleRUDSerializer - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer |IsEstablishmentManager] def get_object(self): """ @@ -64,21 +64,21 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): """Establishment schedule Create view""" serializer_class = ScheduleCreateSerializer queryset = Timetable.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class SocialChoiceListCreateView(generics.ListCreateAPIView): @@ -116,14 +116,14 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Plate RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PhonesListCreateView(generics.ListCreateAPIView): @@ -131,14 +131,14 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Phones RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmailListCreateView(generics.ListCreateAPIView): @@ -146,14 +146,14 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Email RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] class EmployeeListCreateView(generics.ListCreateAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 30055c44..432c653b 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import AccessToken from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken - +from establishment.models import EstablishmentSubType +from location.models import Address class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): """ @@ -56,8 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ - + SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') def has_permission(self, request, view): + rules = [ request.user.is_superuser, request.method in permissions.SAFE_METHODS @@ -306,7 +308,6 @@ class IsEstablishmentManager(IsStandardUser): rules = [ # special! super().has_permission(request, view) - # super().has_object_permission(request, view, obj) ] role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ @@ -319,7 +320,6 @@ class IsEstablishmentManager(IsStandardUser): ).exists(), # special! super().has_permission(request, view) - # super().has_object_permission(request, view, obj) ] return any(rules) @@ -368,7 +368,7 @@ class IsRestaurantReviewer(IsStandardUser): # 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' + .first() rules = [ UserRole.objects.filter(user=request.user, role=role, @@ -394,3 +394,45 @@ class IsRestaurantReviewer(IsStandardUser): ] return any(rules) + + +class IsWineryReviewer(IsStandardUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + if 'type_id' in request.data and 'address_id' in request.data and request.user: + countries = Address.objects.filter(id=request.data['address_id']) + + est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) + if est.exists(): + role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est], + role=Role.WINERY_REVIEWER, + country_id__in=[country.id for country in countries]) \ + .first() + + rules.append( + UserRole.objects.filter(user=request.user, role=role).exists() + ) + + return any(rules) + + def has_object_permission(self, request, view, obj): + rules = [ + super().has_object_permission(request, view, obj) + ] + if hasattr(obj, 'type_id'): + est = EstablishmentSubType.objects.filter(establishment_type_id=obj.type_id) + role = Role.objects.filter(role=Role.WINERY_REVIEWER, + establishment_subtype_id__in=[id for type.id in est], + country_id=obj.country_id).first() + + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.object_id + ).exists(), + super().has_object_permission(request, view, obj) + ] + return any(rules) \ No newline at end of file From 8af8cf92e97d97c52a0617a05cd7c99d6f511996 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 6 Dec 2019 17:00:54 +0300 Subject: [PATCH 24/44] added objects related statistic in Collection backoffice serializer --- apps/collection/models.py | 36 +++++++++++++++++++++++++++++ apps/collection/serializers/back.py | 4 ++++ 2 files changed, 40 insertions(+) diff --git a/apps/collection/models.py b/apps/collection/models.py index 58ca9f07..7acd9991 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -87,6 +87,42 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, verbose_name = _('collection') verbose_name_plural = _('collections') + @property + def _related_objects(self) -> list: + """Return list of related objects.""" + related_objects = [] + # get related objects + for related_object in self._meta.related_objects: + related_objects.append(related_object) + return related_objects + + @property + def count_related_objects(self) -> int: + """Return count of related objects.""" + counter = 0 + # count of related objects + for related_object in [related_object.name for related_object in self._related_objects]: + counter += getattr(self, f'{related_object}').count() + return counter + + @property + def related_object_names(self) -> list: + """Return related object names.""" + raw_object_names = [] + for related_object in [related_object.name for related_object in self._related_objects]: + instances = getattr(self, f'{related_object}') + if instances.exists(): + for instance in instances.all(): + raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None) + + # parse slugs + object_names = [] + re_pattern = r'[\w]+' + for raw_name in raw_object_names: + result = re.findall(re_pattern, raw_name) + if result: object_names.append(' '.join(result).capitalize()) + return set(object_names) + class GuideTypeQuerySet(models.QuerySet): """QuerySet for model GuideType.""" diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index bb88a778..48c25f6c 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -19,6 +19,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): collection_type_display = serializers.CharField( source='get_collection_type_display', read_only=True) country = CountrySimpleSerializer(read_only=True) + count_related_objects = serializers.IntegerField(read_only=True) + related_object_names = serializers.ListField(read_only=True) class Meta: model = models.Collection @@ -36,6 +38,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'slug', 'start', 'end', + 'count_related_objects', + 'related_object_names', ] From f1fa8a58de3c3d7a9eb6d90b99b8d57332e3e6f1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 6 Dec 2019 17:37:33 +0300 Subject: [PATCH 25/44] small refactoring --- apps/establishment/views/web.py | 5 +---- apps/product/views/common.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 71994522..74c20451 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -114,10 +114,7 @@ class EstablishmentCommentListView(generics.ListAPIView): """Override get_queryset method""" establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug']) - return comment_models.Comment.objects.by_content_type(app_label='establishment', - model='establishment') \ - .by_object_id(object_id=establishment.pk) \ - .order_by('-created') + return establishment.comments.order_by('-created') class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/product/views/common.py b/apps/product/views/common.py index f984a87b..650c1dfe 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -60,10 +60,7 @@ class ProductCommentListView(generics.ListAPIView): def get_queryset(self): """Override get_queryset method""" product = get_object_or_404(Product, slug=self.kwargs['slug']) - return Comment.objects.by_content_type(app_label='product', - model='product') \ - .by_object_id(object_id=product.pk) \ - .order_by('-created') + return product.comments.order_by('-created') class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView): From 831cb4bc81a48f7418f645d3d0a4998e3c8abda1 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 6 Dec 2019 18:17:16 +0300 Subject: [PATCH 26/44] add home_page --- _dockerfiles/db/Dockerfile | 2 +- .../commands/add_home_page_carousel.py | 37 +++++++++++++++++++ apps/transfer/models.py | 2 +- make_data_migration.sh | 2 + project/settings/base.py | 3 ++ project/settings/local.py | 2 +- 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 apps/main/management/commands/add_home_page_carousel.py diff --git a/_dockerfiles/db/Dockerfile b/_dockerfiles/db/Dockerfile index e8a9ded3..c3e35955 100644 --- a/_dockerfiles/db/Dockerfile +++ b/_dockerfiles/db/Dockerfile @@ -1,3 +1,3 @@ -FROM mdillon/postgis:latest +FROM mdillon/postgis:10 RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8 ENV LANG ru_RU.utf8 diff --git a/apps/main/management/commands/add_home_page_carousel.py b/apps/main/management/commands/add_home_page_carousel.py new file mode 100644 index 00000000..5d4f2703 --- /dev/null +++ b/apps/main/management/commands/add_home_page_carousel.py @@ -0,0 +1,37 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from establishment.models import Establishment +from main.models import Carousel +from transfer.models import HomePages +from location.models import Country +from django.db.models import F + + +class Command(BaseCommand): + help = '''Add establishment form HomePage to Carousel!''' + + @staticmethod + def get_country(country_code): + return Country.objects.filter(code__iexact=country_code).first() + + def handle(self, *args, **kwargs): + objects = [] + deleted = 0 + hp_list = HomePages.objects.annotate( + country=F('site__country_code_2'), + ).all() + for hm in tqdm(hp_list, desc='Add home_page.establishments to carousel'): + est = Establishment.objects.filter(old_id=hm.selection_of_week).first() + if est: + if est.carousels.exists(): + est.carousels.all().delete() + deleted += 1 + carousel = Carousel( + content_object=est, + country=self.get_country(hm.country) + ) + objects.append(carousel) + Carousel.objects.bulk_create(objects) + self.stdout.write( + self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} carousel objects.')) \ No newline at end of file diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 2ebcfb65..019f38aa 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1000,7 +1000,7 @@ class ProductNotes(MigrateMixin): db_table = 'product_notes' -class HomePages(models.Model): +class HomePages(MigrateMixin): using = 'legacy' site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True) diff --git a/make_data_migration.sh b/make_data_migration.sh index 70e27ccb..13fba591 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -2,6 +2,8 @@ ./manage.py transfer -a ./manage.py transfer -d ./manage.py transfer -e +./manage.py transfer -n +./manage.py rm_empty_images # команда для удаления картинок с относительным урлом из news.description ./manage.py upd_transportation ./manage.py transfer --fill_city_gallery ./manage.py transfer -l diff --git a/project/settings/base.py b/project/settings/base.py index 1d2c5744..76af8966 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -524,3 +524,6 @@ INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next'] THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine' COOKIE_DOMAIN = None + +ELASTICSEARCH_DSL = {} +ELASTICSEARCH_INDEX_NAMES = {} diff --git a/project/settings/local.py b/project/settings/local.py index c56f9042..d9c7cab8 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -42,7 +42,7 @@ DATABASES = { 'HOST': os.environ.get('DB_HOSTNAME'), 'PORT': os.environ.get('DB_PORT'), 'OPTIONS': { - 'options': '-c search_path=gm' + 'options': '-c search_path=gm,public' }, }, 'legacy': { From b5a113c875c4245a82db8d1c69d50ddbc54e4c51 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 18:53:40 +0300 Subject: [PATCH 27/44] user last visit middleware --- apps/utils/middleware.py | 11 ++++++++++- project/settings/base.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/utils/middleware.py b/apps/utils/middleware.py index f3936169..1a421322 100644 --- a/apps/utils/middleware.py +++ b/apps/utils/middleware.py @@ -1,5 +1,6 @@ """Custom middleware.""" -from django.utils import translation +from django.utils import translation, timezone +from account.models import User from configuration.models import TranslationSettings from translation.models import Language @@ -12,6 +13,14 @@ def get_locale(cookie_dict): def get_country_code(cookie_dict): return cookie_dict.get('country_code') +def user_last_visit(get_response): + """Updates user last visit w/ current""" + def middleware(request): + response = get_response(request) + if request.user.is_authenticated: + User.objects.filter(pk=request.user.pk).update(last_login=timezone.now()) + return response + return middleware def parse_cookies(get_response): """Parse cookies.""" diff --git a/project/settings/base.py b/project/settings/base.py index 76af8966..5a48c261 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -118,6 +118,7 @@ MIDDLEWARE = [ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'utils.middleware.parse_cookies', + 'utils.middleware.user_last_visit', ] ROOT_URLCONF = 'project.urls' From 8451ccf17ecf1f4ee7b60d3c58ac0cd40e790417 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 6 Dec 2019 19:17:38 +0300 Subject: [PATCH 28/44] add ordering & optimize serializer --- apps/account/serializers/back.py | 19 ++++++++++++++++++- apps/account/views/back.py | 13 +++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index b2316734..699210e7 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -16,7 +16,24 @@ class RoleSerializer(serializers.ModelSerializer): class BackUserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = '__all__' + fields = ( + 'id', + 'last_login', + 'is_superuser', + 'username', + 'last_name', + 'first_name', + 'is_active', + 'date_joined', + 'image_url', + 'cropped_image_url', + 'email', + 'email_confirmed', + 'unconfirmed_email', + 'email_confirmed', + 'newsletter', + 'roles', + ) extra_kwargs = { 'password': {'write_only': True} } diff --git a/apps/account/views/back.py b/apps/account/views/back.py index 80775b3a..fbbc986e 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -1,5 +1,6 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions +from rest_framework.filters import OrderingFilter from account import models from account.models import User @@ -18,10 +19,10 @@ class UserRoleLstView(generics.ListCreateAPIView): class UserLstView(generics.ListCreateAPIView): """User list create view.""" - queryset = User.objects.all() + queryset = User.objects.prefetch_related('roles') serializer_class = serializers.BackUserSerializer permission_classes = (permissions.IsAdminUser,) - filter_backends = (DjangoFilterBackend,) + filter_backends = (DjangoFilterBackend, OrderingFilter) filterset_fields = ( 'email_confirmed', 'is_staff', @@ -29,6 +30,14 @@ class UserLstView(generics.ListCreateAPIView): 'is_superuser', 'roles', ) + ordering_fields = ( + 'email_confirmed', + 'is_staff', + 'is_active', + 'is_superuser', + 'roles', + 'last_login' + ) class UserRUDView(generics.RetrieveUpdateDestroyAPIView): From af7aef6d233408cad387a4e319328ec06c374f5a 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: Mon, 9 Dec 2019 09:38:18 +0300 Subject: [PATCH 29/44] Add winery reviewer --- apps/utils/permissions.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 432c653b..0d21c096 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -423,15 +423,28 @@ class IsWineryReviewer(IsStandardUser): rules = [ super().has_object_permission(request, view, obj) ] - if hasattr(obj, 'type_id'): - est = EstablishmentSubType.objects.filter(establishment_type_id=obj.type_id) + # AttributeError: 'Establishment' object has no attribute 'object_id' + if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'): + type_id: int + if hasattr(obj, 'type_id'): + type_id = obj.type_id + else: + type_id = obj.establishment_type_id + + est = EstablishmentSubType.objects.filter(establishment_type_id=type_id) role = Role.objects.filter(role=Role.WINERY_REVIEWER, establishment_subtype_id__in=[id for type.id in est], country_id=obj.country_id).first() + object_id: int + if hasattr(obj, 'object_id'): + object_id = obj.object_id + else: + object_id = obj.establishment_id + rules = [ UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.object_id + establishment_id=object_id ).exists(), super().has_object_permission(request, view, obj) ] From 5cac9659e5d54ebcdfde01aab85c927d65e1dfb0 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: Mon, 9 Dec 2019 09:38:58 +0300 Subject: [PATCH 30/44] Clean code --- apps/utils/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 0d21c096..498f5932 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -423,7 +423,7 @@ class IsWineryReviewer(IsStandardUser): rules = [ super().has_object_permission(request, view, obj) ] - # AttributeError: 'Establishment' object has no attribute 'object_id' + if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'): type_id: int if hasattr(obj, 'type_id'): From 38839f75f9d63da93cf15ba69823858978561149 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: Mon, 9 Dec 2019 09:59:17 +0300 Subject: [PATCH 31/44] Fix account test --- apps/account/serializers/back.py | 1 + apps/account/urls/back.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 699210e7..15e0684e 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -33,6 +33,7 @@ class BackUserSerializer(serializers.ModelSerializer): 'email_confirmed', 'newsletter', 'roles', + 'password' ) extra_kwargs = { 'password': {'write_only': True} diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 630a4cb9..30f21573 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -8,6 +8,6 @@ app_name = 'account' urlpatterns = [ path('role/', views.RoleLstView.as_view(), name='role-list-create'), path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), - path('user/', views.UserLstView.as_view(), name='user-list-create'), + path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), ] From 3e0fad21391560488e679ba31edd05ee45427d95 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Mon, 9 Dec 2019 12:14:55 +0300 Subject: [PATCH 32/44] add merge of migrations --- .../product/migrations/0020_merge_20191209_0911.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/product/migrations/0020_merge_20191209_0911.py diff --git a/apps/product/migrations/0020_merge_20191209_0911.py b/apps/product/migrations/0020_merge_20191209_0911.py new file mode 100644 index 00000000..b8c83246 --- /dev/null +++ b/apps/product/migrations/0020_merge_20191209_0911.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.7 on 2019-12-09 09:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0019_auto_20191204_1420'), + ('product', '0019_product_serial_number'), + ] + + operations = [ + ] From dc7a6a911aadc1156fe8867ba462eab708164db7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 9 Dec 2019 14:29:54 +0300 Subject: [PATCH 33/44] refactoring --- apps/advertisement/models.py | 4 ++++ apps/advertisement/serializers/common.py | 5 ----- apps/advertisement/views/common.py | 5 ++++- apps/advertisement/views/web.py | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/advertisement/models.py b/apps/advertisement/models.py index 920e3389..66cd0024 100644 --- a/apps/advertisement/models.py +++ b/apps/advertisement/models.py @@ -22,6 +22,10 @@ class AdvertisementQuerySet(models.QuerySet): """Filter Advertisement by page type.""" return self.filter(page_type__name=page_type) + def by_country(self, code: str): + """Filter Advertisement by country code.""" + return self.filter(sites__country__code=code) + def by_locale(self, locale): """Filter by locale.""" return self.filter(target_languages__locale=locale) diff --git a/apps/advertisement/serializers/common.py b/apps/advertisement/serializers/common.py index 0adb74f9..9caee0c2 100644 --- a/apps/advertisement/serializers/common.py +++ b/apps/advertisement/serializers/common.py @@ -11,14 +11,11 @@ from main.models import SiteSettings class AdvertisementBaseSerializer(serializers.ModelSerializer): """Base serializer for model Advertisement.""" - languages = LanguageSerializer(many=True, read_only=True, - source='target_languages') target_languages = serializers.PrimaryKeyRelatedField( queryset=Language.objects.all(), many=True, write_only=True ) - sites = SiteShortSerializer(many=True, read_only=True) target_sites = serializers.PrimaryKeyRelatedField( queryset=SiteSettings.objects.all(), many=True, @@ -33,9 +30,7 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer): 'uuid', 'url', 'block_level', - 'languages', 'target_languages', - 'sites', 'target_sites', 'start', 'end', diff --git a/apps/advertisement/views/common.py b/apps/advertisement/views/common.py index 43c6e965..02c61873 100644 --- a/apps/advertisement/views/common.py +++ b/apps/advertisement/views/common.py @@ -28,5 +28,8 @@ class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView) product_type = self.kwargs.get('page_type') qs = super(AdvertisementPageTypeListView, self).get_queryset() if product_type: - return qs.by_page_type(product_type) + return qs.by_page_type(product_type) \ + .by_country(self.request.country_code) \ + .by_locale(self.request.locale) \ + .distinct('id') return qs.none() diff --git a/apps/advertisement/views/web.py b/apps/advertisement/views/web.py index db1cfde8..a9e527a0 100644 --- a/apps/advertisement/views/web.py +++ b/apps/advertisement/views/web.py @@ -7,3 +7,4 @@ class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView): """Advertisement mobile list view.""" serializer_class = AdvertisementPageTypeWebListSerializer + From ea760fc674784e520e16501ac7b0d19d43d7f7ae Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 15:00:15 +0300 Subject: [PATCH 34/44] rename attrs --- apps/search_indexes/serializers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index a43ebaf7..3b5561fa 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -243,8 +243,8 @@ class WineOriginSerializer(serializers.Serializer): class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): """Establishment document serializer.""" - establishment_type = EstablishmentTypeSerializer() - establishment_subtypes = EstablishmentTypeSerializer(many=True) + type = EstablishmentTypeSerializer(source='establishment_type') + subtypes = EstablishmentTypeSerializer(many=True, source='establishment_subtypes') address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True, source='visible_tags') restaurant_category = TagsDocumentSerializer(many=True, allow_null=True) @@ -280,8 +280,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'wine_origins', # 'works_now', # 'collections', - # 'establishment_type', - # 'establishment_subtypes', + 'type', + 'subtypes', ) From 912431690d5c12384542c5834b5df3c4b05d89cd Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 15:21:39 +0300 Subject: [PATCH 35/44] rename attrs #2 --- apps/establishment/serializers/common.py | 4 ++-- apps/transfer/serializers/guide.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 52d7ae69..5c77ccbf 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -450,13 +450,13 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): address = AddressDetailSerializer(read_only=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) - establishment_type = EstablishmentTypeGeoSerializer() + type = EstablishmentTypeGeoSerializer(source='establishment_type') artisan_category = TagBaseSerializer(many=True, allow_null=True) class Meta(EstablishmentBaseSerializer.Meta): fields = EstablishmentBaseSerializer.Meta.fields + [ 'schedule', - 'establishment_type', + 'type', 'artisan_category', ] diff --git a/apps/transfer/serializers/guide.py b/apps/transfer/serializers/guide.py index f0cddd02..8a083e55 100644 --- a/apps/transfer/serializers/guide.py +++ b/apps/transfer/serializers/guide.py @@ -68,7 +68,7 @@ class GuideSerializer(TransferSerializerMixin): class GuideFilterSerializer(TransferSerializerMixin): id = serializers.IntegerField() year = serializers.CharField(allow_null=True) - establishment_type = serializers.CharField(allow_null=True) + type = serializers.CharField(allow_null=True, source='establishment_type') countries = serializers.CharField(allow_null=True) regions = serializers.CharField(allow_null=True) subregions = serializers.CharField(allow_null=True) @@ -86,7 +86,7 @@ class GuideFilterSerializer(TransferSerializerMixin): fields = ( 'id', 'year', - 'establishment_type', + 'type', 'countries', 'regions', 'subregions', From 188f8877b485f12d205cbdbf05e87c94db4c5a08 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: Mon, 9 Dec 2019 16:35:38 +0300 Subject: [PATCH 36/44] Fix test --- apps/establishment/tests.py | 52 ------------------------------------- 1 file changed, 52 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index c68bf029..f0c26abe 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -88,58 +88,6 @@ class BaseTestCase(APITestCase): ) -class WineryBackTests(BaseTestCase): - def setUp(self): - super().setUp() - self.user_role.delete() - self.role.delete() - - def test_establishment_CRUD(self): - params = {'page': 1, 'page_size': 1, } - response = self.client.get('/api/back/establishments/', params, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.establishment_subtype = EstablishmentSubType.objects.create( - name={"en-GB":"some text"}, - index_name='Index name', - establishment_type_id=self.establishment_type.id - ) - - self.establishment_subtype.save() - self.role = Role.objects.create(role=Role.WINERY_REVIEWER, - establishment_subtype_id=self.establishment_subtype.id) - self.role.save() - self.establishment.add_establishment_subtype(self.establishment_subtype) - - data = { - 'name': 'Test establishment', - 'type_id': self.establishment_type.id, - 'is_publish': True, - 'slug': 'test-establishment-slug', - 'tz': py_tz('Europe/Moscow').zone, - 'address_id': self.address.id - } - - response = self.client.post('/api/back/establishments/', data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - update_data = { - 'name': 'Test new establishment' - } - - response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', - data=update_data) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', - format='json') - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - - class EstablishmentBackTests(BaseTestCase): def test_establishment_CRUD(self): params = {'page': 1, 'page_size': 1, } From 0f11a47a790c0b49dbd9b306eaff90644d0a8431 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 16:47:46 +0300 Subject: [PATCH 37/44] rest category & rest cuisine for favs (cherry picked from commit 79a70fb) --- apps/establishment/serializers/common.py | 6 +++++- apps/favorites/views.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 5c77ccbf..19b4b764 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -451,13 +451,17 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): address = AddressDetailSerializer(read_only=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) type = EstablishmentTypeGeoSerializer(source='establishment_type') - artisan_category = TagBaseSerializer(many=True, allow_null=True) + artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True) + restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True) + restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True) class Meta(EstablishmentBaseSerializer.Meta): fields = EstablishmentBaseSerializer.Meta.fields + [ 'schedule', 'type', 'artisan_category', + 'restaurant_category', + 'restaurant_cuisine', ] diff --git a/apps/favorites/views.py b/apps/favorites/views.py index bee25ced..4faa3e07 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -30,6 +30,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView): """Override get_queryset method""" return Establishment.objects.filter(favorites__user=self.request.user) \ .order_by('-favorites').with_base_related() \ + .with_certain_tag_category_related('category', 'restaurant_category') \ + .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ .with_certain_tag_category_related('shop_category', 'artisan_category') From a6728e3148e4c5c272af33503ca778d0ea3b1a7c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 16:59:40 +0300 Subject: [PATCH 38/44] wine_origins for favorites establishments --- apps/product/serializers/common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index d794ca93..51ff8e1e 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -6,7 +6,8 @@ from comment.models import Comment from comment.serializers import CommentSerializer from establishment.serializers import EstablishmentProductShortSerializer from establishment.serializers.common import _EstablishmentAddressShortSerializer -from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer +from location.serializers import WineOriginRegionBaseSerializer,\ + WineOriginBaseSerializer, EstablishmentWineOriginBaseSerializer from main.serializers import AwardSerializer from product import models from review.serializers import ReviewShortSerializer @@ -95,6 +96,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): preview_image_url = serializers.URLField(allow_null=True, read_only=True) in_favorites = serializers.BooleanField(allow_null=True) + wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True) class Meta: """Meta class.""" From d12ad93fe1c258631f68d317335fcfb92c54a4cb Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 17:19:29 +0300 Subject: [PATCH 39/44] try to fix issue w/ news tag binding --- apps/search_indexes/documents/tag_category.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/documents/tag_category.py b/apps/search_indexes/documents/tag_category.py index ed69c8b7..3c335cdf 100644 --- a/apps/search_indexes/documents/tag_category.py +++ b/apps/search_indexes/documents/tag_category.py @@ -2,6 +2,7 @@ from django.conf import settings from django_elasticsearch_dsl import Document, Index, fields from tag import models +from news.models import News TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category')) TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2) @@ -26,7 +27,7 @@ class TagCategoryDocument(Document): 'public', 'value_type' ) - related_models = [models.Tag] + related_models = [models.Tag, News] def get_queryset(self): From be793eed73658727fb66da7e75b2284634247e35 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 17:40:05 +0300 Subject: [PATCH 40/44] fix issue w/ tags binding --- apps/search_indexes/documents/tag_category.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/search_indexes/documents/tag_category.py b/apps/search_indexes/documents/tag_category.py index 3c335cdf..cd2c8a90 100644 --- a/apps/search_indexes/documents/tag_category.py +++ b/apps/search_indexes/documents/tag_category.py @@ -32,3 +32,11 @@ class TagCategoryDocument(Document): def get_queryset(self): return super().get_queryset().with_base_related() + + def get_instances_from_related(self, related_instance): + """If related_models is set, define how to retrieve the Car instance(s) from the related model. + The related_models option should be used with caution because it can lead in the index + to the updating of a lot of items. + """ + if isinstance(related_instance, News): + return related_instance.tags \ No newline at end of file From 6af7a7da5bb6bbe211025ad86f053c52d11ca687 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Mon, 9 Dec 2019 17:47:37 +0300 Subject: [PATCH 41/44] add footer migrations and api --- apps/main/management/commands/add_footers.py | 32 ++++++++++++++++++++ apps/main/migrations/0040_footer.py | 29 ++++++++++++++++++ apps/main/models.py | 9 ++++++ apps/main/serializers.py | 30 ++++++++++++++++++ apps/main/urls/back.py | 2 ++ apps/main/views/back.py | 16 +++++++++- apps/transfer/models.py | 14 +++++++++ make_data_migration.sh | 3 ++ 8 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 apps/main/management/commands/add_footers.py create mode 100644 apps/main/migrations/0040_footer.py diff --git a/apps/main/management/commands/add_footers.py b/apps/main/management/commands/add_footers.py new file mode 100644 index 00000000..5982fd43 --- /dev/null +++ b/apps/main/management/commands/add_footers.py @@ -0,0 +1,32 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from main.models import SiteSettings, Footer +from transfer.models import Footers + + +class Command(BaseCommand): + help = '''Add footers from legacy DB.''' + + def handle(self, *args, **kwargs): + objects = [] + deleted = 0 + footers_list = Footers.objects.all() + + for old_footer in tqdm(footers_list, desc='Add footers'): + site = SiteSettings.objects.filter(old_id=old_footer.site_id).first() + if site: + if site.footers.exists(): + site.footers.all().delete() + deleted += 1 + footer = Footer( + site=site, + about_us=old_footer.about_us, + copyright=old_footer.copyright, + created=old_footer.created_at, + modified=old_footer.updated_at + ) + objects.append(footer) + Footer.objects.bulk_create(objects) + self.stdout.write( + self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.')) diff --git a/apps/main/migrations/0040_footer.py b/apps/main/migrations/0040_footer.py new file mode 100644 index 00000000..688e76d1 --- /dev/null +++ b/apps/main/migrations/0040_footer.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.7 on 2019-12-09 13:21 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0039_sitefeature_old_id'), + ] + + operations = [ + migrations.CreateModel( + name='Footer', + 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')), + ('about_us', models.TextField(verbose_name='about_us')), + ('copyright', models.TextField(verbose_name='copyright')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='footers', to='main.SiteSettings', verbose_name='footer')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index f9c1225f..0869169e 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -351,3 +351,12 @@ class PageType(ProjectBaseMixin): def __str__(self): """Overridden dunder method.""" return self.name + + +class Footer(ProjectBaseMixin): + site = models.ForeignKey( + 'main.SiteSettings', related_name='footers', verbose_name=_('footer'), + on_delete=models.PROTECT + ) + about_us = models.TextField(_('about_us')) + copyright = models.TextField(_('copyright')) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index a41a643f..033da976 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -22,6 +22,7 @@ class FeatureSerializer(serializers.ModelSerializer): 'site_settings', ) + class CurrencySerializer(ProjectModelSerializer): """Currency serializer.""" @@ -36,6 +37,33 @@ class CurrencySerializer(ProjectModelSerializer): ] +class FooterSerializer(serializers.ModelSerializer): + """Footer serializer.""" + + class Meta: + model = models.Footer + fields = [ + 'id', + 'about_us', + 'copyright', + 'created', + 'modified', + ] + + +class FooterBackSerializer(FooterSerializer): + site_id = serializers.PrimaryKeyRelatedField( + queryset=models.SiteSettings.objects.all(), + source='site' + ) + + class Meta: + model = models.Footer + fields = FooterSerializer.Meta.fields + [ + 'site_id' + ] + + class SiteFeatureSerializer(serializers.ModelSerializer): id = serializers.IntegerField(source='feature.id') slug = serializers.CharField(source='feature.slug') @@ -68,6 +96,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer): country_name = serializers.CharField(source='country.name_translated', read_only=True) time_format = serializers.CharField(source='country.time_format', read_only=True) + footers = FooterSerializer(many=True, read_only=True) class Meta: """Meta class.""" @@ -87,6 +116,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer): 'published_features', 'currency', 'country_name', + 'footers', ] diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 609e61f7..a9e55311 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -18,6 +18,8 @@ urlpatterns = [ name='site-feature-list-create'), path('site-feature//', views.SiteFeatureRUDBackView.as_view(), name='site-feature-rud'), + path('footer/', views.FooterBackView.as_view(), name='footer-list-create'), + path('footer//', views.FooterRUDBackView.as_view(), name='footer-rud'), ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 1faf79a0..3d73f88c 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics, permissions from main import serializers from main.filters import AwardFilter -from main.models import Award +from main.models import Award, Footer from main.views import SiteSettingsView, SiteListView @@ -67,3 +67,17 @@ class SiteSettingsBackOfficeView(SiteSettingsView): class SiteListBackOfficeView(SiteListView): """Site settings View.""" serializer_class = serializers.SiteSerializer + + +class FooterBackView(generics.ListCreateAPIView): + """Footer back list/create view.""" + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + serializer_class = serializers.FooterBackSerializer + queryset = Footer.objects.all() + + +class FooterRUDBackView(generics.RetrieveUpdateDestroyAPIView): + """Footer back RUD view.""" + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + serializer_class = serializers.FooterBackSerializer + queryset = Footer.objects.all() diff --git a/apps/transfer/models.py b/apps/transfer/models.py index 019f38aa..d8268d6d 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1208,3 +1208,17 @@ class NewsletterSubscriber(MigrateMixin): class Meta: managed = False db_table = 'newsletter_subscriptions' + + +class Footers(MigrateMixin): + using = 'legacy' + + about_us = models.TextField(blank=True, null=True) + copyright = models.TextField(blank=True, null=True) + site = models.ForeignKey('Sites', models.DO_NOTHING, blank=True, null=True) + created_at = models.DateTimeField() + updated_at = models.DateTimeField() + + class Meta: + managed = False + db_table = 'footers' diff --git a/make_data_migration.sh b/make_data_migration.sh index 50be7fc4..965eac19 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -8,9 +8,12 @@ ./manage.py transfer --fill_city_gallery ./manage.py transfer -l ./manage.py transfer --product +# Утеряна четкая связь между последовательностью миграций для импорта тегов продуктов, +# что может привести к удалению уже импортированных тегов командой выше. ./manage.py transfer --souvenir ./manage.py transfer --establishment_note ./manage.py transfer --product_note +./manage.py transfer --check_serial_number ./manage.py transfer --wine_characteristics ./manage.py transfer --inquiries ./manage.py transfer --assemblage From 7910c98c8a8e9ec69857422dddc6dec9612d2279 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 18:20:23 +0300 Subject: [PATCH 42/44] add wine_origins --- apps/product/serializers/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index 51ff8e1e..e0617e63 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -115,6 +115,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): 'wine_regions', 'wine_colors', 'in_favorites', + 'wine_origins', ] From a2395b807e6d6bb6f1a80365b22dcc89a9a202a0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 9 Dec 2019 20:42:47 +0300 Subject: [PATCH 43/44] User locale & city reading --- apps/account/models.py | 4 ++++ apps/account/serializers/back.py | 8 +++++--- apps/utils/middleware.py | 11 ++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 8f6c6233..70a3cd69 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -107,6 +107,10 @@ class User(AbstractUser): email_confirmed = models.BooleanField(_('email status'), default=False) newsletter = models.NullBooleanField(default=True) old_id = models.IntegerField(null=True, blank=True, default=None) + locale = models.CharField(max_length=10, blank=True, default=None, null=True, + verbose_name=_('User last used locale')) + city = models.TextField(default=None, blank=True, null=True, + verbose_name=_('User last visited from city')) EMAIL_FIELD = 'email' USERNAME_FIELD = 'username' diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 15e0684e..01889411 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -33,12 +33,14 @@ class BackUserSerializer(serializers.ModelSerializer): 'email_confirmed', 'newsletter', 'roles', - 'password' + 'password', + 'city', + 'locale', ) extra_kwargs = { - 'password': {'write_only': True} + 'password': {'write_only': True}, } - read_only_fields = ('old_password', 'last_login', 'date_joined') + read_only_fields = ('old_password', 'last_login', 'date_joined', 'city', 'locale') def create(self, validated_data): user = super().create(validated_data) diff --git a/apps/utils/middleware.py b/apps/utils/middleware.py index 1a421322..fa9f9950 100644 --- a/apps/utils/middleware.py +++ b/apps/utils/middleware.py @@ -1,8 +1,9 @@ -"""Custom middleware.""" +"""Custom middlewares.""" from django.utils import translation, timezone -from account.models import User +from account.models import User from configuration.models import TranslationSettings +from main.methods import determine_user_city from translation.models import Language @@ -18,7 +19,11 @@ def user_last_visit(get_response): def middleware(request): response = get_response(request) if request.user.is_authenticated: - User.objects.filter(pk=request.user.pk).update(last_login=timezone.now()) + User.objects.filter(pk=request.user.pk).update(**{ + 'last_login': timezone.now(), + 'locale': request.locale, + 'city': determine_user_city(request), + }) return response return middleware From ed18d1c44d7890a6da80371012a98cd15c8a1463 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 09:24:32 +0300 Subject: [PATCH 44/44] forgotten migrations --- .../migrations/0025_auto_20191210_0623.py | 23 +++++++++++++++++++ make_data_migration.sh | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 apps/account/migrations/0025_auto_20191210_0623.py diff --git a/apps/account/migrations/0025_auto_20191210_0623.py b/apps/account/migrations/0025_auto_20191210_0623.py new file mode 100644 index 00000000..464e9b20 --- /dev/null +++ b/apps/account/migrations/0025_auto_20191210_0623.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.7 on 2019-12-10 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0024_role_establishment_subtype'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='city', + field=models.TextField(blank=True, default=None, null=True, verbose_name='User last visited from city'), + ), + migrations.AddField( + model_name='user', + name='locale', + field=models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='User last used locale'), + ), + ] diff --git a/make_data_migration.sh b/make_data_migration.sh index 965eac19..b16c0b2d 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -18,4 +18,5 @@ ./manage.py transfer --inquiries ./manage.py transfer --assemblage ./manage.py transfer --purchased_plaques -./manage.py rm_empty_images \ No newline at end of file +./manage.py rm_empty_images +./manage.py add_artisan_subtype # добавляет подтипы для заведений артизанов \ No newline at end of file