From 413c93bead79dd59924a0dac697d151849fafcb4 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 13:35:59 +0300 Subject: [PATCH 001/319] fixed problem with image, refactored favorites, collection --- apps/collection/admin.py | 5 --- .../migrations/0009_delete_collectionitem.py | 16 +++++++ .../migrations/0010_collection_description.py | 19 ++++++++ apps/collection/models.py | 28 +++--------- apps/collection/serializers/common.py | 14 +----- apps/collection/urls/common.py | 7 +-- apps/collection/views/common.py | 44 +++++++++++-------- .../0024_establishment_collections.py | 19 ++++++++ .../migrations/0025_merge_20190920_1012.py | 14 ++++++ .../0026_establishment_preview_image_url.py | 18 ++++++++ apps/establishment/models.py | 18 +++++--- apps/establishment/serializers/common.py | 15 +++---- apps/establishment/urls/common.py | 2 +- apps/establishment/views/web.py | 16 ++++--- apps/favorites/serializers.py | 13 ------ apps/favorites/urls.py | 1 - apps/favorites/views.py | 15 +++---- apps/main/models.py | 8 ++-- apps/main/serializers.py | 4 +- .../search_indexes/documents/establishment.py | 13 +++--- 20 files changed, 166 insertions(+), 123 deletions(-) create mode 100644 apps/collection/migrations/0009_delete_collectionitem.py create mode 100644 apps/collection/migrations/0010_collection_description.py create mode 100644 apps/establishment/migrations/0024_establishment_collections.py create mode 100644 apps/establishment/migrations/0025_merge_20190920_1012.py create mode 100644 apps/establishment/migrations/0026_establishment_preview_image_url.py diff --git a/apps/collection/admin.py b/apps/collection/admin.py index 3d0a8e3c..2e2c22e2 100644 --- a/apps/collection/admin.py +++ b/apps/collection/admin.py @@ -8,11 +8,6 @@ class CollectionAdmin(admin.ModelAdmin): """Collection admin.""" -@admin.register(models.CollectionItem) -class CollectionItemAdmin(admin.ModelAdmin): - """CollectionItem admin.""" - - @admin.register(models.Guide) class GuideAdmin(admin.ModelAdmin): """Guide admin.""" diff --git a/apps/collection/migrations/0009_delete_collectionitem.py b/apps/collection/migrations/0009_delete_collectionitem.py new file mode 100644 index 00000000..ea8fca72 --- /dev/null +++ b/apps/collection/migrations/0009_delete_collectionitem.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.4 on 2019-09-20 08:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0008_auto_20190916_1158'), + ] + + operations = [ + migrations.DeleteModel( + name='CollectionItem', + ), + ] diff --git a/apps/collection/migrations/0010_collection_description.py b/apps/collection/migrations/0010_collection_description.py new file mode 100644 index 00000000..b66d8156 --- /dev/null +++ b/apps/collection/migrations/0010_collection_description.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-09-20 09:27 + +from django.db import migrations +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0009_delete_collectionitem'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='description', + field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 3ef4d0bf..50af7ec7 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -1,10 +1,11 @@ from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import ContentType -from django.contrib.contenttypes import fields as generic +from utils.models import TJSONField from django.db import models from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, ImageMixin +from utils.models import TranslatedFieldsMixin # Mixins @@ -40,7 +41,7 @@ class CollectionQuerySet(models.QuerySet): return self.filter(is_publish=True) -class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): +class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, TranslatedFieldsMixin): """Collection model.""" ORDINARY = 0 # Ordinary collection POP = 1 # POP collection @@ -65,6 +66,9 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): block_size = JSONField( _('collection block properties'), null=True, blank=True, default=None, help_text='{"width": "250px", "height":"250px"}') + description = TJSONField( + _('description'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') objects = CollectionQuerySet.as_manager() @@ -78,26 +82,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): return f'{self.name}' -class CollectionItemQuerySet(models.QuerySet): - """QuerySet for model CollectionItem.""" - - def by_collection(self, collection_id): - """Filter by collection id""" - return self.filter(collection=collection_id) - - -class CollectionItem(ProjectBaseMixin): - """CollectionItem model.""" - collection = models.ForeignKey( - Collection, verbose_name=_('collection'), on_delete=models.CASCADE) - content_type = models.ForeignKey(ContentType, default=None, - null=True, blank=True, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField(default=None, null=True, blank=True) - content_object = generic.GenericForeignKey('content_type', 'object_id') - - objects = CollectionItemQuerySet.as_manager() - - class GuideQuerySet(models.QuerySet): """QuerySet for Guide.""" diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 87bf7802..5e0029ee 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -9,6 +9,7 @@ class CollectionSerializer(serializers.ModelSerializer): """Collection serializer""" # RESPONSE image_url = serializers.ImageField(source='image.image') + description_translated = serializers.CharField(read_only=True, allow_null=True) # COMMON block_size = serializers.JSONField() @@ -30,6 +31,7 @@ class CollectionSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', + 'description_translated', 'start', 'end', 'image', @@ -41,18 +43,6 @@ class CollectionSerializer(serializers.ModelSerializer): ] -class CollectionItemSerializer(serializers.ModelSerializer): - """CollectionItem serializer""" - class Meta: - model = models.CollectionItem - fields = [ - 'id', - 'collection', - 'content_type', - 'object_id', - ] - - class GuideSerializer(serializers.ModelSerializer): """Guide serializer""" class Meta: diff --git a/apps/collection/urls/common.py b/apps/collection/urls/common.py index 4396bff4..41414a10 100644 --- a/apps/collection/urls/common.py +++ b/apps/collection/urls/common.py @@ -7,11 +7,8 @@ app_name = 'collection' urlpatterns = [ path('', views.CollectionListView.as_view(), name='list'), - path('/', views.CollectionRetrieveView.as_view(), name='detail'), - - path('items/', views.CollectionItemListView.as_view(), name='collection-items-list'), - path('items//', views.CollectionItemRetrieveView.as_view(), - name='collection-items-detail'), + path('/establishments/', views.CollectionEstablishmentListView.as_view(), + name='detail'), path('guides/', views.GuideListView.as_view(), name='guides-list'), path('guides//', views.GuideRetrieveView.as_view(), name='guides-detail'), diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index af7c4849..8c386930 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -2,6 +2,9 @@ from rest_framework import generics from rest_framework import permissions from collection import models +from utils.pagination import ProjectPageNumberPagination +from django.shortcuts import get_object_or_404 +from establishment.serializers import EstablishmentListSerializer from collection.serializers import common as serializers @@ -12,12 +15,6 @@ class CollectionViewMixin(generics.GenericAPIView): queryset = models.Collection.objects.all() -class CollectionItemViewMixin(generics.GenericAPIView): - """Mixin for CollectionItem view""" - model = models.CollectionItem - queryset = models.CollectionItem.objects.all() - - class GuideViewMixin(generics.GenericAPIView): """Mixin for Guide view""" model = models.Guide @@ -28,7 +25,6 @@ class GuideViewMixin(generics.GenericAPIView): # Collections class CollectionListView(CollectionViewMixin, generics.ListAPIView): """List Collection view""" - pagination_class = None permission_classes = (permissions.AllowAny,) serializer_class = serializers.CollectionSerializer @@ -39,23 +35,33 @@ class CollectionListView(CollectionViewMixin, generics.ListAPIView): .order_by('-on_top', '-created') -class CollectionRetrieveView(CollectionViewMixin, generics.RetrieveAPIView): - """Retrieve Collection view""" +class CollectionEstablishmentListView(CollectionListView): + """Retrieve list of establishment for collection.""" permission_classes = (permissions.AllowAny,) - serializer_class = serializers.CollectionSerializer + pagination_class = ProjectPageNumberPagination + serializer_class = EstablishmentListSerializer + def get_queryset(self): + """ + Override get_queryset method. + """ + queryset = super(CollectionEstablishmentListView, self).get_queryset() + # Perform the lookup filtering. + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field -# CollectionItem -class CollectionItemListView(CollectionItemViewMixin, generics.ListAPIView): - """List CollectionItem view""" - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.CollectionItemSerializer + assert lookup_url_kwarg in self.kwargs, ( + 'Expected view %s to be called with a URL keyword argument ' + 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + 'attribute on the view correctly.' % + (self.__class__.__name__, lookup_url_kwarg) + ) + collection = get_object_or_404(queryset, pk=self.kwargs['pk']) -class CollectionItemRetrieveView(CollectionItemViewMixin, generics.RetrieveAPIView): - """Retrieve CollectionItem view""" - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.CollectionItemSerializer + # May raise a permission denied + self.check_object_permissions(self.request, collection) + + return collection.establishments.all() # Guide diff --git a/apps/establishment/migrations/0024_establishment_collections.py b/apps/establishment/migrations/0024_establishment_collections.py new file mode 100644 index 00000000..b801ce3d --- /dev/null +++ b/apps/establishment/migrations/0024_establishment_collections.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-09-20 08:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0009_delete_collectionitem'), + ('establishment', '0023_merge_20190919_1136'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='collections', + field=models.ManyToManyField(related_name='establishments', to='collection.Collection', verbose_name='Collections'), + ), + ] diff --git a/apps/establishment/migrations/0025_merge_20190920_1012.py b/apps/establishment/migrations/0025_merge_20190920_1012.py new file mode 100644 index 00000000..b8821b76 --- /dev/null +++ b/apps/establishment/migrations/0025_merge_20190920_1012.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-09-20 10:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0024_establishment_collections'), + ('establishment', '0024_merge_20190919_1456'), + ] + + operations = [ + ] diff --git a/apps/establishment/migrations/0026_establishment_preview_image_url.py b/apps/establishment/migrations/0026_establishment_preview_image_url.py new file mode 100644 index 00000000..c398737e --- /dev/null +++ b/apps/establishment/migrations/0026_establishment_preview_image_url.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-20 10:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0025_merge_20190920_1012'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='preview_image_url', + field=models.URLField(blank=True, default=None, null=True, verbose_name='Preview image URL path'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 298b32ff..2841301f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -124,7 +124,7 @@ class EstablishmentQuerySet(models.QuerySet): """ return self.annotate(intermediate_public_mark=models.Case( models.When( - collections__collection__collection_type=Collection.POP, + collections__collection_type=Collection.POP, public_mark__isnull=True, then=10 ), @@ -164,7 +164,7 @@ class EstablishmentQuerySet(models.QuerySet): """ return self.annotate( total_mark=(models.F('distance_mark') + models.F('additional_mark')) * - models.F('intermediate_public_mark')) + models.F('intermediate_public_mark')) def similar(self, establishment_pk: int): """ @@ -176,7 +176,7 @@ class EstablishmentQuerySet(models.QuerySet): establishment = establishment_qs.first() return self.exclude(pk=establishment_pk) \ .filter(is_publish=True, - image__isnull=False, + image_url__isnull=False, reviews__isnull=False, reviews__status=Review.READY, public_mark__gte=10) \ @@ -269,13 +269,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): # help_text=_('Holidays closing date from')) # holidays_to = models.DateTimeField(verbose_name=_('Holidays to'), # help_text=_('Holidays closing date to')) + transportation = models.TextField(blank=True, null=True, default=None, + verbose_name=_('Transportation')) + collections = models.ManyToManyField(to='collection.Collection', + verbose_name=_('Collections'), + related_name='establishments') + preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), + blank=True, null=True, default=None) + awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') - transportation = models.TextField(blank=True, null=True, default=None, - verbose_name=_('Transportation')) - collections = generic.GenericRelation(to='collection.CollectionItem') + favorites = generic.GenericRelation(to='favorites.Favorites') objects = EstablishmentQuerySet.as_manager() diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 956130d6..79075fcf 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -11,6 +11,7 @@ from main.serializers import MetaDataContentSerializer, AwardSerializer, Currenc from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions +from django.utils.translation import gettext_lazy as _ class ContactPhonesSerializer(serializers.ModelSerializer): @@ -145,7 +146,6 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): subtypes = EstablishmentSubTypeSerializer(many=True) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) - preview_image = serializers.SerializerMethodField() class Meta: """Meta class.""" @@ -159,16 +159,11 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): 'public_mark', 'type', 'subtypes', - 'preview_image', 'address', 'tags', + 'image_url', ] - def get_preview_image(self, obj): - """Get preview image""" - return obj.get_full_image_url(request=self.context.get('request'), - thumbnail_key='establishment_preview') - class EstablishmentListSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" @@ -181,6 +176,7 @@ class EstablishmentListSerializer(EstablishmentBaseSerializer): model = models.Establishment fields = EstablishmentBaseSerializer.Meta.fields + [ 'in_favorites', + 'preview_image_url', ] @@ -195,7 +191,6 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', many=True) menu = MenuSerializers(source='menu_set', many=True, read_only=True) - preview_image = serializers.SerializerMethodField() best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) @@ -209,7 +204,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): fields = EstablishmentListSerializer.Meta.fields + [ 'description_translated', 'price_level', - 'image', + 'image_url', 'awards', 'schedule', 'website', @@ -316,7 +311,7 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): # Check establishment obj by pk from lookup_kwarg if not establishment_qs.exists(): - return serializers.ValidationError() + raise serializers.ValidationError({'detail': _('Object not found.')}) # Check existence in favorites if self.get_user().favorites.by_content_type(app_label='establishment', diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 3fdd46d3..1d1379ee 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -16,5 +16,5 @@ urlpatterns = [ path('/comments//', views.EstablishmentCommentRUDView.as_view(), name='rud-comment'), path('/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), - name='add-favorites') + name='add-to-favorites') ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 73c1621d..daa26d6a 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -30,10 +30,13 @@ class EstablishmentSimilarListView(EstablishmentListView): def get_queryset(self): """Override get_queryset method""" qs = super().get_queryset() + if not qs.filter(pk=self.kwargs.get('pk')).exists(): + return qs.none() return qs.similar(establishment_pk=self.kwargs.get('pk'))\ .order_by('-total_mark')[:13] + class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): """Resource for getting a establishment.""" serializer_class = serializers.EstablishmentDetailSerializer @@ -78,7 +81,7 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = self.filter_queryset(self.get_queryset()) lookup_url_kwargs = ('pk', 'comment_id') - assert lookup_url_kwargs not in self.kwargs.keys(), ( + assert lookup_url_kwargs in self.kwargs.keys(), ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % @@ -104,17 +107,16 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D """ Returns the object the view is displaying. """ - lookup_url_kwargs = ('pk',) - assert lookup_url_kwargs not in self.kwargs.keys(), ( + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + assert lookup_url_kwarg in self.kwargs, ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwargs) + (self.__class__.__name__, lookup_url_kwarg) ) obj = get_object_or_404( - self.request.user.favorites.by_user(user=self.request.user) - .by_content_type(app_label='establishment', + self.request.user.favorites.by_content_type(app_label='establishment', model='establishment') .by_object_id(object_id=self.kwargs['pk'])) # May raise a permission denied @@ -164,7 +166,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """ lookup_url_kwargs = ('pk', 'schedule_id') - assert lookup_url_kwargs not in self.kwargs.keys(), ( + assert lookup_url_kwargs in self.kwargs.keys(), ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % diff --git a/apps/favorites/serializers.py b/apps/favorites/serializers.py index d4485c54..bf0db4fb 100644 --- a/apps/favorites/serializers.py +++ b/apps/favorites/serializers.py @@ -2,16 +2,3 @@ from .models import Favorites from rest_framework import serializers from establishment.serializers import EstablishmentBaseSerializer - - -class FavoritesEstablishmentListSerializer(serializers.ModelSerializer): - """Serializer for model Favorites""" - detail = EstablishmentBaseSerializer(source='content_object') - - class Meta: - """Meta class.""" - model = Favorites - fields = ( - 'id', - 'detail', - ) diff --git a/apps/favorites/urls.py b/apps/favorites/urls.py index 80498e65..bd0c1d16 100644 --- a/apps/favorites/urls.py +++ b/apps/favorites/urls.py @@ -8,5 +8,4 @@ app_name = 'favorites' urlpatterns = [ path('establishments/', views.FavoritesEstablishmentListView.as_view(), name='establishment-list'), - path('remove//', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'), ] diff --git a/apps/favorites/views.py b/apps/favorites/views.py index c35edcf5..aed11709 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -1,6 +1,8 @@ """Views for app favorites.""" from rest_framework import generics -from .serializers import FavoritesEstablishmentListSerializer + +from establishment.models import Establishment +from establishment.serializers import EstablishmentListSerializer from .models import Favorites @@ -11,15 +13,10 @@ class FavoritesBaseView(generics.GenericAPIView): return Favorites.objects.by_user(self.request.user) -class FavoritesEstablishmentListView(FavoritesBaseView, generics.ListAPIView): +class FavoritesEstablishmentListView(generics.ListAPIView): """List views for favorites""" - serializer_class = FavoritesEstablishmentListSerializer + serializer_class = EstablishmentListSerializer def get_queryset(self): """Override get_queryset method""" - return super().get_queryset().by_content_type(app_label='establishment', - model='establishment') - - -class FavoritesDestroyView(FavoritesBaseView, generics.DestroyAPIView): - """Destroy view for favorites""" + return Establishment.objects.filter(favorites__user=self.request.user) diff --git a/apps/main/models.py b/apps/main/models.py index fcf8667b..47ac90be 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -333,11 +333,9 @@ class Carousel(models.Model): return self.content_object.public_mark @property - def image(self): - if hasattr(self.content_object.image, 'url'): - return self.content_object.image - if hasattr(self.content_object.image.image, 'url'): - return self.content_object.image.image + def image_url(self): + if hasattr(self.content_object, 'image_url'): + return self.content_object.image_url @property def model_name(self): diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 607e3a30..d04335f9 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -138,7 +138,7 @@ class CarouselListSerializer(serializers.ModelSerializer): name = serializers.CharField() toque_number = serializers.CharField() public_mark = serializers.CharField() - image = serializers.ImageField() + image_url = serializers.URLField() awards = AwardBaseSerializer(many=True) vintage_year = serializers.IntegerField() @@ -152,7 +152,7 @@ class CarouselListSerializer(serializers.ModelSerializer): 'awards', 'toque_number', 'public_mark', - 'image', + 'image_url', 'vintage_year', ] diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 8c96066e..10f95d6c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -68,12 +68,13 @@ class EstablishmentDocument(Document): ), } ) - collections = fields.ObjectField( - properties={ - 'id': fields.IntegerField(attr='collection.id'), - 'collection_type': fields.IntegerField(attr='collection.collection_type'), - }, - multi=True) + # todo: need to fix + # collections = fields.ObjectField( + # properties={ + # 'id': fields.IntegerField(attr='collection.id'), + # 'collection_type': fields.IntegerField(attr='collection.collection_type'), + # }, + # multi=True) class Django: From 2089ca067a0ca0ede2344a04ee9a16f04935dc9e Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 14:22:39 +0300 Subject: [PATCH 002/319] chane FK to URLField in Collection model, added properties to field collections in Establishment model --- .../migrations/0011_auto_20190920_1059.py | 22 +++++++++++++++++++ apps/collection/models.py | 8 +++---- apps/collection/serializers/common.py | 6 ----- .../migrations/0027_auto_20190920_1120.py | 18 +++++++++++++++ apps/establishment/models.py | 5 +++-- 5 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 apps/collection/migrations/0011_auto_20190920_1059.py create mode 100644 apps/establishment/migrations/0027_auto_20190920_1120.py diff --git a/apps/collection/migrations/0011_auto_20190920_1059.py b/apps/collection/migrations/0011_auto_20190920_1059.py new file mode 100644 index 00000000..ac3c21c5 --- /dev/null +++ b/apps/collection/migrations/0011_auto_20190920_1059.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-09-20 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0010_collection_description'), + ] + + operations = [ + migrations.RemoveField( + model_name='collection', + name='image', + ), + migrations.AddField( + model_name='collection', + name='image_url', + field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 50af7ec7..a4a9bcae 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -4,7 +4,7 @@ from utils.models import TJSONField from django.db import models from django.utils.translation import gettext_lazy as _ -from utils.models import ProjectBaseMixin, ImageMixin +from utils.models import ProjectBaseMixin, URLImageMixin from utils.models import TranslatedFieldsMixin @@ -41,7 +41,8 @@ class CollectionQuerySet(models.QuerySet): return self.filter(is_publish=True) -class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, TranslatedFieldsMixin): +class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, + TranslatedFieldsMixin, URLImageMixin): """Collection model.""" ORDINARY = 0 # Ordinary collection POP = 1 # POP collection @@ -54,9 +55,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, Tra collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES, default=ORDINARY, verbose_name=_('Collection type')) - image = models.ForeignKey( - 'gallery.Image', null=True, blank=True, default=None, - verbose_name=_('Collection image'), on_delete=models.CASCADE) is_publish = models.BooleanField( default=False, verbose_name=_('Publish status')) on_top = models.BooleanField( diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 5e0029ee..85d66d30 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -1,14 +1,12 @@ from rest_framework import serializers from collection import models -from gallery import models as gallery_models from location import models as location_models class CollectionSerializer(serializers.ModelSerializer): """Collection serializer""" # RESPONSE - image_url = serializers.ImageField(source='image.image') description_translated = serializers.CharField(read_only=True, allow_null=True) # COMMON @@ -22,9 +20,6 @@ class CollectionSerializer(serializers.ModelSerializer): country = serializers.PrimaryKeyRelatedField( queryset=location_models.Country.objects.all(), write_only=True) - image = serializers.PrimaryKeyRelatedField( - queryset=gallery_models.Image.objects.all(), - write_only=True) class Meta: model = models.Collection @@ -34,7 +29,6 @@ class CollectionSerializer(serializers.ModelSerializer): 'description_translated', 'start', 'end', - 'image', 'image_url', 'is_publish', 'on_top', diff --git a/apps/establishment/migrations/0027_auto_20190920_1120.py b/apps/establishment/migrations/0027_auto_20190920_1120.py new file mode 100644 index 00000000..974b8093 --- /dev/null +++ b/apps/establishment/migrations/0027_auto_20190920_1120.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-20 11:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0026_establishment_preview_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='collections', + field=models.ManyToManyField(blank=True, default=None, null=True, related_name='establishments', to='collection.Collection', verbose_name='Collections'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 2841301f..f731cd74 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -272,8 +272,9 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): transportation = models.TextField(blank=True, null=True, default=None, verbose_name=_('Transportation')) collections = models.ManyToManyField(to='collection.Collection', - verbose_name=_('Collections'), - related_name='establishments') + related_name='establishments', + blank=True, null=True, default=None, + verbose_name=_('Collections')) preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) From 49d20705448777677023808cd5c15211312a9c71 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 14:31:02 +0300 Subject: [PATCH 003/319] Add establishment tests --- apps/establishment/tests.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index b64a971c..08e89f10 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -2,7 +2,10 @@ from rest_framework.test import APITestCase from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from establishment.models import Employee +from establishment.models import Establishment, EstablishmentType, Employee + +from rest_framework.reverse import reverse + # Create your tests here. @@ -20,6 +23,29 @@ class BaseTestCase(APITestCase): 'refresh_token': tokkens.get('refresh_token')}) +class EstablishmentTests(BaseTestCase): + def test_establishment_CRD(self): + response = self.client.get('/api/back/establishments/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + establishment_type = EstablishmentType.objects.create(name="Test establishment type") + + data = { + 'name': 'Test establishment', + 'type_id': establishment_type.id, + } + + response = self.client.post('/api/back/establishments/', data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + print(response.json()) + + response = self.client.get('/api/back/establishments/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + class EmployeeTests(BaseTestCase): def test_employee_CRD(self): response = self.client.get('/api/back/establishments/employees/', format='json') @@ -39,6 +65,3 @@ class EmployeeTests(BaseTestCase): response = self.client.delete('/api/back/establishments/employees/1/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - - From 00d8f09881093fddcb83148869e7cf70ee22bf1b Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 14:42:00 +0300 Subject: [PATCH 004/319] Add email tests --- apps/establishment/tests.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 08e89f10..6d84cbe1 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -22,22 +22,23 @@ class BaseTestCase(APITestCase): self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) + self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") + class EstablishmentTests(BaseTestCase): def test_establishment_CRD(self): response = self.client.get('/api/back/establishments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - establishment_type = EstablishmentType.objects.create(name="Test establishment type") + data = { 'name': 'Test establishment', - 'type_id': establishment_type.id, + 'type_id': self.establishment_type.id, } response = self.client.post('/api/back/establishments/', data=data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - print(response.json()) response = self.client.get('/api/back/establishments/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -65,3 +66,27 @@ class EmployeeTests(BaseTestCase): response = self.client.delete('/api/back/establishments/employees/1/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + +class EmailTests(BaseTestCase): + def test_email_CRD(self): + response = self.client.get('/api/back/establishments/emails/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id + ) + + data = { + 'email': "test@test.com", + 'establishment': establishment.id + } + + response = self.client.post('/api/back/establishments/emails/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/establishments/emails/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/emails/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From 09c9d666b57b93fca68826a537fbf973e71a2a8e Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 14:42:34 +0300 Subject: [PATCH 005/319] rename image_url to image, preview_image_url to preview_image --- apps/establishment/serializers/common.py | 9 ++++++--- apps/main/serializers.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 79075fcf..56583ba2 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -161,7 +161,6 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): 'subtypes', 'address', 'tags', - 'image_url', ] @@ -170,13 +169,15 @@ class EstablishmentListSerializer(EstablishmentBaseSerializer): # Annotated fields in_favorites = serializers.BooleanField(allow_null=True) + preview_image = serializers.URLField(source='preview_image_url') + class Meta: """Meta class.""" model = models.Establishment fields = EstablishmentBaseSerializer.Meta.fields + [ 'in_favorites', - 'preview_image_url', + 'preview_image', ] @@ -197,6 +198,8 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): in_favorites = serializers.SerializerMethodField() + image = serializers.URLField(source='image_url') + class Meta: """Meta class.""" @@ -204,7 +207,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): fields = EstablishmentListSerializer.Meta.fields + [ 'description_translated', 'price_level', - 'image_url', + 'image', 'awards', 'schedule', 'website', diff --git a/apps/main/serializers.py b/apps/main/serializers.py index d04335f9..f2beb768 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -138,7 +138,7 @@ class CarouselListSerializer(serializers.ModelSerializer): name = serializers.CharField() toque_number = serializers.CharField() public_mark = serializers.CharField() - image_url = serializers.URLField() + image = serializers.URLField(source='image_url') awards = AwardBaseSerializer(many=True) vintage_year = serializers.IntegerField() @@ -152,7 +152,7 @@ class CarouselListSerializer(serializers.ModelSerializer): 'awards', 'toque_number', 'public_mark', - 'image_url', + 'image', 'vintage_year', ] From ee93905556107536c8db43c1fd0e162923a5451f Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 14:49:04 +0300 Subject: [PATCH 006/319] Add phone tests --- apps/establishment/tests.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 6d84cbe1..43f7547f 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -2,9 +2,7 @@ from rest_framework.test import APITestCase from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from establishment.models import Establishment, EstablishmentType, Employee - -from rest_framework.reverse import reverse +from establishment.models import Establishment, EstablishmentType # Create your tests here. @@ -90,3 +88,28 @@ class EmailTests(BaseTestCase): response = self.client.delete('/api/back/establishments/emails/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class PhoneTests(BaseTestCase): + def test_phone_CRD(self): + response = self.client.get('/api/back/establishments/phones/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id + ) + + data = { + 'phone': "+79999999999", + 'establishment': establishment.id + } + + response = self.client.post('/api/back/establishments/phones/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/establishments/phones/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/phones/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From e616d3970cc84a803e4c4cd593224c0af059eb65 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 14:55:34 +0300 Subject: [PATCH 007/319] Add class to test childs --- apps/establishment/tests.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 43f7547f..d9573191 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -65,19 +65,25 @@ class EmployeeTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class EmailTests(BaseTestCase): - def test_email_CRD(self): - response = self.client.get('/api/back/establishments/emails/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - establishment = Establishment.objects.create( +# Class to test childs +class ChildTestCase(BaseTestCase): + def setUp(self): + super().setUp() + self.establishment = Establishment.objects.create( name="Test establishment", establishment_type_id=self.establishment_type.id ) + +# Test childs +class EmailTests(ChildTestCase): + def test_email_CRD(self): + response = self.client.get('/api/back/establishments/emails/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = { 'email': "test@test.com", - 'establishment': establishment.id + 'establishment': self.establishment.id } response = self.client.post('/api/back/establishments/emails/', data=data) @@ -90,19 +96,14 @@ class EmailTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class PhoneTests(BaseTestCase): +class PhoneTests(ChildTestCase): def test_phone_CRD(self): response = self.client.get('/api/back/establishments/phones/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - establishment = Establishment.objects.create( - name="Test establishment", - establishment_type_id=self.establishment_type.id - ) - data = { 'phone': "+79999999999", - 'establishment': establishment.id + 'establishment': self.establishment.id } response = self.client.post('/api/back/establishments/phones/', data=data) From 5f3947601a7c2f919aa7802bb1f72b9bf39f8837 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 15:11:33 +0300 Subject: [PATCH 008/319] rename url path collection to collections, delete property in M2M field --- .../migrations/0028_auto_20190920_1205.py | 18 ++++++++++++++++++ apps/establishment/models.py | 2 +- project/urls/web.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 apps/establishment/migrations/0028_auto_20190920_1205.py diff --git a/apps/establishment/migrations/0028_auto_20190920_1205.py b/apps/establishment/migrations/0028_auto_20190920_1205.py new file mode 100644 index 00000000..abdd4d7f --- /dev/null +++ b/apps/establishment/migrations/0028_auto_20190920_1205.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-20 12:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0027_auto_20190920_1120'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='collections', + field=models.ManyToManyField(blank=True, default=None, related_name='establishments', to='collection.Collection', verbose_name='Collections'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index f731cd74..042c25e0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -273,7 +273,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Transportation')) collections = models.ManyToManyField(to='collection.Collection', related_name='establishments', - blank=True, null=True, default=None, + blank=True, default=None, verbose_name=_('Collections')) preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) diff --git a/project/urls/web.py b/project/urls/web.py index 18bb8fd9..0a81672d 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -20,7 +20,7 @@ app_name = 'web' urlpatterns = [ path('account/', include('account.urls.web')), path('re_blocks/', include('advertisement.urls.web')), - path('collection/', include('collection.urls.web')), + path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), path('news/', include('news.urls.web')), path('notifications/', include('notification.urls.web')), From 193ecfba7a4a7ea22e07d70683f8fde8c7aea80e Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 15:38:20 +0300 Subject: [PATCH 009/319] fixed comments --- apps/collection/views/common.py | 9 --------- apps/establishment/serializers/common.py | 2 +- apps/establishment/views/web.py | 25 ------------------------ 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 8c386930..8d2eb109 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -47,15 +47,6 @@ class CollectionEstablishmentListView(CollectionListView): """ queryset = super(CollectionEstablishmentListView, self).get_queryset() # Perform the lookup filtering. - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - - assert lookup_url_kwarg in self.kwargs, ( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwarg) - ) - collection = get_object_or_404(queryset, pk=self.kwargs['pk']) # May raise a permission denied diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 56583ba2..65522942 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -263,7 +263,7 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer establishment_id = self.context.get('request').parser_context.get('kwargs').get('pk') establishment_qs = models.Establishment.objects.filter(id=establishment_id) if not establishment_qs.exists(): - return serializers.ValidationError() + raise serializers.ValidationError({'detail': _('Establishment not found.')}) attrs['establishment'] = establishment_qs.first() return attrs diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index daa26d6a..53382be9 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -79,14 +79,6 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): Returns the object the view is displaying. """ queryset = self.filter_queryset(self.get_queryset()) - lookup_url_kwargs = ('pk', 'comment_id') - - assert lookup_url_kwargs in self.kwargs.keys(), ( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwargs) - ) establishment_obj = get_object_or_404(queryset, pk=self.kwargs['pk']) @@ -107,14 +99,6 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D """ Returns the object the view is displaying. """ - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - assert lookup_url_kwarg in self.kwargs, ( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwarg) - ) - obj = get_object_or_404( self.request.user.favorites.by_content_type(app_label='establishment', model='establishment') @@ -164,15 +148,6 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """ Returns the object the view is displaying. """ - lookup_url_kwargs = ('pk', 'schedule_id') - - assert lookup_url_kwargs in self.kwargs.keys(), ( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwargs) - ) - establishment_pk = self.kwargs['pk'] schedule_id = self.kwargs['schedule_id'] From 014931d2c4fe550fa52a07883ca9e91d002d383f Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 15:39:53 +0300 Subject: [PATCH 010/319] Add plate tests Add is_publish to serializer Add TODO for is_publish --- apps/establishment/serializers/back.py | 4 +- apps/establishment/tests.py | 53 +++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index caa13742..e20cc087 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -34,7 +34,9 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): 'type_id', 'type', 'socials', - 'image_url' + 'image_url', + # TODO: check in admin filters + 'is_publish' ] diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index d9573191..ee4e444a 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -28,20 +28,19 @@ class EstablishmentTests(BaseTestCase): response = self.client.get('/api/back/establishments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = { 'name': 'Test establishment', 'type_id': self.establishment_type.id, + 'is_publish': True } response = self.client.post('/api/back/establishments/', data=data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get('/api/back/establishments/1/', format='json') + response = self.client.get('/api/back/establishments/2/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete('/api/back/establishments/1/', format='json') + response = self.client.delete('/api/back/establishments/2/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -71,7 +70,8 @@ class ChildTestCase(BaseTestCase): super().setUp() self.establishment = Establishment.objects.create( name="Test establishment", - establishment_type_id=self.establishment_type.id + establishment_type_id=self.establishment_type.id, + is_publish=True ) @@ -114,3 +114,46 @@ class PhoneTests(ChildTestCase): response = self.client.delete('/api/back/establishments/phones/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class SocialTests(ChildTestCase): + def test_social_CRD(self): + response = self.client.get('/api/back/establishments/socials/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'title': "Test social", + 'url': 'https://testsocial.com', + 'establishment': self.establishment.id + } + + response = self.client.post('/api/back/establishments/socials/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/establishments/socials/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/socials/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class PlatesTests(ChildTestCase): + def test_plates_CRD(self): + response = self.client.get('/api/back/establishments/plates/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'title': "Test social", + 'url': 'https://testsocial.com', + 'establishment': self.establishment.id + } + + response = self.client.post('/api/back/establishments/plates/', data=data) + print(f"PLATE: {response.json()}") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/establishments/plates/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/plates/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From 1e1b09660d9fd6f2071f0ee94d1b96b63332f9db Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 20 Sep 2019 15:55:37 +0300 Subject: [PATCH 011/319] fix news_type --- apps/establishment/tests.py | 8 +++++--- apps/search_indexes/documents/news.py | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index b64a971c..0aa44a0f 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -13,11 +13,13 @@ class BaseTestCase(APITestCase): self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' self.newsletter = True - self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) #get tokkens tokkens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + self.client.cookies = SimpleCookie( + {'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) class EmployeeTests(BaseTestCase): diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index bf67a0f6..45e50c42 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -46,3 +46,11 @@ class NewsDocument(Document): def prepare_description(self, instance): return instance.description + + 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, models.NewsType): + return related_instance.news_set.all() From 2f74c63fd9c2be470dd550e5ef7b3bad2ec26475 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 15:58:23 +0300 Subject: [PATCH 012/319] Fix plate tests Add menu tests --- apps/establishment/tests.py | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index ee4e444a..726eee5b 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -1,8 +1,10 @@ +import json from rest_framework.test import APITestCase from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from establishment.models import Establishment, EstablishmentType +from main.models import Currency +from establishment.models import Establishment, EstablishmentType, Menu # Create your tests here. @@ -138,14 +140,22 @@ class SocialTests(ChildTestCase): class PlatesTests(ChildTestCase): - def test_plates_CRD(self): + def test_plate_CRD(self): response = self.client.get('/api/back/establishments/plates/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + menu = Menu.objects.create( + category=json.dumps({"en-GB": "Test category"}), + establishment=self.establishment + ) + currency = Currency.objects.create(name="Test currency") + data = { - 'title': "Test social", - 'url': 'https://testsocial.com', - 'establishment': self.establishment.id + 'name': json.dumps({"en-GB": "Test plate"}), + 'establishment': self.establishment.id, + 'price': 10, + 'menu': menu.id, + 'currency_id': currency.id } response = self.client.post('/api/back/establishments/plates/', data=data) @@ -157,3 +167,23 @@ class PlatesTests(ChildTestCase): response = self.client.delete('/api/back/establishments/plates/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class MenusTests(ChildTestCase): + def test_menu_CRD(self): + response = self.client.get('/api/back/establishments/menus/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'category': json.dumps({"en-GB": "Test category"}), + 'establishment': self.establishment.id + } + + response = self.client.post('/api/back/establishments/menus/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/establishments/menus/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/establishments/menus/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From df0afc56538a320015dd63a3c4324b0b3c63dbbf Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:00:16 +0300 Subject: [PATCH 013/319] Fix plates RUD view --- apps/establishment/urls/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index 9c0aac73..0983efa4 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -17,7 +17,7 @@ urlpatterns = [ path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), - path('plates//', views.PlateListCreateView.as_view(), name='plate-rud'), + path('plates//', views.PlateRUDView.as_view(), name='plate-rud'), path('socials/', views.SocialListCreateView.as_view(), name='socials'), path('socials//', views.SocialRUDView.as_view(), name='social-rud'), path('phones/', views.PhonesListCreateView.as_view(), name='phones'), From 277c7ba2ebf25e96d1a953281640ef70b4bf802d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 16:08:23 +0300 Subject: [PATCH 014/319] added field old_password to endpoint for change user password --- apps/account/serializers/common.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index d5f89526..e8d6ba30 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -1,5 +1,6 @@ """Common account serializers""" from django.conf import settings +from django.utils.translation import gettext_lazy as _ from django.contrib.auth import password_validation as password_validators from fcm_django.models import FCMDevice from rest_framework import exceptions @@ -80,23 +81,31 @@ class ChangePasswordSerializer(serializers.ModelSerializer): """Serializer for model User.""" password = serializers.CharField(write_only=True) + old_password = serializers.CharField(write_only=True) class Meta: """Meta class""" model = models.User - fields = ('password', ) + fields = ( + 'password', + 'old_password', + ) def validate(self, attrs): """Override validate method""" password = attrs.get('password') + old_password = attrs.get('old_password') try: + # Check old password + if not self.instance.check_password(raw_password=old_password): + raise serializers.ValidationError(_('Old password mismatch.')) # Compare new password with the old ones if self.instance.check_password(raw_password=password): - raise utils_exceptions.PasswordsAreEqual() + raise serializers.ValidationError(_('Password is already in use')) # Validate password password_validators.validate_password(password=password) except serializers.ValidationError as e: - raise serializers.ValidationError(str(e)) + raise serializers.ValidationError({'detail': e.detail}) else: return attrs From 2d8b30c3eeecd759a7aeac5efc3eb1985a7e6832 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:10:51 +0300 Subject: [PATCH 015/319] Add patch test to plates class --- apps/establishment/tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index d4a6418b..c3c50617 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -161,12 +161,18 @@ class PlatesTests(ChildTestCase): } response = self.client.post('/api/back/establishments/plates/', data=data) - print(f"PLATE: {response.json()}") self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.get('/api/back/establishments/plates/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'name': json.dumps({"en-GB": "Test new plate"}) + } + + response = self.client.patch('/api/back/establishments/plates/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/plates/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From aaff7fd3bae3cb3758695c470ef4de939ec68b25 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:22:41 +0300 Subject: [PATCH 016/319] Add schedule tests --- apps/establishment/tests.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index c3c50617..c112e067 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -39,6 +39,7 @@ class EstablishmentTests(BaseTestCase): } response = self.client.post('/api/back/establishments/', data=data, format='json') + print(f"ESTABLISHMENT: {response.json()}") self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.get('/api/back/establishments/2/', format='json') @@ -141,8 +142,8 @@ class SocialTests(ChildTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class PlatesTests(ChildTestCase): - def test_plate_CRD(self): +class PlateTests(ChildTestCase): + def test_plate_CRUD(self): response = self.client.get('/api/back/establishments/plates/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -177,7 +178,7 @@ class PlatesTests(ChildTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class MenusTests(ChildTestCase): +class MenuTests(ChildTestCase): def test_menu_CRD(self): response = self.client.get('/api/back/establishments/menus/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -195,3 +196,18 @@ class MenusTests(ChildTestCase): response = self.client.delete('/api/back/establishments/menus/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class EstablishShedulerTests(ChildTestCase): + def test_shedule_CRD(self): + data = { + 'weekday': 1 + } + response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/1/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From 85fef10eb301371c62c1ee5ce3aa25139882a5f9 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:28:23 +0300 Subject: [PATCH 017/319] Fix establishment tests --- apps/establishment/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index c112e067..12afdce2 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -39,13 +39,14 @@ class EstablishmentTests(BaseTestCase): } response = self.client.post('/api/back/establishments/', data=data, format='json') - print(f"ESTABLISHMENT: {response.json()}") self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get('/api/back/establishments/2/', format='json') + establishment = response.json() + + response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete('/api/back/establishments/2/', format='json') + response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From 0d7b26bbb21efde811b45ec3bf85f75f4aa7e098 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:40:53 +0300 Subject: [PATCH 018/319] Add update tests --- apps/establishment/tests.py | 64 +++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 12afdce2..04637068 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -28,7 +28,7 @@ class BaseTestCase(APITestCase): class EstablishmentTests(BaseTestCase): - def test_establishment_CRD(self): + def test_establishment_CRUD(self): response = self.client.get('/api/back/establishments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -46,12 +46,19 @@ class EstablishmentTests(BaseTestCase): response = self.client.get(f'/api/back/establishments/{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/{establishment["id"]}/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) class EmployeeTests(BaseTestCase): - def test_employee_CRD(self): + def test_employee_CRUD(self): response = self.client.get('/api/back/establishments/employees/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -66,6 +73,13 @@ class EmployeeTests(BaseTestCase): response = self.client.get('/api/back/establishments/employees/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'name': 'Test new name' + } + + response = self.client.patch('/api/back/establishments/employees/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/employees/1/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -83,7 +97,7 @@ class ChildTestCase(BaseTestCase): # Test childs class EmailTests(ChildTestCase): - def test_email_CRD(self): + def test_email_CRUD(self): response = self.client.get('/api/back/establishments/emails/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -98,12 +112,19 @@ class EmailTests(ChildTestCase): response = self.client.get('/api/back/establishments/emails/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'email': 'testnew@test.com' + } + + response = self.client.patch('/api/back/establishments/emails/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/emails/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) class PhoneTests(ChildTestCase): - def test_phone_CRD(self): + def test_phone_CRUD(self): response = self.client.get('/api/back/establishments/phones/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -118,12 +139,20 @@ class PhoneTests(ChildTestCase): response = self.client.get('/api/back/establishments/phones/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'phone': '+79999999998' + } + + response = self.client.patch('/api/back/establishments/phones/1/', data=update_data) + print(f"PHONE: {response.json()}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/phones/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) class SocialTests(ChildTestCase): - def test_social_CRD(self): + def test_social_CRUD(self): response = self.client.get('/api/back/establishments/socials/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -139,6 +168,13 @@ class SocialTests(ChildTestCase): response = self.client.get('/api/back/establishments/socials/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'title': 'Test new social' + } + + response = self.client.patch('/api/back/establishments/socials/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/socials/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -180,7 +216,7 @@ class PlateTests(ChildTestCase): class MenuTests(ChildTestCase): - def test_menu_CRD(self): + def test_menu_CRUD(self): response = self.client.get('/api/back/establishments/menus/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -195,12 +231,19 @@ class MenuTests(ChildTestCase): response = self.client.get('/api/back/establishments/menus/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'category': json.dumps({"en-GB": "Test new category"}) + } + + response = self.client.patch('/api/back/establishments/menus/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete('/api/back/establishments/menus/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) class EstablishShedulerTests(ChildTestCase): - def test_shedule_CRD(self): + def test_shedule_CRUD(self): data = { 'weekday': 1 } @@ -210,5 +253,12 @@ class EstablishShedulerTests(ChildTestCase): response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/1/') self.assertEqual(response.status_code, status.HTTP_200_OK) + update_data = { + 'weekday': 2 + } + + response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From e39bc835fd9e5f48527abd37b6b050b88daa5d99 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:41:24 +0300 Subject: [PATCH 019/319] Fix class name --- apps/establishment/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 04637068..08f4ac16 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -242,7 +242,7 @@ class MenuTests(ChildTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class EstablishShedulerTests(ChildTestCase): +class EstablishmentShedulerTests(ChildTestCase): def test_shedule_CRUD(self): data = { 'weekday': 1 From 4e495fa47d51b18a2f23508f76d6979f99b825f6 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:42:35 +0300 Subject: [PATCH 020/319] Remove debug --- apps/establishment/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 08f4ac16..5de7d779 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -144,7 +144,6 @@ class PhoneTests(ChildTestCase): } response = self.client.patch('/api/back/establishments/phones/1/', data=update_data) - print(f"PHONE: {response.json()}") self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.delete('/api/back/establishments/phones/1/') From eaac6ca8856bcee6a6bde53dc97f4dd4c5a06baf Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 20 Sep 2019 16:47:20 +0300 Subject: [PATCH 021/319] Fix --- apps/establishment/views/back.py | 5 +++-- apps/establishment/views/web.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index aa52caec..c8915e91 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -83,7 +83,8 @@ class EmployeeListCreateView(generics.ListCreateAPIView): queryset = models.Employee.objects.all() pagination_class = None -class EmployeeRUDView(generics.RetrieveDestroyAPIView): + +class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.EmployeeBackSerializers - queryset = models.Employee.objects.all() \ No newline at end of file + queryset = models.Employee.objects.all() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 53382be9..7be9f49e 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -36,7 +36,6 @@ class EstablishmentSimilarListView(EstablishmentListView): .order_by('-total_mark')[:13] - class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): """Resource for getting a establishment.""" serializer_class = serializers.EstablishmentDetailSerializer From 07d17d5bf1103eb50ef4edb523de85691210abec Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 20 Sep 2019 16:55:04 +0300 Subject: [PATCH 022/319] refactored similar establishment endpoint --- apps/establishment/views/web.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 53382be9..32decde0 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -29,12 +29,8 @@ class EstablishmentSimilarListView(EstablishmentListView): def get_queryset(self): """Override get_queryset method""" - qs = super().get_queryset() - if not qs.filter(pk=self.kwargs.get('pk')).exists(): - return qs.none() - return qs.similar(establishment_pk=self.kwargs.get('pk'))\ - .order_by('-total_mark')[:13] - + return super().get_queryset().similar(establishment_pk=self.kwargs.get('pk'))\ + .order_by('-total_mark')[:13] class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): From 7729ab5653d8e8f280e6538e9e84e7917e33abcb Mon Sep 17 00:00:00 2001 From: michail Date: Sat, 21 Sep 2019 17:44:27 +0500 Subject: [PATCH 023/319] Add news tests --- apps/news/tests.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/apps/news/tests.py b/apps/news/tests.py index 7ce503c2..bf6577d9 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -1,3 +1,44 @@ +from http.cookies import SimpleCookie + from django.test import TestCase +from rest_framework.test import APITestCase +from rest_framework import status + +from news.models import NewsType, News +from account.models import User # Create your tests here. + + +class BaseTestCase(APITestCase): + + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + #get tokkens + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + self.test_news_type = NewsType.objects.create(name="Test news type") + self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, + news_type=self.test_news_type, description={"en-GB": "Description test news"}, + playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", + is_publish=True) + + +class NewsTestCase(BaseTestCase): + + def test_news_list(self): + response = self.client.get("/api/web/news/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_detail(self): + response = self.client.get(f"/api/web/news/{self.test_news.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_type_list(self): + response = self.client.get("/api/web/news/type/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + From b8c734f5e63a6e27338d8ed6c346cd7c867dc6ee Mon Sep 17 00:00:00 2001 From: michail Date: Sun, 22 Sep 2019 15:37:51 +0500 Subject: [PATCH 024/319] Add partner tests --- apps/partner/tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/partner/tests.py b/apps/partner/tests.py index a39b155a..494e7f7e 100644 --- a/apps/partner/tests.py +++ b/apps/partner/tests.py @@ -1 +1,16 @@ # Create your tests here. +from rest_framework.test import APITestCase +from rest_framework import status + +from partner.models import Partner + + +class PartnerTestCase(APITestCase): + + def setUp(self): + self.test_url = "www.example.com" + self.test_partner = Partner.objects.create(url=self.test_url) + + def test_partner_list(self): + response = self.client.get("/api/web/partner/") + self.assertEqual(response.status_code, status.HTTP_200_OK) From 20766768d8c7a560766e5ba65650f1f0afdae3f7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 23 Sep 2019 10:12:14 +0300 Subject: [PATCH 025/319] fixed establishment filter by geo --- apps/establishment/models.py | 4 +--- apps/establishment/views/web.py | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 042c25e0..ca9fccd3 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -219,10 +219,8 @@ class EstablishmentQuerySet(models.QuerySet): :return: all establishments within the specified radius of specified point :param unit: length unit e.g. m, km. Default is 'm'. """ - - from django.contrib.gis.measure import Distance kwargs = {unit: radius} - return self.filter(address__coordinates__distance_lte=(center, Distance(**kwargs))) + return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs))) class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 32decde0..78aa3a5f 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -1,5 +1,6 @@ """Establishment app views.""" +from django.contrib.gis.geos import Point from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions @@ -104,23 +105,25 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D return obj -class EstablishmentNearestRetrieveView(EstablishmentMixin, generics.ListAPIView): +class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView): """Resource for getting list of nearest establishments.""" serializer_class = serializers.EstablishmentListSerializer filter_class = filters.EstablishmentFilter def get_queryset(self): - """Overrided method 'get_queryset'.""" - from django.contrib.gis.geos import Point + """Overridden method 'get_queryset'.""" + lat = self.request.query_params.get('lat') + lon = self.request.query_params.get('lon') + radius = self.request.query_params.get('radius') + unit = self.request.query_params.get('unit') - center = Point(float(self.request.query_params["lat"]), float(self.request.query_params["lon"])) - radius = float(self.request.query_params["radius"]) - unit = self.request.query_params.get("unit", None) - by_distance_from_point_kwargs = {"center": center, "radius": radius, "unit": unit} - return super(EstablishmentNearestRetrieveView, self).get_queryset() \ - .by_distance_from_point(**{k: v for k, v in by_distance_from_point_kwargs.items() if v is not None}) \ - .by_country_code(code=self.request.country_code) \ - .annotate_in_favorites(user=self.request.user) + qs = super(EstablishmentNearestRetrieveView, self).get_queryset() + if lat and lon and radius and unit: + center = Point(float(lat), float(lon)) + filter_kwargs = {'center': center, 'radius': float(radius), 'unit': unit} + return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() + if v is not None}) + return qs class EstablishmentTagListView(generics.ListAPIView): From 9f89deb23b27f4a60f633739d8fc5bdf13663d70 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 11:16:22 +0300 Subject: [PATCH 026/319] Fix establishment RUD serializer --- apps/establishment/serializers/back.py | 30 ++++++++++++++++++++++++++ apps/establishment/urls/back.py | 2 +- apps/establishment/views/back.py | 5 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index e20cc087..7199eb54 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -40,6 +40,36 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): ] +class EstablishmentRUDSerializer(EstablishmentBaseSerializer): + """Establishment create serializer""" + + type_id = serializers.PrimaryKeyRelatedField( + source='establishment_type', + queryset=models.EstablishmentType.objects.all(), write_only=True + ) + phones = ContactPhonesSerializer(read_only=False, many=True, ) + emails = ContactEmailsSerializer(read_only=False, many=True, ) + socials = SocialNetworkRelatedSerializers(read_only=False, many=True, ) + + class Meta: + model = models.Establishment + fields = [ + 'id', + 'name', + 'website', + 'phones', + 'emails', + 'price_level', + 'toque_number', + 'type_id', + 'type', + 'socials', + 'image_url', + # TODO: check in admin filters + 'is_publish' + ] + + class SocialNetworkSerializers(serializers.ModelSerializer): """Social network serializers.""" class Meta: diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index 0983efa4..dca5fb55 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -9,7 +9,7 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListCreateView.as_view(), name='list'), - path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), + path('/', views.EstablishmentRUDView.as_view(), name='detail'), path('/schedule//', views.EstablishmentScheduleRUDView.as_view(), name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index c8915e91..974fca8a 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -13,6 +13,11 @@ class EstablishmentListCreateView(EstablishmentMixin, generics.ListCreateAPIView serializer_class = serializers.EstablishmentListCreateSerializer +class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): + queryset = models.Establishment.objects.all() + serializer_class = serializers.EstablishmentRUDSerializer + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers From 217c11b9709c19540785012c36b8f4673503c8c8 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 23 Sep 2019 12:29:08 +0300 Subject: [PATCH 027/319] =?UTF-8?q?GM-124:=20=D0=94=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=20establishment-=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/establishment/models.py | 2 ++ apps/establishment/serializers/common.py | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index ca9fccd3..5eac27fd 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -227,6 +227,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """Establishment model.""" name = models.CharField(_('name'), max_length=255, default='') + name_transliterated = models.CharField(_('Transliterated name'), + max_length=255, default='') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 65522942..5eb1b9ae 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -154,6 +154,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', + 'name_transliterated', 'price_level', 'toque_number', 'public_mark', From 202aabf15e5df684bb5211c882c7b950e7566633 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 23 Sep 2019 12:40:27 +0300 Subject: [PATCH 028/319] GM-124: added establishment migration --- .../0029_establishment_name_transliterated.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/establishment/migrations/0029_establishment_name_transliterated.py diff --git a/apps/establishment/migrations/0029_establishment_name_transliterated.py b/apps/establishment/migrations/0029_establishment_name_transliterated.py new file mode 100644 index 00000000..54fb4ea0 --- /dev/null +++ b/apps/establishment/migrations/0029_establishment_name_transliterated.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-23 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0028_auto_20190920_1205'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='name_transliterated', + field=models.CharField(default='', max_length=255, verbose_name='Transliterated name'), + ), + ] From 2c43a371088f111163fc4731bd0efd9c9c76f2ba Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 23 Sep 2019 12:49:14 +0300 Subject: [PATCH 029/319] =?UTF-8?q?GM-125:=20=D0=94=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20FB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authorization/views/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index 4231ed7a..bb337dce 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -144,7 +144,8 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin): status=status.HTTP_200_OK) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(access_token=access_token, - refresh_token=refresh_token), + refresh_token=refresh_token, + permanent=True), response=response) From 466c0f80d709ea4ca8b38ac86945fc0e7dba1cf6 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 14:17:19 +0300 Subject: [PATCH 030/319] Add list read test Add detail read test --- apps/collection/tests.py | 93 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 7ce503c2..df75d00f 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -1,3 +1,94 @@ -from django.test import TestCase +import json +from datetime import datetime +from rest_framework.test import APITestCase +from account.models import User +from rest_framework import status +from http.cookies import SimpleCookie + +from collection.models import Collection +from location.models import Country # Create your tests here. + + +class BaseTestCase(APITestCase): + + def setUpT(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.newsletter = True + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) + #get tokkens + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + + +class CollectionListTests(BaseTestCase): + def test_collection_list_Read(self): + response = self.client.get('/api/web/collections/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class CollectionDetailTests(BaseTestCase): + + def setUp(self): + super().setUp() + + country = Country.objects.first() + if not country: + country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="test" + ) + + self.collection = Collection.objects.create( + name='Test collection', + is_publish=True, + start=datetime.now(), + end=datetime.now(), + country=country + ) + + def test_collection_detail_Read(self): + response = self.client.get(f'/api/web/collections/{self.collection.id}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + + # data = { + # 'name': 'Test collection', + # + # } + # + # response = self.client.post('/api/web/collections/', data=data, format='json') + # self.assertEqual(response.status_code, status.HTTP_201_CREATED) + # + # establishment = response.json() + # + # response = self.client.get(f'/api/back/establishments/{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/{establishment["id"]}/', data=update_data) + # self.assertEqual(response.status_code, status.HTTP_200_OK) + # + # response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json') + # self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +# Class to test childs +# class ChildTestCase(BaseTestCase): +# def setUp(self): +# super().setUp() +# self.establishment = Establishment.objects.create( +# name="Test establishment", +# establishment_type_id=self.establishment_type.id, +# is_publish=True +# ) From aedcd795a9cd0c8ea889444791f36425fa37e55b Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 14:21:05 +0300 Subject: [PATCH 031/319] Fix establishment model --- apps/establishment/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5eac27fd..5b188c3c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -227,7 +227,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """Establishment model.""" name = models.CharField(_('name'), max_length=255, default='') - name_transliterated = models.CharField(_('Transliterated name'), + name_translated = models.CharField(_('Transliterated name'), max_length=255, default='') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), From 06764d05e8295749731a89ec68d2a390f2d262d5 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 23 Sep 2019 16:21:48 +0500 Subject: [PATCH 032/319] Add recipe tests --- apps/recipe/tests.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 apps/recipe/tests.py diff --git a/apps/recipe/tests.py b/apps/recipe/tests.py new file mode 100644 index 00000000..d0a17a36 --- /dev/null +++ b/apps/recipe/tests.py @@ -0,0 +1,33 @@ +from http.cookies import SimpleCookie + + +from rest_framework.test import APITestCase +from rest_framework import status + +from account.models import User +from recipe.models import Recipe + + +class BaseTestCase(APITestCase): + + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + self.test_recipe = Recipe.objects.create(title={"en-GB": "test title"}, description={"en-GB": "test description"}, + state=2, author="Test Author", created_by=self.user, + modified_by=self.user) + + def test_recipe_list(self): + response = self.client.get("/api/web/recipes/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_recipe_detail(self): + print(self.test_recipe.id) + response = self.client.get(f"/api/web/recipes/{self.test_recipe.id}/") + print(response.json()) + self.assertEqual(response.status_code, status.HTTP_200_OK) From b69822444aeddb447de148d951aefde3ee551ea6 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, 23 Sep 2019 15:06:55 +0300 Subject: [PATCH 033/319] Refactor tests for authorization --- apps/account/tests.py | 3 --- apps/account/tests/__init__.py | 0 apps/account/tests/tests_common.py | 11 +++++++++++ apps/authorization/tests/tests.py | 29 +++++++++++++++++++++-------- 4 files changed, 32 insertions(+), 11 deletions(-) delete mode 100644 apps/account/tests.py create mode 100644 apps/account/tests/__init__.py create mode 100644 apps/account/tests/tests_common.py diff --git a/apps/account/tests.py b/apps/account/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/apps/account/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/account/tests/__init__.py b/apps/account/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py new file mode 100644 index 00000000..fcabe946 --- /dev/null +++ b/apps/account/tests/tests_common.py @@ -0,0 +1,11 @@ +from rest_framework.test import APITestCase + + +# Create your tests here. + + +class AccountTests(APITestCase): + + def test_default(self): + print("account") + self.assertTrue(False) diff --git a/apps/authorization/tests/tests.py b/apps/authorization/tests/tests.py index 839af828..d9fd7b71 100644 --- a/apps/authorization/tests/tests.py +++ b/apps/authorization/tests/tests.py @@ -3,15 +3,28 @@ from account.models import User # Create your tests here. +def get_tokens_for_user( + username='sedragurda', password='sedragurdaredips19', email='sedragurda@desoz.com'): + + user = User.objects.create_user(username=username, email=email, password=password) + tokens = User.create_jwt_tokens(user) + + return { + "username": username, + "password": password, + "email": email, + "newsletter": True, + "user": user, + "tokens": tokens + } + + class AuthorizationTests(APITestCase): def setUp(self): - self.username = 'sedragurda' - self.password = 'sedragurdaredips19' - self.email = 'sedragurda@desoz.com' - self.newsletter = True - self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - self.tokkens = User.create_jwt_tokens(self.user) + data = get_tokens_for_user() + self.username = data["username"] + self.password = data["password"] def LoginTests(self): data ={ @@ -20,6 +33,6 @@ class AuthorizationTests(APITestCase): 'remember': True } response = self.client.post('/api/auth/login/', data=data) - self.assertEqual(response.data['access_token'], self.tokkens.get('access_token')) - self.assertEqual(response.data['refresh_token'], self.tokkens.get('refresh_token')) + self.assertEqual(response.data['access_token'], self.tokens.get('access_token')) + self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token')) From b4a2893fcd7fa9d33fb98db4f92a07a2299d112c Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 15:27:38 +0300 Subject: [PATCH 034/319] Add tests to guide --- apps/collection/tests.py | 66 ++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/apps/collection/tests.py b/apps/collection/tests.py index df75d00f..463d5fe5 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -1,11 +1,11 @@ -import json +import json, pytz from datetime import datetime from rest_framework.test import APITestCase from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from collection.models import Collection +from collection.models import Collection, Guide from location.models import Country # Create your tests here. @@ -13,7 +13,7 @@ from location.models import Country class BaseTestCase(APITestCase): - def setUpT(self): + def setUp(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' @@ -24,7 +24,8 @@ class BaseTestCase(APITestCase): tokkens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + 'refresh_token': tokkens.get('refresh_token'), + 'country_code': 'en'}) class CollectionListTests(BaseTestCase): @@ -42,53 +43,40 @@ class CollectionDetailTests(BaseTestCase): if not country: country = Country.objects.create( name=json.dumps({"en-GB": "Test country"}), - code="test" + code="en" ) self.collection = Collection.objects.create( name='Test collection', is_publish=True, - start=datetime.now(), - end=datetime.now(), + start=datetime.now(pytz.utc), + end=datetime.now(pytz.utc), country=country ) def test_collection_detail_Read(self): - response = self.client.get(f'/api/web/collections/{self.collection.id}/', format='json') + response = self.client.get(f'/api/web/collections/{self.collection.id}/establishments/?country_code=en', + format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) +class CollectionGuideTests(CollectionDetailTests): - # data = { - # 'name': 'Test collection', - # - # } - # - # response = self.client.post('/api/web/collections/', data=data, format='json') - # self.assertEqual(response.status_code, status.HTTP_201_CREATED) - # - # establishment = response.json() - # - # response = self.client.get(f'/api/back/establishments/{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/{establishment["id"]}/', data=update_data) - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # - # response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json') - # self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + def test_guide_list_Read(self): + response = self.client.get('/api/web/collections/guides/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) -# Class to test childs -# class ChildTestCase(BaseTestCase): -# def setUp(self): -# super().setUp() -# self.establishment = Establishment.objects.create( -# name="Test establishment", -# establishment_type_id=self.establishment_type.id, -# is_publish=True -# ) +class CollectionGuideDetailTests(CollectionDetailTests): + + def setUp(self): + super().setUp() + self.guide = Guide.objects.create( + collection=self.collection, + start=datetime.now(pytz.utc), + end=datetime.now(pytz.utc) + ) + + def test_guide_detail_Read(self): + response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) From eca921592d00c2d3a395c59c3c284f3e57763f6c Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 15:28:59 +0300 Subject: [PATCH 035/319] Fix guide parent --- apps/collection/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index a4a9bcae..928370d7 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -91,7 +91,9 @@ class GuideQuerySet(models.QuerySet): class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): """Guide model.""" parent = models.ForeignKey( - 'self', verbose_name=_('parent'), on_delete=models.CASCADE) + 'self', verbose_name=_('parent'), on_delete=models.CASCADE, + null=True, blank=True, default=None + ) advertorials = JSONField( _('advertorials'), null=True, blank=True, default=None, help_text='{"key":"value"}') From 23759974d9fbdddb7662200987e39fd70e05238e 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, 23 Sep 2019 15:34:40 +0300 Subject: [PATCH 036/319] Add account/user/ test for GET --- apps/account/tests/tests_common.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index fcabe946..2e66b330 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -1,11 +1,25 @@ from rest_framework.test import APITestCase - - +from rest_framework import status +from authorization.tests.tests import get_tokens_for_user +from http.cookies import SimpleCookie # Create your tests here. class AccountTests(APITestCase): - def test_default(self): - print("account") - self.assertTrue(False) + url = '/api/web/account/user/' + + def setUp(self): + self.data = get_tokens_for_user() + + def test_user_url(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + self.client.cookies = SimpleCookie( + {'access_token': self.data['tokens'].get('access_token'), + 'refresh_token': self.data['tokens'].get('access_token')}) + + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + From dc4b63d8b22c9aa3965e45330ce54a3aa5efb228 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 15:35:37 +0300 Subject: [PATCH 037/319] Refactor news --- apps/location/serializers/common.py | 14 +++ .../migrations/0010_auto_20190923_1131.py | 54 +++++++++++ .../migrations/0011_auto_20190923_1134.py | 27 ++++++ apps/news/models.py | 74 ++++++++------ apps/news/serializers.py | 97 +++++++++++++++++++ apps/news/serializers/__init__.py | 0 apps/news/serializers/common.py | 76 --------------- apps/news/serializers/web.py | 0 apps/news/urls/back.py | 11 +++ apps/news/urls/web.py | 11 +-- apps/news/views.py | 68 +++++++++++++ apps/news/views/__init__.py | 0 apps/news/views/common.py | 38 -------- apps/news/views/web.py | 0 apps/utils/serializers.py | 10 +- project/urls/back.py | 1 + 16 files changed, 329 insertions(+), 152 deletions(-) create mode 100644 apps/news/migrations/0010_auto_20190923_1131.py create mode 100644 apps/news/migrations/0011_auto_20190923_1134.py create mode 100644 apps/news/serializers.py delete mode 100644 apps/news/serializers/__init__.py delete mode 100644 apps/news/serializers/common.py delete mode 100644 apps/news/serializers/web.py create mode 100644 apps/news/urls/back.py create mode 100644 apps/news/views.py delete mode 100644 apps/news/views/__init__.py delete mode 100644 apps/news/views/common.py delete mode 100644 apps/news/views/web.py diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 63fe39e1..555cf499 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -1,7 +1,9 @@ +"""Location app common serializers.""" from django.contrib.gis.geos import Point from rest_framework import serializers from location import models +from utils.serializers import TranslatedField class CountrySerializer(serializers.ModelSerializer): @@ -20,6 +22,18 @@ class CountrySerializer(serializers.ModelSerializer): ] +class CountrySimpleSerializer(serializers.ModelSerializer): + """Simple country serializer.""" + + name_translated = TranslatedField() + + class Meta: + """Meta class.""" + + model = models.Country + fields = ('id', 'code', 'name_translated') + + class RegionSerializer(serializers.ModelSerializer): """Region serializer""" diff --git a/apps/news/migrations/0010_auto_20190923_1131.py b/apps/news/migrations/0010_auto_20190923_1131.py new file mode 100644 index 00000000..0bd6fd24 --- /dev/null +++ b/apps/news/migrations/0010_auto_20190923_1131.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.4 on 2019-09-23 11:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0009_auto_20190901_1032'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='author', + field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author'), + ), + migrations.AddField( + model_name='news', + name='image_url', + field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'), + ), + migrations.AddField( + model_name='news', + name='preview_image_url', + field=models.URLField(blank=True, default=None, null=True, verbose_name='Preview image URL path'), + ), + migrations.AlterField( + model_name='news', + name='address', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address'), + ), + migrations.AlterField( + model_name='news', + name='country', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='country'), + ), + migrations.AlterField( + model_name='news', + name='end', + field=models.DateTimeField(verbose_name='End'), + ), + migrations.AlterField( + model_name='news', + name='news_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='news.NewsType', verbose_name='news type'), + ), + migrations.AlterField( + model_name='news', + name='start', + field=models.DateTimeField(verbose_name='Start'), + ), + ] diff --git a/apps/news/migrations/0011_auto_20190923_1134.py b/apps/news/migrations/0011_auto_20190923_1134.py new file mode 100644 index 00000000..859cb636 --- /dev/null +++ b/apps/news/migrations/0011_auto_20190923_1134.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-09-23 11:34 + +from django.db import migrations +from django.conf import settings + + +def copy_image_links(apps, schemaeditor): + News = apps.get_model('news', 'News') + for news in News.objects.all(): + if news.image: + news.image_url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{news.image.image.url}' + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0010_auto_20190923_1131'), + ] + + operations = [ + migrations.RunPython(copy_image_links, migrations.RunPython.noop), + + migrations.RemoveField( + model_name='news', + name='image', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 3be34261..75ae49c9 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,25 +1,30 @@ """News app models.""" from django.db import models +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse - from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin class NewsType(models.Model): """NewsType model.""" + name = models.CharField(_('name'), max_length=250) class Meta: + """Meta class.""" + verbose_name_plural = _('news types') verbose_name = _('news type') def __str__(self): + """Overrided __str__ method.""" return self.name class NewsQuerySet(models.QuerySet): """QuerySet for model News""" + def by_type(self, news_type): """Filter News by type""" return self.filter(news_type__name=news_type) @@ -30,48 +35,55 @@ class NewsQuerySet(models.QuerySet): def published(self): """Return only published news""" - return self.filter(is_publish=True) + now = timezone.now() + return self.filter(is_publish=True, start__lte=now, end__gte=now) + + def with_related(self): + """Return qs with related objects.""" + return self.select_related('news_type') class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" - image = models.ForeignKey( - 'gallery.Image', null=True, blank=True, default=None, - verbose_name=_('News image'), on_delete=models.CASCADE) - news_type = models.ForeignKey( - NewsType, verbose_name=_('news type'), on_delete=models.CASCADE) - - title = TJSONField( - _('title'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') - subtitle = TJSONField( - _('subtitle'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}' - ) - description = TJSONField( - _('description'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}' - ) - start = models.DateTimeField(_('start')) - end = models.DateTimeField(_('end')) + news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, + verbose_name=_('news type')) + title = TJSONField(blank=True, null=True, default=None, + verbose_name=_('title'), + help_text='{"en-GB":"some text"}') + subtitle = TJSONField(blank=True, null=True, default=None, + verbose_name=_('subtitle'), + help_text='{"en-GB":"some text"}') + description = TJSONField(blank=True, null=True, default=None, + verbose_name=_('description'), + help_text='{"en-GB":"some text"}') + start = models.DateTimeField(verbose_name=_('Start')) + end = models.DateTimeField(verbose_name=_('End')) playlist = models.IntegerField(_('playlist')) - address = models.ForeignKey( - 'location.Address', verbose_name=_('address'), blank=True, - null=True, default=None, on_delete=models.CASCADE) - is_publish = models.BooleanField( - default=False, verbose_name=_('Publish status')) - country = models.ForeignKey( - 'location.Country', blank=True, null=True, - verbose_name=_('country'), on_delete=models.CASCADE) - is_highlighted = models.BooleanField( - default=False, verbose_name=_('Is highlighted')) + is_publish = models.BooleanField(default=False, + verbose_name=_('Publish status')) + author = models.CharField(max_length=255, blank=True, null=True, + default=None,verbose_name=_('Author')) + is_highlighted = models.BooleanField(default=False, + verbose_name=_('Is highlighted')) # TODO: metadata_keys - описание ключей для динамического построения полей метаданных # TODO: metadata_values - Описание значений для динамических полей из MetadataKeys + image_url = models.URLField(blank=True, null=True, default=None, + verbose_name=_('Image URL path')) + preview_image_url = models.URLField(blank=True, null=True, default=None, + verbose_name=_('Preview image URL path')) + address = models.ForeignKey('location.Address', blank=True, null=True, + default=None, verbose_name=_('address'), + on_delete=models.SET_NULL) + country = models.ForeignKey('location.Country', blank=True, null=True, + on_delete=models.SET_NULL, + verbose_name=_('country')) objects = NewsQuerySet.as_manager() class Meta: + """Meta class.""" + verbose_name = _('news') verbose_name_plural = _('news') diff --git a/apps/news/serializers.py b/apps/news/serializers.py new file mode 100644 index 00000000..a734a264 --- /dev/null +++ b/apps/news/serializers.py @@ -0,0 +1,97 @@ +"""News app common serializers.""" +from rest_framework import serializers +from location import models as location_models +from location.serializers import CountrySimpleSerializer +from news import models +from utils.serializers import TranslatedField + + +class NewsTypeSerializer(serializers.ModelSerializer): + """News type serializer.""" + + class Meta: + """Meta class.""" + + model = models.NewsType + fields = ('id', 'name') + + +class NewsBaseSerializer(serializers.ModelSerializer): + """Base serializer for News model.""" + + # read only fields + title_translated = TranslatedField() + subtitle_translated = TranslatedField() + + # related fields + news_type = NewsTypeSerializer(read_only=True) + country = CountrySimpleSerializer(read_only=True) + + class Meta: + """Meta class.""" + + model = models.News + fields = ( + 'id', + 'title_translated', + 'subtitle_translated', + 'image_url', + 'image_url', + 'preview_image_url', + 'news_type', + 'country', + ) + + +class NewsDetailSerializer(NewsBaseSerializer): + """News detail serializer.""" + + description_translated = TranslatedField() + + class Meta(NewsBaseSerializer.Meta): + """Meta class.""" + + fields = NewsBaseSerializer.Meta.fields + ( + 'description_translated', + 'start', + 'end', + 'playlist', + 'is_highlighted', + 'is_publish', + ) + + +class NewsBackOfficeBaseSerializer(NewsBaseSerializer): + """News back office base serializer.""" + + class Meta(NewsBaseSerializer.Meta): + """Meta class.""" + + fields = NewsBaseSerializer.Meta.fields + ( + 'title', + 'subtitle', + ) + + +class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, + NewsDetailSerializer): + """News detail serializer for back-office users.""" + + news_type_id = serializers.PrimaryKeyRelatedField( + source='news_type', write_only=True, + queryset=models.NewsType.objects.all()) + + country_id = serializers.PrimaryKeyRelatedField( + source='country', write_only=True, + queryset=location_models.Country.objects.all()) + + class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): + """Meta class.""" + + fields = NewsBackOfficeBaseSerializer.Meta.fields + \ + NewsDetailSerializer.Meta.fields + ( + 'description', + 'news_type_id', + 'country_id', + ) + diff --git a/apps/news/serializers/__init__.py b/apps/news/serializers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/news/serializers/common.py b/apps/news/serializers/common.py deleted file mode 100644 index b64a7fc9..00000000 --- a/apps/news/serializers/common.py +++ /dev/null @@ -1,76 +0,0 @@ -"""News app common serializers.""" -from rest_framework import serializers -from gallery import models as gallery_models -from location.models import Address -from location.serializers import AddressSerializer -from news import models - - -class NewsTypeSerializer(serializers.ModelSerializer): - """News type serializer.""" - class Meta: - model = models.NewsType - fields = [ - 'id', - 'name' - ] - - -class NewsSerializer(serializers.ModelSerializer): - """News serializer.""" - - address = AddressSerializer() - title_translated = serializers.CharField(read_only=True, allow_null=True) - subtitle_translated = serializers.CharField(read_only=True, allow_null=True) - description_translated = serializers.CharField(read_only=True, allow_null=True) - image_url = serializers.ImageField(source='image.image', allow_null=True) - - class Meta: - model = models.News - fields = [ - 'id', - 'news_type', - 'start', - 'end', - 'playlist', - 'address', - 'is_highlighted', - 'image_url', - # Localized fields - 'title_translated', - 'subtitle_translated', - 'description_translated', - ] - - -class NewsCreateUpdateSerializer(NewsSerializer): - """News update serializer.""" - title = serializers.JSONField() - subtitle = serializers.JSONField() - description = serializers.JSONField() - image = serializers.PrimaryKeyRelatedField( - queryset=gallery_models.Image.objects.all(), required=True,) - news_type = serializers.PrimaryKeyRelatedField( - queryset=models.NewsType.objects.all(), write_only=True) - address = serializers.PrimaryKeyRelatedField( - queryset=Address.objects.all(), write_only=True) - - class Meta: - model = models.News - read_only_fields = [ - 'id' - ] - fields = [ - 'id', - 'news_type', - 'title', - 'subtitle', - 'description', - 'start', - 'end', - 'playlist', - 'address', - 'image', - 'is_publish', - 'country' - ] diff --git a/apps/news/serializers/web.py b/apps/news/serializers/web.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py new file mode 100644 index 00000000..8522592e --- /dev/null +++ b/apps/news/urls/back.py @@ -0,0 +1,11 @@ +"""News app urlpatterns for backoffice""" +from django.urls import path +from news import views + +app_name = 'news' + +urlpatterns = [ + path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), + path('/', views.NewsBackOfficeRUDView.as_view(), + name='retrieve-update-destroy'), +] \ No newline at end of file diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 1dbcc0fe..9ec7e644 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -1,12 +1,11 @@ -"""Location app urlconf.""" +"""News app urlconf.""" from django.urls import path - -from news.views import common +from news import views app_name = 'news' urlpatterns = [ - path('', common.NewsListView.as_view(), name='list'), - path('/', common.NewsDetailView.as_view(), name='rud'), - path('type/', common.NewsTypeListView.as_view(), name='type'), + path('', views.NewsListView.as_view(), name='list'), + path('/', views.NewsDetailView.as_view(), name='rud'), + path('types/', views.NewsTypeListView.as_view(), name='type'), ] diff --git a/apps/news/views.py b/apps/news/views.py new file mode 100644 index 00000000..7d6da3f7 --- /dev/null +++ b/apps/news/views.py @@ -0,0 +1,68 @@ +"""News app views.""" +from rest_framework import generics, permissions +from news import filters, models, serializers + + +class NewsMixinView: + """News mixin.""" + + permission_classes = (permissions.AllowAny, ) + serializer_class = serializers.NewsBaseSerializer + + def get_queryset(self, *args, **kwargs): + """Override get_queryset method.""" + qs = models.News.objects.with_related().published()\ + .order_by('-is_highlighted', '-created') + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs + + +class NewsListView(NewsMixinView, generics.ListAPIView): + """News list view.""" + + filter_class = filters.NewsListFilterSet + + +class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): + """News detail view.""" + + serializer_class = serializers.NewsDetailSerializer + + +class NewsTypeListView(generics.ListAPIView): + """NewsType list view.""" + + pagination_class = None + permission_classes = (permissions.AllowAny, ) + queryset = models.NewsType.objects.all() + serializer_class = serializers.NewsTypeSerializer + + +class NewsBackOfficeMixinView: + """News back office mixin view.""" + + permission_classes = (permissions.IsAuthenticated,) + queryset = models.News.objects.with_related() \ + .order_by('-is_highlighted', '-created') + + +class NewsBackOfficeLCView(NewsBackOfficeMixinView, + generics.ListCreateAPIView): + """Resource for a list of news for back-office users.""" + + serializer_class = serializers.NewsBackOfficeBaseSerializer + create_serializers_class = serializers.NewsBackOfficeDetailSerializer + + def get_serializer_class(self): + if self.request.method == 'POST': + return self.create_serializers_class + return super().get_serializer_class() + + +class NewsBackOfficeRUDView(NewsBackOfficeMixinView, + generics.RetrieveUpdateDestroyAPIView): + """Resource for detailed information about news for back-office users.""" + + serializer_class = serializers.NewsBackOfficeDetailSerializer + diff --git a/apps/news/views/__init__.py b/apps/news/views/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/news/views/common.py b/apps/news/views/common.py deleted file mode 100644 index cf3c7e29..00000000 --- a/apps/news/views/common.py +++ /dev/null @@ -1,38 +0,0 @@ -"""News app common app.""" -from rest_framework import generics, permissions - -from news import filters, models -from news.serializers import common as serializers -from utils.views import JWTGenericViewMixin - - -class NewsMixin: - """News mixin.""" - - permission_classes = (permissions.AllowAny, ) - serializer_class = serializers.NewsSerializer - - def get_queryset(self, *args, **kwargs): - """Override get_queryset method""" - return models.News.objects.published() \ - .by_country_code(code=self.request.country_code) \ - .order_by('-is_highlighted', '-created') - - -class NewsListView(NewsMixin, generics.ListAPIView): - """News list view.""" - - filter_class = filters.NewsListFilterSet - - -class NewsDetailView(NewsMixin, JWTGenericViewMixin, generics.RetrieveAPIView): - """News detail view.""" - - -class NewsTypeListView(generics.ListAPIView): - """NewsType list view.""" - - serializer_class = serializers.NewsTypeSerializer - permission_classes = (permissions.AllowAny, ) - pagination_class = None - queryset = models.NewsType.objects.all() diff --git a/apps/news/views/web.py b/apps/news/views/web.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 33463ebe..1f4c6f96 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -1,6 +1,5 @@ """Utils app serializer.""" from rest_framework import serializers - from utils.models import PlatformMixin @@ -13,3 +12,12 @@ class SourceSerializerMixin(serializers.Serializer): source = serializers.ChoiceField(choices=PlatformMixin.SOURCES, default=PlatformMixin.WEB, write_only=True) + + +class TranslatedField(serializers.CharField): + """Translated field.""" + + def __init__(self, allow_null=True, required=False, read_only=True, + **kwargs): + super().__init__(allow_null=allow_null, required=required, + read_only=read_only, **kwargs) diff --git a/project/urls/back.py b/project/urls/back.py index 5a64b955..7b4146eb 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,4 +7,5 @@ urlpatterns = [ namespace='gallery')), path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), + path('news/', include('news.urls.back')) ] \ No newline at end of file From 009533d7644c509461368c216ff7e54767d8b9d4 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 16:06:40 +0300 Subject: [PATCH 038/319] Tags & disable elasticsearch indexing --- apps/main/serializers.py | 16 ++++++++-------- apps/news/models.py | 4 +++- apps/news/serializers.py | 8 ++++++-- apps/search_indexes/urls.py | 2 +- project/settings/development.py | 2 +- project/settings/local.py | 2 +- project/settings/stage.py | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index f2beb768..10d75f2b 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -1,9 +1,9 @@ """Main app serializers.""" from rest_framework import serializers - from advertisement.serializers.web import AdvertisementSerializer from location.serializers import CountrySerializer from main import models +from utils.serializers import TranslatedField class FeatureSerializer(serializers.ModelSerializer): @@ -109,16 +109,16 @@ class AwardSerializer(AwardBaseSerializer): class MetaDataContentSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(source='metadata.id', read_only=True, ) - label_translated = serializers.CharField( - source='metadata.label_translated', read_only=True, allow_null=True) + """MetaData content serializer.""" + + id = serializers.IntegerField(source='metadata.id', read_only=True) + label_translated = TranslatedField(source='metadata.label_translated') class Meta: + """Meta class.""" + model = models.MetaDataContent - fields = [ - 'id', - 'label_translated', - ] + fields = ('id', 'label_translated') class CurrencySerializer(serializers.ModelSerializer): diff --git a/apps/news/models.py b/apps/news/models.py index 75ae49c9..c132f6fc 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,5 +1,6 @@ """News app models.""" from django.db import models +from django.contrib.contenttypes import fields as generic from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse @@ -40,7 +41,7 @@ class NewsQuerySet(models.QuerySet): def with_related(self): """Return qs with related objects.""" - return self.select_related('news_type') + return self.select_related('news_type', 'country').prefetch_related('tags') class News(BaseAttributes, TranslatedFieldsMixin): @@ -78,6 +79,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): country = models.ForeignKey('location.Country', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('country')) + tags = generic.GenericRelation(to='main.MetaDataContent') objects = NewsQuerySet.as_manager() diff --git a/apps/news/serializers.py b/apps/news/serializers.py index a734a264..c0d424ad 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers from location import models as location_models from location.serializers import CountrySimpleSerializer +from main.serializers import MetaDataContentSerializer from news import models from utils.serializers import TranslatedField @@ -25,7 +26,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) - country = CountrySimpleSerializer(read_only=True) + tags = MetaDataContentSerializer(read_only=True, many=True) class Meta: """Meta class.""" @@ -39,7 +40,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'image_url', 'preview_image_url', 'news_type', - 'country', + 'tags', ) @@ -47,6 +48,7 @@ class NewsDetailSerializer(NewsBaseSerializer): """News detail serializer.""" description_translated = TranslatedField() + country = CountrySimpleSerializer(read_only=True) class Meta(NewsBaseSerializer.Meta): """Meta class.""" @@ -58,6 +60,8 @@ class NewsDetailSerializer(NewsBaseSerializer): 'playlist', 'is_highlighted', 'is_publish', + 'author', + 'country', ) diff --git a/apps/search_indexes/urls.py b/apps/search_indexes/urls.py index 60b05fb5..664e0b99 100644 --- a/apps/search_indexes/urls.py +++ b/apps/search_indexes/urls.py @@ -4,7 +4,7 @@ from search_indexes import views router = routers.SimpleRouter() -router.register(r'news', views.NewsDocumentViewSet, basename='news') +# router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') urlpatterns = router.urls diff --git a/project/settings/development.py b/project/settings/development.py index a7c8d9fd..43a60935 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -24,7 +24,7 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { - 'search_indexes.documents.news': 'development_news', + # 'search_indexes.documents.news': 'development_news', # temporarily disabled 'search_indexes.documents.establishment': 'development_establishment', } diff --git a/project/settings/local.py b/project/settings/local.py index febe4dd4..503ad191 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -64,6 +64,6 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { - 'search_indexes.documents.news': 'local_news', + # 'search_indexes.documents.news': 'local_news', 'search_indexes.documents.establishment': 'local_establishment', } \ No newline at end of file diff --git a/project/settings/stage.py b/project/settings/stage.py index 59fc78ed..c0d6fdb1 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -22,6 +22,6 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { - 'search_indexes.documents.news': 'stage_news', + # 'search_indexes.documents.news': 'stage_news', #temporarily disabled 'search_indexes.documents.establishment': 'stage_establishment', } From d424704cec2079f782b3374636ee5d5bfc26e99a Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 16:13:48 +0300 Subject: [PATCH 039/319] fix migration --- apps/news/migrations/0011_auto_20190923_1134.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/news/migrations/0011_auto_20190923_1134.py b/apps/news/migrations/0011_auto_20190923_1134.py index 859cb636..6e457b80 100644 --- a/apps/news/migrations/0011_auto_20190923_1134.py +++ b/apps/news/migrations/0011_auto_20190923_1134.py @@ -9,6 +9,7 @@ def copy_image_links(apps, schemaeditor): for news in News.objects.all(): if news.image: news.image_url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{news.image.image.url}' + news.save() class Migration(migrations.Migration): From 6e4d2f738fa29a74503fdbe6bee47ab04ccb3c5e 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, 23 Sep 2019 16:18:36 +0300 Subject: [PATCH 040/319] Test for /api/web/account/user/ --- apps/account/tests/tests_common.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 2e66b330..c92fe89e 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -5,7 +5,7 @@ from http.cookies import SimpleCookie # Create your tests here. -class AccountTests(APITestCase): +class AccountUserTests(APITestCase): url = '/api/web/account/user/' @@ -23,3 +23,23 @@ class AccountTests(APITestCase): response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) + data = { + "username": self.data["username"], + "first_name": "Test first name", + "last_name": "Test last name", + "cropped_image_url": "http://localhost/image/cropped.png", + "image_url": "http://localhost/image/avatar.png", + "email": "sedragurdatest@desoz.com", + "newsletter": self.data["newsletter"] + } + response = self.client.patch(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data["email"] = "sedragurdatest2@desoz.com" + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + + From b75ac46c882dc86bb4160ebe9f60c68335c6b177 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 23 Sep 2019 18:38:39 +0500 Subject: [PATCH 041/319] Add favorites tests --- apps/favorites/tests.py | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index a39b155a..bc3313ae 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -1 +1,55 @@ # Create your tests here. +from http.cookies import SimpleCookie +from django.contrib.contenttypes.models import ContentType + +from rest_framework.test import APITestCase +from rest_framework import status + +from account.models import User +from favorites.models import Favorites +from establishment.models import Establishment, EstablishmentType, EstablishmentSubType +from news.models import NewsType, News + + +class BaseTestCase(APITestCase): + + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + + self.test_news_type = NewsType.objects.create(name="Test news type") + self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, + news_type=self.test_news_type, + description={"en-GB": "Description test news"}, + playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", + is_publish=True) + + self.test_content_type = ContentType.objects.get(app_label="news", model="news") + + self.test_favorites = Favorites.objects.create(user=self.user, content_type=self.test_content_type, + object_id=self.test_news.id) + + self.test_establishment_type = EstablishmentType.objects.create(name={"en-GB": "test establishment type"}, + use_subtypes=False) + + self.test_establishment = Establishment.objects.create(name="test establishment", + name_transliterated="test-establishment", + description={"en-GB": "description of test establishment"}, + establishment_type=self.test_establishment_type, + is_publish=True) + # value for GenericRelation(reverse side) field must be iterable + # value for GenericRelation(reverse side) field must be assigned through "set" method of field + self.test_establishment.favorites.set([self.test_favorites]) + + +class FavoritesTestCase(BaseTestCase): + + def test_func(self): + response = self.client.get("/api/web/favorites/establishments/") + print(response.json()) + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file From 77b9d3f4a7b5a90fe730acf5e9c7feb6bdeadc2c 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, 23 Sep 2019 16:38:57 +0300 Subject: [PATCH 042/319] Test account --- apps/account/tests/tests_common.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index c92fe89e..6a9a1fdd 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -41,5 +41,19 @@ class AccountUserTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) +class AccountChangePasswordTests(APITestCase): + url = '/api/web/account/change-password/' + + def setUp(self): + self.data = get_tokens_for_user() + + def test_change_password(self): + data = { + "password": "new password" + } + + response = self.client.patch(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + From 83fddd63c3f9df7680d92b5b1df40ff07f1674d7 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 16:41:20 +0300 Subject: [PATCH 043/319] fix migration establishment --- .../migrations/0030_auto_20190923_1340.py | 18 ++++++++++++++++++ apps/establishment/serializers/common.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/establishment/migrations/0030_auto_20190923_1340.py diff --git a/apps/establishment/migrations/0030_auto_20190923_1340.py b/apps/establishment/migrations/0030_auto_20190923_1340.py new file mode 100644 index 00000000..9599b200 --- /dev/null +++ b/apps/establishment/migrations/0030_auto_20190923_1340.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-23 13:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0029_establishment_name_transliterated'), + ] + + operations = [ + migrations.RenameField( + model_name='establishment', + old_name='name_transliterated', + new_name='name_translated', + ), + ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 5eb1b9ae..2edf811c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -154,7 +154,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', - 'name_transliterated', + 'name_translated', 'price_level', 'toque_number', 'public_mark', From 8f60937e4ba68c2fd17683918856410a67ff5ce2 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 16:44:28 +0300 Subject: [PATCH 044/319] Fix migration collection --- .../migrations/0012_auto_20190923_1340.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 apps/collection/migrations/0012_auto_20190923_1340.py diff --git a/apps/collection/migrations/0012_auto_20190923_1340.py b/apps/collection/migrations/0012_auto_20190923_1340.py new file mode 100644 index 00000000..50725597 --- /dev/null +++ b/apps/collection/migrations/0012_auto_20190923_1340.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-09-23 13:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0011_auto_20190920_1059'), + ] + + operations = [ + migrations.AlterField( + model_name='guide', + name='parent', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='collection.Guide', verbose_name='parent'), + ), + ] From fd265a397bba03f050d3fc1cb19b5e111575c616 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 16:53:33 +0300 Subject: [PATCH 045/319] Update NewsBaseSerializer --- apps/news/serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c0d424ad..67d81501 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -36,7 +36,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'id', 'title_translated', 'subtitle_translated', - 'image_url', + 'is_highlighted', 'image_url', 'preview_image_url', 'news_type', @@ -58,7 +58,6 @@ class NewsDetailSerializer(NewsBaseSerializer): 'start', 'end', 'playlist', - 'is_highlighted', 'is_publish', 'author', 'country', From b4320ab4a6d4d72e22dcb7279a1d34adcd58baf9 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 23 Sep 2019 17:17:42 +0300 Subject: [PATCH 046/319] End of news may be None --- .../news/migrations/0012_auto_20190923_1416.py | 18 ++++++++++++++++++ apps/news/models.py | 7 +++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 apps/news/migrations/0012_auto_20190923_1416.py diff --git a/apps/news/migrations/0012_auto_20190923_1416.py b/apps/news/migrations/0012_auto_20190923_1416.py new file mode 100644 index 00000000..2a59e2b0 --- /dev/null +++ b/apps/news/migrations/0012_auto_20190923_1416.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-23 14:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0011_auto_20190923_1134'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='end', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='End'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index c132f6fc..bf61f51a 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -37,7 +37,9 @@ class NewsQuerySet(models.QuerySet): def published(self): """Return only published news""" now = timezone.now() - return self.filter(is_publish=True, start__lte=now, end__gte=now) + return self.filter(models.Q(models.Q(end__gte=now) | + models.Q(end__isnull=True)), + is_publish=True, start__lte=now) def with_related(self): """Return qs with related objects.""" @@ -59,7 +61,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('description'), help_text='{"en-GB":"some text"}') start = models.DateTimeField(verbose_name=_('Start')) - end = models.DateTimeField(verbose_name=_('End')) + end = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('End')) playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) From 70f9100813b02958b78e46688b5b6d6ea1390027 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, 23 Sep 2019 17:31:51 +0300 Subject: [PATCH 047/319] Add test for change-password --- apps/account/tests/tests_common.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 6a9a1fdd..fed80882 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -49,11 +49,19 @@ class AccountChangePasswordTests(APITestCase): def test_change_password(self): data = { + "old_password" : self.data["password"], "password": "new password" } response = self.client.patch(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.client.cookies = SimpleCookie( + {'access_token': self.data['tokens'].get('access_token'), + 'refresh_token': self.data['tokens'].get('access_token')}) + + response = self.client.patch(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + From 22c237c3d74d4020b7250db97ac90d3780cbcad0 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, 23 Sep 2019 18:01:06 +0300 Subject: [PATCH 048/319] add test confirm email --- apps/account/tests/tests_common.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index fed80882..bd8c13d5 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -2,6 +2,8 @@ from rest_framework.test import APITestCase from rest_framework import status from authorization.tests.tests import get_tokens_for_user from http.cookies import SimpleCookie +from account.models import User +from os import path # Create your tests here. @@ -49,7 +51,7 @@ class AccountChangePasswordTests(APITestCase): def test_change_password(self): data = { - "old_password" : self.data["password"], + "old_password": self.data["password"], "password": "new password" } @@ -64,4 +66,15 @@ class AccountChangePasswordTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) +class AccountChangePasswordTests(APITestCase): + # "/web/account/email/confirm/{uidb64}/{token}/" + url = "/web/account/email/confirm/" + def setUp(self): + self.data = get_tokens_for_user() + + def test_confirm_email(self): + user = User.objects.get(email=self.data["email"]) + token = user.confirm_email_token + uid64 = user.get_user_uidb64 + url = path.join(self.url, uid64, token, "") From 80701f27a92665b72fdd4d99044026239b427330 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Mon, 23 Sep 2019 18:02:25 +0300 Subject: [PATCH 049/319] Add translation tests to model and API --- apps/utils/tests.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 apps/utils/tests.py diff --git a/apps/utils/tests.py b/apps/utils/tests.py new file mode 100644 index 00000000..1c9fa71d --- /dev/null +++ b/apps/utils/tests.py @@ -0,0 +1,64 @@ +import pytz +from datetime import datetime + +from rest_framework.test import APITestCase +from rest_framework import status +from http.cookies import SimpleCookie + +from account.models import User +from news.models import News, NewsType + + +class BaseTestCase(APITestCase): + + def setUp(self): + + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.newsletter = True + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) + + # get tokkens + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token'), + 'locale': "en" + }) + + self.news_type = NewsType.objects.create(name="Test news type") + + self.news_item = News.objects.create( + created_by=self.user, + modified_by=self.user, + title={ + "en-GB": "Test news item", + "ru-RU": "Тестовая новость" + }, + description={"en-GB": "Test description"}, + playlist=1, + start=datetime.now(pytz.utc), + end=datetime.now(pytz.utc), + is_publish=True, + news_type=self.news_type + ) + + +class TranslateFieldModel(BaseTestCase): + + def test_model_field(self): + self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) + + +class TranslateFieldReview(BaseTestCase): + + def test_read_locale(self): + response = self.client.get(f"/api/web/news/{self.news_item.id}/", format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + news_data = response.json() + + self.assertIn("title_translated", news_data) + + self.assertEqual(news_data['title_translated'], "Test news item") From 7f630e9e9449468ebb13e92c39e5c920ada0c402 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: Tue, 24 Sep 2019 09:23:57 +0300 Subject: [PATCH 050/319] Test web email confirm --- apps/account/tests/tests_common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index bd8c13d5..963ba08d 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -68,7 +68,7 @@ class AccountChangePasswordTests(APITestCase): class AccountChangePasswordTests(APITestCase): # "/web/account/email/confirm/{uidb64}/{token}/" - url = "/web/account/email/confirm/" + url = "/api/web/account/email/confirm/" def setUp(self): self.data = get_tokens_for_user() @@ -78,3 +78,5 @@ class AccountChangePasswordTests(APITestCase): token = user.confirm_email_token uid64 = user.get_user_uidb64 url = path.join(self.url, uid64, token, "") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file From 199ca1fa77935a8d4d059975b5a2a12e96588e9f 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: Tue, 24 Sep 2019 10:25:44 +0300 Subject: [PATCH 051/319] Refactor test code --- apps/account/tests/tests_common.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 963ba08d..5e3935b5 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -3,13 +3,12 @@ from rest_framework import status from authorization.tests.tests import get_tokens_for_user from http.cookies import SimpleCookie from account.models import User -from os import path -# Create your tests here. +from django.urls import reverse class AccountUserTests(APITestCase): - url = '/api/web/account/user/' + url = reverse('web:account:user-retrieve-update') def setUp(self): self.data = get_tokens_for_user() @@ -44,7 +43,8 @@ class AccountUserTests(APITestCase): class AccountChangePasswordTests(APITestCase): - url = '/api/web/account/change-password/' + + url = reverse('web:account:change-password') def setUp(self): self.data = get_tokens_for_user() @@ -67,16 +67,16 @@ class AccountChangePasswordTests(APITestCase): class AccountChangePasswordTests(APITestCase): - # "/web/account/email/confirm/{uidb64}/{token}/" - url = "/api/web/account/email/confirm/" - def setUp(self): self.data = get_tokens_for_user() def test_confirm_email(self): user = User.objects.get(email=self.data["email"]) token = user.confirm_email_token - uid64 = user.get_user_uidb64 - url = path.join(self.url, uid64, token, "") + uidb64 = user.get_user_uidb64 + url = reverse('web:account:confirm-email', kwargs={ + 'uidb64': uidb64, + 'token': token + }) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file From 1ce71fdcdcd2821d8f48fa4de995234e46b3c412 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 23 Sep 2019 16:37:33 +0300 Subject: [PATCH 052/319] slugs for news & establishments --- .../migrations/0012_collection_slug.py | 34 +++++++++++++++++++ apps/collection/models.py | 4 +++ .../migrations/0029_establishment_slug.py | 33 ++++++++++++++++++ apps/establishment/models.py | 11 +++--- apps/establishment/serializers/back.py | 2 ++ apps/establishment/serializers/common.py | 9 +++-- apps/establishment/urls/common.py | 12 +++---- apps/establishment/views/web.py | 15 +++++--- apps/news/migrations/0010_news_slug.py | 33 ++++++++++++++++++ apps/news/models.py | 11 ++++-- apps/news/serializers/common.py | 0 apps/news/urls/web.py | 2 +- apps/news/views/common.py | 0 apps/search_indexes/views.py | 4 +-- 14 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 apps/collection/migrations/0012_collection_slug.py create mode 100644 apps/establishment/migrations/0029_establishment_slug.py create mode 100644 apps/news/migrations/0010_news_slug.py create mode 100644 apps/news/serializers/common.py create mode 100644 apps/news/views/common.py diff --git a/apps/collection/migrations/0012_collection_slug.py b/apps/collection/migrations/0012_collection_slug.py new file mode 100644 index 00000000..7fb683c2 --- /dev/null +++ b/apps/collection/migrations/0012_collection_slug.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.4 on 2019-09-23 12:55 + +from django.db import migrations +import django_extensions.db.fields + +from collection.models import Collection + + +def migrate_data_forward(apps, schema_editor): + for instance in Collection.objects.all(): + print("Generating slug for %s" % instance) + instance.save() # Will trigger slug update + +def migrate_data_backward(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0011_auto_20190920_1059'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='slug', + field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['name', 'pk'], db_index=True, verbose_name='Collection slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 928370d7..5406abf5 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -1,5 +1,7 @@ from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import ContentType +from django_extensions.db.fields import AutoSlugField + from utils.models import TJSONField from django.db import models from django.utils.translation import gettext_lazy as _ @@ -67,6 +69,8 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, description = TJSONField( _('description'), null=True, blank=True, default=None, help_text='{"en-GB":"some text"}') + slug = AutoSlugField(max_length=50, db_index=True, populate_from=['name', 'pk'], + verbose_name=_('Collection slug'), editable=True) objects = CollectionQuerySet.as_manager() diff --git a/apps/establishment/migrations/0029_establishment_slug.py b/apps/establishment/migrations/0029_establishment_slug.py new file mode 100644 index 00000000..6cc01122 --- /dev/null +++ b/apps/establishment/migrations/0029_establishment_slug.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.4 on 2019-09-23 12:55 + +from django.db import migrations +import django_extensions.db.fields + +from establishment.models import Establishment + + +def migrate_data_forward(apps, schema_editor): + for instance in Establishment.objects.all(): + print("Generating slug for %s" % instance) + instance.save() # Will trigger slug update + +def migrate_data_backward(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0028_auto_20190920_1205'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='slug', + field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['name'], db_index=True, verbose_name='Establishment slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5b188c3c..729aec00 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from django_extensions.db.fields import AutoSlugField from phonenumber_field.modelfields import PhoneNumberField from location.models import Address @@ -166,15 +167,15 @@ class EstablishmentQuerySet(models.QuerySet): total_mark=(models.F('distance_mark') + models.F('additional_mark')) * models.F('intermediate_public_mark')) - def similar(self, establishment_pk: int): + def similar(self, establishment_slug: str): """ Return QuerySet with objects that similar to Establishment. - :param establishment_pk: integer + :param establishment_slug: str Establishment slug """ - establishment_qs = Establishment.objects.filter(pk=establishment_pk) + establishment_qs = Establishment.objects.filter(slug=establishment_slug) if establishment_qs.exists(): establishment = establishment_qs.first() - return self.exclude(pk=establishment_pk) \ + return self.exclude(slug=establishment_slug) \ .filter(is_publish=True, image_url__isnull=False, reviews__isnull=False, @@ -277,6 +278,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Collections')) preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) + slug = AutoSlugField(db_index=True, max_length=50, populate_from=['name'], + verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 7199eb54..41692b45 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -20,6 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) + slug = serializers.SlugField(allow_blank=True) class Meta: model = models.Establishment @@ -35,6 +36,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): 'type', 'socials', 'image_url', + 'slug', # TODO: check in admin filters 'is_publish' ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 2edf811c..15b183f8 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -146,6 +146,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): subtypes = EstablishmentSubTypeSerializer(many=True) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) + slug = serializers.SlugField(allow_blank=True) class Meta: """Meta class.""" @@ -162,6 +163,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): 'subtypes', 'address', 'tags', + 'slug', ] @@ -197,6 +199,8 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) + slug = serializers.SlugField(read_only=True) + in_favorites = serializers.SerializerMethodField() image = serializers.URLField(source='image_url') @@ -224,6 +228,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 'best_price_menu', 'best_price_carte', 'transportation', + 'slug', ] def get_review(self, obj): @@ -261,8 +266,8 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer def validate(self, attrs): """Override validate method""" # Check establishment object - establishment_id = self.context.get('request').parser_context.get('kwargs').get('pk') - establishment_qs = models.Establishment.objects.filter(id=establishment_id) + establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug') + establishment_qs = models.Establishment.objects.filter(slug=establishment_slug) if not establishment_qs.exists(): raise serializers.ValidationError({'detail': _('Establishment not found.')}) attrs['establishment'] = establishment_qs.first() diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 1d1379ee..bd53c7eb 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -8,13 +8,13 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), - path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), - path('/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), - path('/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), - path('/comments/create/', views.EstablishmentCommentCreateView.as_view(), + path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), + path('/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), + path('/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), + path('/comments/create/', views.EstablishmentCommentCreateView.as_view(), name='create-comment'), - path('/comments//', views.EstablishmentCommentRUDView.as_view(), + path('/comments//', views.EstablishmentCommentRUDView.as_view(), name='rud-comment'), - path('/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), + path('/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), name='add-to-favorites') ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 441f89c4..ce2bed05 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -30,11 +30,12 @@ class EstablishmentSimilarListView(EstablishmentListView): def get_queryset(self): """Override get_queryset method""" - return super().get_queryset().similar(establishment_pk=self.kwargs.get('pk'))\ + return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug'))\ .order_by('-total_mark')[:13] class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): """Resource for getting a establishment.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentDetailSerializer @@ -48,6 +49,7 @@ class EstablishmentTypeListView(generics.ListAPIView): class EstablishmentCommentCreateView(generics.CreateAPIView): """View for create new comment.""" + lookup_field = 'slug' serializer_class = serializers.EstablishmentCommentCreateSerializer queryset = comment_models.Comment.objects.all() @@ -59,9 +61,11 @@ class EstablishmentCommentListView(generics.ListAPIView): def get_queryset(self): """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=self.kwargs.get('pk'))\ + .by_object_id(object_id=establishment.pk)\ .order_by('-created') @@ -77,7 +81,7 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = self.filter_queryset(self.get_queryset()) establishment_obj = get_object_or_404(queryset, - pk=self.kwargs['pk']) + slug=self.kwargs['slug']) comment_obj = get_object_or_404(establishment_obj.comments.by_user(self.request.user), pk=self.kwargs['comment_id']) @@ -90,15 +94,18 @@ class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPIView): """View for create/destroy establishment from favorites.""" serializer_class = serializers.EstablishmentFavoritesCreateSerializer + lookup_field = 'slug' def get_object(self): """ Returns the object the view is displaying. """ + establishment_obj = get_object_or_404(models.Establishment, + slug=self.kwargs['slug']) obj = get_object_or_404( self.request.user.favorites.by_content_type(app_label='establishment', model='establishment') - .by_object_id(object_id=self.kwargs['pk'])) + .by_object_id(object_id=establishment_obj.pk)) # May raise a permission denied self.check_object_permissions(self.request, obj) return obj diff --git a/apps/news/migrations/0010_news_slug.py b/apps/news/migrations/0010_news_slug.py new file mode 100644 index 00000000..367357d3 --- /dev/null +++ b/apps/news/migrations/0010_news_slug.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.4 on 2019-09-23 12:55 + +from django.db import migrations +import django_extensions.db.fields + +from news.models import News + +def migrate_data_forward(apps, schema_editor): + for instance in News.objects.all(): + print("Generating slug for %s" % instance) + instance.save() # Will trigger slug update + +def migrate_data_backward(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0009_auto_20190901_1032'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='slug', + field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['title'], db_index=True, verbose_name='News slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index bf61f51a..83e44e6c 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -3,6 +3,7 @@ from django.db import models from django.contrib.contenttypes import fields as generic from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from django_extensions.db.fields import AutoSlugField from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin @@ -49,6 +50,10 @@ class NewsQuerySet(models.QuerySet): class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" + image = models.ForeignKey( + 'gallery.Image', null=True, blank=True, default=None, + verbose_name=_('News image'), on_delete=models.CASCADE) + news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, @@ -63,6 +68,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): start = models.DateTimeField(verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) + slug = AutoSlugField(db_index=True, max_length=50, populate_from=['title'], + verbose_name=_('News slug'), editable=True,) playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) @@ -93,8 +100,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name_plural = _('news') def __str__(self): - return f'news: {self.id}' + return f'news: {self.slug}' @property def web_url(self): - return reverse('web:news:rud', kwargs={'pk': self.pk}) + return reverse('web:news:rud', kwargs={'slug': self.slug}) diff --git a/apps/news/serializers/common.py b/apps/news/serializers/common.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 9ec7e644..c483b05a 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -6,6 +6,6 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), - path('/', views.NewsDetailView.as_view(), name='rud'), + path('/', views.NewsDetailView.as_view(), name='rud'), path('types/', views.NewsTypeListView.as_view(), name='type'), ] diff --git a/apps/news/views/common.py b/apps/news/views/common.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 1547fd9d..72cab583 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -13,7 +13,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): """News document ViewSet.""" document = NewsDocument - lookup_field = 'id' + lookup_field = 'slug' pagination_class = PageNumberPagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.NewsDocumentSerializer @@ -39,7 +39,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): """Establishment document ViewSet.""" document = EstablishmentDocument - lookup_field = 'id' + lookup_field = 'slug' pagination_class = PageNumberPagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentDocumentSerializer From 6420a676df4ef4c92eb6fda1c0629da68424eb44 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 23 Sep 2019 18:02:42 +0300 Subject: [PATCH 053/319] create slugs manually --- apps/collection/models.py | 5 ++--- apps/collection/urls/common.py | 2 +- apps/collection/views/common.py | 3 ++- apps/establishment/models.py | 3 +-- apps/establishment/serializers/back.py | 2 +- apps/establishment/serializers/common.py | 4 ++-- apps/news/models.py | 3 +-- 7 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 5406abf5..beec9b08 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -1,6 +1,5 @@ from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import ContentType -from django_extensions.db.fields import AutoSlugField from utils.models import TJSONField from django.db import models @@ -69,8 +68,8 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, description = TJSONField( _('description'), null=True, blank=True, default=None, help_text='{"en-GB":"some text"}') - slug = AutoSlugField(max_length=50, db_index=True, populate_from=['name', 'pk'], - verbose_name=_('Collection slug'), editable=True) + slug = models.SlugField(max_length=50, unique=True, + verbose_name=_('Collection slug'), editable=True, null=True) objects = CollectionQuerySet.as_manager() diff --git a/apps/collection/urls/common.py b/apps/collection/urls/common.py index 41414a10..01385c3d 100644 --- a/apps/collection/urls/common.py +++ b/apps/collection/urls/common.py @@ -7,7 +7,7 @@ app_name = 'collection' urlpatterns = [ path('', views.CollectionListView.as_view(), name='list'), - path('/establishments/', views.CollectionEstablishmentListView.as_view(), + path('/establishments/', views.CollectionEstablishmentListView.as_view(), name='detail'), path('guides/', views.GuideListView.as_view(), name='guides-list'), diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 8d2eb109..d42bd851 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -40,6 +40,7 @@ class CollectionEstablishmentListView(CollectionListView): permission_classes = (permissions.AllowAny,) pagination_class = ProjectPageNumberPagination serializer_class = EstablishmentListSerializer + lookup_field = 'slug' def get_queryset(self): """ @@ -47,7 +48,7 @@ class CollectionEstablishmentListView(CollectionListView): """ queryset = super(CollectionEstablishmentListView, self).get_queryset() # Perform the lookup filtering. - collection = get_object_or_404(queryset, pk=self.kwargs['pk']) + collection = get_object_or_404(queryset, slug=self.kwargs['slug']) # May raise a permission denied self.check_object_permissions(self.request, collection) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 729aec00..e3384dfb 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -9,7 +9,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django_extensions.db.fields import AutoSlugField from phonenumber_field.modelfields import PhoneNumberField from location.models import Address @@ -278,7 +277,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Collections')) preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) - slug = AutoSlugField(db_index=True, max_length=50, populate_from=['name'], + slug = models.SlugField(unique=True, max_length=50, null=True, verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 41692b45..b4d8e747 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -20,7 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) - slug = serializers.SlugField(allow_blank=True) + slug = serializers.SlugField(required=True, allow_blank=False) class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 15b183f8..e1cf312c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -146,7 +146,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): subtypes = EstablishmentSubTypeSerializer(many=True) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) - slug = serializers.SlugField(allow_blank=True) + slug = serializers.SlugField(allow_blank=False, required=True) class Meta: """Meta class.""" @@ -199,7 +199,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) - slug = serializers.SlugField(read_only=True) + slug = serializers.SlugField(required=True, allow_blank=False,) in_favorites = serializers.SerializerMethodField() diff --git a/apps/news/models.py b/apps/news/models.py index 83e44e6c..5e7fbb9d 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -3,7 +3,6 @@ from django.db import models from django.contrib.contenttypes import fields as generic from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django_extensions.db.fields import AutoSlugField from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin @@ -68,7 +67,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): start = models.DateTimeField(verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) - slug = AutoSlugField(db_index=True, max_length=50, populate_from=['title'], + slug = models.SlugField(unique=True, max_length=50, null=True, verbose_name=_('News slug'), editable=True,) playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, From 73a858cc8c01b89fc66925c46c57c253e12a29ae Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 23 Sep 2019 18:21:59 +0300 Subject: [PATCH 054/319] Slugs feature migrations --- .../migrations/0013_auto_20190923_1505.py | 38 ++++++++++++++++++ .../migrations/0030_auto_20190923_1505.py | 39 +++++++++++++++++++ .../migrations/0011_auto_20190923_1505.py | 38 ++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 apps/collection/migrations/0013_auto_20190923_1505.py create mode 100644 apps/establishment/migrations/0030_auto_20190923_1505.py create mode 100644 apps/news/migrations/0011_auto_20190923_1505.py diff --git a/apps/collection/migrations/0013_auto_20190923_1505.py b/apps/collection/migrations/0013_auto_20190923_1505.py new file mode 100644 index 00000000..6a44ee57 --- /dev/null +++ b/apps/collection/migrations/0013_auto_20190923_1505.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.4 on 2019-09-23 15:05 +from django.core.validators import EMPTY_VALUES +from django.db import migrations, models + +from collection.models import Collection + + +def migrate_data_forward(apps, schema_editor): + for instance in Collection.objects.all(): + if instance.slug in EMPTY_VALUES: + instance.slug = None + instance.save(update_fields=['slug']) + +def migrate_data_backward(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0012_collection_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='slug', + field=models.SlugField(null=True, verbose_name='Collection slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + migrations.AlterField( + model_name='collection', + name='slug', + field=models.SlugField(null=True, unique=True, verbose_name='Collection slug'), + ), + ] diff --git a/apps/establishment/migrations/0030_auto_20190923_1505.py b/apps/establishment/migrations/0030_auto_20190923_1505.py new file mode 100644 index 00000000..9614eca1 --- /dev/null +++ b/apps/establishment/migrations/0030_auto_20190923_1505.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.4 on 2019-09-23 15:05 +from django.core.validators import EMPTY_VALUES +from django.db import migrations, models + +from establishment.models import Establishment + + +def migrate_data_forward(apps, schema_editor): + for instance in Establishment.objects.all(): + if instance.slug in EMPTY_VALUES: + instance.slug = None + instance.save(update_fields=['slug']) + +def migrate_data_backward(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0029_establishment_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='slug', + field=models.SlugField(null=True, verbose_name='Establishment slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + migrations.AlterField( + model_name='establishment', + name='slug', + field=models.SlugField(unique=True, null=True, verbose_name='Establishment slug'), + ), + ] diff --git a/apps/news/migrations/0011_auto_20190923_1505.py b/apps/news/migrations/0011_auto_20190923_1505.py new file mode 100644 index 00000000..b56a2d12 --- /dev/null +++ b/apps/news/migrations/0011_auto_20190923_1505.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.4 on 2019-09-23 15:05 +from django.core.validators import EMPTY_VALUES +from django.db import migrations, models + +from news.models import News + + +def migrate_data_forward(apps, schema_editor): + for instance in News.objects.all(): + if instance.slug in EMPTY_VALUES: + instance.slug = None + instance.save(update_fields=['slug']) + +def migrate_data_backward(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0010_news_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='slug', + field=models.SlugField(null=True, verbose_name='News slug'), + ), + migrations.RunPython( + migrate_data_forward, + migrate_data_backward, + ), + migrations.AlterField( + model_name='news', + name='slug', + field=models.SlugField(null=True, unique=True, verbose_name='News slug'), + ), + ] From 2cc0d01d79021a006c3ff6763215f60ee0a89053 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 23 Sep 2019 18:49:27 +0300 Subject: [PATCH 055/319] Fix issues after rebase --- apps/news/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/views.py b/apps/news/views.py index 7d6da3f7..5f531305 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -26,7 +26,7 @@ class NewsListView(NewsMixinView, generics.ListAPIView): class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """News detail view.""" - + lookup_field = 'slug' serializer_class = serializers.NewsDetailSerializer From b091da3f5647daee601ffa88ef5dbcce1ff80315 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: Tue, 24 Sep 2019 11:07:07 +0300 Subject: [PATCH 056/319] Fix --- apps/account/tests/tests_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 5e3935b5..0992b930 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -79,4 +79,4 @@ class AccountChangePasswordTests(APITestCase): 'token': token }) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file + self.assertEqual(response.status_code, status.HTTP_200_OK) From d224c77a01129b656987aa750c762403db54fa6d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 12:06:11 +0300 Subject: [PATCH 057/319] Fix issues with test && recreate migrations --- .../migrations/0012_collection_slug.py | 34 ---------------- .../migrations/0013_auto_20190923_1505.py | 38 ------------------ .../migrations/0013_collection_slug.py | 18 +++++++++ apps/collection/tests.py | 13 ++++--- .../migrations/0029_establishment_slug.py | 33 ---------------- .../migrations/0030_auto_20190923_1505.py | 39 ------------------- .../migrations/0031_establishment_slug.py | 18 +++++++++ apps/establishment/tests.py | 6 ++- apps/favorites/tests.py | 1 - apps/news/migrations/0010_news_slug.py | 33 ---------------- .../migrations/0011_auto_20190923_1505.py | 38 ------------------ .../migrations/0013_auto_20190924_0806.py | 25 ++++++++++++ apps/news/tests.py | 11 +++--- apps/news/urls/back.py | 1 + apps/news/views.py | 1 - 15 files changed, 79 insertions(+), 230 deletions(-) delete mode 100644 apps/collection/migrations/0012_collection_slug.py delete mode 100644 apps/collection/migrations/0013_auto_20190923_1505.py create mode 100644 apps/collection/migrations/0013_collection_slug.py delete mode 100644 apps/establishment/migrations/0029_establishment_slug.py delete mode 100644 apps/establishment/migrations/0030_auto_20190923_1505.py create mode 100644 apps/establishment/migrations/0031_establishment_slug.py delete mode 100644 apps/news/migrations/0010_news_slug.py delete mode 100644 apps/news/migrations/0011_auto_20190923_1505.py create mode 100644 apps/news/migrations/0013_auto_20190924_0806.py diff --git a/apps/collection/migrations/0012_collection_slug.py b/apps/collection/migrations/0012_collection_slug.py deleted file mode 100644 index 7fb683c2..00000000 --- a/apps/collection/migrations/0012_collection_slug.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 12:55 - -from django.db import migrations -import django_extensions.db.fields - -from collection.models import Collection - - -def migrate_data_forward(apps, schema_editor): - for instance in Collection.objects.all(): - print("Generating slug for %s" % instance) - instance.save() # Will trigger slug update - -def migrate_data_backward(apps, schema_editor): - pass - - -class Migration(migrations.Migration): - - dependencies = [ - ('collection', '0011_auto_20190920_1059'), - ] - - operations = [ - migrations.AddField( - model_name='collection', - name='slug', - field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['name', 'pk'], db_index=True, verbose_name='Collection slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - ] diff --git a/apps/collection/migrations/0013_auto_20190923_1505.py b/apps/collection/migrations/0013_auto_20190923_1505.py deleted file mode 100644 index 6a44ee57..00000000 --- a/apps/collection/migrations/0013_auto_20190923_1505.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 15:05 -from django.core.validators import EMPTY_VALUES -from django.db import migrations, models - -from collection.models import Collection - - -def migrate_data_forward(apps, schema_editor): - for instance in Collection.objects.all(): - if instance.slug in EMPTY_VALUES: - instance.slug = None - instance.save(update_fields=['slug']) - -def migrate_data_backward(apps, schema_editor): - pass - -class Migration(migrations.Migration): - - dependencies = [ - ('collection', '0012_collection_slug'), - ] - - operations = [ - migrations.AlterField( - model_name='collection', - name='slug', - field=models.SlugField(null=True, verbose_name='Collection slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - migrations.AlterField( - model_name='collection', - name='slug', - field=models.SlugField(null=True, unique=True, verbose_name='Collection slug'), - ), - ] diff --git a/apps/collection/migrations/0013_collection_slug.py b/apps/collection/migrations/0013_collection_slug.py new file mode 100644 index 00000000..cf7f5929 --- /dev/null +++ b/apps/collection/migrations/0013_collection_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-24 08:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0012_auto_20190923_1340'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='slug', + field=models.SlugField(null=True, unique=True, verbose_name='Collection slug'), + ), + ] diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 463d5fe5..6a985fc0 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -20,11 +20,11 @@ class BaseTestCase(APITestCase): self.newsletter = True self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - #get tokkens - tokkens = User.create_jwt_tokens(self.user) + #get tokens + tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token'), + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token'), 'country_code': 'en'}) @@ -51,11 +51,12 @@ class CollectionDetailTests(BaseTestCase): is_publish=True, start=datetime.now(pytz.utc), end=datetime.now(pytz.utc), - country=country + country=country, + slug='test-collection-slug', ) def test_collection_detail_Read(self): - response = self.client.get(f'/api/web/collections/{self.collection.id}/establishments/?country_code=en', + response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/establishment/migrations/0029_establishment_slug.py b/apps/establishment/migrations/0029_establishment_slug.py deleted file mode 100644 index 6cc01122..00000000 --- a/apps/establishment/migrations/0029_establishment_slug.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 12:55 - -from django.db import migrations -import django_extensions.db.fields - -from establishment.models import Establishment - - -def migrate_data_forward(apps, schema_editor): - for instance in Establishment.objects.all(): - print("Generating slug for %s" % instance) - instance.save() # Will trigger slug update - -def migrate_data_backward(apps, schema_editor): - pass - -class Migration(migrations.Migration): - - dependencies = [ - ('establishment', '0028_auto_20190920_1205'), - ] - - operations = [ - migrations.AddField( - model_name='establishment', - name='slug', - field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['name'], db_index=True, verbose_name='Establishment slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - ] diff --git a/apps/establishment/migrations/0030_auto_20190923_1505.py b/apps/establishment/migrations/0030_auto_20190923_1505.py deleted file mode 100644 index 9614eca1..00000000 --- a/apps/establishment/migrations/0030_auto_20190923_1505.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 15:05 -from django.core.validators import EMPTY_VALUES -from django.db import migrations, models - -from establishment.models import Establishment - - -def migrate_data_forward(apps, schema_editor): - for instance in Establishment.objects.all(): - if instance.slug in EMPTY_VALUES: - instance.slug = None - instance.save(update_fields=['slug']) - -def migrate_data_backward(apps, schema_editor): - pass - - -class Migration(migrations.Migration): - - dependencies = [ - ('establishment', '0029_establishment_slug'), - ] - - operations = [ - migrations.AlterField( - model_name='establishment', - name='slug', - field=models.SlugField(null=True, verbose_name='Establishment slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - migrations.AlterField( - model_name='establishment', - name='slug', - field=models.SlugField(unique=True, null=True, verbose_name='Establishment slug'), - ), - ] diff --git a/apps/establishment/migrations/0031_establishment_slug.py b/apps/establishment/migrations/0031_establishment_slug.py new file mode 100644 index 00000000..2ff68380 --- /dev/null +++ b/apps/establishment/migrations/0031_establishment_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-24 08:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0030_auto_20190923_1340'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='slug', + field=models.SlugField(null=True, unique=True, verbose_name='Establishment slug'), + ), + ] diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 5de7d779..e4e3b02c 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -29,13 +29,15 @@ class BaseTestCase(APITestCase): class EstablishmentTests(BaseTestCase): def test_establishment_CRUD(self): - response = self.client.get('/api/back/establishments/', format='json') + params = {'page': 1, 'page_size': 1,} + response = self.client.get('/api/back/establishments/', params, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) data = { 'name': 'Test establishment', 'type_id': self.establishment_type.id, - 'is_publish': True + 'is_publish': True, + 'slug': 'test-establishment-slug', } response = self.client.post('/api/back/establishments/', data=data, format='json') diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index bc3313ae..208cf0db 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -38,7 +38,6 @@ class BaseTestCase(APITestCase): use_subtypes=False) self.test_establishment = Establishment.objects.create(name="test establishment", - name_transliterated="test-establishment", description={"en-GB": "description of test establishment"}, establishment_type=self.test_establishment_type, is_publish=True) diff --git a/apps/news/migrations/0010_news_slug.py b/apps/news/migrations/0010_news_slug.py deleted file mode 100644 index 367357d3..00000000 --- a/apps/news/migrations/0010_news_slug.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 12:55 - -from django.db import migrations -import django_extensions.db.fields - -from news.models import News - -def migrate_data_forward(apps, schema_editor): - for instance in News.objects.all(): - print("Generating slug for %s" % instance) - instance.save() # Will trigger slug update - -def migrate_data_backward(apps, schema_editor): - pass - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0009_auto_20190901_1032'), - ] - - operations = [ - migrations.AddField( - model_name='news', - name='slug', - field=django_extensions.db.fields.AutoSlugField(blank=True, editable=True, populate_from=['title'], db_index=True, verbose_name='News slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - ] diff --git a/apps/news/migrations/0011_auto_20190923_1505.py b/apps/news/migrations/0011_auto_20190923_1505.py deleted file mode 100644 index b56a2d12..00000000 --- a/apps/news/migrations/0011_auto_20190923_1505.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-23 15:05 -from django.core.validators import EMPTY_VALUES -from django.db import migrations, models - -from news.models import News - - -def migrate_data_forward(apps, schema_editor): - for instance in News.objects.all(): - if instance.slug in EMPTY_VALUES: - instance.slug = None - instance.save(update_fields=['slug']) - -def migrate_data_backward(apps, schema_editor): - pass - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0010_news_slug'), - ] - - operations = [ - migrations.AlterField( - model_name='news', - name='slug', - field=models.SlugField(null=True, verbose_name='News slug'), - ), - migrations.RunPython( - migrate_data_forward, - migrate_data_backward, - ), - migrations.AlterField( - model_name='news', - name='slug', - field=models.SlugField(null=True, unique=True, verbose_name='News slug'), - ), - ] diff --git a/apps/news/migrations/0013_auto_20190924_0806.py b/apps/news/migrations/0013_auto_20190924_0806.py new file mode 100644 index 00000000..7d6d0e1f --- /dev/null +++ b/apps/news/migrations/0013_auto_20190924_0806.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.4 on 2019-09-24 08:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0001_initial'), + ('news', '0012_auto_20190923_1416'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='image', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='gallery.Image', verbose_name='News image'), + ), + migrations.AddField( + model_name='news', + name='slug', + field=models.SlugField(null=True, unique=True, verbose_name='News slug'), + ), + ] diff --git a/apps/news/tests.py b/apps/news/tests.py index bf6577d9..9ac8742c 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -1,8 +1,8 @@ from http.cookies import SimpleCookie -from django.test import TestCase from rest_framework.test import APITestCase from rest_framework import status +from datetime import datetime, timedelta from news.models import NewsType, News from account.models import User @@ -24,8 +24,9 @@ class BaseTestCase(APITestCase): self.test_news_type = NewsType.objects.create(name="Test news type") self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, news_type=self.test_news_type, description={"en-GB": "Description test news"}, - playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", - is_publish=True) + playlist=1, start=datetime.now() + timedelta(hours=-2), + end=datetime.now() + timedelta(hours=2), + is_publish=True, slug='test-news-slug',) class NewsTestCase(BaseTestCase): @@ -35,10 +36,10 @@ class NewsTestCase(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_news_detail(self): - response = self.client.get(f"/api/web/news/{self.test_news.id}/") + response = self.client.get(f"/api/web/news/{self.test_news.slug}/") self.assertEqual(response.status_code, status.HTTP_200_OK) def test_news_type_list(self): - response = self.client.get("/api/web/news/type/") + response = self.client.get("/api/web/news/types/") self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 8522592e..5fec7a04 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -8,4 +8,5 @@ urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'), + path('types/', views.NewsTypeListView.as_view(), name='type-news'), ] \ No newline at end of file diff --git a/apps/news/views.py b/apps/news/views.py index 5f531305..74abe33f 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -20,7 +20,6 @@ class NewsMixinView: class NewsListView(NewsMixinView, generics.ListAPIView): """News list view.""" - filter_class = filters.NewsListFilterSet From 2ad2b3b56d229b92431ff02e6f88c9e04e3045c3 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: Tue, 24 Sep 2019 12:13:39 +0300 Subject: [PATCH 058/319] Refactor --- apps/account/tests/tests_common.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 0992b930..05185952 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -7,21 +7,19 @@ from django.urls import reverse class AccountUserTests(APITestCase): - - url = reverse('web:account:user-retrieve-update') - def setUp(self): self.data = get_tokens_for_user() def test_user_url(self): - response = self.client.get(self.url) + url = reverse('web:account:user-retrieve-update') + response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.client.cookies = SimpleCookie( {'access_token': self.data['tokens'].get('access_token'), 'refresh_token': self.data['tokens'].get('access_token')}) - response = self.client.get(self.url) + response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) data = { @@ -33,19 +31,16 @@ class AccountUserTests(APITestCase): "email": "sedragurdatest@desoz.com", "newsletter": self.data["newsletter"] } - response = self.client.patch(self.url, data=data, format='json') + response = self.client.patch(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) data["email"] = "sedragurdatest2@desoz.com" - response = self.client.put(self.url, data=data, format='json') + response = self.client.put(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) class AccountChangePasswordTests(APITestCase): - - url = reverse('web:account:change-password') - def setUp(self): self.data = get_tokens_for_user() @@ -54,15 +49,16 @@ class AccountChangePasswordTests(APITestCase): "old_password": self.data["password"], "password": "new password" } + url = reverse('web:account:change-password') - response = self.client.patch(self.url, data=data, format='json') + response = self.client.patch(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.client.cookies = SimpleCookie( {'access_token': self.data['tokens'].get('access_token'), 'refresh_token': self.data['tokens'].get('access_token')}) - response = self.client.patch(self.url, data=data, format='json') + response = self.client.patch(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) From 65914659e5031799d7742ff4fbb6d3a5eefb50ea Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 12:31:35 +0300 Subject: [PATCH 059/319] Slugs serializers --- apps/collection/serializers/common.py | 2 ++ apps/establishment/serializers/back.py | 2 +- apps/establishment/serializers/common.py | 4 ++-- apps/news/serializers.py | 3 +++ apps/news/serializers/common.py | 0 5 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 apps/news/serializers/common.py diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 85d66d30..601518fa 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -13,6 +13,7 @@ class CollectionSerializer(serializers.ModelSerializer): block_size = serializers.JSONField() is_publish = serializers.BooleanField() on_top = serializers.BooleanField() + slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) # REQUEST start = serializers.DateTimeField(write_only=True) @@ -34,6 +35,7 @@ class CollectionSerializer(serializers.ModelSerializer): 'on_top', 'country', 'block_size', + 'slug', ] diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index b4d8e747..ad278092 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -20,7 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) - slug = serializers.SlugField(required=True, allow_blank=False) + slug = serializers.SlugField(required=True, allow_blank=False, unique=True, max_length=50) class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index e1cf312c..6a8289de 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -146,7 +146,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): subtypes = EstablishmentSubTypeSerializer(many=True) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) - slug = serializers.SlugField(allow_blank=False, required=True) + slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) class Meta: """Meta class.""" @@ -199,7 +199,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) - slug = serializers.SlugField(required=True, allow_blank=False,) + slug = serializers.SlugField(required=True, allow_blank=False, unique=True, max_length=50) in_favorites = serializers.SerializerMethodField() diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 67d81501..5265e94c 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -28,6 +28,8 @@ class NewsBaseSerializer(serializers.ModelSerializer): news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) + slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) + class Meta: """Meta class.""" @@ -41,6 +43,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'preview_image_url', 'news_type', 'tags', + 'slug', ) diff --git a/apps/news/serializers/common.py b/apps/news/serializers/common.py deleted file mode 100644 index e69de29b..00000000 From 33055df2ae27e5af7dd63bca9ac3b550c9620fea Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 24 Sep 2019 12:31:52 +0300 Subject: [PATCH 060/319] GeoIP: update logger level --- apps/main/methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main/methods.py b/apps/main/methods.py index 3d4349e4..e9ec780c 100644 --- a/apps/main/methods.py +++ b/apps/main/methods.py @@ -32,7 +32,7 @@ def determine_country_code(ip_addr): country_code = geoip.country_code(ip_addr) country_code = country_code.lower() except GeoIP2Exception as ex: - logger.error(f'GEOIP Exception: {ex}') + logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}') except Exception as ex: logger.error(f'GEOIP Base exception: {ex}') return country_code From 0b5e40640c17a918b156e377186239a63ae814f5 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 12:36:52 +0300 Subject: [PATCH 061/319] Remove test url --- apps/news/urls/back.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 5fec7a04..8522592e 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -8,5 +8,4 @@ urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'), - path('types/', views.NewsTypeListView.as_view(), name='type-news'), ] \ No newline at end of file From f49ea12108c01f283f42d02caf0a9d1db5dba81d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 12:39:08 +0300 Subject: [PATCH 062/319] Remove unnecessary field --- apps/news/migrations/0013_auto_20190924_0806.py | 5 ----- apps/news/models.py | 4 ---- 2 files changed, 9 deletions(-) diff --git a/apps/news/migrations/0013_auto_20190924_0806.py b/apps/news/migrations/0013_auto_20190924_0806.py index 7d6d0e1f..efd81652 100644 --- a/apps/news/migrations/0013_auto_20190924_0806.py +++ b/apps/news/migrations/0013_auto_20190924_0806.py @@ -12,11 +12,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='news', - name='image', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='gallery.Image', verbose_name='News image'), - ), migrations.AddField( model_name='news', name='slug', diff --git a/apps/news/models.py b/apps/news/models.py index 5e7fbb9d..140c89c9 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -49,10 +49,6 @@ class NewsQuerySet(models.QuerySet): class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" - image = models.ForeignKey( - 'gallery.Image', null=True, blank=True, default=None, - verbose_name=_('News image'), on_delete=models.CASCADE) - news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, From eebd66fa275c4ab45816ca97939fa9ade24048f6 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 13:35:05 +0300 Subject: [PATCH 063/319] Add tests to created_by and modified_by --- apps/utils/tests.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 1c9fa71d..786ed940 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -8,6 +8,8 @@ from http.cookies import SimpleCookie from account.models import User from news.models import News, NewsType +from establishment.models import Establishment, EstablishmentType, Employee + class BaseTestCase(APITestCase): @@ -28,6 +30,12 @@ class BaseTestCase(APITestCase): 'locale': "en" }) + +class TranslateFieldTests(BaseTestCase): + + def setUp(self): + super().setUp() + self.news_type = NewsType.objects.create(name="Test news type") self.news_item = News.objects.create( @@ -45,15 +53,9 @@ class BaseTestCase(APITestCase): news_type=self.news_type ) - -class TranslateFieldModel(BaseTestCase): - def test_model_field(self): self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) - -class TranslateFieldReview(BaseTestCase): - def test_read_locale(self): response = self.client.get(f"/api/web/news/{self.news_item.id}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -62,3 +64,32 @@ class TranslateFieldReview(BaseTestCase): self.assertIn("title_translated", news_data) self.assertEqual(news_data['title_translated'], "Test news item") + + +class BaseAttributeTests(BaseTestCase): + + def setUp(self): + super().setUp() + + self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") + self.establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id, + is_publish=True + ) + + def test_base_attr_api(self): + data = { + 'user': self.user.id, + 'name': 'Test name' + } + + response = self.client.post('/api/back/establishments/employees/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response_data = response.json() + self.assertIn("id", response_data) + + employee = Employee.objects.get(id=response_data['id']) + self.assertEqual(self.user, employee.created_by) + self.assertEqual(self.user, employee.modified_by) From 28ae1ab8d14f02aa89031a61ac021b40a1418f44 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: Tue, 24 Sep 2019 14:03:55 +0300 Subject: [PATCH 064/319] Test reset password --- apps/account/tests/tests_web.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 apps/account/tests/tests_web.py diff --git a/apps/account/tests/tests_web.py b/apps/account/tests/tests_web.py new file mode 100644 index 00000000..33ba109f --- /dev/null +++ b/apps/account/tests/tests_web.py @@ -0,0 +1,24 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from authorization.tests.tests import get_tokens_for_user +from django.urls import reverse + + +class AccountResetPassWordTests(APITestCase): + + def setUp(self): + self.data = get_tokens_for_user() + + def test_reset_password(self): + url = reverse('web:account:password-reset') + data = { + "username_or_email": self.data["email"] + } + response = self.client.post(url, data=data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + "username_or_email": self.data["username"] + } + response = self.client.post(url, data=data) + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file From 1534c9b09c322ee426df97331020ba88211b013d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 14:06:54 +0300 Subject: [PATCH 065/319] Remove unique (cherry picked from commit f793c20) --- apps/collection/serializers/common.py | 2 +- apps/establishment/serializers/back.py | 2 +- apps/establishment/serializers/common.py | 4 ++-- apps/news/serializers.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 601518fa..f7319561 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -13,7 +13,7 @@ class CollectionSerializer(serializers.ModelSerializer): block_size = serializers.JSONField() is_publish = serializers.BooleanField() on_top = serializers.BooleanField() - slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) + slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) # REQUEST start = serializers.DateTimeField(write_only=True) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index ad278092..7013fe6a 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -20,7 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) - slug = serializers.SlugField(required=True, allow_blank=False, unique=True, max_length=50) + slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 6a8289de..7b974887 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -146,7 +146,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): subtypes = EstablishmentSubTypeSerializer(many=True) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) - slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) + slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) class Meta: """Meta class.""" @@ -199,7 +199,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) - slug = serializers.SlugField(required=True, allow_blank=False, unique=True, max_length=50) + slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) in_favorites = serializers.SerializerMethodField() diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 5265e94c..dbcd0f62 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -28,7 +28,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) - slug = serializers.SlugField(allow_blank=False, required=True, unique=True, max_length=50) + slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) class Meta: """Meta class.""" From 67a5f8b2f0bedc4eb641135eb520aa974af577c5 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: Tue, 24 Sep 2019 15:07:00 +0300 Subject: [PATCH 066/319] Test web account --- apps/account/tests/tests_web.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/account/tests/tests_web.py b/apps/account/tests/tests_web.py index 33ba109f..e771b31a 100644 --- a/apps/account/tests/tests_web.py +++ b/apps/account/tests/tests_web.py @@ -2,6 +2,7 @@ from rest_framework.test import APITestCase from rest_framework import status from authorization.tests.tests import get_tokens_for_user from django.urls import reverse +from account.models import User class AccountResetPassWordTests(APITestCase): @@ -21,4 +22,28 @@ class AccountResetPassWordTests(APITestCase): "username_or_email": self.data["username"] } response = self.client.post(url, data=data) - self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class AccountResetPassWordTests(APITestCase): + + def setUp(self): + self.data = get_tokens_for_user() + + def test_reset_password_confirm(self): + data ={ + "password": "newpasswordnewpassword" + } + user = User.objects.get(email=self.data["email"]) + token = user.reset_password_token + uidb64 = user.get_user_uidb64 + + url = reverse('web:account:password-reset-confirm', kwargs={ + 'uidb64': uidb64, + 'token': token + }) + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + From 82d29d2795f9564f79dced9f713ca44f3efdc440 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 15:17:08 +0300 Subject: [PATCH 067/319] Add signal --- apps/utils/models.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/utils/models.py b/apps/utils/models.py index 632cf4a2..929b5e5b 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -11,6 +11,9 @@ from easy_thumbnails.fields import ThumbnailerImageField from utils.methods import image_path, svg_image_path from utils.validators import svg_image_validator +from django.db.models.signals import pre_save +from django.dispatch import receiver + class ProjectBaseMixin(models.Model): """Base mixin model.""" @@ -123,6 +126,23 @@ class BaseAttributes(ProjectBaseMixin): null=True, related_name='%(class)s_records_modified' ) + @receiver(pre_save) + def _pre_save(sender, instance, **kwargs): + if not issubclass(sender, BaseAttributes): + return + + # debug + from establishment.models import Employee + if not isinstance(instance, Employee): + return + + user = False + + instance.modified_by = user + + if instance._state.adding: + instance.created_by = user + class Meta: """Meta class.""" From a83a62b26b1f22b1cfa6818d682a1c71fff439d2 Mon Sep 17 00:00:00 2001 From: michail Date: Tue, 24 Sep 2019 17:32:18 +0500 Subject: [PATCH 068/319] Add notification tests --- apps/notification/tests.py | 116 +++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 apps/notification/tests.py diff --git a/apps/notification/tests.py b/apps/notification/tests.py new file mode 100644 index 00000000..d78c7fca --- /dev/null +++ b/apps/notification/tests.py @@ -0,0 +1,116 @@ +from http.cookies import SimpleCookie + +from django.test import TestCase +from rest_framework.test import APITestCase +from rest_framework import status + +from account.models import User +from notification.models import Subscriber + + +class BaseTestCase(APITestCase): + + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + # get tokkens + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + + +class NotificationAnonSubscribeTestCase(APITestCase): + + def test_subscribe(self): + + test_data = { + "email": "test@email.com", + "state": 1 + } + + response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()["email"], test_data["email"]) + self.assertEqual(response.json()["state"], test_data["state"]) + + +class NotificationSubscribeTestCase(BaseTestCase): + + def setUp(self): + super().setUp() + + self.test_data = { + "email": self.email, + "state": 1 + } + + def test_subscribe(self): + + response = self.client.post("/api/web/notifications/subscribe/", data=self.test_data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()["email"], self.email) + self.assertEqual(response.json()["state"], self.test_data["state"]) + + def test_subscribe_info_auth_user(self): + + Subscriber.objects.create(user=self.user, email=self.email, state=1) + + response = self.client.get("/api/web/notifications/subscribe-info/", data=self.test_data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class NotificationSubscribeInfoTestCase(APITestCase): + + def test_subscribe_info(self): + + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + + test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) + + response = self.client.get(f"/api/web/notifications/subscribe-info/{test_subscriber.update_code}/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class NotificationUnsubscribeAuthUserTestCase(BaseTestCase): + + def test_unsubscribe_auth_user(self): + + Subscriber.objects.create(user=self.user, email=self.email, state=1) + + self.test_data = { + "email": self.email, + "state": 1 + } + + response = self.client.patch("/api/web/notifications/unsubscribe/", data=self.test_data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class NotificationUnsubscribeTestCase(APITestCase): + + def test_unsubscribe(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) + + self.test_data = { + "email": self.email, + "state": 1 + } + + test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) + + response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/", + data=self.test_data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) From 7e5844a24f293849498d56323a6d215bebc04084 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: Tue, 24 Sep 2019 16:06:31 +0300 Subject: [PATCH 069/319] Fix test auth --- apps/account/tests/tests_common.py | 2 +- apps/account/tests/tests_web.py | 2 +- apps/authorization/tests/__init__.py | 0 apps/authorization/tests/{tests.py => tests_authorization.py} | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 apps/authorization/tests/__init__.py rename apps/authorization/tests/{tests.py => tests_authorization.py} (98%) diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index 05185952..dea807c4 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -1,6 +1,6 @@ from rest_framework.test import APITestCase from rest_framework import status -from authorization.tests.tests import get_tokens_for_user +from authorization.tests.tests_authorization import get_tokens_for_user from http.cookies import SimpleCookie from account.models import User from django.urls import reverse diff --git a/apps/account/tests/tests_web.py b/apps/account/tests/tests_web.py index e771b31a..a3d93f4e 100644 --- a/apps/account/tests/tests_web.py +++ b/apps/account/tests/tests_web.py @@ -1,6 +1,6 @@ from rest_framework.test import APITestCase from rest_framework import status -from authorization.tests.tests import get_tokens_for_user +from authorization.tests.tests_authorization import get_tokens_for_user from django.urls import reverse from account.models import User diff --git a/apps/authorization/tests/__init__.py b/apps/authorization/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/authorization/tests/tests.py b/apps/authorization/tests/tests_authorization.py similarity index 98% rename from apps/authorization/tests/tests.py rename to apps/authorization/tests/tests_authorization.py index d9fd7b71..4a5d2a2b 100644 --- a/apps/authorization/tests/tests.py +++ b/apps/authorization/tests/tests_authorization.py @@ -22,6 +22,7 @@ def get_tokens_for_user( class AuthorizationTests(APITestCase): def setUp(self): + print("Auth!") data = get_tokens_for_user() self.username = data["username"] self.password = data["password"] From 2f61df2c650f4cbef67f1a81849a728154199f7a Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 16:20:04 +0300 Subject: [PATCH 070/319] Add address CRUD tests --- apps/location/tests.py | 74 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index 7ce503c2..cab871a0 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -1,3 +1,73 @@ -from django.test import TestCase +from rest_framework.test import APITestCase +from account.models import User +from rest_framework import status +from http.cookies import SimpleCookie -# Create your tests here. +from location.models import City, Region, Country + + +class BaseTestCase(APITestCase): + + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.newsletter = True + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) + + # get tokens + + tokkens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokkens.get('access_token'), + 'refresh_token': tokkens.get('refresh_token')}) + + +class AddressTests(BaseTestCase): + + def setUp(self): + super().setUp() + + self.country = Country( + name="Test country", + code="+7" + ) + + self.region = Region( + name="Test region", + code="812", + country=self.country + ) + + self.city = City( + name="Test region", + code="812", + region=self.region, + country=self.country + ) + + def test_address_CRUD(self): + response = self.client.get('/api/back/location/addresses/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'user': self.user.id, + 'name': 'Test name' + } + + response = self.client.post('/api/back/location/addresses/', data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get('/api/back/location/addresses/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'name': 'Test new name' + } + + response = self.client.patch('/api/back/location/addresses/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete('/api/back/location/addresses/1/', format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From ecdf2fcaa095446eadf0ff0c56324a52ac2ea117 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 24 Sep 2019 16:45:36 +0300 Subject: [PATCH 071/319] Change slugs and str url order --- apps/news/urls/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index c483b05a..10513910 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -6,6 +6,6 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), - path('/', views.NewsDetailView.as_view(), name='rud'), path('types/', views.NewsTypeListView.as_view(), name='type'), + path('/', views.NewsDetailView.as_view(), name='rud'), ] From 77d39954c39b4c1584409e6f322cd59ca9737036 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 24 Sep 2019 17:54:22 +0300 Subject: [PATCH 072/319] added ordering to list of favorites --- apps/favorites/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/favorites/views.py b/apps/favorites/views.py index aed11709..a80960a8 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -19,4 +19,5 @@ class FavoritesEstablishmentListView(generics.ListAPIView): def get_queryset(self): """Override get_queryset method""" - return Establishment.objects.filter(favorites__user=self.request.user) + return Establishment.objects.filter(favorites__user=self.request.user)\ + .order_by('-favorites') From a55695b12060210fc3da6b40098cea16578b7c86 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 20:12:09 +0300 Subject: [PATCH 073/319] Switch to decorator Add update test --- apps/establishment/serializers/back.py | 9 ++++++- apps/utils/decorators.py | 35 ++++++++++++++++++++++++++ apps/utils/models.py | 20 --------------- apps/utils/tests.py | 23 +++++++++++++++++ 4 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 apps/utils/decorators.py diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 7199eb54..b4269c5f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -7,6 +7,9 @@ from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer ) + +from utils.decorators import with_base_attributes + from main.models import Currency @@ -121,7 +124,10 @@ class ContactEmailBackSerializers(PlateSerializer): ] +# TODO: test decorator +@with_base_attributes class EmployeeBackSerializers(serializers.ModelSerializer): + """Social network serializers.""" class Meta: model = models.Employee @@ -129,4 +135,5 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'id', 'user', 'name' - ] \ No newline at end of file + ] + diff --git a/apps/utils/decorators.py b/apps/utils/decorators.py new file mode 100644 index 00000000..12233021 --- /dev/null +++ b/apps/utils/decorators.py @@ -0,0 +1,35 @@ +from functools import wraps + +def with_base_attributes(cls): + + def create(self, validated_data): + user = None + request = self.context.get("request") + + if request and hasattr(request, "user"): + user = request.user + + if user is not None: + validated_data['created_by'] = user + validated_data['modified_by'] = user + + obj = self.Meta.model.objects.create(**validated_data) + return obj + + def update(self, validated_data): + user = None + request = self.context.get("request") + + if request and hasattr(request, "user"): + user = request.user + + if user is not None: + validated_data['modified_by'] = user + + obj = self.Meta.model.objects.create(**validated_data) + return obj + + setattr(cls, "create", create) + setattr(cls, "update", update) + + return cls diff --git a/apps/utils/models.py b/apps/utils/models.py index 929b5e5b..632cf4a2 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -11,9 +11,6 @@ from easy_thumbnails.fields import ThumbnailerImageField from utils.methods import image_path, svg_image_path from utils.validators import svg_image_validator -from django.db.models.signals import pre_save -from django.dispatch import receiver - class ProjectBaseMixin(models.Model): """Base mixin model.""" @@ -126,23 +123,6 @@ class BaseAttributes(ProjectBaseMixin): null=True, related_name='%(class)s_records_modified' ) - @receiver(pre_save) - def _pre_save(sender, instance, **kwargs): - if not issubclass(sender, BaseAttributes): - return - - # debug - from establishment.models import Employee - if not isinstance(instance, Employee): - return - - user = False - - instance.modified_by = user - - if instance._state.adding: - instance.created_by = user - class Meta: """Meta class.""" diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 786ed940..dafe0fee 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -91,5 +91,28 @@ class BaseAttributeTests(BaseTestCase): self.assertIn("id", response_data) employee = Employee.objects.get(id=response_data['id']) + self.assertEqual(self.user, employee.created_by) self.assertEqual(self.user, employee.modified_by) + + modify_user = User.objects.create_user( + username='sedragurda', + password='sedragurdaredips19', + email='sedragurda@desoz.com', + ) + + modify_tokkens = User.create_jwt_tokens(modify_user) + self.client.cookies = SimpleCookie( + {'access_token': modify_tokkens.get('access_token'), + 'refresh_token': modify_tokkens.get('refresh_token'), + 'locale': "en" + }) + + update_data = { + 'name': 'Test new name' + } + + response = self.client.patch('/api/back/establishments/employees/1/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertEqual(modify_user, employee.modified_by) From d006184b14f905c2d9ec8f772b9927db6ddaa298 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 20:22:27 +0300 Subject: [PATCH 074/319] Fix update --- apps/utils/decorators.py | 9 +++++---- apps/utils/tests.py | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/utils/decorators.py b/apps/utils/decorators.py index 12233021..e22fc3dc 100644 --- a/apps/utils/decorators.py +++ b/apps/utils/decorators.py @@ -1,4 +1,3 @@ -from functools import wraps def with_base_attributes(cls): @@ -16,7 +15,7 @@ def with_base_attributes(cls): obj = self.Meta.model.objects.create(**validated_data) return obj - def update(self, validated_data): + def update(self, instance, validated_data): user = None request = self.context.get("request") @@ -26,8 +25,10 @@ def with_base_attributes(cls): if user is not None: validated_data['modified_by'] = user - obj = self.Meta.model.objects.create(**validated_data) - return obj + obj = self.Meta.model + obj.objects.filter(pk=instance.id).update(**validated_data) + + return instance setattr(cls, "create", create) setattr(cls, "update", update) diff --git a/apps/utils/tests.py b/apps/utils/tests.py index dafe0fee..8645a020 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -96,9 +96,9 @@ class BaseAttributeTests(BaseTestCase): self.assertEqual(self.user, employee.modified_by) modify_user = User.objects.create_user( - username='sedragurda', - password='sedragurdaredips19', - email='sedragurda@desoz.com', + username='sedragurda2', + password='sedragurdaredips192', + email='sedragurda2@desoz.com', ) modify_tokkens = User.create_jwt_tokens(modify_user) @@ -115,4 +115,5 @@ class BaseAttributeTests(BaseTestCase): response = self.client.patch('/api/back/establishments/employees/1/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) + employee.refresh_from_db() self.assertEqual(modify_user, employee.modified_by) From d62cee341cddc5195f8aa97c43cbd254c942dde7 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 21:03:50 +0300 Subject: [PATCH 075/319] Add address tests --- apps/location/tests.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index cab871a0..a3213e69 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -1,8 +1,11 @@ +import json + from rest_framework.test import APITestCase from account.models import User from rest_framework import status from http.cookies import SimpleCookie +from django.contrib.gis.db.models import PointField from location.models import City, Region, Country @@ -28,19 +31,18 @@ class AddressTests(BaseTestCase): def setUp(self): super().setUp() - - self.country = Country( - name="Test country", - code="+7" + self.country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="test" ) - self.region = Region( + self.region = Region.objects.create( name="Test region", code="812", country=self.country ) - self.city = City( + self.city = City.objects.create( name="Test region", code="812", region=self.region, @@ -52,18 +54,25 @@ class AddressTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) data = { - 'user': self.user.id, - 'name': 'Test name' + 'city_id': self.city.id, + 'number': '+79999999', + "coordinates": { + "latitude": 37.0625, + "longitude": -95.677068 + }, + "geo_lon": -95.677068, + "geo_lat": 37.0625 } - response = self.client.post('/api/back/location/addresses/', data=data) + response = self.client.post('/api/back/location/addresses/', data=data, format='json') + print(f"=========RESPONSE: {response.json()}") self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.get('/api/back/location/addresses/1/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) update_data = { - 'name': 'Test new name' + 'number': '+79999991' } response = self.client.patch('/api/back/location/addresses/1/', data=update_data) From 6b3558e3acf56ade095cd7989b072983c640162d Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 21:28:11 +0300 Subject: [PATCH 076/319] Add country tests --- apps/location/tests.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index a3213e69..0d7803bc 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -27,6 +27,35 @@ class BaseTestCase(APITestCase): 'refresh_token': tokkens.get('refresh_token')}) +class CountryTests(BaseTestCase): + + def test_country_CRUD(self): + response = self.client.get('/api/back/location/countries/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'name': 'Test country', + 'code': 'test' + } + + response = self.client.post('/api/back/location/countries/', data=data, format='json') + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/back/location/countries/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'name': json.dumps({"en-GB": "Test new country"}) + } + + response = self.client.patch(f'/api/back/location/countries/{response_data["id"]}/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete(f'/api/back/location/countries/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + class AddressTests(BaseTestCase): def setUp(self): @@ -65,18 +94,18 @@ class AddressTests(BaseTestCase): } response = self.client.post('/api/back/location/addresses/', data=data, format='json') - print(f"=========RESPONSE: {response.json()}") + response_data = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get('/api/back/location/addresses/1/', format='json') + response = self.client.get(f'/api/back/location/addresses/{response_data["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) update_data = { 'number': '+79999991' } - response = self.client.patch('/api/back/location/addresses/1/', data=update_data) + response = self.client.patch(f'/api/back/location/addresses/{response_data["id"]}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete('/api/back/location/addresses/1/', format='json') + response = self.client.delete(f'/api/back/location/addresses/{response_data["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From 7455d2c8564144b4a746b930042f3399ec3404b6 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 21:30:59 +0300 Subject: [PATCH 077/319] Add region tests --- apps/location/tests.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/location/tests.py b/apps/location/tests.py index 0d7803bc..ceeb2043 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -56,6 +56,43 @@ class CountryTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) +class RegionTests(BaseTestCase): + + def setUp(self): + super().setUp() + self.country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="test" + ) + + def test_region_CRUD(self): + response = self.client.get('/api/back/location/countries/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'name': 'Test country', + 'code': 'test', + 'country_id': self.country.id + } + + response = self.client.post('/api/back/location/regions/', data=data, format='json') + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/back/location/regions/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'name': json.dumps({"en-GB": "Test new country"}) + } + + response = self.client.patch(f'/api/back/location/regions/{response_data["id"]}/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete(f'/api/back/location/regions/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + class AddressTests(BaseTestCase): def setUp(self): From c50bdc863745caf442a8a51f77e964a43be7bbf9 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 21:34:21 +0300 Subject: [PATCH 078/319] Add city tests --- apps/location/tests.py | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index ceeb2043..d85db414 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -66,7 +66,7 @@ class RegionTests(BaseTestCase): ) def test_region_CRUD(self): - response = self.client.get('/api/back/location/countries/', format='json') + response = self.client.get('/api/back/location/regions/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) data = { @@ -93,6 +93,46 @@ class RegionTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) +class CityTests(RegionTests): + + def setUp(self): + super().setUp() + + self.region = Region.objects.create( + name="Test region", + code="812", + country=self.country + ) + + def test_city_CRUD(self): + response = self.client.get('/api/back/location/cities/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = { + 'name': 'Test country', + 'code': 'test', + 'country_id': self.country.id, + 'region_id': self.region.id + } + + response = self.client.post('/api/back/location/cities/', data=data, format='json') + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/back/location/cities/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'name': json.dumps({"en-GB": "Test new country"}) + } + + response = self.client.patch(f'/api/back/location/cities/{response_data["id"]}/', data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete(f'/api/back/location/cities/{response_data["id"]}/', format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + class AddressTests(BaseTestCase): def setUp(self): From 2261875d3c64c00d449bbe988e0f6ccf8a4309ee Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 24 Sep 2019 21:36:59 +0300 Subject: [PATCH 079/319] Fix tests --- apps/location/tests.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index d85db414..f68ba56b 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -5,7 +5,6 @@ from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from django.contrib.gis.db.models import PointField from location.models import City, Region, Country @@ -93,11 +92,16 @@ class RegionTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -class CityTests(RegionTests): +class CityTests(BaseTestCase): def setUp(self): super().setUp() + self.country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="test" + ) + self.region = Region.objects.create( name="Test region", code="812", @@ -137,6 +141,7 @@ class AddressTests(BaseTestCase): def setUp(self): super().setUp() + self.country = Country.objects.create( name=json.dumps({"en-GB": "Test country"}), code="test" From 6bbbd1d6fe2414b8c5cabf9b84519c9da4722566 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 11:11:12 +0300 Subject: [PATCH 080/319] Switch decorator to validate --- apps/utils/decorators.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/apps/utils/decorators.py b/apps/utils/decorators.py index e22fc3dc..c48a26c7 100644 --- a/apps/utils/decorators.py +++ b/apps/utils/decorators.py @@ -1,7 +1,7 @@ def with_base_attributes(cls): - def create(self, validated_data): + def validate(self, data): user = None request = self.context.get("request") @@ -9,28 +9,13 @@ def with_base_attributes(cls): user = request.user if user is not None: - validated_data['created_by'] = user - validated_data['modified_by'] = user + data.update({'modified_by': user}) - obj = self.Meta.model.objects.create(**validated_data) - return obj + if not self.instance: + data.update({'created_by': user}) - def update(self, instance, validated_data): - user = None - request = self.context.get("request") + return data - if request and hasattr(request, "user"): - user = request.user - - if user is not None: - validated_data['modified_by'] = user - - obj = self.Meta.model - obj.objects.filter(pk=instance.id).update(**validated_data) - - return instance - - setattr(cls, "create", create) - setattr(cls, "update", update) + setattr(cls, "validate", validate) return cls From 617beb652f38659f7eebc4dc828a161bbbab5d9d Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 11:12:17 +0300 Subject: [PATCH 081/319] Add test to modified item --- apps/utils/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 8645a020..e9ad3c23 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -117,3 +117,4 @@ class BaseAttributeTests(BaseTestCase): employee.refresh_from_db() self.assertEqual(modify_user, employee.modified_by) + self.assertEqual(self.user, employee.created_by) From cf19904349bd31fd8dcd3c817f82ab2b3ee5fc42 Mon Sep 17 00:00:00 2001 From: michail Date: Wed, 25 Sep 2019 13:17:24 +0500 Subject: [PATCH 082/319] Fixed urls and tests for news --- apps/news/tests.py | 13 ++++++++++--- apps/news/urls/web.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/news/tests.py b/apps/news/tests.py index 9ac8742c..7d6724d7 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -35,11 +35,18 @@ class NewsTestCase(BaseTestCase): response = self.client.get("/api/web/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_detail(self): - response = self.client.get(f"/api/web/news/{self.test_news.slug}/") + def test_news_web_detail(self): + response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_back_detail(self): + response = self.client.get(f"/api/back/news/{self.test_news.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_list_back(self): + response = self.client.get("/api/back/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) def test_news_type_list(self): response = self.client.get("/api/web/news/types/") self.assertEqual(response.status_code, status.HTTP_200_OK) - diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 10513910..80fcf072 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -7,5 +7,5 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), path('types/', views.NewsTypeListView.as_view(), name='type'), - path('/', views.NewsDetailView.as_view(), name='rud'), + path('slug//', views.NewsDetailView.as_view(), name='rud'), ] From 0e643a1dfa69f2cc4c7fbc8fac70a8811fc7f478 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 25 Sep 2019 12:23:25 +0300 Subject: [PATCH 083/319] Merge branch 'develop' of /home/a.feteleu/projects/gm-backend with conflicts. --- apps/establishment/models.py | 77 ++++++------------------------------ apps/gallery/views.py | 2 - 2 files changed, 12 insertions(+), 67 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index e3384dfb..4731a212 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -101,70 +101,16 @@ class EstablishmentQuerySet(models.QuerySet): DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m, output_field=models.FloatField())) - def annotate_distance_mark(self): + def annotate_mark_similarity(self, mark): """ - Return QuerySet with annotated field - distance_mark. - Required fields: distance. + Return a QuerySet with annotated field - mark_similarity Description: - If the radius of the establishments in QuerySet does not exceed 500 meters, - then distance_mark is set to 0.6, otherwise 0. + Similarity mark determined by comparison with compared establishment mark """ - return self.annotate(distance_mark=models.Case( - models.When(distance__lte=500, - then=0.6), - default=0, - output_field=models.FloatField())) - - def annotate_intermediate_public_mark(self): - """ - Return QuerySet with annotated field - intermediate_public_mark. - Description: - If establishments in collection POP and its mark is null, then - intermediate_mark is set to 10; - """ - return self.annotate(intermediate_public_mark=models.Case( - models.When( - collections__collection_type=Collection.POP, - public_mark__isnull=True, - then=10 - ), - default='public_mark', - output_field=models.FloatField())) - - def annotate_additional_mark(self, public_mark: float): - """ - Return QuerySet with annotated field - additional_mark. - Required fields: intermediate_public_mark - Description: - IF - establishments public_mark + 3 > compared establishment public_mark - OR - establishments public_mark - 3 > compared establishment public_mark, - THEN - additional_mark is set to 0.4, - ELSE - set to 0. - """ - return self.annotate(additional_mark=models.Case( - models.When( - models.Q(intermediate_public_mark__lte=public_mark + 3) | - models.Q(intermediate_public_mark__lte=public_mark - 3), - then=0.4), - default=0, - output_field=models.FloatField())) - - def annotate_total_mark(self): - """ - Return QuerySet with annotated field - total_mark. - Required fields: distance_mark, additional_mark. - Fields - Description: - Annotated field is obtained by formula: - (distance + additional marks) * intermediate_public_mark. - """ - return self.annotate( - total_mark=(models.F('distance_mark') + models.F('additional_mark')) * - models.F('intermediate_public_mark')) + return self.annotate(mark_similarity=models.ExpressionWrapper( + mark - models.F('mark'), + output_field=models.FloatField() + )) def similar(self, establishment_slug: str): """ @@ -181,10 +127,11 @@ class EstablishmentQuerySet(models.QuerySet): reviews__status=Review.READY, public_mark__gte=10) \ .annotate_distance(point=establishment.address.coordinates) \ - .annotate_distance_mark() \ - .annotate_intermediate_public_mark() \ - .annotate_additional_mark(public_mark=establishment.public_mark) \ - .annotate_total_mark() + .annotate_mark_similarity(establishment_qs.first().public_mark) + # .annotate_distance_mark() \ + # .annotate_intermediate_public_mark() \ + # .annotate_additional_mark(public_mark=establishment.public_mark) \ + # .annotate_total_mark() else: return self.none() diff --git a/apps/gallery/views.py b/apps/gallery/views.py index 109a01ef..8a9195c3 100644 --- a/apps/gallery/views.py +++ b/apps/gallery/views.py @@ -1,6 +1,5 @@ from rest_framework import generics -from utils.permissions import IsAuthenticatedAndTokenIsValid from . import models, serializers @@ -9,4 +8,3 @@ class ImageUploadView(generics.CreateAPIView): model = models.Image queryset = models.Image.objects.all() serializer_class = serializers.ImageSerializer - permission_classes = (IsAuthenticatedAndTokenIsValid, ) From 90c9e321f7a627512aadf1274ac537c2bd767fd8 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 25 Sep 2019 13:05:07 +0300 Subject: [PATCH 084/319] fixed similar establishments --- apps/establishment/models.py | 30 ++++++++++++++++++++++-------- apps/establishment/views/web.py | 7 +++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4731a212..2fbff239 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -101,6 +101,22 @@ class EstablishmentQuerySet(models.QuerySet): DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m, output_field=models.FloatField())) + def annotate_intermediate_public_mark(self): + """ + Return QuerySet with annotated field - intermediate_public_mark. + Description: + If establishments in collection POP and its mark is null, then + intermediate_mark is set to 10; + """ + return self.annotate(intermediate_public_mark=models.Case( + models.When( + collections__collection_type=Collection.POP, + public_mark__isnull=True, + then=10 + ), + default='public_mark', + output_field=models.FloatField())) + def annotate_mark_similarity(self, mark): """ Return a QuerySet with annotated field - mark_similarity @@ -108,7 +124,7 @@ class EstablishmentQuerySet(models.QuerySet): Similarity mark determined by comparison with compared establishment mark """ return self.annotate(mark_similarity=models.ExpressionWrapper( - mark - models.F('mark'), + mark - models.F('intermediate_public_mark'), output_field=models.FloatField() )) @@ -117,7 +133,8 @@ class EstablishmentQuerySet(models.QuerySet): Return QuerySet with objects that similar to Establishment. :param establishment_slug: str Establishment slug """ - establishment_qs = Establishment.objects.filter(slug=establishment_slug) + establishment_qs = Establishment.objects.filter(slug=establishment_slug, + public_mark__isnull=False) if establishment_qs.exists(): establishment = establishment_qs.first() return self.exclude(slug=establishment_slug) \ @@ -127,11 +144,8 @@ class EstablishmentQuerySet(models.QuerySet): reviews__status=Review.READY, public_mark__gte=10) \ .annotate_distance(point=establishment.address.coordinates) \ - .annotate_mark_similarity(establishment_qs.first().public_mark) - # .annotate_distance_mark() \ - # .annotate_intermediate_public_mark() \ - # .annotate_additional_mark(public_mark=establishment.public_mark) \ - # .annotate_total_mark() + .annotate_intermediate_public_mark() \ + .annotate_mark_similarity(mark=establishment.public_mark) else: return self.none() @@ -225,7 +239,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) slug = models.SlugField(unique=True, max_length=50, null=True, - verbose_name=_('Establishment slug'), editable=True) + verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index ce2bed05..fef7fd5b 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -30,8 +30,11 @@ class EstablishmentSimilarListView(EstablishmentListView): def get_queryset(self): """Override get_queryset method""" - return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug'))\ - .order_by('-total_mark')[:13] + number_objects = 12 # Count of similar objects + return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug')) \ + .order_by('distance')[:number_objects * 3] \ + .order_by('mark_similarity')[:number_objects] + class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): """Resource for getting a establishment.""" From 8766388256438b25cbec076ddbd8a296a9df481a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 25 Sep 2019 13:16:02 +0300 Subject: [PATCH 085/319] fixed similar establishments --- apps/establishment/models.py | 7 +++++-- apps/establishment/views/web.py | 5 +---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 2fbff239..5391d066 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -128,10 +128,11 @@ class EstablishmentQuerySet(models.QuerySet): output_field=models.FloatField() )) - def similar(self, establishment_slug: str): + def similar(self, establishment_slug: str, output_objects: int = 12): """ Return QuerySet with objects that similar to Establishment. :param establishment_slug: str Establishment slug + :param output_objects: int of output objects """ establishment_qs = Establishment.objects.filter(slug=establishment_slug, public_mark__isnull=False) @@ -145,7 +146,9 @@ class EstablishmentQuerySet(models.QuerySet): public_mark__gte=10) \ .annotate_distance(point=establishment.address.coordinates) \ .annotate_intermediate_public_mark() \ - .annotate_mark_similarity(mark=establishment.public_mark) + .annotate_mark_similarity(mark=establishment.public_mark) \ + .order_by('distance') \ + .order_by('mark_similarity')[:output_objects] else: return self.none() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index fef7fd5b..da10300f 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -30,10 +30,7 @@ class EstablishmentSimilarListView(EstablishmentListView): def get_queryset(self): """Override get_queryset method""" - number_objects = 12 # Count of similar objects - return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug')) \ - .order_by('distance')[:number_objects * 3] \ - .order_by('mark_similarity')[:number_objects] + return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug')) class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): From a2ade761b463454ec2c7bfc3ace7a2f93c6f35bf Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 14:04:52 +0300 Subject: [PATCH 086/319] Update web urls --- apps/establishment/urls/common.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index bd53c7eb..99027d12 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -8,13 +8,13 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), - path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), - path('/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), - path('/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), - path('/comments/create/', views.EstablishmentCommentCreateView.as_view(), + path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), + path('slug//similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), + path('slug//comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), + path('slug//comments/create/', views.EstablishmentCommentCreateView.as_view(), name='create-comment'), - path('/comments//', views.EstablishmentCommentRUDView.as_view(), + path('slug//comments//', views.EstablishmentCommentRUDView.as_view(), name='rud-comment'), - path('/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), + path('slug//favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), name='add-to-favorites') ] From f38924db42b8cfe2ddda3a6d40332c7773f1c76b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 25 Sep 2019 14:09:24 +0300 Subject: [PATCH 087/319] Fix favorites creation by slug --- apps/establishment/serializers/common.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7b974887..691f3b43 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -315,20 +315,22 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): def validate(self, attrs): """Override validate method""" # Check establishment object - establishment_id = self.context.get('request').parser_context.get('kwargs').get('pk') - establishment_qs = models.Establishment.objects.filter(id=establishment_id) + establishment_slug = self.context.get('request').parser_context.get('kwargs').get('slug') + establishment_qs = models.Establishment.objects.filter(slug=establishment_slug) - # Check establishment obj by pk from lookup_kwarg + # Check establishment obj by slug from lookup_kwarg if not establishment_qs.exists(): raise serializers.ValidationError({'detail': _('Object not found.')}) + else: + establishment = establishment_qs.first() # Check existence in favorites if self.get_user().favorites.by_content_type(app_label='establishment', model='establishment')\ - .by_object_id(object_id=establishment_id).exists(): + .by_object_id(object_id=establishment.id).exists(): raise utils_exceptions.FavoritesError() - attrs['establishment'] = establishment_qs.first() + attrs['establishment'] = establishment return attrs def create(self, validated_data, *args, **kwargs): From f18c18110cdb46ebc56f8c01b2e65741cc6959e9 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 25 Sep 2019 15:32:45 +0300 Subject: [PATCH 088/319] Refactor some establishment serializer --- apps/establishment/models.py | 31 ++++++++--- apps/establishment/serializers/back.py | 9 ++- apps/establishment/serializers/common.py | 71 +++++++++++------------- apps/establishment/views/back.py | 11 +++- apps/establishment/views/common.py | 15 ----- apps/establishment/views/web.py | 41 +++++++++----- apps/location/serializers/common.py | 17 +++++- 7 files changed, 112 insertions(+), 83 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index e3384dfb..4ab2fe24 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -13,8 +13,9 @@ from phonenumber_field.modelfields import PhoneNumberField from location.models import Address from collection.models import Collection +from main.models import MetaDataContent from review.models import Review -from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, URLImageMixin, +from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -70,6 +71,20 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): class EstablishmentQuerySet(models.QuerySet): """Extended queryset for Establishment model.""" + def with_base_related(self): + """Return qs with related objects.""" + return self.select_related('address').prefetch_related( + models.Prefetch('tags', + MetaDataContent.objects.select_related( + 'metadata__category')) + ) + + def with_extended_related(self): + return self.select_related('establishment_type').\ + prefetch_related('establishment_subtypes', 'awards', 'schedule', + 'phones').\ + prefetch_actual_employees() + def search(self, value, locale=None): """Search text in JSON fields.""" if locale is not None: @@ -228,7 +243,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): name = models.CharField(_('name'), max_length=255, default='') name_translated = models.CharField(_('Transliterated name'), - max_length=255, default='') + max_length=255, default='') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') @@ -312,12 +327,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): return country.low_price, country.high_price # todo: make via prefetch - @property - def subtypes(self): - return EstablishmentSubType.objects.filter( - subtype_establishment=self, - establishment_type=self.establishment_type, - establishment_type__use_subtypes=True) + # @property + # def subtypes(self): + # return EstablishmentSubType.objects.filter( + # subtype_establishment=self, + # establishment_type=self.establishment_type, + # establishment_type__use_subtypes=True) def set_establishment_type(self, establishment_type): self.establishment_type = establishment_type diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 7013fe6a..17d5f1d5 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,12 +1,9 @@ -import json from rest_framework import serializers - from establishment import models -from timetable.models import Timetable from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, - ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer -) + ContactPhonesSerializer, SocialNetworkRelatedSerializers, + EstablishmentTypeSerializer) from main.models import Currency @@ -21,6 +18,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) + type = EstablishmentTypeSerializer(source='establishment_type') class Meta: model = models.Establishment @@ -52,6 +50,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=False, many=True, ) emails = ContactEmailsSerializer(read_only=False, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=False, many=True, ) + type = EstablishmentTypeSerializer(source='establishment_type') class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7b974887..f1b2d5ed 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -1,17 +1,17 @@ """Establishment serializers.""" from rest_framework import serializers - +from django.utils.translation import gettext_lazy as _ from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites -from location.serializers import AddressSerializer +from location.serializers import AddressSimpleSerializer from main.models import MetaDataContent from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions -from django.utils.translation import gettext_lazy as _ +from utils.serializers import TranslatedField class ContactPhonesSerializer(serializers.ModelSerializer): @@ -142,11 +142,11 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): class EstablishmentBaseSerializer(serializers.ModelSerializer): """Base serializer for Establishment model.""" - type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) - subtypes = EstablishmentSubTypeSerializer(many=True) - address = AddressSerializer() - tags = MetaDataContentSerializer(many=True) + + preview_image = serializers.URLField(source='preview_image_url') slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) + address = AddressSimpleSerializer() + tags = MetaDataContentSerializer(many=True) class Meta: """Meta class.""" @@ -159,60 +159,53 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): 'price_level', 'toque_number', 'public_mark', - 'type', - 'subtypes', + 'slug', + 'preview_image', + 'in_favorites', 'address', 'tags', - 'slug', ] class EstablishmentListSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" - # Annotated fields + in_favorites = serializers.BooleanField(allow_null=True) - preview_image = serializers.URLField(source='preview_image_url') - - class Meta: + class Meta(EstablishmentBaseSerializer.Meta): """Meta class.""" - model = models.Establishment fields = EstablishmentBaseSerializer.Meta.fields + [ 'in_favorites', - 'preview_image', ] class EstablishmentDetailSerializer(EstablishmentListSerializer): """Serializer for Establishment model.""" - description_translated = serializers.CharField(allow_null=True) + + description_translated = TranslatedField() + image = serializers.URLField(source='image_url') + type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) + subtypes = EstablishmentSubTypeSerializer(many=True, source='establishment_subtypes') awards = AwardSerializer(many=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) - phones = ContactPhonesSerializer(read_only=True, many=True, ) - emails = ContactEmailsSerializer(read_only=True, many=True, ) + phones = ContactPhonesSerializer(read_only=True, many=True) + emails = ContactEmailsSerializer(read_only=True, many=True) review = serializers.SerializerMethodField() employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', many=True) 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) - slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) - - in_favorites = serializers.SerializerMethodField() - - image = serializers.URLField(source='image_url') - - class Meta: + class Meta(EstablishmentListSerializer.Meta): """Meta class.""" - model = models.Establishment fields = EstablishmentListSerializer.Meta.fields + [ 'description_translated', - 'price_level', 'image', + 'subtypes', + 'type', 'awards', 'schedule', 'website', @@ -228,23 +221,23 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 'best_price_menu', 'best_price_carte', 'transportation', - 'slug', ] + # todo: refactor this s (make as prefetch to model as attr or make as model property) def get_review(self, obj): """Serializer method for getting last published review""" return ReviewSerializer(obj.reviews.by_status(status=review_models.Review.READY) .order_by('-published_at').first()).data - def get_in_favorites(self, obj): - """Get in_favorites status flag""" - user = self.context.get('request').user - if user.is_authenticated: - return obj.id in user.favorites.by_content_type(app_label='establishment', - model='establishment')\ - .values_list('object_id', flat=True) - else: - return False + # def get_in_favorites(self, obj): + # """Get in_favorites status flag""" + # user = self.context.get('request').user + # if user.is_authenticated: + # return obj.id in user.favorites.by_content_type(app_label='establishment', + # model='establishment')\ + # .values_list('object_id', flat=True) + # else: + # return False class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 974fca8a..78526bdd 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -4,10 +4,17 @@ from rest_framework import generics from establishment import models from establishment import serializers -from establishment.views.common import EstablishmentMixin -class EstablishmentListCreateView(EstablishmentMixin, generics.ListCreateAPIView): +class EstablishmentMixinViews: + """Establishment mixin.""" + + def get_queryset(self): + """Overrided method 'get_queryset'.""" + return models.Establishment.objects.published().with_related() + + +class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView): """Establishment list/create view.""" queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer diff --git a/apps/establishment/views/common.py b/apps/establishment/views/common.py index b57d6ef6..3029c080 100644 --- a/apps/establishment/views/common.py +++ b/apps/establishment/views/common.py @@ -1,16 +1 @@ """Establishment app views.""" - -from rest_framework import permissions - -from establishment import models - - -class EstablishmentMixin: - """Establishment mixin.""" - - permission_classes = (permissions.AllowAny,) - - def get_queryset(self): - """Overrided method 'get_queryset'.""" - return models.Establishment.objects.published() \ - .prefetch_actual_employees() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index ce2bed05..3748b95f 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -1,27 +1,47 @@ """Establishment app views.""" - from django.contrib.gis.geos import Point from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from comment import models as comment_models -from establishment import filters -from establishment import models, serializers -from establishment.views import EstablishmentMixin +from establishment import filters, models, serializers from main.models import MetaDataContent from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer -class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): +class EstablishmentMixinView: + """Establishment mixin.""" + + permission_classes = (permissions.AllowAny,) + + def get_queryset(self): + """Overrided method 'get_queryset'.""" + return models.Establishment.objects.published().with_base_related().\ + annotate_in_favorites(self.request.user) + + +class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): """Resource for getting a list of establishments.""" - serializer_class = serializers.EstablishmentListSerializer + filter_class = filters.EstablishmentFilter + serializer_class = serializers.EstablishmentListSerializer def get_queryset(self): """Overridden method 'get_queryset'.""" qs = super(EstablishmentListView, self).get_queryset() - return qs.by_country_code(code=self.request.country_code) \ - .annotate_in_favorites(user=self.request.user) + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs + + +class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): + """Resource for getting a establishment.""" + + lookup_field = 'slug' + serializer_class = serializers.EstablishmentDetailSerializer + + def get_queryset(self): + return super().get_queryset().with_extended_related() class EstablishmentSimilarListView(EstablishmentListView): @@ -33,11 +53,6 @@ class EstablishmentSimilarListView(EstablishmentListView): return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug'))\ .order_by('-total_mark')[:13] -class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): - """Resource for getting a establishment.""" - lookup_field = 'slug' - serializer_class = serializers.EstablishmentDetailSerializer - class EstablishmentTypeListView(generics.ListAPIView): """Resource for getting a list of establishment types.""" diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 555cf499..1dee92ed 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -1,7 +1,6 @@ """Location app common serializers.""" from django.contrib.gis.geos import Point from rest_framework import serializers - from location import models from utils.serializers import TranslatedField @@ -86,6 +85,7 @@ class CitySerializer(serializers.ModelSerializer): class AddressSerializer(serializers.ModelSerializer): """Address serializer.""" + city_id = serializers.PrimaryKeyRelatedField( source='city', queryset=models.City.objects.all()) @@ -128,3 +128,18 @@ class AddressSerializer(serializers.ModelSerializer): setattr(instance, 'geo_lon', float(0)) return super().to_representation(instance) + +class AddressSimpleSerializer(serializers.ModelSerializer): + """Serializer for address obj in related objects.""" + + class Meta: + """Meta class.""" + + model = models.Address + fields = ( + 'id', + 'street_name_1', + 'street_name_2', + 'number', + 'postal_code', + ) From 07a0f8e295e37d954f069f5a1ab1fce5565ee42f Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 17:34:11 +0300 Subject: [PATCH 089/319] Temp commit --- apps/establishment/tests.py | 54 +++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index e4e3b02c..3b5a44dc 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -27,7 +27,7 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") -class EstablishmentTests(BaseTestCase): +class EstablishmentBTests(BaseTestCase): def test_establishment_CRUD(self): params = {'page': 1, 'page_size': 1,} response = self.client.get('/api/back/establishments/', params, format='json') @@ -93,7 +93,8 @@ class ChildTestCase(BaseTestCase): self.establishment = Establishment.objects.create( name="Test establishment", establishment_type_id=self.establishment_type.id, - is_publish=True + is_publish=True, + slug="test" ) @@ -263,3 +264,52 @@ class EstablishmentShedulerTests(ChildTestCase): response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +# Web tests +class EstablishmentWebTests(BaseTestCase): + + def test_establishment_Read(self): + params = {'page': 1, 'page_size': 1,} + response = self.client.get('/api/web/establishments/', params, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class EstablishmentWebTagTests(BaseTestCase): + + def test_tag_Read(self): + response = self.client.get('/api/web/establishments/tags/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class EstablishmentWebSlugTests(ChildTestCase): + + def test_slug_Read(self): + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class EstablishmentWebSimilarTests(ChildTestCase): + + def test_similar_Read(self): + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/similar/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class EstablishmentWebCommentsTests(ChildTestCase): + + def test_comments_Read(self): + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + + + + + +# class EstablishmentWebFavoriteTests(ChildTestCase): +# +# def test_comments_Read(self): +# response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', format='json') +# self.assertEqual(response.status_code, status.HTTP_200_OK) From cabee12fab98e3ebb22d2c0e452908a7d554f1fa Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 18:58:52 +0300 Subject: [PATCH 090/319] Add comment tests Add favorite tests --- apps/establishment/tests.py | 51 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 3b5a44dc..a001b055 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -298,18 +298,55 @@ class EstablishmentWebSimilarTests(ChildTestCase): class EstablishmentWebCommentsTests(ChildTestCase): - def test_comments_Read(self): + def test_comments_CRUD(self): + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + data = { + 'text': 'test', + 'user': self.user.id, + 'mark': 4 + } + + response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/comments/create/', + data=data) + + comment = response.json() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + update_data = { + 'text': 'Test new establishment' + } + + response = self.client.patch(f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + data=update_data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.delete( + f'/api/web/establishments/slug/{self.establishment.slug}/comments/{comment["id"]}/', + format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) +class EstablishmentWebFavoriteTests(ChildTestCase): + def test_favorite_cd(self): + data = { + "user": self.user.id, + "object_id": self.establishment.id + } + response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', + data=data) + print(f"================================RESPONSE: {response.json()}") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) - -# class EstablishmentWebFavoriteTests(ChildTestCase): -# -# def test_comments_Read(self): -# response = self.client.get(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', format='json') -# self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.delete( + f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', + format='json') + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) From d8dd1fd7d669a09b93b70ec4cbf2f372fee02677 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 25 Sep 2019 19:00:10 +0300 Subject: [PATCH 091/319] Fix carousel field types --- apps/main/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 10d75f2b..c981f0ee 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -136,8 +136,8 @@ class CarouselListSerializer(serializers.ModelSerializer): """Serializer for retrieving list of carousel items.""" model_name = serializers.CharField() name = serializers.CharField() - toque_number = serializers.CharField() - public_mark = serializers.CharField() + toque_number = serializers.IntegerField() + public_mark = serializers.IntegerField() image = serializers.URLField(source='image_url') awards = AwardBaseSerializer(many=True) vintage_year = serializers.IntegerField() From 207e35feb143ea38afbaffb2a52abe16a11d38c1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 25 Sep 2019 19:39:37 +0300 Subject: [PATCH 092/319] GM-127: in progress --- apps/establishment/models.py | 37 +++++++++++++----------- apps/establishment/serializers/common.py | 19 ++---------- apps/establishment/views/web.py | 10 ++++++- apps/review/models.py | 4 +++ apps/utils/pagination.py | 7 +++++ project/settings/base.py | 2 ++ 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5391d066..666b7194 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,18 +1,20 @@ """Establishment models.""" from functools import reduce -from django.contrib.gis.db.models.functions import Distance -from django.contrib.gis.measure import Distance as DistanceMeasure -from django.contrib.gis.geos import Point +from django.conf import settings from django.contrib.contenttypes import fields as generic +from django.contrib.gis.db.models.functions import Distance +from django.contrib.gis.geos import Point +from django.contrib.gis.measure import Distance as DistanceMeasure from django.core.exceptions import ValidationError from django.db import models +from django.db.models import When, Case, F, ExpressionWrapper, Subquery from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField -from location.models import Address from collection.models import Collection +from location.models import Address from review.models import Review from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -128,27 +130,28 @@ class EstablishmentQuerySet(models.QuerySet): output_field=models.FloatField() )) - def similar(self, establishment_slug: str, output_objects: int = 12): + def similar(self, establishment_slug: str): """ Return QuerySet with objects that similar to Establishment. :param establishment_slug: str Establishment slug - :param output_objects: int of output objects """ establishment_qs = Establishment.objects.filter(slug=establishment_slug, public_mark__isnull=False) if establishment_qs.exists(): establishment = establishment_qs.first() - return self.exclude(slug=establishment_slug) \ - .filter(is_publish=True, - image_url__isnull=False, - reviews__isnull=False, - reviews__status=Review.READY, - public_mark__gte=10) \ - .annotate_distance(point=establishment.address.coordinates) \ - .annotate_intermediate_public_mark() \ - .annotate_mark_similarity(mark=establishment.public_mark) \ - .order_by('distance') \ - .order_by('mark_similarity')[:output_objects] + subquery_filter_by_distance = Subquery( + self.exclude(slug=establishment_slug) + .filter(image_url__isnull=False, public_mark__gte=10) + .published() + .has_published_reviews() + .annotate_distance(point=establishment.address.coordinates) + .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] + .values('id') + ) + return Establishment.objects.filter(id__in=subquery_filter_by_distance) \ + .annotate_intermediate_public_mark() \ + .annotate_mark_similarity(mark=establishment.public_mark) \ + .order_by('mark_similarity') else: return self.none() diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7b974887..1f001886 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -191,7 +191,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): schedule = ScheduleRUDSerializer(many=True, allow_null=True) phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) - review = serializers.SerializerMethodField() + review = ReviewSerializer(source='last_published_review', allow_null=True) employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', many=True) menu = MenuSerializers(source='menu_set', many=True, read_only=True) @@ -201,7 +201,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) - in_favorites = serializers.SerializerMethodField() + in_favorites = serializers.BooleanField() image = serializers.URLField(source='image_url') @@ -231,21 +231,6 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 'slug', ] - def get_review(self, obj): - """Serializer method for getting last published review""" - return ReviewSerializer(obj.reviews.by_status(status=review_models.Review.READY) - .order_by('-published_at').first()).data - - def get_in_favorites(self, obj): - """Get in_favorites status flag""" - user = self.context.get('request').user - if user.is_authenticated: - return obj.id in user.favorites.by_content_type(app_label='establishment', - model='establishment')\ - .values_list('object_id', flat=True) - else: - return False - class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): """Create comment serializer""" diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index da10300f..5a69bd87 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -9,6 +9,7 @@ from establishment import filters from establishment import models, serializers from establishment.views import EstablishmentMixin from main.models import MetaDataContent +from utils.pagination import EstablishmentPagination from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer @@ -27,10 +28,12 @@ class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): class EstablishmentSimilarListView(EstablishmentListView): """Resource for getting a list of establishments.""" serializer_class = serializers.EstablishmentListSerializer + pagination_class = EstablishmentPagination def get_queryset(self): """Override get_queryset method""" - return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug')) + return super().get_queryset() \ + .similar(establishment_slug=self.kwargs.get('slug')) class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): @@ -38,6 +41,11 @@ class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): lookup_field = 'slug' serializer_class = serializers.EstablishmentDetailSerializer + def get_queryset(self): + """Override 'get_queryset' method.""" + return super(EstablishmentRetrieveView, self).get_queryset() \ + .annotate_in_favorites(self.request.user) + class EstablishmentTypeListView(generics.ListAPIView): """Resource for getting a list of establishment types.""" diff --git a/apps/review/models.py b/apps/review/models.py index d0076f68..9d3a39c4 100644 --- a/apps/review/models.py +++ b/apps/review/models.py @@ -23,6 +23,10 @@ class ReviewQuerySet(models.QuerySet): """Filter by status""" return self.filter(status=status) + def published(self): + """Return published reviews""" + return self.filter(status=Review.READY) + class Review(BaseAttributes, TranslatedFieldsMixin): """Review model""" diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index 85bd293c..efaf329e 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -44,3 +44,10 @@ class ProjectMobilePagination(ProjectPageNumberPagination): if not self.page.has_previous(): return None return self.page.previous_page_number() + + +class EstablishmentPagination(ProjectMobilePagination): + """ + Pagination for app establishments with limit page size equal to 12 + """ + page_size = 12 diff --git a/project/settings/base.py b/project/settings/base.py index 4aec6f4c..2007d908 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -412,3 +412,5 @@ SOLO_CACHE_TIMEOUT = 300 SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_NAME = 'Gault & Millau' + +LIMITING_QUERY_NUMBER = 36 # Need to restrict objects to sort (used in establishments) From 877d3d9e6a4233cdfa8e10b146ec0b41a74c9439 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 25 Sep 2019 19:44:33 +0300 Subject: [PATCH 093/319] Fix test Add todo to fix error --- apps/establishment/models.py | 3 +++ apps/establishment/tests.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index e3384dfb..dc596600 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -174,6 +174,9 @@ class EstablishmentQuerySet(models.QuerySet): establishment_qs = Establishment.objects.filter(slug=establishment_slug) if establishment_qs.exists(): establishment = establishment_qs.first() + + # TODO fix error: + # AttributeError: 'NoneType' object has no attribute 'coordinates' return self.exclude(slug=establishment_slug) \ .filter(is_publish=True, image_url__isnull=False, diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index a001b055..f80cb448 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -343,7 +343,7 @@ class EstablishmentWebFavoriteTests(ChildTestCase): response = self.client.post(f'/api/web/establishments/slug/{self.establishment.slug}/favorites/', data=data) - print(f"================================RESPONSE: {response.json()}") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) response = self.client.delete( From c2bd4b7da83a17a9668c5bf8311c7e9b98d36a1b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 25 Sep 2019 19:58:25 +0300 Subject: [PATCH 094/319] add slug && preview_image_url to search results --- apps/search_indexes/documents/establishment.py | 2 ++ apps/search_indexes/serializers.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 10f95d6c..16321723 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -84,6 +84,8 @@ class EstablishmentDocument(Document): 'name', 'toque_number', 'price_level', + 'preview_image_url', + 'slug', ) def get_queryset(self): diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index e6950bdd..480f509d 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -63,6 +63,8 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'collections', 'establishment_type', 'establishment_subtypes', + 'preview_image_url', + 'slug', ) @staticmethod From 3279fce33917d50b4f27f5ce365f187d66fef6a3 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Thu, 26 Sep 2019 11:19:06 +0300 Subject: [PATCH 095/319] Update tests --- apps/establishment/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index f80cb448..90a8f80c 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -335,7 +335,7 @@ class EstablishmentWebCommentsTests(ChildTestCase): class EstablishmentWebFavoriteTests(ChildTestCase): - def test_favorite_cd(self): + def test_favorite_CR(self): data = { "user": self.user.id, "object_id": self.establishment.id From 86d754f4bb9ea85a75e17637f5be64775290b427 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, 26 Sep 2019 11:38:37 +0300 Subject: [PATCH 096/319] validate json translated field --- apps/establishment/serializers/back.py | 3 +- apps/establishment/serializers/common.py | 8 ++--- apps/establishment/tests.py | 1 - apps/utils/models.py | 16 +++++++--- apps/utils/serializers.py | 25 ++++++++++++++++ apps/utils/tests.py | 38 ++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 7013fe6a..95491bec 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -8,6 +8,7 @@ from establishment.serializers import ( ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer ) from main.models import Currency +from utils.serializers import TJSONSerializer class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -86,7 +87,7 @@ class SocialNetworkSerializers(serializers.ModelSerializer): class PlatesSerializers(PlateSerializer): """Social network serializers.""" - name = serializers.JSONField() + name = TJSONSerializer currency_id = serializers.PrimaryKeyRelatedField( source='currency', queryset=Currency.objects.all(), write_only=True diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7b974887..d736ee99 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -12,7 +12,7 @@ from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions from django.utils.translation import gettext_lazy as _ - +from utils.serializers import TJSONSerializer class ContactPhonesSerializer(serializers.ModelSerializer): """Contact phone serializer""" @@ -60,7 +60,7 @@ class PlateSerializer(serializers.ModelSerializer): class MenuSerializers(serializers.ModelSerializer): plates = PlateSerializer(read_only=True, many=True, source='plate_set') - category = serializers.JSONField() + category = TJSONSerializer() category_translated = serializers.CharField(read_only=True) class Meta: @@ -74,9 +74,9 @@ class MenuSerializers(serializers.ModelSerializer): ] -class MenuRUDSerializers(serializers.ModelSerializer): +class MenuRUDSerializers(serializers.ModelSerializer, ): plates = PlateSerializer(read_only=True, many=True, source='plate_set') - category = serializers.JSONField() + category = TJSONSerializer() class Meta: model = models.Menu diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index e4e3b02c..1f3151fd 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -5,7 +5,6 @@ from rest_framework import status from http.cookies import SimpleCookie from main.models import Currency from establishment.models import Establishment, EstablishmentType, Menu - # Create your tests here. diff --git a/apps/utils/models.py b/apps/utils/models.py index 632cf4a2..4e6df35e 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -10,6 +10,8 @@ from django.utils.translation import ugettext_lazy as _, get_language from easy_thumbnails.fields import ThumbnailerImageField from utils.methods import image_path, svg_image_path from utils.validators import svg_image_validator +from django.db.models.fields import Field +from django.core import exceptions class ProjectBaseMixin(models.Model): @@ -26,6 +28,10 @@ class ProjectBaseMixin(models.Model): abstract = True +def valid(value): + print("Run") + + class TJSONField(JSONField): """Overrided JsonField.""" @@ -52,6 +58,7 @@ def translate_field(self, field_name): if isinstance(field, dict): return field.get(to_locale(get_language())) return None + return translate @@ -70,6 +77,7 @@ def index_field(self, field_name): for key, value in field.items(): setattr(obj, key, value) return obj + return index @@ -236,7 +244,8 @@ class LocaleManagerMixin(models.Manager): queryset = self.filter(**filters) # Prepare field for annotator - localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in fields} + localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in + fields} # Annotate them for _ in fields: @@ -245,7 +254,6 @@ class LocaleManagerMixin(models.Manager): class GMTokenGenerator(PasswordResetTokenGenerator): - CHANGE_EMAIL = 0 RESET_PASSWORD = 1 CHANGE_PASSWORD = 2 @@ -268,10 +276,10 @@ class GMTokenGenerator(PasswordResetTokenGenerator): """ fields = [str(timestamp), str(user.is_active), str(user.pk)] if self.purpose == self.CHANGE_EMAIL or \ - self.purpose == self.CONFIRM_EMAIL: + self.purpose == self.CONFIRM_EMAIL: fields.extend([str(user.email_confirmed), str(user.email)]) elif self.purpose == self.RESET_PASSWORD or \ - self.purpose == self.CHANGE_PASSWORD: + self.purpose == self.CHANGE_PASSWORD: fields.append(str(user.password)) return fields diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 1f4c6f96..d30a046c 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -1,6 +1,8 @@ """Utils app serializer.""" from rest_framework import serializers from utils.models import PlatformMixin +from django.core import exceptions +from translation.models import Language class EmptySerializer(serializers.Serializer): @@ -21,3 +23,26 @@ class TranslatedField(serializers.CharField): **kwargs): super().__init__(allow_null=allow_null, required=required, read_only=read_only, **kwargs) + + +def validate_tjson(value): + + if not isinstance(value, dict): + raise exceptions.ValidationError( + 'invalid_json', + code='invalid_json', + params={'value': value}, + ) + + lang_count = Language.objects.filter(locale__in=value.keys()).count() + + if lang_count == 0: + raise exceptions.ValidationError( + 'invalid_translated_keys', + code='invalid_translated_keys', + params={'value': value}, + ) + + +class TJSONSerializer(serializers.JSONField): + validators = [validate_tjson] diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 1c9fa71d..81fa02a3 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -8,6 +8,11 @@ from http.cookies import SimpleCookie from account.models import User from news.models import News, NewsType +from django.test import TestCase +from translation.models import Language +from django.core import exceptions + +from .serializers import validate_tjson class BaseTestCase(APITestCase): @@ -62,3 +67,36 @@ class TranslateFieldReview(BaseTestCase): self.assertIn("title_translated", news_data) self.assertEqual(news_data['title_translated'], "Test news item") + + +class ValidJSONTest(TestCase): + + def test_valid_json(self): + lang = Language.objects.create(title='English', locale='en-GB') + lang.save() + + data = 'str' + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_json') + + data = { + "string": "value" + } + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_translated_keys') + + data = { + "en-GB": "English" + } + + try: + validate_tjson(data) + self.assertTrue(True) + except exceptions.ValidationError: + self.assert_(False, "Test json translated FAILED") From 6ea876cdc60b6a6022b8f7b48661ada8166f6071 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 26 Sep 2019 11:39:48 +0300 Subject: [PATCH 097/319] test small fix --- apps/collection/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 6a985fc0..4d5ba4ce 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -20,7 +20,7 @@ class BaseTestCase(APITestCase): self.newsletter = True self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - #get tokens + # get tokens tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokens.get('access_token'), From 7c0e5057983f5b118723ac8ec810b3b9decb7c9d 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, 26 Sep 2019 11:48:05 +0300 Subject: [PATCH 098/319] commit changes --- apps/utils/{!tests.py => tests.py} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename apps/utils/{!tests.py => tests.py} (98%) diff --git a/apps/utils/!tests.py b/apps/utils/tests.py similarity index 98% rename from apps/utils/!tests.py rename to apps/utils/tests.py index 13058144..0ca77b6b 100644 --- a/apps/utils/!tests.py +++ b/apps/utils/tests.py @@ -8,11 +8,9 @@ from http.cookies import SimpleCookie from account.models import User from news.models import News, NewsType - from django.test import TestCase from translation.models import Language from django.core import exceptions - from .serializers import validate_tjson from establishment.models import Establishment, EstablishmentType, Employee @@ -126,6 +124,7 @@ class BaseAttributeTests(BaseTestCase): self.assertEqual(modify_user, employee.modified_by) self.assertEqual(self.user, employee.created_by) + class ValidJSONTest(TestCase): def test_valid_json(self): From 0e0d6e318e1593716203273a559d1529b8fa5749 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 26 Sep 2019 14:12:49 +0300 Subject: [PATCH 099/319] GM-127: finished --- apps/establishment/models.py | 19 +++++++++---------- apps/establishment/views/web.py | 10 ++++++---- apps/main/methods.py | 16 +++++++++++++++- apps/utils/middleware.py | 11 ----------- apps/utils/pagination.py | 6 ++++-- project/settings/base.py | 11 ++++++++++- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 666b7194..aed78d9f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -114,7 +114,7 @@ class EstablishmentQuerySet(models.QuerySet): models.When( collections__collection_type=Collection.POP, public_mark__isnull=True, - then=10 + then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK ), default='public_mark', output_field=models.FloatField())) @@ -135,23 +135,22 @@ class EstablishmentQuerySet(models.QuerySet): Return QuerySet with objects that similar to Establishment. :param establishment_slug: str Establishment slug """ - establishment_qs = Establishment.objects.filter(slug=establishment_slug, - public_mark__isnull=False) + establishment_qs = self.filter(slug=establishment_slug, + public_mark__isnull=False) if establishment_qs.exists(): establishment = establishment_qs.first() subquery_filter_by_distance = Subquery( self.exclude(slug=establishment_slug) .filter(image_url__isnull=False, public_mark__gte=10) - .published() .has_published_reviews() - .annotate_distance(point=establishment.address.coordinates) + .annotate_distance(point=establishment.location) .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] .values('id') ) - return Establishment.objects.filter(id__in=subquery_filter_by_distance) \ - .annotate_intermediate_public_mark() \ - .annotate_mark_similarity(mark=establishment.public_mark) \ - .order_by('mark_similarity') + return self.filter(id__in=subquery_filter_by_distance) \ + .annotate_intermediate_public_mark() \ + .annotate_mark_similarity(mark=establishment.public_mark) \ + .order_by('mark_similarity') else: return self.none() @@ -168,7 +167,7 @@ class EstablishmentQuerySet(models.QuerySet): favorite_establishments = [] if user.is_authenticated: favorite_establishments = user.favorites.by_content_type(app_label='establishment', - model='establishment')\ + model='establishment') \ .values_list('object_id', flat=True) return self.annotate(in_favorites=models.Case( models.When( diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 5a69bd87..62b12016 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -1,5 +1,6 @@ """Establishment app views.""" +from django.conf import settings from django.contrib.gis.geos import Point from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions @@ -8,9 +9,10 @@ from comment import models as comment_models from establishment import filters from establishment import models, serializers from establishment.views import EstablishmentMixin +from main import methods from main.models import MetaDataContent -from utils.pagination import EstablishmentPagination from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer +from utils.pagination import EstablishmentPortionPagination class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): @@ -28,12 +30,12 @@ class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): class EstablishmentSimilarListView(EstablishmentListView): """Resource for getting a list of establishments.""" serializer_class = serializers.EstablishmentListSerializer - pagination_class = EstablishmentPagination + pagination_class = EstablishmentPortionPagination def get_queryset(self): """Override get_queryset method""" - return super().get_queryset() \ - .similar(establishment_slug=self.kwargs.get('slug')) + qs = super().get_queryset() + return qs.similar(establishment_slug=self.kwargs.get('slug')) class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): diff --git a/apps/main/methods.py b/apps/main/methods.py index e9ec780c..67da3480 100644 --- a/apps/main/methods.py +++ b/apps/main/methods.py @@ -1,9 +1,10 @@ """Main app methods.""" import logging + from django.conf import settings from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception -from main import models +from main import models logger = logging.getLogger(__name__) @@ -38,6 +39,19 @@ def determine_country_code(ip_addr): return country_code +def determine_coordinates(ip_addr): + longitude, latitude = None, None + if ip_addr: + try: + geoip = GeoIP2() + longitude, latitude = geoip.coords(ip_addr) + except GeoIP2Exception as ex: + logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}') + except Exception as ex: + logger.error(f'GEOIP Base exception: {ex}') + return longitude, latitude + + def determine_user_site_url(country_code): """Determine user's site url.""" try: diff --git a/apps/utils/middleware.py b/apps/utils/middleware.py index 7203127c..096f8474 100644 --- a/apps/utils/middleware.py +++ b/apps/utils/middleware.py @@ -31,14 +31,3 @@ def parse_cookies(get_response): response = get_response(request) return response return middleware - - -class CORSMiddleware: - """Added parameter {Access-Control-Allow-Origin: *} to response""" - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - response = self.get_response(request) - response["Access-Control-Allow-Origin"] = '*' - return response diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index efaf329e..2c9e92e5 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -1,6 +1,8 @@ """Pagination settings.""" from base64 import b64encode from urllib import parse as urlparse + +from django.conf import settings from rest_framework.pagination import PageNumberPagination, CursorPagination @@ -46,8 +48,8 @@ class ProjectMobilePagination(ProjectPageNumberPagination): return self.page.previous_page_number() -class EstablishmentPagination(ProjectMobilePagination): +class EstablishmentPortionPagination(ProjectMobilePagination): """ Pagination for app establishments with limit page size equal to 12 """ - page_size = 12 + page_size = settings.LIMITING_OUTPUT_OBJECTS diff --git a/project/settings/base.py b/project/settings/base.py index 2007d908..10eb3544 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -413,4 +413,13 @@ SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_NAME = 'Gault & Millau' -LIMITING_QUERY_NUMBER = 36 # Need to restrict objects to sort (used in establishments) +# Used in annotations for establishments. +DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10 +# Limit output objects (see in pagination classes). +LIMITING_OUTPUT_OBJECTS = 12 +# Need to restrict objects to sort (3 times more then expected). +LIMITING_QUERY_NUMBER = LIMITING_OUTPUT_OBJECTS * 3 + +# GEO +# A Spatial Reference System Identifier +GEO_DEFAULT_SRID = 4326 From c4d8b89a8db7feb1cd9c92c278f10e0276a14886 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 26 Sep 2019 14:19:01 +0300 Subject: [PATCH 100/319] gm-127: finished --- apps/establishment/models.py | 55 ++++++++++++++++++++++++------ apps/establishment/urls/common.py | 2 ++ apps/establishment/views/common.py | 2 +- apps/establishment/views/web.py | 19 +++++++++++ project/settings/base.py | 33 +++++++++++++----- 5 files changed, 90 insertions(+), 21 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index aed78d9f..9334e4b7 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -93,15 +93,20 @@ class EstablishmentQuerySet(models.QuerySet): """ return self.filter(is_publish=True) - def annotate_distance(self, point: Point): + def has_published_reviews(self): + """ + Return QuerySet establishments with published reviews. + """ + return self.filter(reviews__status=Review.READY,) + + def annotate_distance(self, point: Point = None): """ Return QuerySet with annotated field - distance Description: """ - return self.annotate(distance=models.Value( - DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m, - output_field=models.FloatField())) + return self.annotate(distance=Distance('address__coordinates', point, + srid=settings.GEO_DEFAULT_SRID)) def annotate_intermediate_public_mark(self): """ @@ -110,8 +115,8 @@ class EstablishmentQuerySet(models.QuerySet): If establishments in collection POP and its mark is null, then intermediate_mark is set to 10; """ - return self.annotate(intermediate_public_mark=models.Case( - models.When( + return self.annotate(intermediate_public_mark=Case( + When( collections__collection_type=Collection.POP, public_mark__isnull=True, then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK @@ -125,8 +130,8 @@ class EstablishmentQuerySet(models.QuerySet): Description: Similarity mark determined by comparison with compared establishment mark """ - return self.annotate(mark_similarity=models.ExpressionWrapper( - mark - models.F('intermediate_public_mark'), + return self.annotate(mark_similarity=ExpressionWrapper( + mark - F('intermediate_public_mark'), output_field=models.FloatField() )) @@ -154,6 +159,21 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() + def last_reviewed(self, point: Point): + """ + Return QuerySet with last reviewed establishments. + :param point: location Point object, needs to ordering + """ + subquery_filter_by_distance = Subquery( + self.filter(image_url__isnull=False, public_mark__gte=10) + .has_published_reviews() + .annotate_distance(point=point) + .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] + .values('id') + ) + return self.filter(id__in=subquery_filter_by_distance) \ + .order_by('-reviews__published_at') + def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( @@ -169,8 +189,8 @@ class EstablishmentQuerySet(models.QuerySet): favorite_establishments = user.favorites.by_content_type(app_label='establishment', model='establishment') \ .values_list('object_id', flat=True) - return self.annotate(in_favorites=models.Case( - models.When( + return self.annotate(in_favorites=Case( + When( id__in=favorite_establishments, then=True), default=False, @@ -194,7 +214,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): name = models.CharField(_('name'), max_length=255, default='') name_translated = models.CharField(_('Transliterated name'), - max_length=255, default='') + max_length=255, default='') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') @@ -308,6 +328,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): return [{'id': tag.metadata.id, 'label': tag.metadata.label} for tag in self.tags.all()] + @property + def last_published_review(self): + """Return last published review""" + return self.reviews.published()\ + .order_by('-published_at').first() + + @property + def location(self): + """ + Return Point object of establishment location + """ + return self.address.coordinates + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index bd53c7eb..c1161e92 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -8,6 +8,8 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), + path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), + name='recent-reviews'), path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), path('/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), diff --git a/apps/establishment/views/common.py b/apps/establishment/views/common.py index b57d6ef6..922882da 100644 --- a/apps/establishment/views/common.py +++ b/apps/establishment/views/common.py @@ -11,6 +11,6 @@ class EstablishmentMixin: permission_classes = (permissions.AllowAny,) def get_queryset(self): - """Overrided method 'get_queryset'.""" + """Overridden method 'get_queryset'.""" return models.Establishment.objects.published() \ .prefetch_actual_employees() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 62b12016..22ef3abc 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -27,6 +27,25 @@ class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): .annotate_in_favorites(user=self.request.user) +class EstablishmentRecentReviewListView(EstablishmentListView): + """List view for last reviewed establishments.""" + pagination_class = EstablishmentPortionPagination + + def get_queryset(self): + """Overridden method 'get_queryset'.""" + qs = super().get_queryset() + user_ip = methods.get_user_ip(self.request) + query_params = self.request.query_params + if 'longitude' in query_params and 'latitude' in query_params: + longitude, latitude = query_params.get('longitude'), query_params.get('latitude') + else: + longitude, latitude = methods.determine_coordinates(user_ip) + if not longitude or not latitude: + return qs.none() + point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID) + return qs.last_reviewed(point=point) + + class EstablishmentSimilarListView(EstablishmentListView): """Resource for getting a list of establishments.""" serializer_class = serializers.EstablishmentListSerializer diff --git a/project/settings/base.py b/project/settings/base.py index 10eb3544..cfea18a5 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -327,15 +327,30 @@ FCM_DJANGO_SETTINGS = { # Thumbnail settings THUMBNAIL_ALIASES = { - '': { - 'tiny': {'size': (100, 0), }, - 'small': {'size': (480, 0), }, - 'middle': {'size': (700, 0), }, - 'large': {'size': (1500, 0), }, - 'default': {'size': (300, 200), 'crop': True}, - 'gallery': {'size': (240, 160), 'crop': True}, - 'establishment_preview': {'size': (300, 280), 'crop': True}, - } + 'news_preview': { + 'web': {'size': (300, 260), } + }, + 'news_promo_horizontal': { + 'web': {'size': (1900, 600), }, + 'mobile': {'size': (375, 260), }, + }, + 'news_tile_horizontal': { + 'web': {'size': (300, 275), }, + 'mobile': {'size': (343, 180), }, + }, + 'news_tile_vertical': { + 'web': {'size': (300, 380), }, + }, + 'news_highlight_vertical': { + 'web': {'size': (460, 630), }, + }, + 'news_editor': { + 'web': {'size': (940, 430), }, # при загрузке через контент эдитор + 'mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe + }, + 'avatar_comments': { + 'web': {'size': (116, 116), }, + }, } # Password reset From f21b4005aa15e5b51cbb03d3f758f8854aebdaf4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 16:39:30 +0300 Subject: [PATCH 101/319] address coordinates in get api queries --- apps/location/models.py | 4 ++++ apps/location/serializers/common.py | 20 ++++++++++---------- apps/search_indexes/serializers.py | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/location/models.py b/apps/location/models.py index 7084385f..b0aa298c 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -104,6 +104,10 @@ class Address(models.Model): def longitude(self): return self.coordinates.x + @property + def location(self): + return self.location_field_indexing + @property def location_field_indexing(self): return {'lat': self.latitude, diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 555cf499..0b8ac7ed 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -90,8 +90,9 @@ class AddressSerializer(serializers.ModelSerializer): source='city', queryset=models.City.objects.all()) city = CitySerializer(read_only=True) - geo_lon = serializers.FloatField(allow_null=True) - geo_lat = serializers.FloatField(allow_null=True) + geo_lon = serializers.FloatField(allow_null=True, write_only=True) + geo_lat = serializers.FloatField(allow_null=True, write_only=True) + location = serializers.ReadOnlyField() class Meta: model = models.Address @@ -104,7 +105,8 @@ class AddressSerializer(serializers.ModelSerializer): 'number', 'postal_code', 'geo_lon', - 'geo_lat' + 'geo_lat', + 'location', ] def validate(self, attrs): @@ -119,12 +121,10 @@ class AddressSerializer(serializers.ModelSerializer): def to_representation(self, instance): """Override to_representation method""" - if instance.coordinates and isinstance(instance.coordinates, Point): - # Point(longitude, latitude) - setattr(instance, 'geo_lat', instance.coordinates.x) - setattr(instance, 'geo_lon', instance.coordinates.y) - else: - setattr(instance, 'geo_lat', float(0)) - setattr(instance, 'geo_lon', float(0)) + if not instance.coordinates or not isinstance(instance.coordinates, Point): + setattr(instance, 'location', { + 'lat': float(0), + 'lon': float(0), + }) return super().to_representation(instance) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 480f509d..a2ff8d53 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -53,7 +53,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): fields = ( 'id', 'name', - 'description', + # 'description', 'public_mark', 'toque_number', 'price_level', From 441c16281d6c51a1f879fdb538eab5df816f8181 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:23:55 +0300 Subject: [PATCH 102/319] format api elastic results --- apps/search_indexes/serializers.py | 11 +++++++++++ apps/search_indexes/utils.py | 2 ++ apps/utils/tests.py | 5 +++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index a2ff8d53..56af9f5b 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -70,3 +70,14 @@ class EstablishmentDocumentSerializer(DocumentSerializer): @staticmethod def get_description_translated(obj): return get_translated_value(obj.description) + + def to_representation(self, instance): + ret = super().to_representation(instance) + + dict_merge = lambda a, b: a.update(b) or a + ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), ret['tags']) + ret['establishment_subtypes'] = map(lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), ret['establishment_subtypes']) + if ret.get('establishment_type'): + ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name')) + ret['address']['city']['country']['name_translated'] = get_translated_value(ret['address']['city']['country'].pop('name')) + return ret \ No newline at end of file diff --git a/apps/search_indexes/utils.py b/apps/search_indexes/utils.py index 93be9e81..0c1dd187 100644 --- a/apps/search_indexes/utils.py +++ b/apps/search_indexes/utils.py @@ -17,4 +17,6 @@ def get_translated_value(value): return None elif not isinstance(value, dict): field_dict = value.to_dict() + elif isinstance(value, dict): + field_dict = value return field_dict.get(get_current_language()) diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 0ca77b6b..5f8df6d8 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -55,14 +55,15 @@ class TranslateFieldTests(BaseTestCase): start=datetime.now(pytz.utc), end=datetime.now(pytz.utc), is_publish=True, - news_type=self.news_type + news_type=self.news_type, + slug='test', ) def test_model_field(self): self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) def test_read_locale(self): - response = self.client.get(f"/api/web/news/{self.news_item.id}/", format='json') + response = self.client.get(f"/api/web/news/{self.news_item.slug}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) news_data = response.json() From 2fe0ce86d3a6a733fd27716eaaa047d9e365b9a5 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:40:09 +0300 Subject: [PATCH 103/319] Refactor elastic serializer --- apps/search_indexes/serializers.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 56af9f5b..73652ef6 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -46,6 +46,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): description_translated = serializers.SerializerMethodField(allow_null=True) + preview_image = serializers.URLField(source='preview_image_url') class Meta: """Meta class.""" @@ -63,7 +64,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'collections', 'establishment_type', 'establishment_subtypes', - 'preview_image_url', + 'preview_image', 'slug', ) @@ -73,11 +74,19 @@ class EstablishmentDocumentSerializer(DocumentSerializer): def to_representation(self, instance): ret = super().to_representation(instance) - dict_merge = lambda a, b: a.update(b) or a - ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), ret['tags']) - ret['establishment_subtypes'] = map(lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), ret['establishment_subtypes']) + + ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), + ret['tags']) + ret['establishment_subtypes'] = map( + lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), + ret['establishment_subtypes']) if ret.get('establishment_type'): ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name')) - ret['address']['city']['country']['name_translated'] = get_translated_value(ret['address']['city']['country'].pop('name')) + if ret.get('address'): + ret['address']['city']['country']['name_translated'] = get_translated_value( + ret['address']['city']['country'].pop('name')) + + ret['type'] = ret.pop('establishment_type') + ret['subtypes'] = ret.pop('establishment_subtypes') return ret \ No newline at end of file From e0d09486c2f8398e8464323a70c39fc153d1ccc6 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:40:35 +0300 Subject: [PATCH 104/319] Revert "address coordinates in get api queries" This reverts commit f21b400 --- apps/location/models.py | 4 ---- apps/location/serializers/common.py | 20 ++++++++++---------- apps/search_indexes/serializers.py | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/apps/location/models.py b/apps/location/models.py index b0aa298c..7084385f 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -104,10 +104,6 @@ class Address(models.Model): def longitude(self): return self.coordinates.x - @property - def location(self): - return self.location_field_indexing - @property def location_field_indexing(self): return {'lat': self.latitude, diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 0b8ac7ed..555cf499 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -90,9 +90,8 @@ class AddressSerializer(serializers.ModelSerializer): source='city', queryset=models.City.objects.all()) city = CitySerializer(read_only=True) - geo_lon = serializers.FloatField(allow_null=True, write_only=True) - geo_lat = serializers.FloatField(allow_null=True, write_only=True) - location = serializers.ReadOnlyField() + geo_lon = serializers.FloatField(allow_null=True) + geo_lat = serializers.FloatField(allow_null=True) class Meta: model = models.Address @@ -105,8 +104,7 @@ class AddressSerializer(serializers.ModelSerializer): 'number', 'postal_code', 'geo_lon', - 'geo_lat', - 'location', + 'geo_lat' ] def validate(self, attrs): @@ -121,10 +119,12 @@ class AddressSerializer(serializers.ModelSerializer): def to_representation(self, instance): """Override to_representation method""" - if not instance.coordinates or not isinstance(instance.coordinates, Point): - setattr(instance, 'location', { - 'lat': float(0), - 'lon': float(0), - }) + if instance.coordinates and isinstance(instance.coordinates, Point): + # Point(longitude, latitude) + setattr(instance, 'geo_lat', instance.coordinates.x) + setattr(instance, 'geo_lon', instance.coordinates.y) + else: + setattr(instance, 'geo_lat', float(0)) + setattr(instance, 'geo_lon', float(0)) return super().to_representation(instance) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 73652ef6..598b60bf 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -54,7 +54,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): fields = ( 'id', 'name', - # 'description', + 'description', 'public_mark', 'toque_number', 'price_level', From fec3a2f6bc40bee7524f2912387a168f4873a361 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:46:07 +0300 Subject: [PATCH 105/319] Reformat establishment elastic location for api --- apps/search_indexes/serializers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 598b60bf..925bd6c7 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -86,7 +86,12 @@ class EstablishmentDocumentSerializer(DocumentSerializer): if ret.get('address'): ret['address']['city']['country']['name_translated'] = get_translated_value( ret['address']['city']['country'].pop('name')) + location = ret['address'].pop('location') + if location: + ret['address']['geo_lon'] = location['lon'] + ret['address']['geo_lat'] = location['lat'] ret['type'] = ret.pop('establishment_type') ret['subtypes'] = ret.pop('establishment_subtypes') + return ret \ No newline at end of file From ae00c30fac38d7d1ffb35a4fa29e10eff8d68e66 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:50:07 +0300 Subject: [PATCH 106/319] Fix test --- apps/utils/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 5f8df6d8..4adc2f1e 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -1,5 +1,5 @@ import pytz -from datetime import datetime +from datetime import datetime, timedelta from rest_framework.test import APITestCase from rest_framework import status @@ -52,8 +52,8 @@ class TranslateFieldTests(BaseTestCase): }, description={"en-GB": "Test description"}, playlist=1, - start=datetime.now(pytz.utc), - end=datetime.now(pytz.utc), + start=datetime.now(pytz.utc) + timedelta(hours=-13), + end=datetime.now(pytz.utc) + timedelta(hours=13), is_publish=True, news_type=self.news_type, slug='test', @@ -63,7 +63,7 @@ class TranslateFieldTests(BaseTestCase): self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) def test_read_locale(self): - response = self.client.get(f"/api/web/news/{self.news_item.slug}/", format='json') + response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) news_data = response.json() From 87999830d14915cb2c8c0e3ee0ced48d4cf10c98 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 17:53:21 +0300 Subject: [PATCH 107/319] Remove description from search results --- apps/search_indexes/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 925bd6c7..1d8e3ca3 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -54,7 +54,6 @@ class EstablishmentDocumentSerializer(DocumentSerializer): fields = ( 'id', 'name', - 'description', 'public_mark', 'toque_number', 'price_level', From 5793eaad32db7cf90341d2b3d0e349e5a925c949 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Thu, 26 Sep 2019 20:17:50 +0300 Subject: [PATCH 108/319] Add queryset mixin Switch view in urls/common Add homepage view Switch test --- apps/collection/models.py | 4 +++- apps/collection/tests.py | 22 ++++++++++++++++++++++ apps/collection/urls/common.py | 2 +- apps/collection/views/common.py | 24 +++++++++++++++++++++--- apps/utils/querysets.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index beec9b08..25bf1ef9 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -8,6 +8,8 @@ from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, URLImageMixin from utils.models import TranslatedFieldsMixin +from utils.querysets import RelatedObjectsCountMixin + # Mixins class CollectionNameMixin(models.Model): @@ -30,7 +32,7 @@ class CollectionDateMixin(models.Model): # Models -class CollectionQuerySet(models.QuerySet): +class CollectionQuerySet(RelatedObjectsCountMixin): """QuerySet for model Collection""" def by_country_code(self, code): diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 6a985fc0..ceb08477 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -8,6 +8,7 @@ from http.cookies import SimpleCookie from collection.models import Collection, Guide from location.models import Country +from establishment.models import Establishment, EstablishmentType # Create your tests here. @@ -81,3 +82,24 @@ class CollectionGuideDetailTests(CollectionDetailTests): def test_guide_detail_Read(self): response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class CollectionWebHomeTests(CollectionDetailTests): + def setUp(self): + super().setUp() + self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") + + self.establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id, + is_publish=True, + slug="test" + ) + + self.establishment.collections.add(self.collection) + self.establishment.save() + + def test_collection_list_filter(self): + print("========================================================================================") + response = self.client.get('/api/web/collections/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/collection/urls/common.py b/apps/collection/urls/common.py index 01385c3d..7ffa50cf 100644 --- a/apps/collection/urls/common.py +++ b/apps/collection/urls/common.py @@ -6,7 +6,7 @@ from collection.views import common as views app_name = 'collection' urlpatterns = [ - path('', views.CollectionListView.as_view(), name='list'), + path('', views.CollectionHomePageView.as_view(), name='list'), path('/establishments/', views.CollectionEstablishmentListView.as_view(), name='detail'), diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index d42bd851..0818c369 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -30,9 +30,27 @@ class CollectionListView(CollectionViewMixin, generics.ListAPIView): def get_queryset(self): """Override get_queryset method""" - return models.Collection.objects.published()\ - .by_country_code(code=self.request.country_code)\ - .order_by('-on_top', '-created') + queryset = models.Collection.objects.published()\ + .by_country_code(code=self.request.country_code)\ + .order_by('-on_top', '-created') + + return queryset + + +class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): + """List Collection view""" + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.CollectionSerializer + + def get_queryset(self): + """Override get_queryset method""" + queryset = models.Collection.objects.published()\ + .by_country_code(code=self.request.country_code)\ + .annotate_related_objects_count() \ + .filter_related_gt(3) \ + .order_by('-on_top', '-created') + + return queryset class CollectionEstablishmentListView(CollectionListView): diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index f25507f7..b6d118a5 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -1,5 +1,6 @@ """Utils QuerySet Mixins""" from django.db import models +from django.db.models import Q from utils.methods import get_contenttype @@ -15,3 +16,31 @@ class ContentTypeQuerySetMixin(models.QuerySet): """Filter QuerySet by ContentType.""" return self.filter(content_type=get_contenttype(app_label=app_label, model=model)) + + +class RelatedObjectsCountMixin(models.QuerySet): + """QuerySet for ManyToMany related count filter""" + + def get_related_objects_names(self): + related_objects_names = [] + + for related_object in self.model._meta.related_objects: + if isinstance(related_object, models.ManyToManyRel): + related_objects_names.append(related_object.name) + + return related_objects_names + + def annotate_related_objects_count(self): + + annotations = {} + for related_object in self.get_related_objects_names(): + annotations[f"{related_object}_count"] = models.Count(f"{related_object}") + + return self.annotate(**annotations) + + def filter_related_gt(self, count): + q_objects = Q() + for related_object in self.get_related_objects_names(): + q_objects.add(Q(**{f"{related_object}_count__gt": count}), Q.OR) + + return self.filter(q_objects) From 4f2bca78f9dc987e6ba17847fc19638681e54513 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 07:52:57 +0300 Subject: [PATCH 109/319] Update tests Update view --- apps/collection/tests.py | 25 ++++++++++++++----------- apps/collection/views/common.py | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/collection/tests.py b/apps/collection/tests.py index ceb08477..54d7917c 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -89,17 +89,20 @@ class CollectionWebHomeTests(CollectionDetailTests): super().setUp() self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") - self.establishment = Establishment.objects.create( - name="Test establishment", - establishment_type_id=self.establishment_type.id, - is_publish=True, - slug="test" - ) + for i in range(1, 5): + setattr(self, f"establishment{i}", + Establishment.objects.create( + name=f"Test establishment {i}", + establishment_type_id=self.establishment_type.id, + is_publish=True, + slug=f"test-establishment-{i}" + ) + ) - self.establishment.collections.add(self.collection) - self.establishment.save() + getattr(self, f"establishment{i}").collections.add(self.collection) def test_collection_list_filter(self): - print("========================================================================================") - response = self.client.get('/api/web/collections/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get('/api/web/collections/?country_code=en', format='json') + data = response.json() + self.assertIn('count', data) + self.assertGreater(data['count'], 0) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 0818c369..662d35b5 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -46,8 +46,8 @@ class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): """Override get_queryset method""" queryset = models.Collection.objects.published()\ .by_country_code(code=self.request.country_code)\ - .annotate_related_objects_count() \ - .filter_related_gt(3) \ + .annotate_related_objects_count()\ + .filter_related_gt(3)\ .order_by('-on_top', '-created') return queryset From 44f792db0cd20a427779204317d88d627b59e065 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 07:58:21 +0300 Subject: [PATCH 110/319] Switch sorting --- apps/collection/views/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 662d35b5..f41019ae 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -48,7 +48,7 @@ class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): .by_country_code(code=self.request.country_code)\ .annotate_related_objects_count()\ .filter_related_gt(3)\ - .order_by('-on_top', '-created') + .order_by('-on_top', '-modified') return queryset From b1b9ab5ab264b98275bf966c482d41acc5d68a35 Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 11:14:18 +0500 Subject: [PATCH 111/319] First commit --- apps/news/admin.py | 22 ++++++++++++++++++++++ apps/news/tasks.py | 12 ++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 apps/news/tasks.py diff --git a/apps/news/admin.py b/apps/news/admin.py index 7cbfb049..b866d867 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,5 +1,9 @@ from django.contrib import admin + from news import models +from notification.models import Subscriber +from .tasks import send_email_with_news +from establishment.tasks import recalculate_price_levels_by_country @admin.register(models.NewsType) @@ -9,6 +13,24 @@ class NewsTypeAdmin(admin.ModelAdmin): list_display_links = ['id', 'name'] +def send_email_action(modeladmin, request, queryset): + print(queryset) + + news_ids = [n.id for n in queryset] + + print(news_ids) + + # send_email_with_news.delay(news_ids) + recalculate_price_levels_by_country.delay(news_ids) + + print("TEST send_email_action IS CALLED!") + return + + +send_email_action.short_description = "Send the selected news by email" + + @admin.register(models.News) class NewsAdmin(admin.ModelAdmin): """News admin.""" + actions = [send_email_action] diff --git a/apps/news/tasks.py b/apps/news/tasks.py new file mode 100644 index 00000000..a7e43c8c --- /dev/null +++ b/apps/news/tasks.py @@ -0,0 +1,12 @@ +from celery import shared_task + +from notification.models import Subscriber + +@shared_task +def send_email_with_news(news): + + print(news) + + print("EMAILS WAS SENT!") + + return news \ No newline at end of file From 9608fdb92a45bfdced27d48f1919e14c069e49cb Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 11:30:58 +0500 Subject: [PATCH 112/319] property was added to news model --- apps/news/models.py | 19 +++++++++++++++++++ apps/news/serializers.py | 1 + 2 files changed, 20 insertions(+) diff --git a/apps/news/models.py b/apps/news/models.py index 140c89c9..75deee56 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -5,6 +5,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin +from django.contrib.contenttypes.models import ContentType class NewsType(models.Model): @@ -100,3 +101,21 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def web_url(self): return reverse('web:news:rud', kwargs={'slug': self.slug}) + + @property + def list_also_like_news(self): + # news_content_type = ContentType.objects.get(app_label="news", model="news") + + # tg_name = self.tags.filter(metadata__content_type=news_content_type) + + # print(tg_name) + + # tag_id = self.tags.all() + # + like_news = News.objects.filter(is_publish=True, news_type=self.news_type, tags__id=self.tags__id) + # + # print("LINE 112", like_news) + + news_list = [self.description, "extra", "field", "test", self.slug] + + return news_list diff --git a/apps/news/serializers.py b/apps/news/serializers.py index dbcd0f62..981ddae0 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -64,6 +64,7 @@ class NewsDetailSerializer(NewsBaseSerializer): 'is_publish', 'author', 'country', + 'list_also_like_news', ) From 962dc2f480b7c1e79808fbe4e0fc48c6d90c80b8 Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 12:01:30 +0500 Subject: [PATCH 113/319] maybe it is solution --- apps/news/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/news/models.py b/apps/news/models.py index 75deee56..1b9023db 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -111,11 +111,16 @@ class News(BaseAttributes, TranslatedFieldsMixin): # print(tg_name) # tag_id = self.tags.all() + news_content_type = ContentType.objects.get(app_label="news", model="news") # - like_news = News.objects.filter(is_publish=True, news_type=self.news_type, tags__id=self.tags__id) + like_news = News.objects.filter(is_publish=True, news_type=self.news_type, + tags__content_type=news_content_type) + # # print("LINE 112", like_news) - news_list = [self.description, "extra", "field", "test", self.slug] + # news_list = [self.description, "extra", "field", "test", self.slug] + + news_list = [{"id": n.id, "slug": n.slug} for n in like_news] return news_list From aa037d27f563ecc23e46f398a88f590485f56b65 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, 27 Sep 2019 10:41:41 +0300 Subject: [PATCH 114/319] Double api --- apps/search_indexes/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/urls.py b/apps/search_indexes/urls.py index 664e0b99..8e914719 100644 --- a/apps/search_indexes/urls.py +++ b/apps/search_indexes/urls.py @@ -6,5 +6,6 @@ from search_indexes import views router = routers.SimpleRouter() # router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') +router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile') urlpatterns = router.urls From d115d30e821e5413c7d9bb909ac013e977fcba5d 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, 27 Sep 2019 11:19:49 +0300 Subject: [PATCH 115/319] Fix test authorization --- apps/authorization/tests/tests_authorization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/authorization/tests/tests_authorization.py b/apps/authorization/tests/tests_authorization.py index 4a5d2a2b..d9fd7b71 100644 --- a/apps/authorization/tests/tests_authorization.py +++ b/apps/authorization/tests/tests_authorization.py @@ -22,7 +22,6 @@ def get_tokens_for_user( class AuthorizationTests(APITestCase): def setUp(self): - print("Auth!") data = get_tokens_for_user() self.username = data["username"] self.password = data["password"] From 993e58d215e664b2ef191c9bfbf9f24601dbac40 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 11:20:43 +0300 Subject: [PATCH 116/319] fix views after merge --- apps/establishment/views/back.py | 2 +- apps/establishment/views/web.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 78526bdd..5cba8255 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -11,7 +11,7 @@ class EstablishmentMixinViews: def get_queryset(self): """Overrided method 'get_queryset'.""" - return models.Establishment.objects.published().with_related() + return models.Establishment.objects.published().with_base_related() class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView): diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index bda64dfb..4ece55c0 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -7,7 +7,6 @@ from rest_framework import generics, permissions from comment import models as comment_models from establishment import filters from establishment import models, serializers -from establishment.views import EstablishmentMixin from main import methods from main.models import MetaDataContent from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer From c7974a75115dae7b7662116d1918e20a66fed644 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, 27 Sep 2019 11:33:49 +0300 Subject: [PATCH 117/319] Refactor authorization test --- apps/authorization/tests/tests_authorization.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/authorization/tests/tests_authorization.py b/apps/authorization/tests/tests_authorization.py index d9fd7b71..6028e386 100644 --- a/apps/authorization/tests/tests_authorization.py +++ b/apps/authorization/tests/tests_authorization.py @@ -1,5 +1,6 @@ from rest_framework.test import APITestCase from account.models import User +from django.urls import reverse # Create your tests here. @@ -32,7 +33,9 @@ class AuthorizationTests(APITestCase): 'password': self.password, 'remember': True } - response = self.client.post('/api/auth/login/', data=data) + # login + # /api/auth/login/ + response = self.client.post(reverse('auth:authorization:login'), data=data) self.assertEqual(response.data['access_token'], self.tokens.get('access_token')) self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token')) From 62fbded6dea37338579948701a0e8c6d7c7bf19c Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 11:41:13 +0300 Subject: [PATCH 118/319] added todo --- apps/utils/middleware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/utils/middleware.py b/apps/utils/middleware.py index 096f8474..f3936169 100644 --- a/apps/utils/middleware.py +++ b/apps/utils/middleware.py @@ -19,6 +19,7 @@ def parse_cookies(get_response): cookie_dict = request.COOKIES # processing locale cookie locale = get_locale(cookie_dict) + # todo: don't use DB!!! Use cache if not Language.objects.filter(locale=locale).exists(): locale = TranslationSettings.get_solo().default_language translation.activate(locale) From fc5ec0920ec3aa5e409d0461cd5c6a109578c488 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, 27 Sep 2019 11:43:14 +0300 Subject: [PATCH 119/319] fix --- apps/authorization/tests/tests_authorization.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/authorization/tests/tests_authorization.py b/apps/authorization/tests/tests_authorization.py index 6028e386..a6a49ea5 100644 --- a/apps/authorization/tests/tests_authorization.py +++ b/apps/authorization/tests/tests_authorization.py @@ -33,8 +33,6 @@ class AuthorizationTests(APITestCase): 'password': self.password, 'remember': True } - # login - # /api/auth/login/ response = self.client.post(reverse('auth:authorization:login'), data=data) self.assertEqual(response.data['access_token'], self.tokens.get('access_token')) self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token')) From c9bf83fba727745505532d5970cfd40f2bf9767e Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 11:53:40 +0300 Subject: [PATCH 120/319] News slug, disable null, remove editable --- .../migrations/0014_auto_20190927_0845.py | 21 +++++++++++++++++++ .../migrations/0015_auto_20190927_0853.py | 18 ++++++++++++++++ apps/news/models.py | 4 ++-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 apps/news/migrations/0014_auto_20190927_0845.py create mode 100644 apps/news/migrations/0015_auto_20190927_0853.py diff --git a/apps/news/migrations/0014_auto_20190927_0845.py b/apps/news/migrations/0014_auto_20190927_0845.py new file mode 100644 index 00000000..270296a4 --- /dev/null +++ b/apps/news/migrations/0014_auto_20190927_0845.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-27 08:45 +from django.db import migrations + + +def fill_slug(apps,schemaeditor): + News = apps.get_model('news', 'News') + for news in News.objects.all(): + if news.slug is None: + news.slug = f'Slug {news.id}' + news.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0013_auto_20190924_0806'), + ] + + operations = [ + migrations.RunPython(fill_slug, migrations.RunPython.noop) + ] diff --git a/apps/news/migrations/0015_auto_20190927_0853.py b/apps/news/migrations/0015_auto_20190927_0853.py new file mode 100644 index 00000000..e756e2e5 --- /dev/null +++ b/apps/news/migrations/0015_auto_20190927_0853.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-27 08:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0014_auto_20190927_0845'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='slug', + field=models.SlugField(unique=True, verbose_name='News slug'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 140c89c9..4fb8e118 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -63,8 +63,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): start = models.DateTimeField(verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) - slug = models.SlugField(unique=True, max_length=50, null=True, - verbose_name=_('News slug'), editable=True,) + slug = models.SlugField(unique=True, max_length=50, + verbose_name=_('News slug')) playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) From cd951ddd75f369721eb96d6d75caf4d5e4991a70 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 11:57:42 +0300 Subject: [PATCH 121/319] remove custom slug field from serializer, small fix in migration --- apps/news/migrations/0014_auto_20190927_0845.py | 2 +- apps/news/serializers.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/news/migrations/0014_auto_20190927_0845.py b/apps/news/migrations/0014_auto_20190927_0845.py index 270296a4..dcdee4e4 100644 --- a/apps/news/migrations/0014_auto_20190927_0845.py +++ b/apps/news/migrations/0014_auto_20190927_0845.py @@ -6,7 +6,7 @@ def fill_slug(apps,schemaeditor): News = apps.get_model('news', 'News') for news in News.objects.all(): if news.slug is None: - news.slug = f'Slug {news.id}' + news.slug = f'Slug_{news.id}' news.save() diff --git a/apps/news/serializers.py b/apps/news/serializers.py index dbcd0f62..3d15cff9 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -28,8 +28,6 @@ class NewsBaseSerializer(serializers.ModelSerializer): news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) - slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) - class Meta: """Meta class.""" From 910322273e72b095862e6a8e8ff7ab23c3296084 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 12:01:11 +0300 Subject: [PATCH 122/319] Add annotate to filter --- apps/collection/views/common.py | 1 - apps/utils/querysets.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index f41019ae..80a21be4 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -46,7 +46,6 @@ class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): """Override get_queryset method""" queryset = models.Collection.objects.published()\ .by_country_code(code=self.request.country_code)\ - .annotate_related_objects_count()\ .filter_related_gt(3)\ .order_by('-on_top', '-modified') diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index b6d118a5..4937b867 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -43,4 +43,4 @@ class RelatedObjectsCountMixin(models.QuerySet): for related_object in self.get_related_objects_names(): q_objects.add(Q(**{f"{related_object}_count__gt": count}), Q.OR) - return self.filter(q_objects) + return self.annotate_related_objects_count().filter(q_objects) From 46ce25f0f767aafe59a0092e2cd21c8c12342ea3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 12:21:50 +0300 Subject: [PATCH 123/319] fix migration --- apps/news/migrations/0014_auto_20190927_0845.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/news/migrations/0014_auto_20190927_0845.py b/apps/news/migrations/0014_auto_20190927_0845.py index dcdee4e4..5a2b35fb 100644 --- a/apps/news/migrations/0014_auto_20190927_0845.py +++ b/apps/news/migrations/0014_auto_20190927_0845.py @@ -1,11 +1,12 @@ # Generated by Django 2.2.4 on 2019-09-27 08:45 from django.db import migrations +from django.core.validators import EMPTY_VALUES def fill_slug(apps,schemaeditor): News = apps.get_model('news', 'News') for news in News.objects.all(): - if news.slug is None: + if news.slug in EMPTY_VALUES: news.slug = f'Slug_{news.id}' news.save() From b55a8ffda6a35886976a971019e4617eca51f04e Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 12:23:12 +0300 Subject: [PATCH 124/319] Add comments --- apps/utils/querysets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index 4937b867..1c7dfa84 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -22,6 +22,7 @@ class RelatedObjectsCountMixin(models.QuerySet): """QuerySet for ManyToMany related count filter""" def get_related_objects_names(self): + """Get all related objects (with reversed)""" related_objects_names = [] for related_object in self.model._meta.related_objects: @@ -31,7 +32,7 @@ class RelatedObjectsCountMixin(models.QuerySet): return related_objects_names def annotate_related_objects_count(self): - + """Annotate all related objects to queryset""" annotations = {} for related_object in self.get_related_objects_names(): annotations[f"{related_object}_count"] = models.Count(f"{related_object}") @@ -39,6 +40,7 @@ class RelatedObjectsCountMixin(models.QuerySet): return self.annotate(**annotations) def filter_related_gt(self, count): + """QuerySet filter by related objects count""" q_objects = Q() for related_object in self.get_related_objects_names(): q_objects.add(Q(**{f"{related_object}_count__gt": count}), Q.OR) From 755490c6cf92dc170934bd0126ccbbbe96fdee1a Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 17:12:24 +0500 Subject: [PATCH 125/319] done --- apps/news/models.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/news/models.py b/apps/news/models.py index 1b9023db..cfb10d39 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -5,7 +5,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin -from django.contrib.contenttypes.models import ContentType +from random import sample as random_sample class NewsType(models.Model): @@ -104,23 +104,18 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def list_also_like_news(self): - # news_content_type = ContentType.objects.get(app_label="news", model="news") - # tg_name = self.tags.filter(metadata__content_type=news_content_type) + # without "distinct" method the doubles are arising + like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\ + .exclude(id=self.id).distinct() - # print(tg_name) + news_count = like_news.count() - # tag_id = self.tags.all() - news_content_type = ContentType.objects.get(app_label="news", model="news") - # - like_news = News.objects.filter(is_publish=True, news_type=self.news_type, - tags__content_type=news_content_type) + if news_count >= 6: + random_ids = random_sample(range(news_count), 6) + else: + random_ids = random_sample(range(news_count), news_count) - # - # print("LINE 112", like_news) - - # news_list = [self.description, "extra", "field", "test", self.slug] - - news_list = [{"id": n.id, "slug": n.slug} for n in like_news] + news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] return news_list From 785d2a2f0987ba09c1fd5907245d217097fc75d4 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 16:56:26 +0300 Subject: [PATCH 126/319] update news serializer, RUD for backoffice --- apps/news/migrations/0016_news_template.py | 18 ++++++++++++++++++ apps/news/models.py | 12 ++++++++++++ apps/news/serializers.py | 5 +++++ 3 files changed, 35 insertions(+) create mode 100644 apps/news/migrations/0016_news_template.py diff --git a/apps/news/migrations/0016_news_template.py b/apps/news/migrations/0016_news_template.py new file mode 100644 index 00000000..f85959ba --- /dev/null +++ b/apps/news/migrations/0016_news_template.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-27 13:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0015_auto_20190927_0853'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='template', + field=models.PositiveIntegerField(choices=[(0, 'newspaper'), (1, 'main.pdf.erb'), (2, 'main')], default=0), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 4fb8e118..e0c5fbf4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -49,6 +49,17 @@ class NewsQuerySet(models.QuerySet): class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" + # template choices + NEWSPAPER = 0 + MAIN_PDF_ERB = 1 + MAIN = 2 + + TEMPLATE_CHOICES = ( + (NEWSPAPER, 'newspaper'), + (MAIN_PDF_ERB, 'main.pdf.erb'), + (MAIN, 'main'), + ) + news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, @@ -78,6 +89,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('Image URL path')) preview_image_url = models.URLField(blank=True, null=True, default=None, verbose_name=_('Preview image URL path')) + template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), on_delete=models.SET_NULL) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 3d15cff9..82f9980b 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -89,6 +89,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, source='country', write_only=True, queryset=location_models.Country.objects.all()) + template_display = serializers.CharField(source='get_template_display', + read_only=True) + class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): """Meta class.""" @@ -97,5 +100,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'description', 'news_type_id', 'country_id', + 'template', + 'template_display', ) From ae87a51277fdd38647f16598bc7a35d45ffbe676 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, 27 Sep 2019 17:10:08 +0300 Subject: [PATCH 127/319] Author --- apps/news/migrations/0016_remove_news_author.py | 17 +++++++++++++++++ apps/news/models.py | 4 ++-- apps/news/serializers.py | 3 ++- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 apps/news/migrations/0016_remove_news_author.py diff --git a/apps/news/migrations/0016_remove_news_author.py b/apps/news/migrations/0016_remove_news_author.py new file mode 100644 index 00000000..31ad12bb --- /dev/null +++ b/apps/news/migrations/0016_remove_news_author.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-09-27 13:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0015_auto_20190927_0853'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='author', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 4fb8e118..545de59d 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -68,8 +68,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) - author = models.CharField(max_length=255, blank=True, null=True, - default=None,verbose_name=_('Author')) + # author = models.CharField(max_length=255, blank=True, null=True, + # default=None,verbose_name=_('Author')) is_highlighted = models.BooleanField(default=False, verbose_name=_('Is highlighted')) # TODO: metadata_keys - описание ключей для динамического построения полей метаданных diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 3d15cff9..1778d410 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -5,7 +5,7 @@ from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer from news import models from utils.serializers import TranslatedField - +from account.serializers.common import UserSerializer class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -50,6 +50,7 @@ class NewsDetailSerializer(NewsBaseSerializer): description_translated = TranslatedField() country = CountrySimpleSerializer(read_only=True) + author = UserSerializer(source='news_records_created') class Meta(NewsBaseSerializer.Meta): """Meta class.""" From a0a6578ae849d5c0b9c96206b9e3ac8d77c0dbdc Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 17:11:42 +0300 Subject: [PATCH 128/319] Add new filter --- apps/collection/views/common.py | 2 +- apps/utils/querysets.py | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 80a21be4..148c5fab 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -46,7 +46,7 @@ class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): """Override get_queryset method""" queryset = models.Collection.objects.published()\ .by_country_code(code=self.request.country_code)\ - .filter_related_gt(3)\ + .filter_all_related_gt(3)\ .order_by('-on_top', '-modified') return queryset diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index 1c7dfa84..b94cb7b3 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -1,7 +1,8 @@ """Utils QuerySet Mixins""" from django.db import models -from django.db.models import Q - +from django.db.models import Q, Sum, F +from functools import reduce +from operator import add from utils.methods import get_contenttype @@ -21,7 +22,7 @@ class ContentTypeQuerySetMixin(models.QuerySet): class RelatedObjectsCountMixin(models.QuerySet): """QuerySet for ManyToMany related count filter""" - def get_related_objects_names(self): + def _get_related_objects_names(self): """Get all related objects (with reversed)""" related_objects_names = [] @@ -31,10 +32,10 @@ class RelatedObjectsCountMixin(models.QuerySet): return related_objects_names - def annotate_related_objects_count(self): + def _annotate_related_objects_count(self): """Annotate all related objects to queryset""" annotations = {} - for related_object in self.get_related_objects_names(): + for related_object in self._get_related_objects_names(): annotations[f"{related_object}_count"] = models.Count(f"{related_object}") return self.annotate(**annotations) @@ -42,7 +43,13 @@ class RelatedObjectsCountMixin(models.QuerySet): def filter_related_gt(self, count): """QuerySet filter by related objects count""" q_objects = Q() - for related_object in self.get_related_objects_names(): + for related_object in self._get_related_objects_names(): q_objects.add(Q(**{f"{related_object}_count__gt": count}), Q.OR) - return self.annotate_related_objects_count().filter(q_objects) + return self._annotate_related_objects_count().filter(q_objects) + + def filter_all_related_gt(self, count): + exp =reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()]) + return self._annotate_related_objects_count()\ + .annotate(all_related_count=exp)\ + .filter(all_related_count__gt=count) From 7a0999a52032a90299e0f6fa36c90d6e63bdfd14 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Fri, 27 Sep 2019 17:12:12 +0300 Subject: [PATCH 129/319] Add new filter --- apps/utils/querysets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index b94cb7b3..bf2816f2 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -49,6 +49,7 @@ class RelatedObjectsCountMixin(models.QuerySet): return self._annotate_related_objects_count().filter(q_objects) def filter_all_related_gt(self, count): + """Queryset filter by all related objects count""" exp =reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()]) return self._annotate_related_objects_count()\ .annotate(all_related_count=exp)\ From 03753f9059d673138e76b01a9a7f141c76874810 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 27 Sep 2019 17:16:40 +0300 Subject: [PATCH 130/319] News model, state --- .../migrations/0017_auto_20190927_1403.py | 22 ++++++++++++++ apps/news/models.py | 29 ++++++++++++++++--- apps/news/serializers.py | 6 ++-- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 apps/news/migrations/0017_auto_20190927_1403.py diff --git a/apps/news/migrations/0017_auto_20190927_1403.py b/apps/news/migrations/0017_auto_20190927_1403.py new file mode 100644 index 00000000..1886dcec --- /dev/null +++ b/apps/news/migrations/0017_auto_20190927_1403.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-09-27 14:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0016_news_template'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='is_publish', + ), + migrations.AddField( + model_name='news', + name='state', + field=models.PositiveSmallIntegerField(choices=[(0, 'Waiting'), (1, 'Hidden'), (2, 'Published'), (3, 'Published exclusive')], default=0, verbose_name='State'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index e0c5fbf4..9273a1bd 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -39,7 +39,7 @@ class NewsQuerySet(models.QuerySet): now = timezone.now() return self.filter(models.Q(models.Q(end__gte=now) | models.Q(end__isnull=True)), - is_publish=True, start__lte=now) + state__in=[self.model.PUBLISHED_STATES], start__lte=now) def with_related(self): """Return qs with related objects.""" @@ -49,7 +49,9 @@ class NewsQuerySet(models.QuerySet): class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" - # template choices + STR_FIELD_NAME = 'title' + + # TEMPLATE CHOICES NEWSPAPER = 0 MAIN_PDF_ERB = 1 MAIN = 2 @@ -60,6 +62,21 @@ class News(BaseAttributes, TranslatedFieldsMixin): (MAIN, 'main'), ) + # STATE CHOICES + WAITING = 0 + HIDDEN = 1 + PUBLISHED = 2 + PUBLISHED_EXCLUSIVE = 3 + + PUBLISHED_STATES = [PUBLISHED, PUBLISHED_EXCLUSIVE] + + STATE_CHOICES = ( + (WAITING, _('Waiting')), + (HIDDEN, _('Hidden')), + (PUBLISHED, _('Published')), + (PUBLISHED_EXCLUSIVE, _('Published exclusive')), + ) + news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, verbose_name=_('news type')) title = TJSONField(blank=True, null=True, default=None, @@ -77,8 +94,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): slug = models.SlugField(unique=True, max_length=50, verbose_name=_('News slug')) playlist = models.IntegerField(_('playlist')) - is_publish = models.BooleanField(default=False, - verbose_name=_('Publish status')) + state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, + verbose_name=_('State')) author = models.CharField(max_length=255, blank=True, null=True, default=None,verbose_name=_('Author')) is_highlighted = models.BooleanField(default=False, @@ -109,6 +126,10 @@ class News(BaseAttributes, TranslatedFieldsMixin): def __str__(self): return f'news: {self.slug}' + @property + def is_publish(self): + return self.state in self.PUBLISHED_STATES + @property def web_url(self): return reverse('web:news:rud', kwargs={'slug': self.slug}) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 82f9980b..2677ce0f 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -50,6 +50,8 @@ class NewsDetailSerializer(NewsBaseSerializer): description_translated = TranslatedField() country = CountrySimpleSerializer(read_only=True) + state_display = serializers.CharField(source='get_state_display', + read_only=True) class Meta(NewsBaseSerializer.Meta): """Meta class.""" @@ -60,6 +62,8 @@ class NewsDetailSerializer(NewsBaseSerializer): 'end', 'playlist', 'is_publish', + 'state', + 'state_display', 'author', 'country', ) @@ -84,11 +88,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, news_type_id = serializers.PrimaryKeyRelatedField( source='news_type', write_only=True, queryset=models.NewsType.objects.all()) - country_id = serializers.PrimaryKeyRelatedField( source='country', write_only=True, queryset=location_models.Country.objects.all()) - template_display = serializers.CharField(source='get_template_display', read_only=True) From d25e4af4884235166a8a4a1fd5467fe01b2c5fdc 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, 27 Sep 2019 17:21:57 +0300 Subject: [PATCH 131/319] Created by --- apps/news/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 1778d410..320de8bd 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -50,7 +50,7 @@ class NewsDetailSerializer(NewsBaseSerializer): description_translated = TranslatedField() country = CountrySimpleSerializer(read_only=True) - author = UserSerializer(source='news_records_created') + author = UserSerializer(source='created_by') class Meta(NewsBaseSerializer.Meta): """Meta class.""" From 4f3c8042513dcbfe4bd691fb3cbd2e63d7c910bf 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, 27 Sep 2019 17:34:13 +0300 Subject: [PATCH 132/319] Fix --- apps/news/migrations/0018_merge_20190927_1432.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/news/migrations/0018_merge_20190927_1432.py diff --git a/apps/news/migrations/0018_merge_20190927_1432.py b/apps/news/migrations/0018_merge_20190927_1432.py new file mode 100644 index 00000000..c4654943 --- /dev/null +++ b/apps/news/migrations/0018_merge_20190927_1432.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-09-27 14:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0017_auto_20190927_1403'), + ('news', '0016_remove_news_author'), + ] + + operations = [ + ] From ac8356c7d18da921a21081da672264bff5440361 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 19:03:30 +0300 Subject: [PATCH 133/319] Fix some test && routes && add migration --- apps/favorites/tests.py | 2 +- apps/news/migrations/0019_news_author.py | 18 ++++++++++++++++++ apps/news/models.py | 2 +- apps/news/tests.py | 2 +- apps/utils/tests.py | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 apps/news/migrations/0019_news_author.py diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index 208cf0db..99b01444 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -27,7 +27,7 @@ class BaseTestCase(APITestCase): news_type=self.test_news_type, description={"en-GB": "Description test news"}, playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", - is_publish=True) + state=News.PUBLISHED, slug='test-news') self.test_content_type = ContentType.objects.get(app_label="news", model="news") diff --git a/apps/news/migrations/0019_news_author.py b/apps/news/migrations/0019_news_author.py new file mode 100644 index 00000000..41985255 --- /dev/null +++ b/apps/news/migrations/0019_news_author.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-27 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0018_merge_20190927_1432'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='author', + field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 2c6229be..e6967d00 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -39,7 +39,7 @@ class NewsQuerySet(models.QuerySet): now = timezone.now() return self.filter(models.Q(models.Q(end__gte=now) | models.Q(end__isnull=True)), - state__in=[self.model.PUBLISHED_STATES], start__lte=now) + state__in=self.model.PUBLISHED_STATES, start__lte=now) def with_related(self): """Return qs with related objects.""" diff --git a/apps/news/tests.py b/apps/news/tests.py index 7d6724d7..2e24ac45 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -26,7 +26,7 @@ class BaseTestCase(APITestCase): news_type=self.test_news_type, description={"en-GB": "Description test news"}, playlist=1, start=datetime.now() + timedelta(hours=-2), end=datetime.now() + timedelta(hours=2), - is_publish=True, slug='test-news-slug',) + state=News.PUBLISHED, slug='test-news-slug',) class NewsTestCase(BaseTestCase): diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 4adc2f1e..10af2a92 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -54,9 +54,9 @@ class TranslateFieldTests(BaseTestCase): playlist=1, start=datetime.now(pytz.utc) + timedelta(hours=-13), end=datetime.now(pytz.utc) + timedelta(hours=13), - is_publish=True, news_type=self.news_type, slug='test', + state=News.PUBLISHED, ) def test_model_field(self): From d71ed0c1de634dee9335b28efde89f41047e5ff9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 19:44:00 +0300 Subject: [PATCH 134/319] Fix lot/lat x/y mapping && address serialization --- apps/establishment/serializers/common.py | 11 +++++++++-- apps/establishment/views/web.py | 2 +- apps/location/serializers/common.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 69a029e2..0e33921c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -5,7 +5,7 @@ from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites -from location.serializers import AddressSimpleSerializer +from location.serializers import AddressSimpleSerializer, AddressSerializer from main.models import MetaDataContent from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer from review import models as review_models @@ -146,7 +146,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer): preview_image = serializers.URLField(source='preview_image_url') slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) - address = AddressSimpleSerializer() + address = AddressSerializer() tags = MetaDataContentSerializer(many=True) class Meta: @@ -180,6 +180,13 @@ class EstablishmentListSerializer(EstablishmentBaseSerializer): 'in_favorites', ] +class EstablishmentAllListSerializer(EstablishmentListSerializer): + """ Serailizer for api/*/establishments """ + address = AddressSimpleSerializer() + + class Meta(EstablishmentListSerializer.Meta): + pass + class EstablishmentDetailSerializer(EstablishmentListSerializer): """Serializer for Establishment model.""" diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 4ece55c0..f4558b71 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -28,7 +28,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): """Resource for getting a list of establishments.""" filter_class = filters.EstablishmentFilter - serializer_class = serializers.EstablishmentListSerializer + serializer_class = serializers.EstablishmentAllListSerializer def get_queryset(self): """Overridden method 'get_queryset'.""" diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 1dee92ed..37d782de 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -104,7 +104,7 @@ class AddressSerializer(serializers.ModelSerializer): 'number', 'postal_code', 'geo_lon', - 'geo_lat' + 'geo_lat', ] def validate(self, attrs): From 1957ab6300bed2c71d3e6d7f0fb4135ee0eaf57e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 20:54:55 +0300 Subject: [PATCH 135/319] Pagination for ES results --- apps/search_indexes/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 72cab583..0e73ed25 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -4,7 +4,8 @@ from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend, GeoSpatialFilteringFilterBackend) from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet -from django_elasticsearch_dsl_drf.pagination import PageNumberPagination + +from pagination import ProjectPageNumberPagination from search_indexes import serializers, filters from search_indexes.documents import EstablishmentDocument, NewsDocument @@ -14,7 +15,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): document = NewsDocument lookup_field = 'slug' - pagination_class = PageNumberPagination + pagination_class = ProjectPageNumberPagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.NewsDocumentSerializer ordering = ('id',) @@ -40,7 +41,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): document = EstablishmentDocument lookup_field = 'slug' - pagination_class = PageNumberPagination + pagination_class = ProjectPageNumberPagination permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentDocumentSerializer ordering = ('id',) From 4d113a691685b205074ebe765d897c6b26cfb56d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 22:06:46 +0300 Subject: [PATCH 136/319] Fix all test --- apps/establishment/serializers/back.py | 2 +- apps/establishment/tests.py | 7 +++++++ apps/utils/tests.py | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 788fa1e1..5a15aafc 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -22,7 +22,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) - type = EstablishmentTypeSerializer(source='establishment_type') + type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) class Meta: model = models.Establishment diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 16d224b8..39e28861 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -6,6 +6,7 @@ from http.cookies import SimpleCookie from main.models import Currency from establishment.models import Establishment, EstablishmentType, Menu # Create your tests here. +from translation.models import Language class BaseTestCase(APITestCase): @@ -25,6 +26,12 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") + # Create lang object + Language.objects.create( + title='English', + locale='en-GB' + ) + class EstablishmentBTests(BaseTestCase): def test_establishment_CRUD(self): diff --git a/apps/utils/tests.py b/apps/utils/tests.py index 10af2a92..0eaf343d 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests.py @@ -95,6 +95,7 @@ class BaseAttributeTests(BaseTestCase): response_data = response.json() self.assertIn("id", response_data) + self.assertIsInstance(response_data['id'], int) employee = Employee.objects.get(id=response_data['id']) @@ -118,7 +119,7 @@ class BaseAttributeTests(BaseTestCase): 'name': 'Test new name' } - response = self.client.patch('/api/back/establishments/employees/1/', data=update_data) + response = self.client.patch(f'/api/back/establishments/employees/{employee.pk}/', data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) employee.refresh_from_db() From 4388fb73468d57473c7c227dc89d147c435bcba7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 25 Sep 2019 22:00:05 +0300 Subject: [PATCH 137/319] latest award for establishment --- apps/establishment/models.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index f677737d..9c402a76 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -281,7 +281,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): slug = models.SlugField(unique=True, max_length=50, null=True, verbose_name=_('Establishment slug'), editable=True) - awards = generic.GenericRelation(to='main.Award') + establishment_awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') @@ -312,6 +312,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): country = self.address.city.country return country.low_price, country.high_price + @property + def awards(self): + latest_employee_awards = Employee.objects.filter( + establishments=self + ).annotate( + latest_employee_award=Max('awards__vintage_year') + ).filter( + awards__vintage_year=F('awards__vintage_year') + ).get().awards + latest_establishment_award = self.establishment_awards.latest(field_name='vintage_year').get() + awards = [award for award in latest_employee_awards + [latest_establishment_award] if award is not None] + return [awards.sort(key=lambda award: award.vintage_year)[0]] + # todo: make via prefetch # @property # def subtypes(self): From 98b0a9af5d5532c5bd869e40b9521ae71f227e2b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 01:26:52 +0300 Subject: [PATCH 138/319] latest award && test fix --- apps/establishment/models.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9c402a76..bebacd18 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -16,6 +16,8 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from main.models import MetaDataContent from location.models import Address +from collection.models import Collection +from main.models import Award from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -281,7 +283,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): slug = models.SlugField(unique=True, max_length=50, null=True, verbose_name=_('Establishment slug'), editable=True) - establishment_awards = generic.GenericRelation(to='main.Award') + establishment_awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') @@ -314,16 +316,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def awards(self): - latest_employee_awards = Employee.objects.filter( - establishments=self - ).annotate( - latest_employee_award=Max('awards__vintage_year') - ).filter( - awards__vintage_year=F('awards__vintage_year') - ).get().awards - latest_establishment_award = self.establishment_awards.latest(field_name='vintage_year').get() - awards = [award for award in latest_employee_awards + [latest_establishment_award] if award is not None] - return [awards.sort(key=lambda award: award.vintage_year)[0]] + return [Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( + field_name='vintage_year')] # todo: make via prefetch # @property @@ -422,8 +416,8 @@ class Employee(BaseAttributes): verbose_name=_('User')) name = models.CharField(max_length=255, verbose_name=_('Last name')) establishments = models.ManyToManyField(Establishment, related_name='employees', - through=EstablishmentEmployee) - awards = generic.GenericRelation(to='main.Award') + through=EstablishmentEmployee,) + awards = generic.GenericRelation(to='main.Award', related_query_name='employees') tags = generic.GenericRelation(to='main.MetaDataContent') class Meta: From 051ddb617d1c056214081a9768ce43b1c3b1a776 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 26 Sep 2019 18:20:06 +0300 Subject: [PATCH 139/319] fix conflicts after rebase --- apps/establishment/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index bebacd18..fcc6abe6 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos import Point from django.contrib.gis.measure import Distance as DistanceMeasure from django.core.exceptions import ValidationError from django.db import models -from django.db.models import When, Case, F, ExpressionWrapper, Subquery +from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField From 9aafe0d69d04e4ec88871c6a84666681542b746a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 16:00:08 +0300 Subject: [PATCH 140/319] new establishment attribute --- apps/establishment/models.py | 12 ++++++------ apps/main/serializers.py | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index fcc6abe6..819cd172 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -191,6 +191,11 @@ class EstablishmentQuerySet(models.QuerySet): return self.filter(id__in=subquery_filter_by_distance) \ .order_by('-reviews__published_at') + def the_most_recent_award(self): + """ Returns the most recent award for carousel """ + return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( + field_name='vintage_year') + def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( @@ -283,7 +288,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): slug = models.SlugField(unique=True, max_length=50, null=True, verbose_name=_('Establishment slug'), editable=True) - establishment_awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') + awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') @@ -314,11 +319,6 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): country = self.address.city.country return country.low_price, country.high_price - @property - def awards(self): - return [Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( - field_name='vintage_year')] - # todo: make via prefetch # @property # def subtypes(self): diff --git a/apps/main/serializers.py b/apps/main/serializers.py index c981f0ee..e16cdd9b 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -141,6 +141,7 @@ class CarouselListSerializer(serializers.ModelSerializer): image = serializers.URLField(source='image_url') awards = AwardBaseSerializer(many=True) vintage_year = serializers.IntegerField() + last_award = serializers.SerializerMethodField() class Meta: """Meta class.""" @@ -154,8 +155,11 @@ class CarouselListSerializer(serializers.ModelSerializer): 'public_mark', 'image', 'vintage_year', + 'last_award', ] + def get_last_award(self, obj): + return obj.the_most_recent_award() class PageSerializer(serializers.ModelSerializer): page_name = serializers.CharField() From d42bf43d1963355f14a35363d4ef4ef719e7fa60 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 22:56:44 +0300 Subject: [PATCH 141/319] Latest award for carousel --- apps/establishment/models.py | 12 +++++++----- apps/main/serializers.py | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 819cd172..23bc3c8f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -191,11 +191,6 @@ class EstablishmentQuerySet(models.QuerySet): return self.filter(id__in=subquery_filter_by_distance) \ .order_by('-reviews__published_at') - def the_most_recent_award(self): - """ Returns the most recent award for carousel """ - return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( - field_name='vintage_year') - def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( @@ -363,6 +358,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ return self.address.coordinates + @property + def the_most_recent_award(self): + awards = Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) + if awards: + return awards.latest(field_name='vintage_year') + return {} + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/main/serializers.py b/apps/main/serializers.py index e16cdd9b..a0b49525 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -3,6 +3,7 @@ from rest_framework import serializers from advertisement.serializers.web import AdvertisementSerializer from location.serializers import CountrySerializer from main import models +from establishment.models import Establishment from utils.serializers import TranslatedField @@ -159,7 +160,10 @@ class CarouselListSerializer(serializers.ModelSerializer): ] def get_last_award(self, obj): - return obj.the_most_recent_award() + if isinstance(obj.content_object, Establishment): + award = obj.content_object.the_most_recent_award + return AwardBaseSerializer().to_representation(award) if award else None + return None class PageSerializer(serializers.ModelSerializer): page_name = serializers.CharField() From 78e1e448d9575b3463a6f95fa65de1e03bbfba19 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, 30 Sep 2019 10:09:46 +0300 Subject: [PATCH 142/319] News search --- apps/search_indexes/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/urls.py b/apps/search_indexes/urls.py index 8e914719..a566af1f 100644 --- a/apps/search_indexes/urls.py +++ b/apps/search_indexes/urls.py @@ -7,5 +7,6 @@ router = routers.SimpleRouter() # router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile') +router.register(r'news', views.NewsDocumentViewSet, basename='news') urlpatterns = router.urls From 90ae87a14ece3f86e2ae33ffdd8bfb8115ad24f0 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:09:53 +0500 Subject: [PATCH 143/319] added news mail template and task without celery delay --- apps/news/admin.py | 8 +------- apps/news/tasks.py | 28 ++++++++++++++++++++++------ apps/notification/urls/common.py | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index b866d867..dab70ec8 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,9 +1,7 @@ from django.contrib import admin from news import models -from notification.models import Subscriber from .tasks import send_email_with_news -from establishment.tasks import recalculate_price_levels_by_country @admin.register(models.NewsType) @@ -14,16 +12,12 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): - print(queryset) - news_ids = [n.id for n in queryset] - print(news_ids) + send_email_with_news(news_ids) # send_email_with_news.delay(news_ids) - recalculate_price_levels_by_country.delay(news_ids) - print("TEST send_email_action IS CALLED!") return diff --git a/apps/news/tasks.py b/apps/news/tasks.py index a7e43c8c..c3c011d7 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -1,12 +1,28 @@ from celery import shared_task - +from django.core.mail import send_mail from notification.models import Subscriber +from news import models +from django.template.loader import render_to_string +from django.conf import settings +from smtplib import SMTPException -@shared_task -def send_email_with_news(news): - print(news) +# @shared_task +def send_email_with_news(news_ids): - print("EMAILS WAS SENT!") + subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) - return news \ No newline at end of file + for s in subscribers: + try: + for n in news_ids: + sent_news = models.News.objects.get(id=n) + + send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, + {"title": sent_news.title.get(s.country_code), + "subtitle": sent_news.subtitle.get(s.country_code), + "description": sent_news.description.get(s.country_code), + "code": s.update_code, + "domain_uri": settings.DOMAIN_URI}), + settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) + except SMTPException: + continue diff --git a/apps/notification/urls/common.py b/apps/notification/urls/common.py index df43c805..842aa642 100644 --- a/apps/notification/urls/common.py +++ b/apps/notification/urls/common.py @@ -2,6 +2,7 @@ from django.urls import path from notification.views import common +app_name = "notification" urlpatterns = [ path('subscribe/', common.SubscribeView.as_view(), name='subscribe'), From 50b00e5b6d7bb02b779d7c7886b7571fcb1be5f3 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:10:45 +0500 Subject: [PATCH 144/319] added news mail template and task without celery delay --- project/settings/base.py | 1 + project/templates/news/news_email.html | 20 ++++++++++++++++++++ project/urls/web.py | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 project/templates/news/news_email.html diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..719b637b 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -397,6 +397,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1 RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' CHANGE_EMAIL_TEMPLATE = 'account/change_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html' +NEWS_EMAIL_TEMPLATE = "news/news_email.html" # COOKIES diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html new file mode 100644 index 00000000..c9669829 --- /dev/null +++ b/project/templates/news/news_email.html @@ -0,0 +1,20 @@ + + + + + {{ title }} + + +

{{ title }}

+ + {% if subtitle %} +

{{ subtitle }}

+ {% endif %} + +

{{ description }}

+ +https://{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} + + + + diff --git a/project/urls/web.py b/project/urls/web.py index 0a81672d..5bf538f3 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -23,7 +23,7 @@ urlpatterns = [ path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), path('news/', include('news.urls.web')), - path('notifications/', include('notification.urls.web')), + path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), path('main/', include('main.urls')), From 6d72c1abfdf5c77290191e853d9cf51355a36b74 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:18:18 +0500 Subject: [PATCH 145/319] fix locale and country code in task --- apps/news/tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index c3c011d7..98eaa8b1 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -18,9 +18,9 @@ def send_email_with_news(news_ids): sent_news = models.News.objects.get(id=n) send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, - {"title": sent_news.title.get(s.country_code), - "subtitle": sent_news.subtitle.get(s.country_code), - "description": sent_news.description.get(s.country_code), + {"title": sent_news.title.get(s.locale), + "subtitle": sent_news.subtitle.get(s.locale), + "description": sent_news.description.get(s.locale), "code": s.update_code, "domain_uri": settings.DOMAIN_URI}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) From 5ef33c0d1920c913ea422691b514d35d4eedbe02 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, 30 Sep 2019 10:19:09 +0300 Subject: [PATCH 146/319] News search url --- apps/search_indexes/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/urls.py b/apps/search_indexes/urls.py index a566af1f..549e569d 100644 --- a/apps/search_indexes/urls.py +++ b/apps/search_indexes/urls.py @@ -10,3 +10,4 @@ router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, ba router.register(r'news', views.NewsDocumentViewSet, basename='news') urlpatterns = router.urls + From 88df92a7a54edaaf5ca0f9a5913f727fea383a57 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:42:49 +0500 Subject: [PATCH 147/319] added country_code to news email template --- apps/news/tasks.py | 3 ++- project/templates/news/news_email.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 98eaa8b1..7bc2ce23 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -22,7 +22,8 @@ def send_email_with_news(news_ids): "subtitle": sent_news.subtitle.get(s.locale), "description": sent_news.description.get(s.locale), "code": s.update_code, - "domain_uri": settings.DOMAIN_URI}), + "domain_uri": settings.DOMAIN_URI, + "country_code": s.country_code}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) except SMTPException: continue diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index c9669829..a47af685 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -13,7 +13,7 @@

{{ description }}

-https://{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} +https://{{ country_code }}.{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} From 055e99dba231efc9e1898d3c234aa0e9109ad66d Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 13:17:38 +0500 Subject: [PATCH 148/319] added "on the same theme" news list and "you should read" news list --- apps/news/models.py | 33 +++++++++++++++++++++++++++++++++ apps/news/serializers.py | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/news/models.py b/apps/news/models.py index cfb10d39..fc07d4cd 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -119,3 +119,36 @@ class News(BaseAttributes, TranslatedFieldsMixin): news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] return news_list + + @property + def on_the_same_theme_news(self): + + # without "distinct" method the doubles are arising + like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\ + .order_by("-start").exclude(id=self.id).distinct() + + news_count = like_news.count() + + if news_count >= 3: + like_news = like_news[:3] + + news_list = [{"id": n.id, "slug": n.slug} for n in like_news] + + return news_list + + @property + def you_should_read_news(self): + + # without "distinct" method the doubles are arising + like_news = News.objects.published().filter(news_type=self.news_type).exclude(id=self.id).distinct() + + news_count = like_news.count() + + if news_count >= 3: + random_ids = random_sample(range(news_count), 3) + else: + random_ids = random_sample(range(news_count), news_count) + + news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] + + return news_list \ No newline at end of file diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 981ddae0..a36279b9 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -64,7 +64,9 @@ class NewsDetailSerializer(NewsBaseSerializer): 'is_publish', 'author', 'country', - 'list_also_like_news', + # 'list_also_like_news', + 'on_the_same_theme_news', + 'you_should_read_news', ) From 310928d95cd75ff3b760dd2717920937b768604d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Sep 2019 12:46:58 +0300 Subject: [PATCH 149/319] Fix import package issue --- apps/search_indexes/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 0e73ed25..a69caf1f 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -5,7 +5,7 @@ from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend GeoSpatialFilteringFilterBackend) from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet -from pagination import ProjectPageNumberPagination +from utils.pagination import ProjectPageNumberPagination from search_indexes import serializers, filters from search_indexes.documents import EstablishmentDocument, NewsDocument From 019ac97d19a7691ce93cc65d21a350ceb825a38c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Sep 2019 13:50:17 +0300 Subject: [PATCH 150/319] Add vintage year to detail --- apps/establishment/models.py | 7 +++++++ apps/establishment/serializers/common.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 23bc3c8f..ba34098d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -332,6 +332,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): raise ValidationError('Establishment type of subtype does not match') self.establishment_subtypes.add(establishment_subtype) + + @property + def vintage_year(self): + review_qs = self.reviews.by_status(Review.READY) + if review_qs.exists(): + return review_qs.last().vintage + @property def best_price_menu(self): return 150 diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 69a029e2..fa7d4b63 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -199,6 +199,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) + vintage_year = serializers.ReadOnlyField() class Meta(EstablishmentListSerializer.Meta): """Meta class.""" @@ -222,6 +223,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 'best_price_menu', 'best_price_carte', 'transportation', + 'vintage_year', ] # def get_in_favorites(self, obj): From b1e09d806a9bfa23e52f51074813eff2c315b78d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Sep 2019 14:00:34 +0300 Subject: [PATCH 151/319] Add ordering to timetable object --- apps/timetable/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/timetable/models.py b/apps/timetable/models.py index e1e7fae7..53670d02 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -36,3 +36,4 @@ class Timetable(ProjectBaseMixin): """Meta class.""" verbose_name = _('Timetable') verbose_name_plural = _('Timetables') + ordering = ['weekday'] From beb031d0b67a4b2b6425cf969496da7aa74988d4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Sep 2019 14:05:38 +0300 Subject: [PATCH 152/319] Add slug to carousel serializer --- apps/main/models.py | 5 +++++ apps/main/serializers.py | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/main/models.py b/apps/main/models.py index 47ac90be..6d4305d4 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -337,6 +337,11 @@ class Carousel(models.Model): if hasattr(self.content_object, 'image_url'): return self.content_object.image_url + @property + def slug(self): + if hasattr(self.content_object, 'slug'): + return self.content_object.slug + @property def model_name(self): return self.content_object.__class__.__name__ diff --git a/apps/main/serializers.py b/apps/main/serializers.py index a0b49525..6395e25f 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -157,6 +157,7 @@ class CarouselListSerializer(serializers.ModelSerializer): 'image', 'vintage_year', 'last_award', + 'slug', ] def get_last_award(self, obj): From 33478580aadbe0a0c2ebefd27fe63e1f90fbaf6f Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Sep 2019 16:40:46 +0300 Subject: [PATCH 153/319] Review fixes --- apps/establishment/models.py | 16 ++++++---------- apps/establishment/serializers/common.py | 2 +- apps/main/models.py | 11 ++++++++--- apps/main/serializers.py | 7 +------ 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index ba34098d..589c2e22 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,10 +14,8 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection -from main.models import MetaDataContent +from main.models import Award, MetaDataContent from location.models import Address -from collection.models import Collection -from main.models import Award from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -335,9 +333,9 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def vintage_year(self): - review_qs = self.reviews.by_status(Review.READY) - if review_qs.exists(): - return review_qs.last().vintage + last_review = self.reviews.by_status(Review.READY).last() + if last_review: + return last_review.vintage @property def best_price_menu(self): @@ -367,10 +365,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def the_most_recent_award(self): - awards = Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) - if awards: - return awards.latest(field_name='vintage_year') - return {} + return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( + field_name='vintage_year') class Position(BaseAttributes, TranslatedFieldsMixin): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index fa7d4b63..5e74b178 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -198,8 +198,8 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 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) - vintage_year = serializers.ReadOnlyField() + class Meta(EstablishmentListSerializer.Meta): """Meta class.""" diff --git a/apps/main/models.py b/apps/main/models.py index 6d4305d4..2270cbf9 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -318,9 +318,9 @@ class Carousel(models.Model): @property def vintage_year(self): if hasattr(self.content_object, 'reviews'): - review_qs = self.content_object.reviews.by_status(Review.READY) - if review_qs.exists(): - return review_qs.last().vintage + last_review = self.content_object.reviews.by_status(Review.READY).last() + if last_review: + return last_review.vintage @property def toque_number(self): @@ -342,6 +342,11 @@ class Carousel(models.Model): if hasattr(self.content_object, 'slug'): return self.content_object.slug + @property + def the_most_recent_award(self): + if hasattr(self.content_object, 'the_most_recent_award'): + return self.content_object.the_most_recent_award + @property def model_name(self): return self.content_object.__class__.__name__ diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 6395e25f..03ea73e6 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -142,7 +142,7 @@ class CarouselListSerializer(serializers.ModelSerializer): image = serializers.URLField(source='image_url') awards = AwardBaseSerializer(many=True) vintage_year = serializers.IntegerField() - last_award = serializers.SerializerMethodField() + last_award = AwardBaseSerializer(source='the_most_recent_award', allow_null=True) class Meta: """Meta class.""" @@ -160,11 +160,6 @@ class CarouselListSerializer(serializers.ModelSerializer): 'slug', ] - def get_last_award(self, obj): - if isinstance(obj.content_object, Establishment): - award = obj.content_object.the_most_recent_award - return AwardBaseSerializer().to_representation(award) if award else None - return None class PageSerializer(serializers.ModelSerializer): page_name = serializers.CharField() From 693d895aa4d9afe498ddb71bccb6cf3e03439113 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: Tue, 1 Oct 2019 11:52:46 +0300 Subject: [PATCH 154/319] Fix tests --- project/settings/local.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/project/settings/local.py b/project/settings/local.py index 503ad191..a644e9b8 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -1,5 +1,6 @@ """Local settings.""" from .base import * +import sys ALLOWED_HOSTS = ['*', ] @@ -65,5 +66,9 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { # 'search_indexes.documents.news': 'local_news', - 'search_indexes.documents.establishment': 'local_establishment', -} \ No newline at end of file + 'search_indexes.documents.establishment': 'local_establishment', +} + +TESTING = sys.argv[1:2] == ['test'] +if TESTING: + ELASTICSEARCH_INDEX_NAMES = {} From 051957c7cf37268c128228ffa79cbe6ee73077d8 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 1 Oct 2019 13:03:59 +0300 Subject: [PATCH 155/319] Refactor JSON Validation --- apps/establishment/serializers/back.py | 5 +++-- apps/establishment/serializers/common.py | 27 ++++++------------------ apps/news/serializers.py | 10 +++++---- apps/news/views.py | 3 +++ apps/utils/serializers.py | 24 +++++++++++++-------- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 5a15aafc..d0c70b2f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -8,7 +8,6 @@ from establishment.serializers import ( from utils.decorators import with_base_attributes from main.models import Currency -from utils.serializers import TJSONSerializer class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -89,13 +88,15 @@ class SocialNetworkSerializers(serializers.ModelSerializer): class PlatesSerializers(PlateSerializer): """Social network serializers.""" - name = TJSONSerializer + currency_id = serializers.PrimaryKeyRelatedField( source='currency', queryset=Currency.objects.all(), write_only=True ) class Meta: + """Meta class.""" + model = models.Plate fields = PlateSerializer.Meta.fields + [ 'name', diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index dc718d4f..e3857bd7 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -1,6 +1,6 @@ """Establishment serializers.""" -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models @@ -11,8 +11,7 @@ from main.serializers import MetaDataContentSerializer, AwardSerializer, Currenc from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions -from utils.serializers import TranslatedField -from utils.serializers import TJSONSerializer +from utils.serializers import TranslatedField, ProjectModelSerializer class ContactPhonesSerializer(serializers.ModelSerializer): @@ -44,9 +43,9 @@ class SocialNetworkRelatedSerializers(serializers.ModelSerializer): ] -class PlateSerializer(serializers.ModelSerializer): +class PlateSerializer(ProjectModelSerializer): - name_translated = serializers.CharField(allow_null=True, read_only=True) + name_translated = TranslatedField() currency = CurrencySerializer(read_only=True) class Meta: @@ -59,9 +58,8 @@ class PlateSerializer(serializers.ModelSerializer): ] -class MenuSerializers(serializers.ModelSerializer): +class MenuSerializers(ProjectModelSerializer): plates = PlateSerializer(read_only=True, many=True, source='plate_set') - category = TJSONSerializer() category_translated = serializers.CharField(read_only=True) class Meta: @@ -75,9 +73,8 @@ class MenuSerializers(serializers.ModelSerializer): ] -class MenuRUDSerializers(serializers.ModelSerializer, ): +class MenuRUDSerializers(ProjectModelSerializer): plates = PlateSerializer(read_only=True, many=True, source='plate_set') - category = TJSONSerializer() class Meta: model = models.Menu @@ -141,7 +138,7 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'position_translated', 'awards', 'priority') -class EstablishmentBaseSerializer(serializers.ModelSerializer): +class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" preview_image = serializers.URLField(source='preview_image_url') @@ -233,16 +230,6 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): 'vintage_year', ] - # def get_in_favorites(self, obj): - # """Get in_favorites status flag""" - # user = self.context.get('request').user - # if user.is_authenticated: - # return obj.id in user.favorites.by_content_type(app_label='establishment', - # model='establishment')\ - # .values_list('object_id', flat=True) - # else: - # return False - class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): """Create comment serializer""" diff --git a/apps/news/serializers.py b/apps/news/serializers.py index b144fdc5..248a03b7 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,11 +1,12 @@ """News app common serializers.""" from rest_framework import serializers +from account.serializers.common import UserSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer from news import models -from utils.serializers import TranslatedField -from account.serializers.common import UserSerializer +from utils.serializers import TranslatedField, ProjectModelSerializer + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -17,7 +18,7 @@ class NewsTypeSerializer(serializers.ModelSerializer): fields = ('id', 'name') -class NewsBaseSerializer(serializers.ModelSerializer): +class NewsBaseSerializer(ProjectModelSerializer): """Base serializer for News model.""" # read only fields @@ -50,7 +51,8 @@ class NewsDetailSerializer(NewsBaseSerializer): description_translated = TranslatedField() country = CountrySimpleSerializer(read_only=True) - author = UserSerializer(source='created_by') + # todo: check the data redundancy + author = UserSerializer(source='created_by', read_only=True) state_display = serializers.CharField(source='get_state_display', read_only=True) diff --git a/apps/news/views.py b/apps/news/views.py index 74abe33f..c5e69ab0 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -20,11 +20,13 @@ class NewsMixinView: class NewsListView(NewsMixinView, generics.ListAPIView): """News list view.""" + filter_class = filters.NewsListFilterSet class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """News detail view.""" + lookup_field = 'slug' serializer_class = serializers.NewsDetailSerializer @@ -54,6 +56,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, create_serializers_class = serializers.NewsBackOfficeDetailSerializer def get_serializer_class(self): + """Override serializer class.""" if self.request.method == 'POST': return self.create_serializers_class return super().get_serializer_class() diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index d30a046c..2b2282d1 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -1,7 +1,7 @@ """Utils app serializer.""" -from rest_framework import serializers -from utils.models import PlatformMixin from django.core import exceptions +from rest_framework import serializers +from utils import models from translation.models import Language @@ -11,8 +11,8 @@ class EmptySerializer(serializers.Serializer): class SourceSerializerMixin(serializers.Serializer): """Base authorization serializer mixin""" - source = serializers.ChoiceField(choices=PlatformMixin.SOURCES, - default=PlatformMixin.WEB, + source = serializers.ChoiceField(choices=models.PlatformMixin.SOURCES, + default=models.PlatformMixin.WEB, write_only=True) @@ -25,18 +25,16 @@ class TranslatedField(serializers.CharField): read_only=read_only, **kwargs) +# todo: view validation in more detail def validate_tjson(value): - if not isinstance(value, dict): raise exceptions.ValidationError( 'invalid_json', code='invalid_json', params={'value': value}, ) - lang_count = Language.objects.filter(locale__in=value.keys()).count() - - if lang_count == 0: + if lang_count != len(value.keys()): raise exceptions.ValidationError( 'invalid_translated_keys', code='invalid_translated_keys', @@ -44,5 +42,13 @@ def validate_tjson(value): ) -class TJSONSerializer(serializers.JSONField): +class TJSONField(serializers.JSONField): + """Custom serializer's JSONField for model's TJSONField.""" + validators = [validate_tjson] + + +class ProjectModelSerializer(serializers.ModelSerializer): + """Overrided ModelSerializer.""" + + serializers.ModelSerializer.serializer_field_mapping[models.TJSONField] = TJSONField From f034377405feb3868f6311b9fccc931c4254f3d8 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 1 Oct 2019 15:11:14 +0300 Subject: [PATCH 156/319] fix establishment serializer --- apps/establishment/serializers/common.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index e3857bd7..f3ae3dc0 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -145,6 +145,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressSerializer() tags = MetaDataContentSerializer(many=True) + in_favorites = serializers.BooleanField(allow_null=True) class Meta: """Meta class.""" @@ -168,14 +169,6 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): class EstablishmentListSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" - in_favorites = serializers.BooleanField(allow_null=True) - - class Meta(EstablishmentBaseSerializer.Meta): - """Meta class.""" - - fields = EstablishmentBaseSerializer.Meta.fields + [ - 'in_favorites', - ] class EstablishmentAllListSerializer(EstablishmentListSerializer): """ Serailizer for api/*/establishments """ From a497230883caa0724eb6fae766ab37e775a0708a Mon Sep 17 00:00:00 2001 From: michail Date: Tue, 1 Oct 2019 17:42:14 +0500 Subject: [PATCH 157/319] fix names of methods and fields of serializer --- apps/news/models.py | 8 +++++--- apps/news/serializers.py | 5 ++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/news/models.py b/apps/news/models.py index fc07d4cd..e99e7785 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -121,7 +121,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): return news_list @property - def on_the_same_theme_news(self): + def same_theme(self): + # on the same theme news # without "distinct" method the doubles are arising like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\ @@ -137,7 +138,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): return news_list @property - def you_should_read_news(self): + def should_read(self): + # you should read news # without "distinct" method the doubles are arising like_news = News.objects.published().filter(news_type=self.news_type).exclude(id=self.id).distinct() @@ -151,4 +153,4 @@ class News(BaseAttributes, TranslatedFieldsMixin): news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] - return news_list \ No newline at end of file + return news_list diff --git a/apps/news/serializers.py b/apps/news/serializers.py index a36279b9..c3ca3863 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -64,9 +64,8 @@ class NewsDetailSerializer(NewsBaseSerializer): 'is_publish', 'author', 'country', - # 'list_also_like_news', - 'on_the_same_theme_news', - 'you_should_read_news', + 'same_theme', + 'should_read', ) From 516c4458ac7c053bf83d5cf6bef1f3d100cc8f33 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 17:11:24 +0300 Subject: [PATCH 158/319] Make model name translated --- apps/main/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/main/models.py b/apps/main/models.py index 2270cbf9..fa6cf7d1 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -349,7 +349,9 @@ class Carousel(models.Model): @property def model_name(self): - return self.content_object.__class__.__name__ + if hasattr(self.content_object, 'establishment_type'): + return self.content_object.establishment_type.name_translated + class Page(models.Model): From ee221d093817e5aaa6fb2f2119938d78584536e8 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: Tue, 1 Oct 2019 17:33:21 +0300 Subject: [PATCH 159/319] Fix celery --- apps/news/admin.py | 11 +++-------- apps/news/tasks.py | 8 +++++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index dab70ec8..ad4a87c7 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -3,7 +3,6 @@ from django.contrib import admin from news import models from .tasks import send_email_with_news - @admin.register(models.NewsType) class NewsTypeAdmin(admin.ModelAdmin): """News type admin.""" @@ -12,13 +11,9 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): - news_ids = [n.id for n in queryset] - - send_email_with_news(news_ids) - - # send_email_with_news.delay(news_ids) - - return + news_ids =queryset.values('id') + list_id = list(queryset.values_list('id', flat=True)) + send_email_with_news.delay(list_id) send_email_action.short_description = "Send the selected news by email" diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 7bc2ce23..41a4a8c7 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -1,4 +1,4 @@ -from celery import shared_task +from celery import shared_task, task from django.core.mail import send_mail from notification.models import Subscriber from news import models @@ -7,15 +7,16 @@ from django.conf import settings from smtplib import SMTPException -# @shared_task +@shared_task def send_email_with_news(news_ids): - subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) for s in subscribers: try: for n in news_ids: + print(n) sent_news = models.News.objects.get(id=n) + # settings.NEWS_EMAIL_TEMPLATE send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, {"title": sent_news.title.get(s.locale), @@ -26,4 +27,5 @@ def send_email_with_news(news_ids): "country_code": s.country_code}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) except SMTPException: + print('SMTPException') continue From 9f280857effb0264ba5af6ba83b1c84fe8c7b404 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 1 Oct 2019 17:42:37 +0300 Subject: [PATCH 160/319] change docker-compose for working celery --- apps/collection/tests.py | 14 +++++++------- docker-compose.yml | 12 +----------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/apps/collection/tests.py b/apps/collection/tests.py index ea13fff9..72b40c37 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -1,15 +1,15 @@ -import json, pytz +import json +import pytz from datetime import datetime -from rest_framework.test import APITestCase -from account.models import User -from rest_framework import status from http.cookies import SimpleCookie -from collection.models import Collection, Guide -from location.models import Country +from rest_framework import status +from rest_framework.test import APITestCase +from account.models import User +from collection.models import Collection, Guide from establishment.models import Establishment, EstablishmentType -# Create your tests here. +from location.models import Country class BaseTestCase(APITestCase): diff --git a/docker-compose.yml b/docker-compose.yml index 3fb05f98..7c4e49d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,6 @@ services: - POSTGRES_DB=postgres ports: - "5436:5432" - networks: - - db-net volumes: - gm-db:/var/lib/postgresql/data/ elasticsearch: @@ -28,8 +26,7 @@ services: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.type=single-node - xpack.security.enabled=false - networks: - - app-net + # RabbitMQ rabbitmq: image: rabbitmq:latest @@ -83,19 +80,12 @@ services: - worker - worker_beat - elasticsearch - networks: - - app-net - - db-net volumes: - .:/code - gm-media:/media-data ports: - "8000:8000" -networks: - app-net: - db-net: - volumes: gm-db: name: gm-db From 9793c0a49f3e967b5ab4d6967d3dd2ab98598e39 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 17:42:37 +0300 Subject: [PATCH 161/319] Resend confirmation email method --- apps/authorization/serializers/common.py | 30 +++++++++++++++++++++++- apps/authorization/urls/common.py | 1 + apps/authorization/views/common.py | 11 +++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 5d8bb3a8..e19fdae2 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -4,7 +4,7 @@ from django.contrib.auth import authenticate from django.contrib.auth import password_validation as password_validators from django.db.models import Q from rest_framework import serializers -from rest_framework import validators as rest_validators +from rest_framework.generics import get_object_or_404 from account import models as account_models from authorization import tasks @@ -76,6 +76,34 @@ class SignupSerializer(serializers.ModelSerializer): return obj +class ReconfirmSerializer(serializers.ModelSerializer): + email = serializers.EmailField(write_only=True) + + class Meta: + model = account_models.User + fields = ('email',) + + def validate_email(self, value): + """Validate email""" + if not account_models.User.objects.filter(email=value).filter(email_confirmed=False).exists(): + raise serializers.ValidationError() + return value + + def create(self, validated_data): + """Override create method""" + queryset = account_models.User.objects.all() + obj = get_object_or_404(queryset, email=validated_data.get('email').lower()) + if settings.USE_CELERY: + tasks.send_confirm_email.delay( + user_id=obj.id, + country_code=self.context.get('request').country_code) + else: + tasks.send_confirm_email( + user_id=obj.id, + country_code=self.context.get('request').country_code) + return obj + + class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, serializers.ModelSerializer): """Serializer for login user""" diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index 4e6e59e1..213744d4 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -29,6 +29,7 @@ urlpatterns_oauth2 = [ urlpatterns_jwt = [ path('signup/', views.SignUpView.as_view(), name='signup'), + path('signup/reconfirm', views.ReconfirmView.as_view(), name='signup-reconfirm'), path('signup/confirm///', views.ConfirmationEmailView.as_view(), name='signup-confirm'), path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'), diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index bb337dce..5d789c8d 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -164,6 +164,17 @@ class SignUpView(generics.GenericAPIView): return Response(status=status.HTTP_201_CREATED) +class ReconfirmView(generics.CreateAPIView): + """ Resends confirmation email whether user's still not confirmed """ + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.ReconfirmSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + return Response(status=status.HTTP_201_CREATED) + + class ConfirmationEmailView(JWTGenericViewMixin): """View for confirmation email""" From 1f85f63d20ae6f3e18f8be9cfa98cf5002facd10 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: Tue, 1 Oct 2019 17:45:57 +0300 Subject: [PATCH 162/319] Fix celery --- apps/news/admin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index ad4a87c7..0bd9b709 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -11,9 +11,7 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): - news_ids =queryset.values('id') - list_id = list(queryset.values_list('id', flat=True)) - send_email_with_news.delay(list_id) + send_email_with_news.delay(list(queryset.values_list('id', flat=True))) send_email_action.short_description = "Send the selected news by email" From 0c79548027243054eac96367b781c3e312fa982e 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: Tue, 1 Oct 2019 17:51:27 +0300 Subject: [PATCH 163/319] Fix --- apps/news/tasks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 41a4a8c7..6d9e25b6 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -14,7 +14,6 @@ def send_email_with_news(news_ids): for s in subscribers: try: for n in news_ids: - print(n) sent_news = models.News.objects.get(id=n) # settings.NEWS_EMAIL_TEMPLATE @@ -27,5 +26,4 @@ def send_email_with_news(news_ids): "country_code": s.country_code}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) except SMTPException: - print('SMTPException') continue From e42f4d061cf991ce02914aa088456179758764d8 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 1 Oct 2019 18:07:04 +0300 Subject: [PATCH 164/319] refactor address --- apps/collection/views/common.py | 4 +- apps/establishment/serializers/common.py | 22 ++---- apps/establishment/views/web.py | 10 ++- apps/favorites/views.py | 7 +- apps/location/models.py | 5 +- apps/location/serializers/back.py | 5 +- apps/location/serializers/common.py | 99 +++++++++++++----------- apps/location/views/back.py | 5 +- apps/location/views/common.py | 6 +- 9 files changed, 79 insertions(+), 84 deletions(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 148c5fab..fd2a4584 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -4,7 +4,7 @@ from rest_framework import permissions from collection import models from utils.pagination import ProjectPageNumberPagination from django.shortcuts import get_object_or_404 -from establishment.serializers import EstablishmentListSerializer +from establishment.serializers import EstablishmentBaseSerializer from collection.serializers import common as serializers @@ -56,7 +56,7 @@ class CollectionEstablishmentListView(CollectionListView): """Retrieve list of establishment for collection.""" permission_classes = (permissions.AllowAny,) pagination_class = ProjectPageNumberPagination - serializer_class = EstablishmentListSerializer + serializer_class = EstablishmentBaseSerializer lookup_field = 'slug' def get_queryset(self): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index f3ae3dc0..f09c8200 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -5,7 +5,7 @@ from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites -from location.serializers import AddressSimpleSerializer, AddressSerializer +from location.serializers import AddressBaseSerializer from main.models import MetaDataContent from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer from review import models as review_models @@ -143,7 +143,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): preview_image = serializers.URLField(source='preview_image_url') slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) - address = AddressSerializer() + address = AddressBaseSerializer() tags = MetaDataContentSerializer(many=True) in_favorites = serializers.BooleanField(allow_null=True) @@ -166,19 +166,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): ] -class EstablishmentListSerializer(EstablishmentBaseSerializer): - """Serializer for Establishment model.""" - - -class EstablishmentAllListSerializer(EstablishmentListSerializer): - """ Serailizer for api/*/establishments """ - address = AddressSimpleSerializer() - - class Meta(EstablishmentListSerializer.Meta): - pass - - -class EstablishmentDetailSerializer(EstablishmentListSerializer): +class EstablishmentDetailSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" description_translated = TranslatedField() @@ -197,10 +185,10 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer): best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) vintage_year = serializers.ReadOnlyField() - class Meta(EstablishmentListSerializer.Meta): + class Meta(EstablishmentBaseSerializer.Meta): """Meta class.""" - fields = EstablishmentListSerializer.Meta.fields + [ + fields = EstablishmentBaseSerializer.Meta.fields + [ 'description_translated', 'image', 'subtypes', diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index f4558b71..8f5d2a26 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -28,7 +28,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): """Resource for getting a list of establishments.""" filter_class = filters.EstablishmentFilter - serializer_class = serializers.EstablishmentAllListSerializer + serializer_class = serializers.EstablishmentBaseSerializer def get_queryset(self): """Overridden method 'get_queryset'.""" @@ -70,7 +70,8 @@ class EstablishmentRecentReviewListView(EstablishmentListView): class EstablishmentSimilarListView(EstablishmentListView): """Resource for getting a list of establishments.""" - serializer_class = serializers.EstablishmentListSerializer + + serializer_class = serializers.EstablishmentBaseSerializer pagination_class = EstablishmentPortionPagination def get_queryset(self): @@ -96,6 +97,7 @@ class EstablishmentCommentCreateView(generics.CreateAPIView): class EstablishmentCommentListView(generics.ListAPIView): """View for return list of establishment comments.""" + permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentCommentCreateSerializer @@ -153,11 +155,13 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView): """Resource for getting list of nearest establishments.""" - serializer_class = serializers.EstablishmentListSerializer + + serializer_class = serializers.EstablishmentBaseSerializer filter_class = filters.EstablishmentFilter def get_queryset(self): """Overridden method 'get_queryset'.""" + # todo: latitude and longitude lat = self.request.query_params.get('lat') lon = self.request.query_params.get('lon') radius = self.request.query_params.get('radius') diff --git a/apps/favorites/views.py b/apps/favorites/views.py index a80960a8..5d99ed4b 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -1,13 +1,13 @@ """Views for app favorites.""" from rest_framework import generics - from establishment.models import Establishment -from establishment.serializers import EstablishmentListSerializer +from establishment.serializers import EstablishmentBaseSerializer from .models import Favorites class FavoritesBaseView(generics.GenericAPIView): """Base view for Favorites.""" + def get_queryset(self): """Override get_queryset method.""" return Favorites.objects.by_user(self.request.user) @@ -15,7 +15,8 @@ class FavoritesBaseView(generics.GenericAPIView): class FavoritesEstablishmentListView(generics.ListAPIView): """List views for favorites""" - serializer_class = EstablishmentListSerializer + + serializer_class = EstablishmentBaseSerializer def get_queryset(self): """Override get_queryset method""" diff --git a/apps/location/models.py b/apps/location/models.py index 7084385f..1cab1815 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -71,6 +71,7 @@ class City(models.Model): class Address(models.Model): + """Address model.""" city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE) street_name_1 = models.CharField( @@ -98,11 +99,11 @@ class Address(models.Model): @property def latitude(self): - return self.coordinates.y + return self.coordinates.y if self.coordinates else float(0) @property def longitude(self): - return self.coordinates.x + return self.coordinates.x if self.coordinates else float(0) @property def location_field_indexing(self): diff --git a/apps/location/serializers/back.py b/apps/location/serializers/back.py index f3b36e64..f25aacf6 100644 --- a/apps/location/serializers/back.py +++ b/apps/location/serializers/back.py @@ -1,11 +1,8 @@ -from django.contrib.gis.geos import Point -from rest_framework import serializers - from location import models from location.serializers import common -class AddressCreateSerializer(common.AddressSerializer): +class AddressCreateSerializer(common.AddressDetailSerializer): """Address create serializer.""" diff --git a/apps/location/serializers/common.py b/apps/location/serializers/common.py index 37d782de..87d0df4e 100644 --- a/apps/location/serializers/common.py +++ b/apps/location/serializers/common.py @@ -1,5 +1,6 @@ """Location app common serializers.""" from django.contrib.gis.geos import Point +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from location import models from utils.serializers import TranslatedField @@ -83,55 +84,18 @@ class CitySerializer(serializers.ModelSerializer): ] -class AddressSerializer(serializers.ModelSerializer): - """Address serializer.""" - - city_id = serializers.PrimaryKeyRelatedField( - source='city', - queryset=models.City.objects.all()) - city = CitySerializer(read_only=True) - geo_lon = serializers.FloatField(allow_null=True) - geo_lat = serializers.FloatField(allow_null=True) - - class Meta: - model = models.Address - fields = [ - 'id', - 'city_id', - 'city', - 'street_name_1', - 'street_name_2', - 'number', - 'postal_code', - 'geo_lon', - 'geo_lat', - ] - - def validate(self, attrs): - # if geo_lat and geo_lon was sent - geo_lat = attrs.pop('geo_lat') if 'geo_lat' in attrs else None - geo_lon = attrs.pop('geo_lon') if 'geo_lon' in attrs else None - - if geo_lat and geo_lon: - # Point(longitude, latitude) - attrs['coordinates'] = Point(geo_lat, geo_lon) - return attrs - - def to_representation(self, instance): - """Override to_representation method""" - if instance.coordinates and isinstance(instance.coordinates, Point): - # Point(longitude, latitude) - setattr(instance, 'geo_lat', instance.coordinates.x) - setattr(instance, 'geo_lon', instance.coordinates.y) - else: - setattr(instance, 'geo_lat', float(0)) - setattr(instance, 'geo_lon', float(0)) - return super().to_representation(instance) - - -class AddressSimpleSerializer(serializers.ModelSerializer): +class AddressBaseSerializer(serializers.ModelSerializer): """Serializer for address obj in related objects.""" + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + + # todo: remove this fields (backward compatibility) + geo_lon = serializers.FloatField(source='longitude', allow_null=True, + read_only=True) + geo_lat = serializers.FloatField(source='latitude', allow_null=True, + read_only=True) + class Meta: """Meta class.""" @@ -142,4 +106,45 @@ class AddressSimpleSerializer(serializers.ModelSerializer): 'street_name_2', 'number', 'postal_code', + 'latitude', + 'longitude', + + # todo: remove this fields (backward compatibility) + 'geo_lon', + 'geo_lat', + ) + + def validate_latitude(self, value): + if -90 <= value <= 90: + return value + raise serializers.ValidationError(_('Invalid value')) + + def validate_longitude(self, value): + if -180 <= value <= 180: + return value + raise serializers.ValidationError(_('Invalid value')) + + def validate(self, attrs): + # validate coordinates + latitude = attrs.pop('latitude', None) + longitude = attrs.pop('longitude', None) + if latitude is not None and longitude is not None: + attrs['coordinates'] = Point(longitude, latitude) + return attrs + + +class AddressDetailSerializer(AddressBaseSerializer): + """Address serializer.""" + + city_id = serializers.PrimaryKeyRelatedField( + source='city', write_only=True, + queryset=models.City.objects.all()) + city = CitySerializer(read_only=True) + + class Meta(AddressBaseSerializer.Meta): + """Meta class.""" + + fields = AddressBaseSerializer.Meta.fields + ( + 'city_id', + 'city', ) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 69ec28bc..ce6589ed 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -1,6 +1,5 @@ """Location app views.""" from rest_framework import generics -from rest_framework import permissions from location import models, serializers from location.views import common @@ -9,13 +8,13 @@ from location.views import common # Address class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" - serializer_class = serializers.AddressSerializer + serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" - serializer_class = serializers.AddressSerializer + serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() diff --git a/apps/location/views/common.py b/apps/location/views/common.py index 6bc332ad..792fce91 100644 --- a/apps/location/views/common.py +++ b/apps/location/views/common.py @@ -100,17 +100,17 @@ class CityUpdateView(CityViewMixin, generics.UpdateAPIView): # Address class AddressCreateView(AddressViewMixin, generics.CreateAPIView): """Create view for model Address""" - serializer_class = serializers.AddressSerializer + serializer_class = serializers.AddressDetailSerializer class AddressRetrieveView(AddressViewMixin, generics.RetrieveAPIView): """Retrieve view for model Address""" - serializer_class = serializers.AddressSerializer + serializer_class = serializers.AddressDetailSerializer class AddressListView(AddressViewMixin, generics.ListAPIView): """List view for model Address""" permission_classes = (permissions.AllowAny, ) - serializer_class = serializers.AddressSerializer + serializer_class = serializers.AddressDetailSerializer From 4c960c8abf0d6463526ec6632ae19ecf61c6d43d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 18:24:33 +0300 Subject: [PATCH 165/319] Booking (squashed commit) --- apps/booking/__init__.py | 0 apps/booking/admin.py | 8 + apps/booking/apps.py | 7 + apps/booking/migrations/0001_initial.py | 27 ++++ apps/booking/migrations/0002_booking_user.py | 21 +++ .../migrations/0003_auto_20190916_1533.py | 61 ++++++++ .../migrations/0004_auto_20190916_1646.py | 33 ++++ .../migrations/0005_auto_20190918_1308.py | 23 +++ .../migrations/0006_booking_country_code.py | 18 +++ .../migrations/0007_booking_booking_id.py | 18 +++ .../migrations/0008_auto_20190919_2008.py | 18 +++ apps/booking/migrations/0009_booking_user.py | 21 +++ .../migrations/0010_auto_20190920_1206.py | 45 ++++++ apps/booking/migrations/__init__.py | 0 apps/booking/models/__init__.py | 0 apps/booking/models/models.py | 67 +++++++++ apps/booking/models/services.py | 141 ++++++++++++++++++ apps/booking/serializers/__init__.py | 0 apps/booking/serializers/web.py | 47 ++++++ apps/booking/tests.py | 3 + apps/booking/urls.py | 14 ++ apps/booking/views.py | 115 ++++++++++++++ .../migrations/0020_auto_20190916_1532.py | 23 +++ apps/establishment/models.py | 4 + apps/establishment/serializers/back.py | 4 +- bin/manage | 3 + project/settings/base.py | 7 + project/settings/production.py | 6 + project/urls/web.py | 1 + 29 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 apps/booking/__init__.py create mode 100644 apps/booking/admin.py create mode 100644 apps/booking/apps.py create mode 100644 apps/booking/migrations/0001_initial.py create mode 100755 apps/booking/migrations/0002_booking_user.py create mode 100644 apps/booking/migrations/0003_auto_20190916_1533.py create mode 100644 apps/booking/migrations/0004_auto_20190916_1646.py create mode 100644 apps/booking/migrations/0005_auto_20190918_1308.py create mode 100644 apps/booking/migrations/0006_booking_country_code.py create mode 100644 apps/booking/migrations/0007_booking_booking_id.py create mode 100644 apps/booking/migrations/0008_auto_20190919_2008.py create mode 100644 apps/booking/migrations/0009_booking_user.py create mode 100644 apps/booking/migrations/0010_auto_20190920_1206.py create mode 100644 apps/booking/migrations/__init__.py create mode 100644 apps/booking/models/__init__.py create mode 100644 apps/booking/models/models.py create mode 100644 apps/booking/models/services.py create mode 100644 apps/booking/serializers/__init__.py create mode 100644 apps/booking/serializers/web.py create mode 100644 apps/booking/tests.py create mode 100644 apps/booking/urls.py create mode 100644 apps/booking/views.py create mode 100644 apps/establishment/migrations/0020_auto_20190916_1532.py create mode 100755 bin/manage diff --git a/apps/booking/__init__.py b/apps/booking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/admin.py b/apps/booking/admin.py new file mode 100644 index 00000000..59e56238 --- /dev/null +++ b/apps/booking/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import models + + +@admin.register(models.Booking) +class BookingModelAdmin(admin.ModelAdmin): + """Model admin for model Comment""" \ No newline at end of file diff --git a/apps/booking/apps.py b/apps/booking/apps.py new file mode 100644 index 00000000..5319bb5b --- /dev/null +++ b/apps/booking/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class BookingConfig(AppConfig): + name = 'booking' + verbose_name = _('Booking') diff --git a/apps/booking/migrations/0001_initial.py b/apps/booking/migrations/0001_initial.py new file mode 100644 index 00000000..f7d5e919 --- /dev/null +++ b/apps/booking/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-09-12 17:55 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Booking', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('type', models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/booking/migrations/0002_booking_user.py b/apps/booking/migrations/0002_booking_user.py new file mode 100755 index 00000000..0ee387b7 --- /dev/null +++ b/apps/booking/migrations/0002_booking_user.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-14 14:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('booking', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='user', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + ] diff --git a/apps/booking/migrations/0003_auto_20190916_1533.py b/apps/booking/migrations/0003_auto_20190916_1533.py new file mode 100644 index 00000000..448d94e7 --- /dev/null +++ b/apps/booking/migrations/0003_auto_20190916_1533.py @@ -0,0 +1,61 @@ +# Generated by Django 2.2.4 on 2019-09-16 15:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0002_booking_user'), + ] + + operations = [ + migrations.AlterModelOptions( + name='booking', + options={'verbose_name': 'Booking', 'verbose_name_plural': 'Booking'}, + ), + migrations.RemoveField( + model_name='booking', + name='user', + ), + migrations.AddField( + model_name='booking', + name='booked_persons_number', + field=models.PositiveIntegerField(default=2, verbose_name='persons number'), + ), + migrations.AddField( + model_name='booking', + name='booking_date', + field=models.DateField(default=None, verbose_name='booking date'), + ), + migrations.AddField( + model_name='booking', + name='booking_time', + field=models.TimeField(default=None, verbose_name='booking time'), + ), + migrations.AddField( + model_name='booking', + name='booking_user_locale', + field=models.CharField(default='en', max_length=10, verbose_name='booking locale'), + ), + migrations.AddField( + model_name='booking', + name='first_name', + field=models.CharField(default=None, max_length=200, verbose_name='booking first name'), + ), + migrations.AddField( + model_name='booking', + name='last_name', + field=models.CharField(default=None, max_length=200, verbose_name='booking last name'), + ), + migrations.AddField( + model_name='booking', + name='phone', + field=models.CharField(default=None, max_length=20, verbose_name='booking phone'), + ), + migrations.AddField( + model_name='booking', + name='restaurant_id', + field=models.PositiveIntegerField(default=None, verbose_name='booking service establishment id'), + ), + ] diff --git a/apps/booking/migrations/0004_auto_20190916_1646.py b/apps/booking/migrations/0004_auto_20190916_1646.py new file mode 100644 index 00000000..66471d26 --- /dev/null +++ b/apps/booking/migrations/0004_auto_20190916_1646.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.4 on 2019-09-16 16:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0003_auto_20190916_1533'), + ] + + operations = [ + migrations.AlterField( + model_name='booking', + name='first_name', + field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking first name'), + ), + migrations.AlterField( + model_name='booking', + name='last_name', + field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking last name'), + ), + migrations.AlterField( + model_name='booking', + name='phone', + field=models.CharField(default=None, max_length=20, null=True, verbose_name='booking phone'), + ), + migrations.AlterField( + model_name='booking', + name='type', + field=models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2, verbose_name='Guestonline or Lastable'), + ), + ] diff --git a/apps/booking/migrations/0005_auto_20190918_1308.py b/apps/booking/migrations/0005_auto_20190918_1308.py new file mode 100644 index 00000000..eae448d2 --- /dev/null +++ b/apps/booking/migrations/0005_auto_20190918_1308.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-09-18 13:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0004_auto_20190916_1646'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='email', + field=models.EmailField(default=None, max_length=254, null=True, verbose_name='Booking email'), + ), + migrations.AddField( + model_name='booking', + name='pending_booking_id', + field=models.TextField(default=None, verbose_name='external service pending booking'), + ), + ] diff --git a/apps/booking/migrations/0006_booking_country_code.py b/apps/booking/migrations/0006_booking_country_code.py new file mode 100644 index 00000000..aed2951e --- /dev/null +++ b/apps/booking/migrations/0006_booking_country_code.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-18 14:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0005_auto_20190918_1308'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='country_code', + field=models.CharField(default=None, max_length=10, null=True, verbose_name='Country code'), + ), + ] diff --git a/apps/booking/migrations/0007_booking_booking_id.py b/apps/booking/migrations/0007_booking_booking_id.py new file mode 100644 index 00000000..a96aecd1 --- /dev/null +++ b/apps/booking/migrations/0007_booking_booking_id.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-19 20:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0006_booking_country_code'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='booking_id', + field=models.TextField(default=None, null=True, verbose_name='external service booking id'), + ), + ] diff --git a/apps/booking/migrations/0008_auto_20190919_2008.py b/apps/booking/migrations/0008_auto_20190919_2008.py new file mode 100644 index 00000000..59255422 --- /dev/null +++ b/apps/booking/migrations/0008_auto_20190919_2008.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-19 20:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0007_booking_booking_id'), + ] + + operations = [ + migrations.AlterField( + model_name='booking', + name='booking_id', + field=models.TextField(db_index=True, default=None, null=True, verbose_name='external service booking id'), + ), + ] diff --git a/apps/booking/migrations/0009_booking_user.py b/apps/booking/migrations/0009_booking_user.py new file mode 100644 index 00000000..d24162f9 --- /dev/null +++ b/apps/booking/migrations/0009_booking_user.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-19 21:18 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('booking', '0008_auto_20190919_2008'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='user', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='booking owner'), + ), + ] diff --git a/apps/booking/migrations/0010_auto_20190920_1206.py b/apps/booking/migrations/0010_auto_20190920_1206.py new file mode 100644 index 00000000..95a0ae97 --- /dev/null +++ b/apps/booking/migrations/0010_auto_20190920_1206.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.4 on 2019-09-20 12:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0009_booking_user'), + ] + + operations = [ + migrations.RemoveField( + model_name='booking', + name='booked_persons_number', + ), + migrations.RemoveField( + model_name='booking', + name='booking_date', + ), + migrations.RemoveField( + model_name='booking', + name='booking_time', + ), + migrations.RemoveField( + model_name='booking', + name='country_code', + ), + migrations.RemoveField( + model_name='booking', + name='email', + ), + migrations.RemoveField( + model_name='booking', + name='first_name', + ), + migrations.RemoveField( + model_name='booking', + name='last_name', + ), + migrations.RemoveField( + model_name='booking', + name='phone', + ), + ] diff --git a/apps/booking/migrations/__init__.py b/apps/booking/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/models/__init__.py b/apps/booking/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/models/models.py b/apps/booking/models/models.py new file mode 100644 index 00000000..203189b8 --- /dev/null +++ b/apps/booking/models/models.py @@ -0,0 +1,67 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers +from utils.models import ProjectBaseMixin +from booking.models.services import LastableService, GuestonlineService +from account.models import User + + +class BookingManager(models.QuerySet): + def by_user(self, user: User): + return self.filter(user=user) + + +class Booking(ProjectBaseMixin): + LASTABLE = 'L' + GUESTONLINE = 'G' + AVAILABLE_SERVICES = ( + (LASTABLE, 'Lastable'), + (GUESTONLINE, 'GuestOnline') + ) + type = models.CharField(max_length=2, choices=AVAILABLE_SERVICES, verbose_name=_('Guestonline or Lastable')) + restaurant_id = models.PositiveIntegerField(verbose_name=_('booking service establishment id'), default=None) + booking_user_locale = models.CharField(verbose_name=_('booking locale'), default='en', max_length=10) + pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None) + booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True, + db_index=True, ) + user = models.ForeignKey( + 'account.User', verbose_name=_('booking owner'), null=True, + related_name='bookings', + blank=True, default=None, on_delete=models.CASCADE) + objects = BookingManager.as_manager() + + @property + def accept_email_spam(self): + return False + + @property + def accept_sms_spam(self): + return False + + @classmethod + def get_service_by_type(cls, type): + if type == cls.GUESTONLINE: + return GuestonlineService() + elif type == cls.LASTABLE: + return LastableService() + else: + return None + + @classmethod + def get_booking_id_by_type(cls, establishment, type): + if type == cls.GUESTONLINE: + return establishment.guestonline_id + elif type == cls.LASTABLE: + return establishment.lastable_id + else: + return None + + def delete(self, using=None, keep_parents=False): + service = self.get_service_by_type(self.type) + if not service.cancel_booking(self.booking_id): + raise serializers.ValidationError(detail='Something went wrong! Unable to cancel.') + super().delete(using, keep_parents) + + class Meta: + verbose_name = _('Booking') + verbose_name_plural = _('Booking') diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py new file mode 100644 index 00000000..1c117f56 --- /dev/null +++ b/apps/booking/models/services.py @@ -0,0 +1,141 @@ +from abc import ABC, abstractmethod +import json +import requests +from django.conf import settings +from rest_framework import status +import booking.models.models as models +from rest_framework import serializers + + +class AbstractBookingService(ABC): + """ Abstract class for Guestonline && Lastable booking services""" + + def __init__(self, service): + self.service = None + self.response = None + if service not in [models.Booking.LASTABLE, models.Booking.GUESTONLINE]: + raise Exception('Service %s is not implemented yet' % service) + self.service = service + if service == models.Booking.GUESTONLINE: + self.token = settings.GUESTONLINE_TOKEN + self.url = settings.GUESTONLINE_SERVICE + elif service == models.Booking.LASTABLE: + self.token = settings.LASTABLE_TOKEN + self.url = settings.LASTABLE_SERVICE + + @staticmethod + def get_certain_keys(d: dict, keys_to_preserve: set) -> dict: + """ Helper """ + return {key: d[key] for key in d.keys() & keys_to_preserve} + + @abstractmethod + def check_whether_booking_available(self, restaurant_id, date): + """ checks whether booking is available """ + pass + + @abstractmethod + def cancel_booking(self, payload): + """ cancels booking and returns the result """ + pass + + @abstractmethod + def create_pending_booking(self, payload): + """ returns pending booking id if created. otherwise False """ + pass + + @abstractmethod + def update_pending_booking(self, payload): + """ updates pending booking with contacts """ + pass + + @abstractmethod + def get_common_headers(self): + pass + + @abstractmethod + def get_booking_details(self, payload): + """ fetches booking details from external service """ + pass + + +class GuestonlineService(AbstractBookingService): + def __init__(self): + super().__init__(models.Booking.GUESTONLINE) + + def get_common_headers(self): + return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'} + + def check_whether_booking_available(self, restaurant_id, date: str): + url = f'{self.url}v1/runtime_services' + params = {'restaurant_id': restaurant_id, 'date': date, 'expands[]': 'table_availabilities'} + r = requests.get(url, headers=self.get_common_headers(), params=params) + if not status.is_success(r.status_code): + return False + response = json.loads(r.content)['runtime_services'] + keys_to_preserve = {'left_seats', 'table_availabilities', 'closed', 'start_time', 'end_time', 'last_booking'} + response = map(lambda x: self.get_certain_keys(x, keys_to_preserve), response) + self.response = response + return True + + def commit_booking(self, payload): + url = self.url + 'v1/pending_bookings/' + payload + '/commit' + r = requests.put(url, headers=self.get_common_headers()) + self.response = json.loads(r.content) + if status.is_success(r.status_code) and self.response is None: + raise serializers.ValidationError(detail='Booking already committed.') + return status.is_success(r.status_code) + + def update_pending_booking(self, payload): + booking_id = payload.pop('pending_booking_id') + url = self.url + 'v1/pending_bookings/' + booking_id + payload['lastname'] = payload.pop('last_name') + payload['firstname'] = payload.pop('first_name') + payload['mobile_phone'] = payload.pop('phone') + headers = self.get_common_headers() + r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload})) + return status.is_success(r.status_code) + + def create_pending_booking(self, payload): + url = self.url + 'v1/pending_bookings' + payload['hour'] = payload.pop('booking_time') + payload['persons'] = payload.pop('booked_persons_number') + payload['date'] = payload.pop('booking_date') + r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload)) + return json.loads(r.content)['id'] if status.is_success(r.status_code) else False + + def cancel_booking(self, payload): + url = f'{self.url}v1/pending_bookings/{payload}' + r = requests.delete(url, headers=self.get_common_headers()) + return status.is_success(r.status_code) + + def get_booking_details(self, payload): + url = f'{self.url}v1/bookings/{payload}' + r = requests.get(url, headers=self.get_common_headers()) + return json.loads(r.content) + + +class LastableService(AbstractBookingService): + def __init__(self): + super().__init__(models.Booking.LASTABLE) + + def create_pending_booking(self, payload): + pass + + def get_common_headers(self): + return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json', + 'Accept': 'application/json'} + + def check_whether_booking_available(self, restaurant_id, date): + return False + + def commit_booking(self, payload): + return False + + def update_pending_booking(self, payload): + return False + + def cancel_booking(self, payload): + return False + + def get_booking_details(self, payload): + return {} diff --git a/apps/booking/serializers/__init__.py b/apps/booking/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/serializers/web.py b/apps/booking/serializers/web.py new file mode 100644 index 00000000..f5b142e0 --- /dev/null +++ b/apps/booking/serializers/web.py @@ -0,0 +1,47 @@ +from rest_framework import serializers +from booking.models import models + + +class BookingSerializer(serializers.ModelSerializer): + class Meta: + model = models.Booking + fields = ( + 'id', + 'type', + ) + + +class PendingBookingSerializer(serializers.ModelSerializer): + restaurant_id = serializers.IntegerField(min_value=0, ) + id = serializers.ReadOnlyField() + + class Meta: + model = models.Booking + fields = ( + 'id', + 'type', + 'restaurant_id', + 'pending_booking_id', + 'user', + ) + + +class UpdateBookingSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField() + + class Meta: + model = models.Booking + fields = ('booking_id', 'id') + + +class GetBookingSerializer(serializers.ModelSerializer): + details = serializers.SerializerMethodField() + + def get_details(self, obj): + booking = self.instance + service = booking.get_service_by_type(booking.type) + return service.get_booking_details(booking.booking_id) + + class Meta: + model = models.Booking + fields = '__all__' diff --git a/apps/booking/tests.py b/apps/booking/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/booking/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/booking/urls.py b/apps/booking/urls.py new file mode 100644 index 00000000..a86178ff --- /dev/null +++ b/apps/booking/urls.py @@ -0,0 +1,14 @@ +"""Booking app urls.""" +from django.urls import path +from booking import views + +app = 'booking' + +urlpatterns = [ + path('/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'), + path('/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'), + path('', views.UpdatePendingBooking.as_view(), name='update-pending-booking'), + path('/cancel', views.CancelBooking.as_view(), name='cancel-existing-booking'), + path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'), + path('retrieve/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'), +] diff --git a/apps/booking/views.py b/apps/booking/views.py new file mode 100644 index 00000000..2bb5ec2b --- /dev/null +++ b/apps/booking/views.py @@ -0,0 +1,115 @@ +from rest_framework import generics, permissions, status + +from django.shortcuts import get_object_or_404 +from establishment.models import Establishment +from booking.models.models import Booking, GuestonlineService, LastableService +from rest_framework.response import Response +from booking.serializers.web import (PendingBookingSerializer, + UpdateBookingSerializer, GetBookingSerializer) +from utils.serializers import EmptySerializer + + +class CheckWhetherBookingAvailable(generics.GenericAPIView): + """ Checks which service to use if establishmend is managed by any """ + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + pagination_class = None + + def get(self, request, *args, **kwargs): + is_booking_available = False + establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id']) + service = None + date = request.query_params.get('date') + g_service = GuestonlineService() + l_service = LastableService() + if not establishment.lastable_id is None and l_service \ + .check_whether_booking_available(establishment.lastable_id, date): + is_booking_available = True + service = l_service + service.service_id = establishment.lastable_id + elif not establishment.guestonline_id is None and g_service \ + .check_whether_booking_available(establishment.guestonline_id, date): + is_booking_available = True + service = g_service + service.service_id = establishment.guestonline_id + + response = { + 'available': is_booking_available, + 'type': service.service, + } + response.update({'details': service.response} if service.response else {}) + return Response(data=response, status=200) + + +class CreatePendingBooking(generics.CreateAPIView): + """ Creates pending booking """ + permission_classes = (permissions.AllowAny,) + serializer_class = PendingBookingSerializer + + def post(self, request, *args, **kwargs): + data = request.data.copy() + establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id']) + data['restaurant_id'] = Booking.get_booking_id_by_type(establishment, data.get('type')) + service = Booking.get_service_by_type(request.data.get('type')) + data['user'] = request.user.pk if request.user else None + data['pending_booking_id'] = service.create_pending_booking(service.get_certain_keys(data, { + 'restaurant_id', + 'booking_time', + 'booking_date', + 'booked_persons_number', + })) + if not data['pending_booking_id']: + return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking') + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_201_CREATED, data=serializer.data) + + +class UpdatePendingBooking(generics.UpdateAPIView): + """ Update pending booking with contacts """ + queryset = Booking.objects.all() + permission_classes = (permissions.AllowAny,) + serializer_class = UpdateBookingSerializer + + def patch(self, request, *args, **kwargs): + instance = self.get_object() + data = request.data.copy() + service = Booking.get_service_by_type(instance.type) + data['pending_booking_id'] = instance.pending_booking_id + service.update_pending_booking(service.get_certain_keys(data, { + 'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', + })) + service.commit_booking(data['pending_booking_id']) + data = { + 'booking_id': service.response.get('id'), + 'id': instance.pk, + } + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.update(instance, data) + return Response(status=status.HTTP_200_OK, data=serializer.data) + + +class CancelBooking(generics.DestroyAPIView): + """ Cancel existing booking """ + queryset = Booking.objects.all() + permission_classes = (permissions.AllowAny,) + + +class LastBooking(generics.RetrieveAPIView): + """ Get last booking by user credentials """ + permission_classes = (permissions.IsAuthenticated,) + serializer_class = GetBookingSerializer + lookup_field = None + + def get_object(self): + return Booking.objects.by_user(self.request.user).latest('modified') + + +class GetBookingById(generics.RetrieveAPIView): + """ Returns booking by its id""" + permission_classes = (permissions.AllowAny,) + serializer_class = GetBookingSerializer + queryset = Booking.objects.all() diff --git a/apps/establishment/migrations/0020_auto_20190916_1532.py b/apps/establishment/migrations/0020_auto_20190916_1532.py new file mode 100644 index 00000000..d22fad07 --- /dev/null +++ b/apps/establishment/migrations/0020_auto_20190916_1532.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-09-16 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0019_establishment_is_publish'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='guestonline_id', + field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='guestonline id'), + ), + migrations.AddField( + model_name='establishment', + name='lastable_id', + field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='lastable id'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 589c2e22..d809d9b5 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -260,6 +260,10 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Twitter URL')) lafourchette = models.URLField(blank=True, null=True, default=None, verbose_name=_('Lafourchette URL')) + guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'), + null=True, default=None,) + lastable_id = models.PositiveIntegerField(blank=True, verbose_name=_('lastable id'), + null=True, default=None,) booking = models.URLField(blank=True, null=True, default=None, verbose_name=_('Booking URL')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index d0c70b2f..8bd09e85 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -39,7 +39,9 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): 'image_url', 'slug', # TODO: check in admin filters - 'is_publish' + 'is_publish', + 'guestonline_id', + 'lastable_id', ] diff --git a/bin/manage b/bin/manage new file mode 100755 index 00000000..62d0ec94 --- /dev/null +++ b/bin/manage @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +docker-compose run --rm gm_app python manage.py "$@" \ No newline at end of file diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..9f72ecde 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -55,6 +55,7 @@ PROJECT_APPS = [ 'advertisement.apps.AdvertisementConfig', 'account.apps.AccountConfig', 'authorization.apps.AuthorizationConfig', + 'booking.apps.BookingConfig', 'collection.apps.CollectionConfig', 'establishment.apps.EstablishmentConfig', 'gallery.apps.GalleryConfig', @@ -262,6 +263,12 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email', } +# Booking API configuration +GUESTONLINE_SERVICE = 'https://api-preprod.guestonline.fr/' +GUESTONLINE_TOKEN = 'iiReiYpyojshpPjpmczS' +LASTABLE_SERVICE = '' +LASTABLE_TOKEN = '' + # SMS Settings SMS_EXPIRATION = 5 SMS_SEND_DELAY = 30 diff --git a/project/settings/production.py b/project/settings/production.py index e491a1fb..76dc31f0 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -1,2 +1,8 @@ """Production settings.""" from .base import * + +# Booking API configuration +GUESTONLINE_SERVICE = 'https://api.guestonline.fr/' +GUESTONLINE_TOKEN = '' +LASTABLE_SERVICE = '' +LASTABLE_TOKEN = '' \ No newline at end of file diff --git a/project/urls/web.py b/project/urls/web.py index 0a81672d..71b8f84f 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -19,6 +19,7 @@ app_name = 'web' urlpatterns = [ path('account/', include('account.urls.web')), + path('booking/', include('booking.urls')), path('re_blocks/', include('advertisement.urls.web')), path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), From 393aa9ef28443b64ee0bf8993f6da8581b9a65f4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 18:30:49 +0300 Subject: [PATCH 166/319] Merge migrations after rebase --- .../migrations/0032_merge_20191001_1530.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/establishment/migrations/0032_merge_20191001_1530.py diff --git a/apps/establishment/migrations/0032_merge_20191001_1530.py b/apps/establishment/migrations/0032_merge_20191001_1530.py new file mode 100644 index 00000000..d0448141 --- /dev/null +++ b/apps/establishment/migrations/0032_merge_20191001_1530.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-01 15:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0020_auto_20190916_1532'), + ('establishment', '0031_establishment_slug'), + ] + + operations = [ + ] From 4cf0ce831b20500bb21c14cb464d211d58f4d9d5 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 20:12:47 +0300 Subject: [PATCH 167/319] Raise error whether field's absent --- apps/booking/models/services.py | 5 ++++- apps/booking/serializers/web.py | 17 ++++++++++++++++- apps/booking/views.py | 5 ++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py index 1c117f56..b5d7fc86 100644 --- a/apps/booking/models/services.py +++ b/apps/booking/models/services.py @@ -31,7 +31,8 @@ class AbstractBookingService(ABC): @abstractmethod def check_whether_booking_available(self, restaurant_id, date): """ checks whether booking is available """ - pass + if date is None: + raise serializers.ValidationError(detail='date query param is required') @abstractmethod def cancel_booking(self, payload): @@ -66,6 +67,7 @@ class GuestonlineService(AbstractBookingService): return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'} def check_whether_booking_available(self, restaurant_id, date: str): + super().check_whether_booking_available(restaurant_id, date) url = f'{self.url}v1/runtime_services' params = {'restaurant_id': restaurant_id, 'date': date, 'expands[]': 'table_availabilities'} r = requests.get(url, headers=self.get_common_headers(), params=params) @@ -126,6 +128,7 @@ class LastableService(AbstractBookingService): 'Accept': 'application/json'} def check_whether_booking_available(self, restaurant_id, date): + super().check_whether_booking_available(restaurant_id, date) return False def commit_booking(self, payload): diff --git a/apps/booking/serializers/web.py b/apps/booking/serializers/web.py index f5b142e0..8b4f1d91 100644 --- a/apps/booking/serializers/web.py +++ b/apps/booking/serializers/web.py @@ -11,9 +11,24 @@ class BookingSerializer(serializers.ModelSerializer): ) +class CheckBookingSerializer(serializers.ModelSerializer): + available = serializers.BooleanField() + type = serializers.ChoiceField(choices=models.Booking.AVAILABLE_SERVICES, allow_null=True) + details = serializers.DictField() + + class Meta: + model = models.Booking + fields = ( + 'available', + 'type', + 'details', + ) + + class PendingBookingSerializer(serializers.ModelSerializer): - restaurant_id = serializers.IntegerField(min_value=0, ) + restaurant_id = serializers.IntegerField(read_only=True) id = serializers.ReadOnlyField() + user = serializers.ReadOnlyField() class Meta: model = models.Booking diff --git a/apps/booking/views.py b/apps/booking/views.py index 2bb5ec2b..5fc342ce 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -5,15 +5,14 @@ from establishment.models import Establishment from booking.models.models import Booking, GuestonlineService, LastableService from rest_framework.response import Response from booking.serializers.web import (PendingBookingSerializer, - UpdateBookingSerializer, GetBookingSerializer) -from utils.serializers import EmptySerializer + UpdateBookingSerializer, GetBookingSerializer, CheckBookingSerializer) class CheckWhetherBookingAvailable(generics.GenericAPIView): """ Checks which service to use if establishmend is managed by any """ permission_classes = (permissions.AllowAny,) - serializer_class = EmptySerializer + serializer_class = CheckBookingSerializer pagination_class = None def get(self, request, *args, **kwargs): From df6b221029ed651568781615df9f28c92afe7a0c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 21:32:24 +0300 Subject: [PATCH 168/319] Review fixes --- apps/authorization/serializers/common.py | 17 +++++++++++------ apps/authorization/urls/common.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index e19fdae2..07c561f8 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -77,7 +77,6 @@ class SignupSerializer(serializers.ModelSerializer): class ReconfirmSerializer(serializers.ModelSerializer): - email = serializers.EmailField(write_only=True) class Meta: model = account_models.User @@ -85,22 +84,28 @@ class ReconfirmSerializer(serializers.ModelSerializer): def validate_email(self, value): """Validate email""" - if not account_models.User.objects.filter(email=value).filter(email_confirmed=False).exists(): - raise serializers.ValidationError() + users = list(account_models.User.objects.filter(email=value.lower()).all()) + if not users: + raise serializers.ValidationError(detail='User with mentioned email does not exist.') + users = list(filter(lambda user: not user.email_confirmed, users)) + if not users: + raise serializers.ValidationError(detail='User with this email is confirmed.') return value def create(self, validated_data): """Override create method""" queryset = account_models.User.objects.all() - obj = get_object_or_404(queryset, email=validated_data.get('email').lower()) + email = validated_data.get('email').lower() + country_code = self.context.get('request').country_code + obj = get_object_or_404(queryset, email=email) if settings.USE_CELERY: tasks.send_confirm_email.delay( user_id=obj.id, - country_code=self.context.get('request').country_code) + country_code=country_code) else: tasks.send_confirm_email( user_id=obj.id, - country_code=self.context.get('request').country_code) + country_code=country_code) return obj diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index 213744d4..814ce836 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -29,7 +29,7 @@ urlpatterns_oauth2 = [ urlpatterns_jwt = [ path('signup/', views.SignUpView.as_view(), name='signup'), - path('signup/reconfirm', views.ReconfirmView.as_view(), name='signup-reconfirm'), + path('signup/reconfirm/', views.ReconfirmView.as_view(), name='signup-reconfirm'), path('signup/confirm///', views.ConfirmationEmailView.as_view(), name='signup-confirm'), path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'), From 6683d4a4eff4f6f4d8cbe778b0fa155eb83cdff4 Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 11:14:18 +0500 Subject: [PATCH 169/319] First commit --- apps/news/admin.py | 22 ++++++++++++++++++++++ apps/news/tasks.py | 12 ++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 apps/news/tasks.py diff --git a/apps/news/admin.py b/apps/news/admin.py index 7cbfb049..b866d867 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,5 +1,9 @@ from django.contrib import admin + from news import models +from notification.models import Subscriber +from .tasks import send_email_with_news +from establishment.tasks import recalculate_price_levels_by_country @admin.register(models.NewsType) @@ -9,6 +13,24 @@ class NewsTypeAdmin(admin.ModelAdmin): list_display_links = ['id', 'name'] +def send_email_action(modeladmin, request, queryset): + print(queryset) + + news_ids = [n.id for n in queryset] + + print(news_ids) + + # send_email_with_news.delay(news_ids) + recalculate_price_levels_by_country.delay(news_ids) + + print("TEST send_email_action IS CALLED!") + return + + +send_email_action.short_description = "Send the selected news by email" + + @admin.register(models.News) class NewsAdmin(admin.ModelAdmin): """News admin.""" + actions = [send_email_action] diff --git a/apps/news/tasks.py b/apps/news/tasks.py new file mode 100644 index 00000000..a7e43c8c --- /dev/null +++ b/apps/news/tasks.py @@ -0,0 +1,12 @@ +from celery import shared_task + +from notification.models import Subscriber + +@shared_task +def send_email_with_news(news): + + print(news) + + print("EMAILS WAS SENT!") + + return news \ No newline at end of file From 5474c2ff692a36a276553688b021e39651a7d986 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:09:53 +0500 Subject: [PATCH 170/319] added news mail template and task without celery delay --- apps/news/admin.py | 8 +------- apps/news/tasks.py | 28 ++++++++++++++++++++++------ apps/notification/urls/common.py | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index b866d867..dab70ec8 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,9 +1,7 @@ from django.contrib import admin from news import models -from notification.models import Subscriber from .tasks import send_email_with_news -from establishment.tasks import recalculate_price_levels_by_country @admin.register(models.NewsType) @@ -14,16 +12,12 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): - print(queryset) - news_ids = [n.id for n in queryset] - print(news_ids) + send_email_with_news(news_ids) # send_email_with_news.delay(news_ids) - recalculate_price_levels_by_country.delay(news_ids) - print("TEST send_email_action IS CALLED!") return diff --git a/apps/news/tasks.py b/apps/news/tasks.py index a7e43c8c..c3c011d7 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -1,12 +1,28 @@ from celery import shared_task - +from django.core.mail import send_mail from notification.models import Subscriber +from news import models +from django.template.loader import render_to_string +from django.conf import settings +from smtplib import SMTPException -@shared_task -def send_email_with_news(news): - print(news) +# @shared_task +def send_email_with_news(news_ids): - print("EMAILS WAS SENT!") + subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) - return news \ No newline at end of file + for s in subscribers: + try: + for n in news_ids: + sent_news = models.News.objects.get(id=n) + + send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, + {"title": sent_news.title.get(s.country_code), + "subtitle": sent_news.subtitle.get(s.country_code), + "description": sent_news.description.get(s.country_code), + "code": s.update_code, + "domain_uri": settings.DOMAIN_URI}), + settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) + except SMTPException: + continue diff --git a/apps/notification/urls/common.py b/apps/notification/urls/common.py index df43c805..842aa642 100644 --- a/apps/notification/urls/common.py +++ b/apps/notification/urls/common.py @@ -2,6 +2,7 @@ from django.urls import path from notification.views import common +app_name = "notification" urlpatterns = [ path('subscribe/', common.SubscribeView.as_view(), name='subscribe'), From b96ed30a492776f5e7d24fbfc9729a7b7bd800b5 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:10:45 +0500 Subject: [PATCH 171/319] added news mail template and task without celery delay --- project/settings/base.py | 1 + project/templates/news/news_email.html | 20 ++++++++++++++++++++ project/urls/web.py | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 project/templates/news/news_email.html diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..719b637b 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -397,6 +397,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1 RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' CHANGE_EMAIL_TEMPLATE = 'account/change_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html' +NEWS_EMAIL_TEMPLATE = "news/news_email.html" # COOKIES diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html new file mode 100644 index 00000000..c9669829 --- /dev/null +++ b/project/templates/news/news_email.html @@ -0,0 +1,20 @@ + + + + + {{ title }} + + +

{{ title }}

+ + {% if subtitle %} +

{{ subtitle }}

+ {% endif %} + +

{{ description }}

+ +https://{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} + + + + diff --git a/project/urls/web.py b/project/urls/web.py index 0a81672d..5bf538f3 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -23,7 +23,7 @@ urlpatterns = [ path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), path('news/', include('news.urls.web')), - path('notifications/', include('notification.urls.web')), + path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), path('main/', include('main.urls')), From c28d1cf3a07cd0f020f09e9f82247d3227cf0dd3 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:18:18 +0500 Subject: [PATCH 172/319] fix locale and country code in task --- apps/news/tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index c3c011d7..98eaa8b1 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -18,9 +18,9 @@ def send_email_with_news(news_ids): sent_news = models.News.objects.get(id=n) send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, - {"title": sent_news.title.get(s.country_code), - "subtitle": sent_news.subtitle.get(s.country_code), - "description": sent_news.description.get(s.country_code), + {"title": sent_news.title.get(s.locale), + "subtitle": sent_news.subtitle.get(s.locale), + "description": sent_news.description.get(s.locale), "code": s.update_code, "domain_uri": settings.DOMAIN_URI}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) From c0149a069583e1b94c2887b107894e812e91e61f Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:42:49 +0500 Subject: [PATCH 173/319] added country_code to news email template --- apps/news/tasks.py | 3 ++- project/templates/news/news_email.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 98eaa8b1..7bc2ce23 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -22,7 +22,8 @@ def send_email_with_news(news_ids): "subtitle": sent_news.subtitle.get(s.locale), "description": sent_news.description.get(s.locale), "code": s.update_code, - "domain_uri": settings.DOMAIN_URI}), + "domain_uri": settings.DOMAIN_URI, + "country_code": s.country_code}), settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) except SMTPException: continue diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index c9669829..a47af685 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -13,7 +13,7 @@

{{ description }}

-https://{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} +https://{{ country_code }}.{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} From 02638a26fe9561f3f449280d36282679bf389e29 Mon Sep 17 00:00:00 2001 From: michail Date: Wed, 2 Oct 2019 12:09:45 +0500 Subject: [PATCH 174/319] added sending through celery --- apps/news/admin.py | 4 +--- apps/news/tasks.py | 2 +- docker-compose.yml | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index dab70ec8..1b869eb2 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -14,9 +14,7 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): news_ids = [n.id for n in queryset] - send_email_with_news(news_ids) - - # send_email_with_news.delay(news_ids) + send_email_with_news.delay(news_ids) return diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 7bc2ce23..35f2c644 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -7,7 +7,7 @@ from django.conf import settings from smtplib import SMTPException -# @shared_task +@shared_task def send_email_with_news(news_ids): subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) diff --git a/docker-compose.yml b/docker-compose.yml index 3fb05f98..2ccd6de3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: - POSTGRES_DB=postgres ports: - "5436:5432" - networks: - - db-net +# networks: +# - db-net volumes: - gm-db:/var/lib/postgresql/data/ elasticsearch: @@ -28,8 +28,8 @@ services: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.type=single-node - xpack.security.enabled=false - networks: - - app-net +# networks: +# - app-net # RabbitMQ rabbitmq: image: rabbitmq:latest @@ -83,18 +83,18 @@ services: - worker - worker_beat - elasticsearch - networks: - - app-net - - db-net +# networks: +# - app-net +# - db-net volumes: - .:/code - gm-media:/media-data ports: - "8000:8000" -networks: - app-net: - db-net: +#networks: +# app-net: +# db-net: volumes: gm-db: From e611e3154d14322fc544e41042a5a6358a9396cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 2 Oct 2019 11:21:28 +0300 Subject: [PATCH 175/319] Push --- apps/news/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index 0bd9b709..c993404d 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -9,7 +9,7 @@ class NewsTypeAdmin(admin.ModelAdmin): list_display = ['id', 'name'] list_display_links = ['id', 'name'] - +# Изменения def send_email_action(modeladmin, request, queryset): send_email_with_news.delay(list(queryset.values_list('id', flat=True))) From fb9e71cbdd8607f831f7b6e4423f63c51c93ff8a Mon Sep 17 00:00:00 2001 From: michail Date: Wed, 2 Oct 2019 13:27:33 +0500 Subject: [PATCH 176/319] fix querysets --- apps/news/admin.py | 3 +-- apps/news/tasks.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/news/admin.py b/apps/news/admin.py index 1b869eb2..cbda27b0 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -12,11 +12,10 @@ class NewsTypeAdmin(admin.ModelAdmin): def send_email_action(modeladmin, request, queryset): - news_ids = [n.id for n in queryset] + news_ids = list(queryset.values_list("id", flat=True)) send_email_with_news.delay(news_ids) - return send_email_action.short_description = "Send the selected news by email" diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 35f2c644..6f48e17c 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -11,12 +11,11 @@ from smtplib import SMTPException def send_email_with_news(news_ids): subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) + sent_news = models.News.objects.filter(id__in=news_ids) for s in subscribers: try: - for n in news_ids: - sent_news = models.News.objects.get(id=n) - + for n in sent_news: send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, {"title": sent_news.title.get(s.locale), "subtitle": sent_news.subtitle.get(s.locale), From 5ec81f417ad6c6267ab9157b76af70f703e76791 Mon Sep 17 00:00:00 2001 From: michail Date: Wed, 2 Oct 2019 13:37:46 +0500 Subject: [PATCH 177/319] fix bug --- apps/news/tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 6f48e17c..99065fc8 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -17,9 +17,9 @@ def send_email_with_news(news_ids): try: for n in sent_news: send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, - {"title": sent_news.title.get(s.locale), - "subtitle": sent_news.subtitle.get(s.locale), - "description": sent_news.description.get(s.locale), + {"title": n.title.get(s.locale), + "subtitle": n.subtitle.get(s.locale), + "description": n.description.get(s.locale), "code": s.update_code, "domain_uri": settings.DOMAIN_URI, "country_code": s.country_code}), From ca9475b182ce59f1202bb62af82297385ed67eed Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 2 Oct 2019 12:35:01 +0300 Subject: [PATCH 178/319] refactor this feature --- apps/account/serializers/common.py | 17 ++++ .../migrations/0020_remove_news_author.py | 17 ++++ apps/news/models.py | 81 ++++++------------- apps/news/serializers.py | 18 ++++- apps/news/views.py | 14 +++- 5 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 apps/news/migrations/0020_remove_news_author.py diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index e8d6ba30..e6076117 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -77,6 +77,23 @@ class UserSerializer(serializers.ModelSerializer): return instance +class UserBaseSerializer(serializers.ModelSerializer): + """Serializer is used to display brief information about the user.""" + + fullname = serializers.CharField(source='get_full_name', read_only=True) + + class Meta: + """Meta class.""" + + model = models.User + fields = ( + 'fullname', + 'cropped_image_url', + 'image_url', + ) + read_only_fields = fields + + class ChangePasswordSerializer(serializers.ModelSerializer): """Serializer for model User.""" diff --git a/apps/news/migrations/0020_remove_news_author.py b/apps/news/migrations/0020_remove_news_author.py new file mode 100644 index 00000000..95f376c9 --- /dev/null +++ b/apps/news/migrations/0020_remove_news_author.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-02 09:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0019_news_author'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='author', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 4fc3628f..efa0b566 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -27,10 +27,21 @@ class NewsType(models.Model): class NewsQuerySet(models.QuerySet): """QuerySet for model News""" + def with_base_related(self): + """Return qs with related objects.""" + return self.select_related('news_type', 'country').prefetch_related('tags') + + def with_extended_related(self): + """Return qs with related objects.""" + return self.select_related('created_by') + def by_type(self, news_type): """Filter News by type""" return self.filter(news_type__name=news_type) + def by_tags(self, tags): + return self.filter(tags__in=tags) + def by_country_code(self, code): """Filter collection by country code.""" return self.filter(country__code=code) @@ -42,9 +53,16 @@ class NewsQuerySet(models.QuerySet): models.Q(end__isnull=True)), state__in=self.model.PUBLISHED_STATES, start__lte=now) - def with_related(self): - """Return qs with related objects.""" - return self.select_related('news_type', 'country').prefetch_related('tags') + # todo: filter by best score + # todo: filter by country? + def should_read(self, news): + return self.model.objects.exclude(pk=news.pk).published().\ + with_base_related().by_type(news.news_type).distinct().order_by('?') + + def same_theme(self, news): + return self.model.objects.exclude(pk=news.pk).published().\ + with_base_related().by_type(news.news_type).\ + by_tags(news.tags.all()).distinct().order_by('-start') class News(BaseAttributes, TranslatedFieldsMixin): @@ -95,15 +113,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): slug = models.SlugField(unique=True, max_length=50, verbose_name=_('News slug')) playlist = models.IntegerField(_('playlist')) - - # author = models.CharField(max_length=255, blank=True, null=True, - # default=None,verbose_name=_('Author')) - state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, verbose_name=_('State')) - author = models.CharField(max_length=255, blank=True, null=True, - default=None,verbose_name=_('Author')) - is_highlighted = models.BooleanField(default=False, verbose_name=_('Is highlighted')) # TODO: metadata_keys - описание ключей для динамического построения полей метаданных @@ -141,54 +152,10 @@ class News(BaseAttributes, TranslatedFieldsMixin): return reverse('web:news:rud', kwargs={'slug': self.slug}) @property - def list_also_like_news(self): - - # without "distinct" method the doubles are arising - like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\ - .exclude(id=self.id).distinct() - - news_count = like_news.count() - - if news_count >= 6: - random_ids = random_sample(range(news_count), 6) - else: - random_ids = random_sample(range(news_count), news_count) - - news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] - - return news_list + def should_read(self): + return self.__class__.objects.should_read(self)[:3] @property def same_theme(self): - # on the same theme news + return self.__class__.objects.same_theme(self)[:3] - # without "distinct" method the doubles are arising - like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\ - .order_by("-start").exclude(id=self.id).distinct() - - news_count = like_news.count() - - if news_count >= 3: - like_news = like_news[:3] - - news_list = [{"id": n.id, "slug": n.slug} for n in like_news] - - return news_list - - @property - def should_read(self): - # you should read news - - # without "distinct" method the doubles are arising - like_news = News.objects.published().filter(news_type=self.news_type).exclude(id=self.id).distinct() - - news_count = like_news.count() - - if news_count >= 3: - random_ids = random_sample(range(news_count), 3) - else: - random_ids = random_sample(range(news_count), news_count) - - news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids] - - return news_list diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 36cef3e5..c473be1d 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,6 +1,6 @@ """News app common serializers.""" from rest_framework import serializers -from account.serializers.common import UserSerializer +from account.serializers.common import UserBaseSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer @@ -51,8 +51,7 @@ class NewsDetailSerializer(NewsBaseSerializer): description_translated = TranslatedField() country = CountrySimpleSerializer(read_only=True) - # todo: check the data redundancy - author = UserSerializer(source='created_by', read_only=True) + author = UserBaseSerializer(source='created_by', read_only=True) state_display = serializers.CharField(source='get_state_display', read_only=True) @@ -69,6 +68,19 @@ class NewsDetailSerializer(NewsBaseSerializer): 'state_display', 'author', 'country', + ) + + +class NewsDetailWebSerializer(NewsDetailSerializer): + """News detail serializer for web users..""" + + same_theme = NewsBaseSerializer(many=True, read_only=True) + should_read = NewsBaseSerializer(many=True, read_only=True) + + class Meta(NewsDetailSerializer.Meta): + """Meta class.""" + + fields = NewsDetailSerializer.Meta.fields + ( 'same_theme', 'should_read', ) diff --git a/apps/news/views.py b/apps/news/views.py index c5e69ab0..081f5a62 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -11,7 +11,7 @@ class NewsMixinView: def get_queryset(self, *args, **kwargs): """Override get_queryset method.""" - qs = models.News.objects.with_related().published()\ + qs = models.News.objects.published().with_base_related()\ .order_by('-is_highlighted', '-created') if self.request.country_code: qs = qs.by_country_code(self.request.country_code) @@ -28,7 +28,11 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """News detail view.""" lookup_field = 'slug' - serializer_class = serializers.NewsDetailSerializer + serializer_class = serializers.NewsDetailWebSerializer + + def get_queryset(self): + """Override get_queryset method.""" + return super().get_queryset().with_extended_related() class NewsTypeListView(generics.ListAPIView): @@ -44,7 +48,7 @@ class NewsBackOfficeMixinView: """News back office mixin view.""" permission_classes = (permissions.IsAuthenticated,) - queryset = models.News.objects.with_related() \ + queryset = models.News.objects.with_base_related() \ .order_by('-is_highlighted', '-created') @@ -61,6 +65,10 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, return self.create_serializers_class return super().get_serializer_class() + def get_queryset(self): + """Override get_queryset method.""" + return super().get_queryset().with_extended_related() + class NewsBackOfficeRUDView(NewsBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): From 753471544e934c54f515afbc507d4f0f77108414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 2 Oct 2019 14:17:06 +0300 Subject: [PATCH 179/319] Add app Rating --- apps/rating/__init__.py | 0 apps/rating/admin.py | 3 +++ apps/rating/apps.py | 5 +++++ apps/rating/migrations/__init__.py | 0 apps/rating/models.py | 3 +++ apps/rating/tests.py | 3 +++ apps/rating/views.py | 3 +++ project/settings/base.py | 1 + 8 files changed, 18 insertions(+) create mode 100644 apps/rating/__init__.py create mode 100644 apps/rating/admin.py create mode 100644 apps/rating/apps.py create mode 100644 apps/rating/migrations/__init__.py create mode 100644 apps/rating/models.py create mode 100644 apps/rating/tests.py create mode 100644 apps/rating/views.py diff --git a/apps/rating/__init__.py b/apps/rating/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/rating/admin.py b/apps/rating/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/rating/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/rating/apps.py b/apps/rating/apps.py new file mode 100644 index 00000000..6f17a343 --- /dev/null +++ b/apps/rating/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class RatingConfig(AppConfig): + name = 'rating' diff --git a/apps/rating/migrations/__init__.py b/apps/rating/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/rating/models.py b/apps/rating/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/apps/rating/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/rating/tests.py b/apps/rating/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/rating/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/rating/views.py b/apps/rating/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/apps/rating/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..e444ef37 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -71,6 +71,7 @@ PROJECT_APPS = [ 'review.apps.ReviewConfig', 'comment.apps.CommentConfig', 'favorites.apps.FavoritesConfig', + 'rating.apps.RatingConfig', ] EXTERNAL_APPS = [ From 7b18d7e43069de542033e95ea128bd2675635e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 2 Oct 2019 14:50:30 +0300 Subject: [PATCH 180/319] Model and admin --- apps/rating/admin.py | 8 +++++++- apps/rating/migrations/0001_initial.py | 28 ++++++++++++++++++++++++++ apps/rating/models.py | 18 ++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 apps/rating/migrations/0001_initial.py diff --git a/apps/rating/admin.py b/apps/rating/admin.py index 8c38f3f3..94a0435e 100644 --- a/apps/rating/admin.py +++ b/apps/rating/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin +from rating import models -# Register your models here. + +@admin.register(models.Rating) +class RatingAdmin(admin.ModelAdmin): + """Rating type admin conf.""" + list_display = ['name', 'ip'] + list_display_links = ['name'] diff --git a/apps/rating/migrations/0001_initial.py b/apps/rating/migrations/0001_initial.py new file mode 100644 index 00000000..03165670 --- /dev/null +++ b/apps/rating/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.4 on 2019-10-02 11:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='Rating', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('ip', models.GenericIPAddressField(verbose_name='ip')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ], + options={ + 'verbose_name': 'rating', + }, + ), + ] diff --git a/apps/rating/models.py b/apps/rating/models.py index 71a83623..61e4378d 100644 --- a/apps/rating/models.py +++ b/apps/rating/models.py @@ -1,3 +1,19 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from django.db import models +from django.utils.translation import gettext_lazy as _ -# Create your models here. + +class Rating(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + ip = models.GenericIPAddressField(verbose_name=_('ip')) + + @property + def name(self): + # Check if Generic obj has name or title + if hasattr(self.content_object, 'name'): + return self.content_object.name + if hasattr(self.content_object, 'title'): + return self.content_object.title_translated From 8e3bb54508216a54c53537b369ccd9c904a0dc99 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 15:09:19 +0300 Subject: [PATCH 181/319] added endpoint for retrieving collection detail information --- apps/collection/serializers/common.py | 24 ++++++++++------ apps/collection/urls/common.py | 1 + apps/collection/views/common.py | 40 ++++++++++----------------- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index f7319561..78612a55 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -4,11 +4,24 @@ from collection import models from location import models as location_models -class CollectionSerializer(serializers.ModelSerializer): - """Collection serializer""" +class CollectionBaseSerializer(serializers.ModelSerializer): + """Collection base serializer""" # RESPONSE description_translated = serializers.CharField(read_only=True, allow_null=True) + class Meta: + model = models.Collection + fields = [ + 'id', + 'name', + 'description_translated', + 'image_url', + 'slug', + ] + + +class CollectionSerializer(CollectionBaseSerializer): + """Collection serializer""" # COMMON block_size = serializers.JSONField() is_publish = serializers.BooleanField() @@ -24,18 +37,13 @@ class CollectionSerializer(serializers.ModelSerializer): class Meta: model = models.Collection - fields = [ - 'id', - 'name', - 'description_translated', + fields = CollectionBaseSerializer.Meta.fields + [ 'start', 'end', - 'image_url', 'is_publish', 'on_top', 'country', 'block_size', - 'slug', ] diff --git a/apps/collection/urls/common.py b/apps/collection/urls/common.py index 7ffa50cf..36801ac5 100644 --- a/apps/collection/urls/common.py +++ b/apps/collection/urls/common.py @@ -7,6 +7,7 @@ app_name = 'collection' urlpatterns = [ path('', views.CollectionHomePageView.as_view(), name='list'), + path('/', views.CollectionDetailView.as_view(), name='detail'), path('/establishments/', views.CollectionEstablishmentListView.as_view(), name='detail'), diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index fd2a4584..16a42f58 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -12,7 +12,14 @@ from collection.serializers import common as serializers class CollectionViewMixin(generics.GenericAPIView): """Mixin for Collection view""" model = models.Collection - queryset = models.Collection.objects.all() + permission_classes = (permissions.AllowAny,) + + def get_queryset(self): + """Override get_queryset method.""" + return models.Collection.objects.published() \ + .by_country_code(code=self.request.country_code) \ + .filter_all_related_gt(3) \ + .order_by('-on_top', '-modified') class GuideViewMixin(generics.GenericAPIView): @@ -23,41 +30,22 @@ class GuideViewMixin(generics.GenericAPIView): # Views # Collections -class CollectionListView(CollectionViewMixin, generics.ListAPIView): - """List Collection view""" - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.CollectionSerializer - - def get_queryset(self): - """Override get_queryset method""" - queryset = models.Collection.objects.published()\ - .by_country_code(code=self.request.country_code)\ - .order_by('-on_top', '-created') - - return queryset - - class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): """List Collection view""" - permission_classes = (permissions.AllowAny,) serializer_class = serializers.CollectionSerializer - def get_queryset(self): - """Override get_queryset method""" - queryset = models.Collection.objects.published()\ - .by_country_code(code=self.request.country_code)\ - .filter_all_related_gt(3)\ - .order_by('-on_top', '-modified') - return queryset +class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView): + """Retrieve detail of Collection instance.""" + lookup_field = 'slug' + serializer_class = serializers.CollectionBaseSerializer -class CollectionEstablishmentListView(CollectionListView): +class CollectionEstablishmentListView(CollectionHomePageView): """Retrieve list of establishment for collection.""" - permission_classes = (permissions.AllowAny,) + lookup_field = 'slug' pagination_class = ProjectPageNumberPagination serializer_class = EstablishmentBaseSerializer - lookup_field = 'slug' def get_queryset(self): """ From fec3e703ced20acf44c765c7795f709854fc06f2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 16:21:38 +0300 Subject: [PATCH 182/319] refactored collection views --- apps/collection/views/common.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/collection/views/common.py b/apps/collection/views/common.py index 16a42f58..5bf8f70e 100644 --- a/apps/collection/views/common.py +++ b/apps/collection/views/common.py @@ -13,12 +13,12 @@ class CollectionViewMixin(generics.GenericAPIView): """Mixin for Collection view""" model = models.Collection permission_classes = (permissions.AllowAny,) + serializer_class = serializers.CollectionSerializer def get_queryset(self): """Override get_queryset method.""" return models.Collection.objects.published() \ .by_country_code(code=self.request.country_code) \ - .filter_all_related_gt(3) \ .order_by('-on_top', '-modified') @@ -30,9 +30,17 @@ class GuideViewMixin(generics.GenericAPIView): # Views # Collections -class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): - """List Collection view""" - serializer_class = serializers.CollectionSerializer +class CollectionListView(CollectionViewMixin, generics.ListAPIView): + """List Collection view.""" + + +class CollectionHomePageView(CollectionListView): + """Collection list view for home page.""" + + def get_queryset(self): + """Override get_queryset.""" + return super(CollectionHomePageView, self).get_queryset() \ + .filter_all_related_gt(3) class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView): @@ -41,7 +49,7 @@ class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView): serializer_class = serializers.CollectionBaseSerializer -class CollectionEstablishmentListView(CollectionHomePageView): +class CollectionEstablishmentListView(CollectionListView): """Retrieve list of establishment for collection.""" lookup_field = 'slug' pagination_class = ProjectPageNumberPagination From f693c36382730d0342d9105922707f8720d76d40 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 17:16:19 +0300 Subject: [PATCH 183/319] switched host email account, changed exception error --- apps/authorization/serializers/common.py | 18 ++++++----------- apps/authorization/views/common.py | 25 ++++++------------------ apps/utils/exceptions.py | 2 +- project/settings/base.py | 6 +++--- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 5d8bb3a8..5049ada1 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -81,7 +81,6 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, """Serializer for login user""" # REQUEST username_or_email = serializers.CharField(write_only=True) - password = serializers.CharField(write_only=True) # For cookie properties (Max-Age) remember = serializers.BooleanField(write_only=True) @@ -101,21 +100,24 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, 'refresh_token', 'access_token', ) + extra_kwargs = { + 'password': {'write_only': True} + } def validate(self, attrs): """Override validate method""" username_or_email = attrs.pop('username_or_email') password = attrs.pop('password') user_qs = account_models.User.objects.filter(Q(username=username_or_email) | - (Q(email=username_or_email))) + Q(email=username_or_email)) if not user_qs.exists(): - raise utils_exceptions.UserNotFoundError() + raise utils_exceptions.WrongAuthCredentials() else: user = user_qs.first() authentication = authenticate(username=user.get_username(), password=password) if not authentication: - raise utils_exceptions.UserNotFoundError() + raise utils_exceptions.WrongAuthCredentials() self.instance = user return attrs @@ -127,10 +129,6 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, return super().to_representation(instance) -class LogoutSerializer(SourceSerializerMixin): - """Serializer for Logout endpoint.""" - - class RefreshTokenSerializer(SourceSerializerMixin): """Serializer for refresh token view""" refresh_token = serializers.CharField(read_only=True) @@ -169,7 +167,3 @@ class RefreshTokenSerializer(SourceSerializerMixin): class OAuth2Serialzier(SourceSerializerMixin): """Serializer OAuth2 authorization""" token = serializers.CharField(max_length=255) - - -class OAuth2LogoutSerializer(SourceSerializerMixin): - """Serializer for logout""" diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index bb337dce..0b1a58e0 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -27,24 +27,6 @@ from utils.permissions import IsAuthenticatedAndTokenIsValid from utils.views import JWTGenericViewMixin -# Mixins -# JWTAuthView mixin -class JWTAuthViewMixin(JWTGenericViewMixin): - """Mixin for authentication views""" - - def post(self, request, *args, **kwargs): - """Implement POST method""" - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - response = Response(serializer.data, status=status.HTTP_200_OK) - access_token = serializer.data.get('access_token') - refresh_token = serializer.data.get('refresh_token') - return self._put_cookies_in_response( - cookies=self._put_data_in_cookies(access_token=access_token, - refresh_token=refresh_token), - response=response) - - # OAuth2 class BaseOAuth2ViewMixin(generics.GenericAPIView): """BaseMixin for classic auth views""" @@ -112,6 +94,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin): # Preparing request data serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + request_data = self.prepare_request_data(serializer.validated_data) source = serializer.validated_data.get('source') request_data.update({ @@ -196,7 +179,7 @@ class ConfirmationEmailView(JWTGenericViewMixin): # Login by username|email + password -class LoginByUsernameOrEmailView(JWTAuthViewMixin): +class LoginByUsernameOrEmailView(JWTGenericViewMixin): """Login by email and password""" permission_classes = (permissions.AllowAny,) serializer_class = serializers.LoginByUsernameOrEmailSerializer @@ -205,10 +188,12 @@ class LoginByUsernameOrEmailView(JWTAuthViewMixin): """Implement POST method""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + response = Response(serializer.data, status=status.HTTP_200_OK) access_token = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token') is_permanent = serializer.validated_data.get('remember') + return self._put_cookies_in_response( cookies=self._put_data_in_cookies(access_token=access_token, refresh_token=refresh_token, @@ -243,9 +228,11 @@ class RefreshTokenView(JWTGenericViewMixin): def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + response = Response(serializer.data, status=status.HTTP_201_CREATED) access_token = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token') + return self._put_cookies_in_response( cookies=self._put_data_in_cookies(access_token=access_token, refresh_token=refresh_token), diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py index df9c2c27..440f4ed4 100644 --- a/apps/utils/exceptions.py +++ b/apps/utils/exceptions.py @@ -123,7 +123,7 @@ class WrongAuthCredentials(AuthErrorMixin): """ The exception should be raised when credentials is not valid for this user """ - default_detail = _('Wrong authorization credentials') + default_detail = _('Incorrect login or password.') class FavoritesError(exceptions.APIException): diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..d61f0829 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -280,9 +280,9 @@ SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.gmail.com' -EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com' -EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt' +EMAIL_HOST = 'smtp.yandex.ru' +EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' +EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' EMAIL_PORT = 587 # Django Rest Swagger From f0001eef93e28d19c379b4cdde7a03a5f9411f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 2 Oct 2019 17:58:22 +0300 Subject: [PATCH 184/319] Add get method in view --- apps/news/views.py | 8 ++++++++ apps/rating/admin.py | 9 +++++++++ apps/rating/send.py | 14 ++++++++++++++ apps/rating/tasks.py | 14 ++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 apps/rating/send.py create mode 100644 apps/rating/tasks.py diff --git a/apps/news/views.py b/apps/news/views.py index 081f5a62..1e24971f 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -34,6 +34,14 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() + def get(self, request, *args, **kwargs): + from rating.tasks import add_rating + print('SLUG') + add_rating.apply_async( + (1, '192.1.1.1.'), countdown=4 + ) + return self.retrieve(request, *args, **kwargs) + class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" diff --git a/apps/rating/admin.py b/apps/rating/admin.py index 94a0435e..d8d46ae9 100644 --- a/apps/rating/admin.py +++ b/apps/rating/admin.py @@ -1,5 +1,11 @@ from django.contrib import admin from rating import models +from rating import tasks + +# +# def add_task_action(modeladmin, request, queryset): +# # 1, "192.168.1.1" +# tasks.add.delay(1, "192.168.1.1") @admin.register(models.Rating) @@ -7,3 +13,6 @@ class RatingAdmin(admin.ModelAdmin): """Rating type admin conf.""" list_display = ['name', 'ip'] list_display_links = ['name'] + # actions = [add_task_action] + + diff --git a/apps/rating/send.py b/apps/rating/send.py new file mode 100644 index 00000000..06e5daff --- /dev/null +++ b/apps/rating/send.py @@ -0,0 +1,14 @@ +from pika import BlockingConnection, ConnectionParameters + + +def mess(): + connection = BlockingConnection(ConnectionParameters('rabbitmq')) + channel = connection.channel() + + channel.queue_declare(queue='rating') + channel.basic_publish( + exchange='', + routing_key='rating', + body='{news_id:127.0.0.1}' + ) + connection.close() \ No newline at end of file diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py new file mode 100644 index 00000000..d3d2216c --- /dev/null +++ b/apps/rating/tasks.py @@ -0,0 +1,14 @@ +from datetime import timedelta +from celery.task import periodic_task, Task +from celery import task + + + + + +@task +def add_rating(object_id, ip): + print('object_id: ' + str(object_id)) + print('ip: ' + str(ip)) + + From f90e1cedede10a4a723bf6a9e9d0a811c9b603d4 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, 3 Oct 2019 11:37:59 +0300 Subject: [PATCH 185/319] Add raiting --- apps/news/views.py | 14 ++++---------- apps/rating/tasks.py | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/news/views.py b/apps/news/views.py index 1e24971f..24cac304 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,7 +1,7 @@ """News app views.""" from rest_framework import generics, permissions from news import filters, models, serializers - +from rating.tasks import add_rating class NewsMixinView: """News mixin.""" @@ -34,15 +34,6 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() - def get(self, request, *args, **kwargs): - from rating.tasks import add_rating - print('SLUG') - add_rating.apply_async( - (1, '192.1.1.1.'), countdown=4 - ) - return self.retrieve(request, *args, **kwargs) - - class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" @@ -84,3 +75,6 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeDetailSerializer + def get(self, request, pk, *args, **kwargs): + add_rating(remote_addr=request.META.get('REMOTE_ADDR'), pk=pk, model='news') + return self.retrieve(request, *args, **kwargs) diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py index d3d2216c..6b214e18 100644 --- a/apps/rating/tasks.py +++ b/apps/rating/tasks.py @@ -1,14 +1,22 @@ from datetime import timedelta -from celery.task import periodic_task, Task from celery import task +from rating.models import Rating +from django.contrib.contenttypes.models import ContentType - +def add_rating(remote_addr, pk, model): + add.apply_async( + (remote_addr, pk, model), countdown=60 + ) @task -def add_rating(object_id, ip): - print('object_id: ' + str(object_id)) - print('ip: ' + str(ip)) +def add(remote_addr, pk, model): + rating = Rating() + rating.ip = remote_addr + rating.object_id = pk + rating.content_type = ContentType.objects.get(model=model) + rating.save() + From 295bda4b4ffb18c9fcee4cb95258376376cbbb32 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, 3 Oct 2019 11:41:52 +0300 Subject: [PATCH 186/319] Add hours --- apps/rating/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py index 6b214e18..4942ccdb 100644 --- a/apps/rating/tasks.py +++ b/apps/rating/tasks.py @@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType def add_rating(remote_addr, pk, model): add.apply_async( - (remote_addr, pk, model), countdown=60 + (remote_addr, pk, model), countdown=60 * 60 ) From 0408d935997a9aca5b3eac8f61a5133a7353cf63 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 3 Oct 2019 13:41:45 +0300 Subject: [PATCH 187/319] Squashed migrations && added lastable credentials --- apps/booking/migrations/0001_initial.py | 27 -------- ...nitial_squashed_0010_auto_20190920_1206.py | 37 +++++++++++ apps/booking/migrations/0002_booking_user.py | 21 ------- .../migrations/0003_auto_20190916_1533.py | 61 ------------------- .../migrations/0004_auto_20190916_1646.py | 33 ---------- .../migrations/0005_auto_20190918_1308.py | 23 ------- .../migrations/0006_booking_country_code.py | 18 ------ .../migrations/0007_booking_booking_id.py | 18 ------ .../migrations/0008_auto_20190919_2008.py | 18 ------ apps/booking/migrations/0009_booking_user.py | 21 ------- .../migrations/0010_auto_20190920_1206.py | 45 -------------- ...3_0943_squashed_0034_auto_20191003_1036.py | 24 ++++++++ apps/establishment/models.py | 2 +- .../migrations/0003_auto_20191003_0943.py | 17 ++++++ project/settings/base.py | 5 +- project/settings/production.py | 3 +- 16 files changed, 84 insertions(+), 289 deletions(-) delete mode 100644 apps/booking/migrations/0001_initial.py create mode 100644 apps/booking/migrations/0001_initial_squashed_0010_auto_20190920_1206.py delete mode 100755 apps/booking/migrations/0002_booking_user.py delete mode 100644 apps/booking/migrations/0003_auto_20190916_1533.py delete mode 100644 apps/booking/migrations/0004_auto_20190916_1646.py delete mode 100644 apps/booking/migrations/0005_auto_20190918_1308.py delete mode 100644 apps/booking/migrations/0006_booking_country_code.py delete mode 100644 apps/booking/migrations/0007_booking_booking_id.py delete mode 100644 apps/booking/migrations/0008_auto_20190919_2008.py delete mode 100644 apps/booking/migrations/0009_booking_user.py delete mode 100644 apps/booking/migrations/0010_auto_20190920_1206.py create mode 100644 apps/establishment/migrations/0033_auto_20191003_0943_squashed_0034_auto_20191003_1036.py create mode 100644 apps/timetable/migrations/0003_auto_20191003_0943.py diff --git a/apps/booking/migrations/0001_initial.py b/apps/booking/migrations/0001_initial.py deleted file mode 100644 index f7d5e919..00000000 --- a/apps/booking/migrations/0001_initial.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-12 17:55 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Booking', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), - ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('type', models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/apps/booking/migrations/0001_initial_squashed_0010_auto_20190920_1206.py b/apps/booking/migrations/0001_initial_squashed_0010_auto_20190920_1206.py new file mode 100644 index 00000000..33131471 --- /dev/null +++ b/apps/booking/migrations/0001_initial_squashed_0010_auto_20190920_1206.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.4 on 2019-10-03 10:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + replaces = [('booking', '0001_initial'), ('booking', '0002_booking_user'), ('booking', '0003_auto_20190916_1533'), ('booking', '0004_auto_20190916_1646'), ('booking', '0005_auto_20190918_1308'), ('booking', '0006_booking_country_code'), ('booking', '0007_booking_booking_id'), ('booking', '0008_auto_20190919_2008'), ('booking', '0009_booking_user'), ('booking', '0010_auto_20190920_1206')] + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Booking', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('type', models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2, verbose_name='Guestonline or Lastable')), + ('booking_user_locale', models.CharField(default='en', max_length=10, verbose_name='booking locale')), + ('restaurant_id', models.PositiveIntegerField(default=None, verbose_name='booking service establishment id')), + ('pending_booking_id', models.TextField(default=None, verbose_name='external service pending booking')), + ('booking_id', models.TextField(db_index=True, default=None, null=True, verbose_name='external service booking id')), + ('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='booking owner')), + ], + options={ + 'abstract': False, + 'verbose_name': 'Booking', + 'verbose_name_plural': 'Booking', + }, + ), + ] diff --git a/apps/booking/migrations/0002_booking_user.py b/apps/booking/migrations/0002_booking_user.py deleted file mode 100755 index 0ee387b7..00000000 --- a/apps/booking/migrations/0002_booking_user.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-14 14:47 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('booking', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='user', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), - ] diff --git a/apps/booking/migrations/0003_auto_20190916_1533.py b/apps/booking/migrations/0003_auto_20190916_1533.py deleted file mode 100644 index 448d94e7..00000000 --- a/apps/booking/migrations/0003_auto_20190916_1533.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-16 15:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0002_booking_user'), - ] - - operations = [ - migrations.AlterModelOptions( - name='booking', - options={'verbose_name': 'Booking', 'verbose_name_plural': 'Booking'}, - ), - migrations.RemoveField( - model_name='booking', - name='user', - ), - migrations.AddField( - model_name='booking', - name='booked_persons_number', - field=models.PositiveIntegerField(default=2, verbose_name='persons number'), - ), - migrations.AddField( - model_name='booking', - name='booking_date', - field=models.DateField(default=None, verbose_name='booking date'), - ), - migrations.AddField( - model_name='booking', - name='booking_time', - field=models.TimeField(default=None, verbose_name='booking time'), - ), - migrations.AddField( - model_name='booking', - name='booking_user_locale', - field=models.CharField(default='en', max_length=10, verbose_name='booking locale'), - ), - migrations.AddField( - model_name='booking', - name='first_name', - field=models.CharField(default=None, max_length=200, verbose_name='booking first name'), - ), - migrations.AddField( - model_name='booking', - name='last_name', - field=models.CharField(default=None, max_length=200, verbose_name='booking last name'), - ), - migrations.AddField( - model_name='booking', - name='phone', - field=models.CharField(default=None, max_length=20, verbose_name='booking phone'), - ), - migrations.AddField( - model_name='booking', - name='restaurant_id', - field=models.PositiveIntegerField(default=None, verbose_name='booking service establishment id'), - ), - ] diff --git a/apps/booking/migrations/0004_auto_20190916_1646.py b/apps/booking/migrations/0004_auto_20190916_1646.py deleted file mode 100644 index 66471d26..00000000 --- a/apps/booking/migrations/0004_auto_20190916_1646.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-16 16:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0003_auto_20190916_1533'), - ] - - operations = [ - migrations.AlterField( - model_name='booking', - name='first_name', - field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking first name'), - ), - migrations.AlterField( - model_name='booking', - name='last_name', - field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking last name'), - ), - migrations.AlterField( - model_name='booking', - name='phone', - field=models.CharField(default=None, max_length=20, null=True, verbose_name='booking phone'), - ), - migrations.AlterField( - model_name='booking', - name='type', - field=models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2, verbose_name='Guestonline or Lastable'), - ), - ] diff --git a/apps/booking/migrations/0005_auto_20190918_1308.py b/apps/booking/migrations/0005_auto_20190918_1308.py deleted file mode 100644 index eae448d2..00000000 --- a/apps/booking/migrations/0005_auto_20190918_1308.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-18 13:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0004_auto_20190916_1646'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='email', - field=models.EmailField(default=None, max_length=254, null=True, verbose_name='Booking email'), - ), - migrations.AddField( - model_name='booking', - name='pending_booking_id', - field=models.TextField(default=None, verbose_name='external service pending booking'), - ), - ] diff --git a/apps/booking/migrations/0006_booking_country_code.py b/apps/booking/migrations/0006_booking_country_code.py deleted file mode 100644 index aed2951e..00000000 --- a/apps/booking/migrations/0006_booking_country_code.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-18 14:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0005_auto_20190918_1308'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='country_code', - field=models.CharField(default=None, max_length=10, null=True, verbose_name='Country code'), - ), - ] diff --git a/apps/booking/migrations/0007_booking_booking_id.py b/apps/booking/migrations/0007_booking_booking_id.py deleted file mode 100644 index a96aecd1..00000000 --- a/apps/booking/migrations/0007_booking_booking_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-19 20:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0006_booking_country_code'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='booking_id', - field=models.TextField(default=None, null=True, verbose_name='external service booking id'), - ), - ] diff --git a/apps/booking/migrations/0008_auto_20190919_2008.py b/apps/booking/migrations/0008_auto_20190919_2008.py deleted file mode 100644 index 59255422..00000000 --- a/apps/booking/migrations/0008_auto_20190919_2008.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-19 20:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0007_booking_booking_id'), - ] - - operations = [ - migrations.AlterField( - model_name='booking', - name='booking_id', - field=models.TextField(db_index=True, default=None, null=True, verbose_name='external service booking id'), - ), - ] diff --git a/apps/booking/migrations/0009_booking_user.py b/apps/booking/migrations/0009_booking_user.py deleted file mode 100644 index d24162f9..00000000 --- a/apps/booking/migrations/0009_booking_user.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-19 21:18 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('booking', '0008_auto_20190919_2008'), - ] - - operations = [ - migrations.AddField( - model_name='booking', - name='user', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='booking owner'), - ), - ] diff --git a/apps/booking/migrations/0010_auto_20190920_1206.py b/apps/booking/migrations/0010_auto_20190920_1206.py deleted file mode 100644 index 95a0ae97..00000000 --- a/apps/booking/migrations/0010_auto_20190920_1206.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-20 12:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0009_booking_user'), - ] - - operations = [ - migrations.RemoveField( - model_name='booking', - name='booked_persons_number', - ), - migrations.RemoveField( - model_name='booking', - name='booking_date', - ), - migrations.RemoveField( - model_name='booking', - name='booking_time', - ), - migrations.RemoveField( - model_name='booking', - name='country_code', - ), - migrations.RemoveField( - model_name='booking', - name='email', - ), - migrations.RemoveField( - model_name='booking', - name='first_name', - ), - migrations.RemoveField( - model_name='booking', - name='last_name', - ), - migrations.RemoveField( - model_name='booking', - name='phone', - ), - ] diff --git a/apps/establishment/migrations/0033_auto_20191003_0943_squashed_0034_auto_20191003_1036.py b/apps/establishment/migrations/0033_auto_20191003_0943_squashed_0034_auto_20191003_1036.py new file mode 100644 index 00000000..48a0fa8d --- /dev/null +++ b/apps/establishment/migrations/0033_auto_20191003_0943_squashed_0034_auto_20191003_1036.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-03 10:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + replaces = [('establishment', '0033_auto_20191003_0943'), ('establishment', '0034_auto_20191003_1036')] + + dependencies = [ + ('establishment', '0032_merge_20191001_1530'), + ] + + operations = [ + migrations.RemoveField( + model_name='establishment', + name='lastable_id', + ), + migrations.AddField( + model_name='establishment', + name='lastable_id', + field=models.TextField(blank=True, default=None, null=True, unique=True, verbose_name='lastable id'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index d809d9b5..2c943d39 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -262,7 +262,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Lafourchette URL')) guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'), null=True, default=None,) - lastable_id = models.PositiveIntegerField(blank=True, verbose_name=_('lastable id'), + lastable_id = models.TextField(blank=True, verbose_name=_('lastable id'), unique=True, null=True, default=None,) booking = models.URLField(blank=True, null=True, default=None, verbose_name=_('Booking URL')) diff --git a/apps/timetable/migrations/0003_auto_20191003_0943.py b/apps/timetable/migrations/0003_auto_20191003_0943.py new file mode 100644 index 00000000..4c5a22b4 --- /dev/null +++ b/apps/timetable/migrations/0003_auto_20191003_0943.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-03 09:43 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0002_auto_20190919_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='timetable', + options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, + ), + ] diff --git a/project/settings/base.py b/project/settings/base.py index 9f72ecde..c135a92c 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -266,8 +266,9 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { # Booking API configuration GUESTONLINE_SERVICE = 'https://api-preprod.guestonline.fr/' GUESTONLINE_TOKEN = 'iiReiYpyojshpPjpmczS' -LASTABLE_SERVICE = '' -LASTABLE_TOKEN = '' +LASTABLE_SERVICE = 'http://34.251.84.222' +LASTABLE_TOKEN = '6dfc608ce5e494' +LASTABLE_PROXY = '' # SMS Settings SMS_EXPIRATION = 5 diff --git a/project/settings/production.py b/project/settings/production.py index 76dc31f0..5682a59d 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -5,4 +5,5 @@ from .base import * GUESTONLINE_SERVICE = 'https://api.guestonline.fr/' GUESTONLINE_TOKEN = '' LASTABLE_SERVICE = '' -LASTABLE_TOKEN = '' \ No newline at end of file +LASTABLE_TOKEN = '' +LASTABLE_PROXY = '' \ No newline at end of file From 14cf7d7b1b9d3203deebc0ff2ac931516da3df13 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, 3 Oct 2019 14:46:34 +0300 Subject: [PATCH 188/319] Add rating value in news --- apps/news/models.py | 7 +++++++ apps/news/views.py | 2 +- apps/rating/tasks.py | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/news/models.py b/apps/news/models.py index efa0b566..2264d897 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -5,6 +5,8 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin +from django.contrib.contenttypes.models import ContentType +from rating.models import Rating from random import sample as random_sample @@ -27,6 +29,9 @@ class NewsType(models.Model): class NewsQuerySet(models.QuerySet): """QuerySet for model News""" + def rating_value(self): + return self.annotate(rating=models.Count('ratings')) + def with_base_related(self): """Return qs with related objects.""" return self.select_related('news_type', 'country').prefetch_related('tags') @@ -132,6 +137,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('country')) tags = generic.GenericRelation(to='main.MetaDataContent') + ratings = generic.GenericRelation(Rating) + objects = NewsQuerySet.as_manager() class Meta: diff --git a/apps/news/views.py b/apps/news/views.py index 24cac304..25dac124 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -76,5 +76,5 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeDetailSerializer def get(self, request, pk, *args, **kwargs): - add_rating(remote_addr=request.META.get('REMOTE_ADDR'), pk=pk, model='news') + add_rating(remote_addr=request.META.get('REMOTE_ADDR'), pk=pk, model='news', app_label='news',) return self.retrieve(request, *args, **kwargs) diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py index 4942ccdb..0e176910 100644 --- a/apps/rating/tasks.py +++ b/apps/rating/tasks.py @@ -4,18 +4,18 @@ from rating.models import Rating from django.contrib.contenttypes.models import ContentType -def add_rating(remote_addr, pk, model): +def add_rating(remote_addr, pk, model, app_label): add.apply_async( - (remote_addr, pk, model), countdown=60 * 60 + (remote_addr, pk, model, app_label), countdown=60 * 60 ) @task -def add(remote_addr, pk, model): +def add(remote_addr, pk, model, app_label): rating = Rating() rating.ip = remote_addr rating.object_id = pk - rating.content_type = ContentType.objects.get(model=model) + rating.content_type = ContentType.objects.get(app_label=app_label, model=model) rating.save() From 9fd064fdec5f4245db9e8c5df42644a3be426fc2 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, 3 Oct 2019 14:51:17 +0300 Subject: [PATCH 189/319] Add rating unique value --- apps/news/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/models.py b/apps/news/models.py index 2264d897..a4aae03a 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -30,7 +30,7 @@ class NewsQuerySet(models.QuerySet): """QuerySet for model News""" def rating_value(self): - return self.annotate(rating=models.Count('ratings')) + return self.annotate(rating=models.Count('ratings__ip', distinct=True)) def with_base_related(self): """Return qs with related objects.""" From 53db4462f89ea74b014254fe032b99729691467c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 3 Oct 2019 14:59:38 +0300 Subject: [PATCH 190/319] Added lastable booking availability checking method --- apps/booking/models/services.py | 21 +++++++++++++++++---- project/settings/base.py | 4 ++-- requirements/base.txt | 1 + 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py index b5d7fc86..68bc68d4 100644 --- a/apps/booking/models/services.py +++ b/apps/booking/models/services.py @@ -80,7 +80,7 @@ class GuestonlineService(AbstractBookingService): return True def commit_booking(self, payload): - url = self.url + 'v1/pending_bookings/' + payload + '/commit' + url = f'{self.url}v1/pending_bookings/{payload}/commit' r = requests.put(url, headers=self.get_common_headers()) self.response = json.loads(r.content) if status.is_success(r.status_code) and self.response is None: @@ -89,7 +89,7 @@ class GuestonlineService(AbstractBookingService): def update_pending_booking(self, payload): booking_id = payload.pop('pending_booking_id') - url = self.url + 'v1/pending_bookings/' + booking_id + url = f'{self.url}v1/pending_bookings/{booking_id}' payload['lastname'] = payload.pop('last_name') payload['firstname'] = payload.pop('first_name') payload['mobile_phone'] = payload.pop('phone') @@ -119,9 +119,16 @@ class GuestonlineService(AbstractBookingService): class LastableService(AbstractBookingService): def __init__(self): super().__init__(models.Booking.LASTABLE) + self.proxies = { + 'http': settings.LASTABLE_PROXY, + 'https': settings.LASTABLE_PROXY, + } def create_pending_booking(self, payload): - pass + return False + # url = f'{self.url}v1/restaurant/5d951f0e2dc6a50017588065/offers' + # r = requests.post(url, headers=self.get_common_headers(), proxies=self.proxies) + # return json.loads(r.content)['id'] if status.is_success(r.status_code) else False def get_common_headers(self): return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json', @@ -129,7 +136,13 @@ class LastableService(AbstractBookingService): def check_whether_booking_available(self, restaurant_id, date): super().check_whether_booking_available(restaurant_id, date) - return False + url = f'{self.url}v1/restaurant/{restaurant_id}/offers' + r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies) + if not status.is_success(r.status_code): + return False + response = json.loads(r.content)['data'] + self.response = response + return True def commit_booking(self, payload): return False diff --git a/project/settings/base.py b/project/settings/base.py index c135a92c..5a90cc5b 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -266,9 +266,9 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { # Booking API configuration GUESTONLINE_SERVICE = 'https://api-preprod.guestonline.fr/' GUESTONLINE_TOKEN = 'iiReiYpyojshpPjpmczS' -LASTABLE_SERVICE = 'http://34.251.84.222' +LASTABLE_SERVICE = 'http://34.251.84.222/' LASTABLE_TOKEN = '6dfc608ce5e494' -LASTABLE_PROXY = '' +LASTABLE_PROXY = 'socks5://octopod:adgjmptw@94.177.171.154:2080' # SMS Settings SMS_EXPIRATION = 5 diff --git a/requirements/base.txt b/requirements/base.txt index 25749c4b..716d21c6 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,6 +9,7 @@ fcm-django django-easy-select2 bootstrap-admin drf-yasg==1.16.0 +PySocks!=1.5.7,>=1.5.6; djangorestframework==3.9.4 markdown From 729a77866db44ee03ed3079ed96d0d806268a8f3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 3 Oct 2019 17:19:30 +0300 Subject: [PATCH 191/319] update elasticsearch index --- apps/establishment/models.py | 12 ++- .../search_indexes/documents/establishment.py | 9 +- apps/search_indexes/serializers.py | 96 +++++++++++-------- apps/search_indexes/views.py | 8 +- 4 files changed, 80 insertions(+), 45 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 589c2e22..5c2a0ff0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,6 +11,7 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from elasticsearch_dsl import Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection @@ -98,6 +99,16 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() + def es_search(self, value, locale=None): + """Search text via ElasticSearch.""" + from search_indexes.documents import EstablishmentDocument + search = EstablishmentDocument.search().filter( + Q('match', name=value) | + Q('match', **{f'description.{locale}': value}) + ).execute() + ids = [result.meta.id for result in search] + return self.filter(id__in=ids) + def by_country_code(self, code): """Return establishments by country code""" return self.filter(address__city__country__code=code) @@ -330,7 +341,6 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): raise ValidationError('Establishment type of subtype does not match') self.establishment_subtypes.add(establishment_subtype) - @property def vintage_year(self): last_review = self.reviews.by_status(Review.READY).last() diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 16321723..a053e2c7 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -14,6 +14,7 @@ EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1) class EstablishmentDocument(Document): """Establishment document.""" + preview_image = fields.KeywordField(attr='preview_image_url') description = fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES) establishment_type = fields.ObjectField( @@ -50,7 +51,8 @@ class EstablishmentDocument(Document): fields={'raw': fields.KeywordField()} ), 'number': fields.IntegerField(), - 'location': fields.GeoPointField(attr='location_field_indexing'), + 'coordinates': fields.GeoPointField(attr='location_field_indexing'), + # todo: remove if not used 'city': fields.ObjectField( properties={ 'id': fields.IntegerField(), @@ -82,9 +84,10 @@ class EstablishmentDocument(Document): fields = ( 'id', 'name', - 'toque_number', + 'name_translated', 'price_level', - 'preview_image_url', + 'toque_number', + 'public_mark', 'slug', ) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 1d8e3ca3..fc08b471 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -40,13 +40,36 @@ class NewsDocumentSerializer(DocumentSerializer): return get_translated_value(obj.description) -# todo: country_name_translated +class TagsDocumentSerializer(serializers.Serializer): + """Tags serializer for ES Document.""" + + id = serializers.IntegerField() + label_translated = serializers.SerializerMethodField() + + def get_label_translated(self, obj): + return get_translated_value(obj.label) + + +class AddressDocumentSerializer(serializers.Serializer): + """Address serializer for ES Document.""" + + id = serializers.IntegerField() + street_name_1 = serializers.CharField() + street_name_2 = serializers.CharField() + number = serializers.IntegerField() + postal_code = serializers.CharField() + latitude = serializers.FloatField(allow_null=True, source='coordinates.lat') + longitude = serializers.FloatField(allow_null=True, source='coordinates.lon') + geo_lon = serializers.FloatField(allow_null=True, source='coordinates.lon') + geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') + + class EstablishmentDocumentSerializer(DocumentSerializer): """Establishment document serializer.""" - description_translated = serializers.SerializerMethodField(allow_null=True) + address = AddressDocumentSerializer() + tags = TagsDocumentSerializer(many=True) - preview_image = serializers.URLField(source='preview_image_url') class Meta: """Meta class.""" @@ -54,43 +77,40 @@ class EstablishmentDocumentSerializer(DocumentSerializer): fields = ( 'id', 'name', - 'public_mark', - 'toque_number', + 'name_translated', 'price_level', - 'description_translated', - 'tags', - 'address', - 'collections', - 'establishment_type', - 'establishment_subtypes', - 'preview_image', + 'toque_number', + 'public_mark', 'slug', + 'preview_image', + 'address', + 'tags', + # 'collections', + # 'establishment_type', + # 'establishment_subtypes', ) - @staticmethod - def get_description_translated(obj): - return get_translated_value(obj.description) - def to_representation(self, instance): - ret = super().to_representation(instance) - dict_merge = lambda a, b: a.update(b) or a - - ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), - ret['tags']) - ret['establishment_subtypes'] = map( - lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), - ret['establishment_subtypes']) - if ret.get('establishment_type'): - ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name')) - if ret.get('address'): - ret['address']['city']['country']['name_translated'] = get_translated_value( - ret['address']['city']['country'].pop('name')) - location = ret['address'].pop('location') - if location: - ret['address']['geo_lon'] = location['lon'] - ret['address']['geo_lat'] = location['lat'] - - ret['type'] = ret.pop('establishment_type') - ret['subtypes'] = ret.pop('establishment_subtypes') - - return ret \ No newline at end of file + # def to_representation(self, instance): + # ret = super().to_representation(instance) + # dict_merge = lambda a, b: a.update(b) or a + # + # ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), + # ret['tags']) + # ret['establishment_subtypes'] = map( + # lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), + # ret['establishment_subtypes']) + # if ret.get('establishment_type'): + # ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name')) + # if ret.get('address'): + # ret['address']['city']['country']['name_translated'] = get_translated_value( + # ret['address']['city']['country'].pop('name')) + # location = ret['address'].pop('location') + # if location: + # ret['address']['geo_lon'] = location['lon'] + # ret['address']['geo_lat'] = location['lat'] + # + # ret['type'] = ret.pop('establishment_type') + # ret['subtypes'] = ret.pop('establishment_subtypes') + # + # return ret \ No newline at end of file diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index a69caf1f..bd2c91d3 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -1,8 +1,10 @@ """Search indexes app views.""" from rest_framework import permissions from django_elasticsearch_dsl_drf import constants -from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend, - GeoSpatialFilteringFilterBackend) +from django_elasticsearch_dsl_drf.filter_backends import ( + FilteringFilterBackend, + GeoSpatialFilteringFilterBackend +) from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet from utils.pagination import ProjectPageNumberPagination @@ -108,7 +110,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): geo_spatial_filter_fields = { 'location': { - 'field': 'address.location', + 'field': 'address.coordinates', 'lookups': [ constants.LOOKUP_FILTER_GEO_BOUNDING_BOX, ] From cb37b5733e7d5883eb1f1e99f7b41d9a8932b363 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 3 Oct 2019 18:10:29 +0300 Subject: [PATCH 192/319] update migration --- .../migrations/0002_auto_20190917_1307.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/advertisement/migrations/0002_auto_20190917_1307.py b/apps/advertisement/migrations/0002_auto_20190917_1307.py index 178d0f3a..b256fa53 100644 --- a/apps/advertisement/migrations/0002_auto_20190917_1307.py +++ b/apps/advertisement/migrations/0002_auto_20190917_1307.py @@ -4,6 +4,13 @@ from django.db import migrations, models import uuid +def fill_uuid(apps, schemaeditor): + Advertisement = apps.get_model('advertisement', 'Advertisement') + for a in Advertisement.objects.all(): + a.uuid = uuid.uuid4() + a.save() + + class Migration(migrations.Migration): dependencies = [ @@ -23,6 +30,12 @@ class Migration(migrations.Migration): field=models.ManyToManyField(to='translation.Language'), ), migrations.AddField( + model_name='advertisement', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.RunPython(fill_uuid, migrations.RunPython.noop), + migrations.AlterField( model_name='advertisement', name='uuid', field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True), From 8b9f6ed864de8604276177cbc156931d2477dd64 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 3 Oct 2019 18:21:04 +0300 Subject: [PATCH 193/319] another fix migraztion --- .../migrations/0002_auto_20190917_1307.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/advertisement/migrations/0002_auto_20190917_1307.py b/apps/advertisement/migrations/0002_auto_20190917_1307.py index b256fa53..6604a979 100644 --- a/apps/advertisement/migrations/0002_auto_20190917_1307.py +++ b/apps/advertisement/migrations/0002_auto_20190917_1307.py @@ -9,7 +9,14 @@ def fill_uuid(apps, schemaeditor): for a in Advertisement.objects.all(): a.uuid = uuid.uuid4() a.save() - + + +def fill_block_level(apps, schemaeditor): + Advertisement = apps.get_model('advertisement', 'Advertisement') + for a in Advertisement.objects.all(): + a.block_level = '' + a.save() + class Migration(migrations.Migration): @@ -45,8 +52,14 @@ class Migration(migrations.Migration): name='block_level', ), migrations.AddField( + model_name='advertisement', + name='block_level', + field=models.CharField(blank=True, null=True, max_length=10, verbose_name='Block level') + ), + migrations.RunPython(fill_block_level, migrations.RunPython.noop), + migrations.AlterField( model_name='advertisement', name='block_level', field=models.CharField(max_length=10, verbose_name='Block level') - ) + ), ] From 3da57b72fcb29f483a0157e28e98a5d082252cc2 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 3 Oct 2019 19:26:09 +0300 Subject: [PATCH 194/319] Lastable naive realization --- apps/booking/models/models.py | 2 +- apps/booking/models/services.py | 35 +++++++++++++++++++++------------ apps/booking/serializers/web.py | 4 +++- apps/booking/views.py | 20 +++++++++++-------- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/apps/booking/models/models.py b/apps/booking/models/models.py index 203189b8..41cfc6d7 100644 --- a/apps/booking/models/models.py +++ b/apps/booking/models/models.py @@ -19,7 +19,7 @@ class Booking(ProjectBaseMixin): (GUESTONLINE, 'GuestOnline') ) type = models.CharField(max_length=2, choices=AVAILABLE_SERVICES, verbose_name=_('Guestonline or Lastable')) - restaurant_id = models.PositiveIntegerField(verbose_name=_('booking service establishment id'), default=None) + restaurant_id = models.TextField(verbose_name=_('booking service establishment id'), default=None) booking_user_locale = models.CharField(verbose_name=_('booking locale'), default='en', max_length=10) pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None) booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True, diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py index 68bc68d4..c7f0c928 100644 --- a/apps/booking/models/services.py +++ b/apps/booking/models/services.py @@ -40,12 +40,12 @@ class AbstractBookingService(ABC): pass @abstractmethod - def create_pending_booking(self, payload): + def create_booking(self, payload): """ returns pending booking id if created. otherwise False """ pass @abstractmethod - def update_pending_booking(self, payload): + def update_booking(self, payload): """ updates pending booking with contacts """ pass @@ -87,7 +87,7 @@ class GuestonlineService(AbstractBookingService): raise serializers.ValidationError(detail='Booking already committed.') return status.is_success(r.status_code) - def update_pending_booking(self, payload): + def update_booking(self, payload): booking_id = payload.pop('pending_booking_id') url = f'{self.url}v1/pending_bookings/{booking_id}' payload['lastname'] = payload.pop('last_name') @@ -97,8 +97,8 @@ class GuestonlineService(AbstractBookingService): r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload})) return status.is_success(r.status_code) - def create_pending_booking(self, payload): - url = self.url + 'v1/pending_bookings' + def create_booking(self, payload): + url = f'{self.url}v1/pending_bookings' payload['hour'] = payload.pop('booking_time') payload['persons'] = payload.pop('booked_persons_number') payload['date'] = payload.pop('booking_date') @@ -124,11 +124,14 @@ class LastableService(AbstractBookingService): 'https': settings.LASTABLE_PROXY, } - def create_pending_booking(self, payload): - return False - # url = f'{self.url}v1/restaurant/5d951f0e2dc6a50017588065/offers' - # r = requests.post(url, headers=self.get_common_headers(), proxies=self.proxies) - # return json.loads(r.content)['id'] if status.is_success(r.status_code) else False + def create_booking(self, payload): + url = f'{self.url}v1/partner/orders' + payload['places'] = payload.pop('booked_persons_number') + payload['hour'] = payload.pop('booking_time') + payload['firstName'] = payload.pop('first_name') + payload['lastName'] = payload.pop('last_name') + r = requests.post(url, headers=self.get_common_headers(), proxies=self.proxies, data=json.dumps(payload)) + return json.loads(r.content)['data']['_id'] if status.is_success(r.status_code) else False def get_common_headers(self): return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json', @@ -145,13 +148,19 @@ class LastableService(AbstractBookingService): return True def commit_booking(self, payload): + """ Lastable service has no pending booking to commit """ return False - def update_pending_booking(self, payload): + def update_booking(self, payload): + """ Lastable service has no pending booking to update """ return False def cancel_booking(self, payload): - return False + url = f'{self.url}v1/partner/orders/{payload}' + r = requests.delete(url, headers=self.get_common_headers(), proxies=self.proxies) + return status.is_success(r.status_code) def get_booking_details(self, payload): - return {} + url = f'{self.url}v1/partner/orders/{payload}' + r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies) + return json.loads(r.content) diff --git a/apps/booking/serializers/web.py b/apps/booking/serializers/web.py index 8b4f1d91..1094aef8 100644 --- a/apps/booking/serializers/web.py +++ b/apps/booking/serializers/web.py @@ -26,7 +26,8 @@ class CheckBookingSerializer(serializers.ModelSerializer): class PendingBookingSerializer(serializers.ModelSerializer): - restaurant_id = serializers.IntegerField(read_only=True) + restaurant_id = serializers.CharField() + booking_id = serializers.CharField(allow_null=True, allow_blank=True) id = serializers.ReadOnlyField() user = serializers.ReadOnlyField() @@ -36,6 +37,7 @@ class PendingBookingSerializer(serializers.ModelSerializer): 'id', 'type', 'restaurant_id', + 'booking_id', 'pending_booking_id', 'user', ) diff --git a/apps/booking/views.py b/apps/booking/views.py index 5fc342ce..391c45d8 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -1,4 +1,4 @@ -from rest_framework import generics, permissions, status +from rest_framework import generics, permissions, status, serializers from django.shortcuts import get_object_or_404 from establishment.models import Establishment @@ -48,18 +48,22 @@ class CreatePendingBooking(generics.CreateAPIView): def post(self, request, *args, **kwargs): data = request.data.copy() + if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None: + raise serializers.ValidationError(detail='Offer_id is required field for Lastable service') establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id']) data['restaurant_id'] = Booking.get_booking_id_by_type(establishment, data.get('type')) service = Booking.get_service_by_type(request.data.get('type')) data['user'] = request.user.pk if request.user else None - data['pending_booking_id'] = service.create_pending_booking(service.get_certain_keys(data, { - 'restaurant_id', - 'booking_time', - 'booking_date', - 'booked_persons_number', - })) + service_to_keys = { + Booking.GUESTONLINE: {'restaurant_id', 'booking_time', 'booking_date', 'booked_persons_number', }, + Booking.LASTABLE: {'booking_time', 'booked_persons_number', 'offer_id', 'email', 'phone', + 'first_name', 'last_name', }, + } + data['pending_booking_id'] = service.create_booking( + service.get_certain_keys(data.copy(), service_to_keys[data.get('type')])) if not data['pending_booking_id']: return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking') + data['booking_id'] = data['pending_booking_id'] if data.get('type') == Booking.LASTABLE else None serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() @@ -77,7 +81,7 @@ class UpdatePendingBooking(generics.UpdateAPIView): data = request.data.copy() service = Booking.get_service_by_type(instance.type) data['pending_booking_id'] = instance.pending_booking_id - service.update_pending_booking(service.get_certain_keys(data, { + service.update_booking(service.get_certain_keys(data, { 'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', })) service.commit_booking(data['pending_booking_id']) From 3f6aa21005a7ead0eb6b97ccd2a90f919723ab8c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 3 Oct 2019 20:41:26 +0300 Subject: [PATCH 195/319] Fix issue w/ Lastable service --- apps/booking/models/services.py | 4 ++-- apps/booking/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py index c7f0c928..fd685548 100644 --- a/apps/booking/models/services.py +++ b/apps/booking/models/services.py @@ -141,9 +141,9 @@ class LastableService(AbstractBookingService): super().check_whether_booking_available(restaurant_id, date) url = f'{self.url}v1/restaurant/{restaurant_id}/offers' r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies) - if not status.is_success(r.status_code): - return False response = json.loads(r.content)['data'] + if not status.is_success(r.status_code) or not response: + return False self.response = response return True diff --git a/apps/booking/views.py b/apps/booking/views.py index 391c45d8..245dbf05 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -22,12 +22,12 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): date = request.query_params.get('date') g_service = GuestonlineService() l_service = LastableService() - if not establishment.lastable_id is None and l_service \ + if (not establishment.lastable_id is None) and l_service \ .check_whether_booking_available(establishment.lastable_id, date): is_booking_available = True service = l_service service.service_id = establishment.lastable_id - elif not establishment.guestonline_id is None and g_service \ + elif (not establishment.guestonline_id is None) and g_service \ .check_whether_booking_available(establishment.guestonline_id, date): is_booking_available = True service = g_service From ca38f369f7ee2ecf55161da24797fb150a7f34b7 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, 4 Oct 2019 10:15:07 +0300 Subject: [PATCH 196/319] Fix --- apps/rating/send.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 apps/rating/send.py diff --git a/apps/rating/send.py b/apps/rating/send.py deleted file mode 100644 index 06e5daff..00000000 --- a/apps/rating/send.py +++ /dev/null @@ -1,14 +0,0 @@ -from pika import BlockingConnection, ConnectionParameters - - -def mess(): - connection = BlockingConnection(ConnectionParameters('rabbitmq')) - channel = connection.channel() - - channel.queue_declare(queue='rating') - channel.basic_publish( - exchange='', - routing_key='rating', - body='{news_id:127.0.0.1}' - ) - connection.close() \ No newline at end of file From b42e9781a11fc4714bae6cccb703ed3a42a458bb 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, 4 Oct 2019 10:20:36 +0300 Subject: [PATCH 197/319] Fix code --- apps/news/views.py | 3 ++- apps/rating/admin.py | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/news/views.py b/apps/news/views.py index 25dac124..61a57251 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -76,5 +76,6 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeDetailSerializer def get(self, request, pk, *args, **kwargs): - add_rating(remote_addr=request.META.get('REMOTE_ADDR'), pk=pk, model='news', app_label='news',) + add_rating(remote_addr=request.META.get('REMOTE_ADDR'), + pk=pk, model='news', app_label='news') return self.retrieve(request, *args, **kwargs) diff --git a/apps/rating/admin.py b/apps/rating/admin.py index d8d46ae9..151f406b 100644 --- a/apps/rating/admin.py +++ b/apps/rating/admin.py @@ -2,17 +2,10 @@ from django.contrib import admin from rating import models from rating import tasks -# -# def add_task_action(modeladmin, request, queryset): -# # 1, "192.168.1.1" -# tasks.add.delay(1, "192.168.1.1") - - @admin.register(models.Rating) class RatingAdmin(admin.ModelAdmin): """Rating type admin conf.""" list_display = ['name', 'ip'] list_display_links = ['name'] - # actions = [add_task_action] From 24f84ae8be970a6dff15fed270380e2635825c75 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 4 Oct 2019 11:16:01 +0300 Subject: [PATCH 198/319] update tag filter --- apps/search_indexes/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index a69caf1f..2b9f4e65 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -60,7 +60,10 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'description', ) filter_fields = { - 'tag': 'tags.id', + 'tag': { + 'field': 'tags.id', + 'lookups': [constants.LOOKUP_QUERY_IN] + }, 'toque_number': { 'field': 'toque_number', 'lookups': [ From 50a9622f78ade337152587dc490d4bc5fbb4d91e Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 4 Oct 2019 12:05:13 +0300 Subject: [PATCH 199/319] News search --- apps/news/models.py | 1 - apps/search_indexes/documents/news.py | 49 +++++++++++-------- apps/search_indexes/serializers.py | 69 +++++++++++++-------------- apps/search_indexes/signals.py | 56 ++++++++++++++++++++-- apps/search_indexes/views.py | 19 +++++++- 5 files changed, 132 insertions(+), 62 deletions(-) diff --git a/apps/news/models.py b/apps/news/models.py index efa0b566..ab3356bf 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -5,7 +5,6 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin -from random import sample as random_sample class NewsType(models.Model): diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 45e50c42..6e0974d8 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -13,18 +13,26 @@ NewsIndex.settings(number_of_shards=1, number_of_replicas=1) class NewsDocument(Document): """News document.""" - news_type = fields.NestedField(properties={ - 'id': fields.IntegerField(), - 'name': fields.KeywordField() - }) - title = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES) - subtitle = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES) - description = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES) - country = fields.NestedField(properties={ - 'id': fields.IntegerField(), - 'code': fields.KeywordField() - }) + news_type = fields.ObjectField(properties={'id': fields.IntegerField(), + 'name': fields.KeywordField()}) + title = fields.ObjectField(attr='title_indexing', + properties=OBJECT_FIELD_PROPERTIES) + subtitle = fields.ObjectField(attr='subtitle_indexing', + properties=OBJECT_FIELD_PROPERTIES) + description = fields.ObjectField(attr='description_indexing', + properties=OBJECT_FIELD_PROPERTIES) + country = fields.ObjectField(properties={'id': fields.IntegerField(), + 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') + tags = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='metadata.id'), + 'label': fields.ObjectField(attr='metadata.label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'category': fields.ObjectField(attr='metadata.category', + properties={'id': fields.IntegerField()}) + }, + multi=True) class Django: @@ -32,20 +40,19 @@ class NewsDocument(Document): fields = ( 'id', 'playlist', + 'start', + 'end', + 'slug', + 'state', + 'is_highlighted', + 'image_url', + 'preview_image_url', + 'template', ) related_models = [models.NewsType] def get_queryset(self): - return super().get_queryset().published() - - def prepare_title(self, instance): - return instance.title - - def prepare_subtitle(self, instance): - return instance.subtitle - - def prepare_description(self, instance): - return instance.description + return super().get_queryset().published().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. diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index fc08b471..18a1e240 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -1,45 +1,11 @@ """Search indexes serializers.""" from rest_framework import serializers from django_elasticsearch_dsl_drf.serializers import DocumentSerializer +from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.utils import get_translated_value -class NewsDocumentSerializer(DocumentSerializer): - """News document serializer.""" - - title_translated = serializers.SerializerMethodField(allow_null=True) - subtitle_translated = serializers.SerializerMethodField(allow_null=True) - description_translated = serializers.SerializerMethodField(allow_null=True) - - class Meta: - """Meta class.""" - - document = NewsDocument - fields = ( - 'id', - 'title', - 'subtitle', - 'description', - 'web_url', - 'title_translated', - 'subtitle_translated', - 'description_translated', - ) - - @staticmethod - def get_title_translated(obj): - return get_translated_value(obj.title) - - @staticmethod - def get_subtitle_translated(obj): - return get_translated_value(obj.subtitle) - - @staticmethod - def get_description_translated(obj): - return get_translated_value(obj.description) - - class TagsDocumentSerializer(serializers.Serializer): """Tags serializer for ES Document.""" @@ -64,6 +30,39 @@ class AddressDocumentSerializer(serializers.Serializer): geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') +class NewsDocumentSerializer(DocumentSerializer): + """News document serializer.""" + + title_translated = serializers.SerializerMethodField(allow_null=True) + subtitle_translated = serializers.SerializerMethodField(allow_null=True) + news_type = NewsTypeSerializer() + tags = TagsDocumentSerializer(many=True) + + class Meta: + """Meta class.""" + + document = NewsDocument + fields = ( + 'id', + 'title_translated', + 'subtitle_translated', + 'is_highlighted', + 'image_url', + 'preview_image_url', + 'news_type', + 'tags', + 'slug', + ) + + @staticmethod + def get_title_translated(obj): + return get_translated_value(obj.title) + + @staticmethod + def get_subtitle_translated(obj): + return get_translated_value(obj.subtitle) + + class EstablishmentDocumentSerializer(DocumentSerializer): """Establishment document serializer.""" diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 2c04b6c6..f7520b57 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -1,5 +1,5 @@ """Search indexes app signals.""" -from django.db.models.signals import post_save, post_delete +from django.db.models.signals import post_save from django.dispatch import receiver from django_elasticsearch_dsl.registries import registry @@ -17,17 +17,65 @@ def update_document(sender, **kwargs): address__city__country=instance) for establishment in establishments: registry.update(establishment) - if model_name == 'city': establishments = Establishment.objects.filter( address__city=instance) for establishment in establishments: registry.update(establishment) - if model_name == 'address': establishments = Establishment.objects.filter( address=instance) for establishment in establishments: registry.update(establishment) -# todo: delete document + if app_label == 'establishment': + if model_name == 'establishmenttype': + establishments = Establishment.objects.filter( + establishment_type=instance) + for establishment in establishments: + registry.update(establishment) + if model_name == 'establishmentsubtype': + establishments = Establishment.objects.filter( + establishment_subtypes=instance) + for establishment in establishments: + registry.update(establishment) + + if app_label == 'main': + if model_name == 'metadata': + establishments = Establishment.objects.filter(tags__metadata=instance) + for establishment in establishments: + registry.update(establishment) + if model_name == 'metadatacontent': + establishments = Establishment.objects.filter(tags=instance) + for establishment in establishments: + registry.update(establishment) + + +@receiver(post_save) +def update_news(sender, **kwargs): + from news.models import News + app_label = sender._meta.app_label + model_name = sender._meta.model_name + instance = kwargs['instance'] + + if app_label == 'location': + if model_name == 'country': + qs = News.objects.filter(country=instance) + for news in qs: + registry.update(news) + + if app_label == 'news': + if model_name == 'newstype': + qs = News.objects.filter(news_type=instance) + for news in qs: + registry.update(news) + + if app_label == 'main': + if model_name == 'metadata': + qs = News.objects.filter(tags__metadata=instance) + for news in qs: + registry.update(news) + if model_name == 'metadatacontent': + qs = News.objects.filter(tags=instance) + for news in qs: + registry.update(news) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index bd2c91d3..57228c58 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -24,6 +24,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): filter_backends = [ filters.CustomSearchFilterBackend, + FilteringFilterBackend, ] search_fields = ( @@ -37,6 +38,16 @@ class NewsDocumentViewSet(BaseDocumentViewSet): 'description', ) + filter_fields = { + 'tag': { + 'field': 'tags.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ] + }, + 'slug': 'slug', + } + class EstablishmentDocumentViewSet(BaseDocumentViewSet): """Establishment document ViewSet.""" @@ -62,7 +73,13 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'description', ) filter_fields = { - 'tag': 'tags.id', + 'slug': 'slug', + 'tag': { + 'field': 'tags.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ] + }, 'toque_number': { 'field': 'toque_number', 'lookups': [ From af0e6af5c43c2370e37e3d9040f50eb2fbb06da7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 13:48:48 +0300 Subject: [PATCH 200/319] refactored email reconfirm view --- apps/account/serializers/common.py | 32 ----------------------- apps/account/urls/common.py | 1 + apps/account/views/common.py | 29 ++++++++++++++------- apps/authorization/serializers/common.py | 33 ------------------------ apps/authorization/tasks.py | 7 ++--- apps/authorization/urls/common.py | 1 - apps/authorization/views/common.py | 11 -------- 7 files changed, 24 insertions(+), 90 deletions(-) diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index e6076117..b68aca7d 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -172,38 +172,6 @@ class ChangeEmailSerializer(serializers.ModelSerializer): return instance -class ConfirmEmailSerializer(serializers.ModelSerializer): - """Confirm user email serializer""" - x = serializers.CharField(default=None) - - class Meta: - """Meta class""" - model = models.User - fields = ( - 'email', - ) - - def validate(self, attrs): - """Override validate method""" - email_confirmed = self.instance.email_confirmed - if email_confirmed: - raise utils_exceptions.EmailConfirmedError() - - return attrs - - def update(self, instance, validated_data): - """ - Override update method - """ - - # Send verification link on user email for change email address - if settings.USE_CELERY: - tasks.confirm_new_email_address.delay(instance.id) - else: - tasks.confirm_new_email_address(instance.id) - return instance - - # Firebase Cloud Messaging serializers class FCMDeviceSerializer(serializers.ModelSerializer): """FCM Device model serializer""" diff --git a/apps/account/urls/common.py b/apps/account/urls/common.py index 34583010..4ea2af66 100644 --- a/apps/account/urls/common.py +++ b/apps/account/urls/common.py @@ -8,5 +8,6 @@ app_name = 'account' urlpatterns = [ path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'), path('change-password/', views.ChangePasswordView.as_view(), name='change-password'), + path('email/confirm/', views.SendConfirmationEmailView.as_view(), name='send-confirm-email'), path('email/confirm///', views.ConfirmEmailView.as_view(), name='confirm-email'), ] diff --git a/apps/account/views/common.py b/apps/account/views/common.py index ab62343f..d29ce2bb 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -1,4 +1,5 @@ """Common account views""" +from django.conf import settings from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_decode from fcm_django.models import FCMDevice @@ -9,6 +10,7 @@ from rest_framework.response import Response from account import models from account.serializers import common as serializers +from authorization.tasks import send_confirm_email from utils import exceptions as utils_exceptions from utils.models import GMTokenGenerator from utils.views import JWTGenericViewMixin @@ -38,19 +40,26 @@ class ChangePasswordView(generics.GenericAPIView): return Response(status=status.HTTP_200_OK) -class SendConfirmationEmailView(JWTGenericViewMixin): +class SendConfirmationEmailView(generics.GenericAPIView): """Confirm email view.""" - serializer_class = serializers.ConfirmEmailSerializer - queryset = models.User.objects.all() - def patch(self, request, *args, **kwargs): - """Implement PATCH-method""" - # Get user instance - instance = self.request.user + def post(self, request, *args, **kwargs): + """Override create method""" + user = self.request.user + country_code = self.request.country_code - serializer = self.get_serializer(data=request.data, instance=instance) - serializer.is_valid(raise_exception=True) - serializer.save() + if user.email_confirmed: + raise utils_exceptions.EmailConfirmedError() + + # Send verification link on user email for change email address + if settings.USE_CELERY: + send_confirm_email.delay( + user_id=user.id, + country_code=country_code) + else: + send_confirm_email( + user_id=user.id, + country_code=country_code) return Response(status=status.HTTP_200_OK) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 3e7195a5..ed68ba9f 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -76,39 +76,6 @@ class SignupSerializer(serializers.ModelSerializer): return obj -class ReconfirmSerializer(serializers.ModelSerializer): - - class Meta: - model = account_models.User - fields = ('email',) - - def validate_email(self, value): - """Validate email""" - users = list(account_models.User.objects.filter(email=value.lower()).all()) - if not users: - raise serializers.ValidationError(detail='User with mentioned email does not exist.') - users = list(filter(lambda user: not user.email_confirmed, users)) - if not users: - raise serializers.ValidationError(detail='User with this email is confirmed.') - return value - - def create(self, validated_data): - """Override create method""" - queryset = account_models.User.objects.all() - email = validated_data.get('email').lower() - country_code = self.context.get('request').country_code - obj = get_object_or_404(queryset, email=email) - if settings.USE_CELERY: - tasks.send_confirm_email.delay( - user_id=obj.id, - country_code=country_code) - else: - tasks.send_confirm_email( - user_id=obj.id, - country_code=country_code) - return obj - - class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, serializers.ModelSerializer): """Serializer for login user""" diff --git a/apps/authorization/tasks.py b/apps/authorization/tasks.py index 9947c2a3..c97fbbae 100644 --- a/apps/authorization/tasks.py +++ b/apps/authorization/tasks.py @@ -4,18 +4,19 @@ from django.utils.translation import gettext_lazy as _ from celery import shared_task from account import models as account_models +from smtplib import SMTPException logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) @shared_task -def send_confirm_email(user_id, country_code): +def send_confirm_email(user_id: int, country_code: str): """Send verification email to user.""" try: obj = account_models.User.objects.get(id=user_id) obj.send_email(subject=_('Email confirmation'), message=obj.confirm_email_template(country_code)) - except: + except Exception as e: logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n' - f'DETAIL: Exception occurred for user: {user_id}') + f'DETAIL: user {user_id}, - {e}') diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index 814ce836..4e6e59e1 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -29,7 +29,6 @@ urlpatterns_oauth2 = [ urlpatterns_jwt = [ path('signup/', views.SignUpView.as_view(), name='signup'), - path('signup/reconfirm/', views.ReconfirmView.as_view(), name='signup-reconfirm'), path('signup/confirm///', views.ConfirmationEmailView.as_view(), name='signup-confirm'), path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'), diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index a2e3e8c9..0b1a58e0 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -147,17 +147,6 @@ class SignUpView(generics.GenericAPIView): return Response(status=status.HTTP_201_CREATED) -class ReconfirmView(generics.CreateAPIView): - """ Resends confirmation email whether user's still not confirmed """ - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.ReconfirmSerializer - - def post(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - return Response(status=status.HTTP_201_CREATED) - - class ConfirmationEmailView(JWTGenericViewMixin): """View for confirmation email""" From 36922e16842cec8ddad5c568205d46aeb18da7a5 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 4 Oct 2019 14:04:08 +0300 Subject: [PATCH 201/319] update settings --- project/settings/development.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/development.py b/project/settings/development.py index 43a60935..ff80b492 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -24,7 +24,7 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { - # 'search_indexes.documents.news': 'development_news', # temporarily disabled + 'search_indexes.documents.news': 'development_news', # temporarily disabled 'search_indexes.documents.establishment': 'development_establishment', } From 175bd4f7b51b962cc73183135a4cbb6aa898d980 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 4 Oct 2019 14:07:50 +0300 Subject: [PATCH 202/319] update establishment document --- apps/search_indexes/documents/establishment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index a053e2c7..2d43154e 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -51,6 +51,7 @@ class EstablishmentDocument(Document): fields={'raw': fields.KeywordField()} ), 'number': fields.IntegerField(), + 'postal_code': fields.KeywordField(), 'coordinates': fields.GeoPointField(attr='location_field_indexing'), # todo: remove if not used 'city': fields.ObjectField( From 9f9c1064bd2d20054577c7323ce38f2de86eefb7 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, 4 Oct 2019 15:39:14 +0300 Subject: [PATCH 203/319] Fix rating --- .../migrations/0002_auto_20191004_0928.py | 22 +++++++++++++++++++ apps/rating/models.py | 3 +++ apps/rating/tasks.py | 17 ++++++-------- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 apps/rating/migrations/0002_auto_20191004_0928.py diff --git a/apps/rating/migrations/0002_auto_20191004_0928.py b/apps/rating/migrations/0002_auto_20191004_0928.py new file mode 100644 index 00000000..a172c6f1 --- /dev/null +++ b/apps/rating/migrations/0002_auto_20191004_0928.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-10-04 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('rating', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='rating', + options={}, + ), + migrations.AlterUniqueTogether( + name='rating', + unique_together={('ip', 'object_id', 'content_type')}, + ), + ] diff --git a/apps/rating/models.py b/apps/rating/models.py index 61e4378d..e1dcec86 100644 --- a/apps/rating/models.py +++ b/apps/rating/models.py @@ -10,6 +10,9 @@ class Rating(models.Model): content_object = GenericForeignKey('content_type', 'object_id') ip = models.GenericIPAddressField(verbose_name=_('ip')) + class Meta: + unique_together = ('ip', 'object_id', 'content_type') + @property def name(self): # Check if Generic obj has name or title diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py index 0e176910..0b6878c4 100644 --- a/apps/rating/tasks.py +++ b/apps/rating/tasks.py @@ -1,22 +1,19 @@ -from datetime import timedelta -from celery import task +from celery import shared_task from rating.models import Rating from django.contrib.contenttypes.models import ContentType def add_rating(remote_addr, pk, model, app_label): add.apply_async( - (remote_addr, pk, model, app_label), countdown=60 * 60 + (remote_addr, pk, model, app_label), countdown=2 # 60 * 60 ) + # TODO Вернуть интервал -@task +@shared_task def add(remote_addr, pk, model, app_label): - rating = Rating() - rating.ip = remote_addr - rating.object_id = pk - rating.content_type = ContentType.objects.get(app_label=app_label, model=model) - rating.save() - + content_type = ContentType.objects.get(app_label=app_label, model=model) + Rating.objects.get_or_create( + ip=remote_addr, object_id=pk, content_type=content_type) From f93d0095f4708123fdd853cdebf9f0341b83317c 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, 4 Oct 2019 15:40:18 +0300 Subject: [PATCH 204/319] Fix rating --- apps/rating/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/rating/tasks.py b/apps/rating/tasks.py index 0b6878c4..5c2653a0 100644 --- a/apps/rating/tasks.py +++ b/apps/rating/tasks.py @@ -5,9 +5,8 @@ from django.contrib.contenttypes.models import ContentType def add_rating(remote_addr, pk, model, app_label): add.apply_async( - (remote_addr, pk, model, app_label), countdown=2 # 60 * 60 + (remote_addr, pk, model, app_label), countdown=60 * 60 ) - # TODO Вернуть интервал @shared_task From 4d55f80e6fc34700312ef3fc06e8f6034f546079 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 4 Oct 2019 15:49:21 +0300 Subject: [PATCH 205/319] Added migration --- .../migrations/0002_auto_20191003_1601.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/booking/migrations/0002_auto_20191003_1601.py diff --git a/apps/booking/migrations/0002_auto_20191003_1601.py b/apps/booking/migrations/0002_auto_20191003_1601.py new file mode 100644 index 00000000..cf868196 --- /dev/null +++ b/apps/booking/migrations/0002_auto_20191003_1601.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-03 16:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0001_initial_squashed_0010_auto_20190920_1206'), + ] + + operations = [ + migrations.AlterField( + model_name='booking', + name='restaurant_id', + field=models.TextField(default=None, verbose_name='booking service establishment id'), + ), + ] From 90b8dafba9bedce2ec21d20338e58bed07d9db72 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 4 Oct 2019 20:24:48 +0300 Subject: [PATCH 206/319] News email #1 --- apps/news/tasks.py | 28 ++++++---- project/templates/news/news_email.html | 77 +++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 99065fc8..beed2060 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -1,3 +1,5 @@ +from datetime import datetime + from celery import shared_task from django.core.mail import send_mail from notification.models import Subscriber @@ -5,24 +7,30 @@ from news import models from django.template.loader import render_to_string from django.conf import settings from smtplib import SMTPException +from django.template.loader import get_template @shared_task def send_email_with_news(news_ids): - subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) sent_news = models.News.objects.filter(id__in=news_ids) - + htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) + year = datetime.now().year for s in subscribers: try: for n in sent_news: - send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, - {"title": n.title.get(s.locale), - "subtitle": n.subtitle.get(s.locale), - "description": n.description.get(s.locale), - "code": s.update_code, - "domain_uri": settings.DOMAIN_URI, - "country_code": s.country_code}), - settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False) + context = {"title": n.title.get(s.locale), + "subtitle": n.subtitle.get(s.locale), + "description": n.description.get(s.locale), + "code": s.update_code, + "image_url": n.image_url, + "domain_uri": settings.DOMAIN_URI, + "slug": n.slug, + "country_code": s.country_code, + "send_to": s.send_to, + "year": year} + send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context), + settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False, + html_message=htmly.render(context)) except SMTPException: continue diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index a47af685..dd4dea5b 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -1,20 +1,79 @@ - + + + + {{ title }} - -

{{ title }}

+ +
- {% if subtitle %} -

{{ subtitle }}

- {% endif %} +
-

{{ description }}

-https://{{ country_code }}.{{ domain_uri }}{% url 'web:notification:unsubscribe' code %} +
+
+
+
+
+ + +
+
+ {{ title }} +
+ {% if not image_url is None %} +
+ +
+ {% endif %} +
+ {{ description | safe }} +
+ + + +
+ +
+ This email has been sent to {{ send_to }} , + click here to unsubscribe +
+ +
+
+
- From 73646eb05f5ef1754ccb5f2d73e1048238234edc Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Sat, 5 Oct 2019 18:08:58 +0300 Subject: [PATCH 207/319] News email #2 --- apps/news/tasks.py | 10 +++++++++- project/templates/news/news_email.html | 18 +++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index beed2060..c2b63b48 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -8,6 +8,8 @@ from django.template.loader import render_to_string from django.conf import settings from smtplib import SMTPException from django.template.loader import get_template +from django.core.validators import EMPTY_VALUES +from main.models import SiteSettings @shared_task @@ -16,17 +18,23 @@ def send_email_with_news(news_ids): sent_news = models.News.objects.filter(id__in=news_ids) htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) year = datetime.now().year + socials = list(SiteSettings.objects.with_country()) + socials = dict(zip(map(lambda s: s.country.code, socials), socials)) for s in subscribers: + socials_for_subscriber = socials.get(s.country_code) try: for n in sent_news: context = {"title": n.title.get(s.locale), "subtitle": n.subtitle.get(s.locale), "description": n.description.get(s.locale), "code": s.update_code, - "image_url": n.image_url, + "image_url": n.image_url if n.image_url not in EMPTY_VALUES else None, "domain_uri": settings.DOMAIN_URI, "slug": n.slug, "country_code": s.country_code, + "twitter_page_url": socials_for_subscriber.twitter_page_url if socials_for_subscriber else '#', + "instagram_page_url": socials_for_subscriber.instagram_page_url if socials_for_subscriber else '#', + "facebook_page_url": socials_for_subscriber.facebook_page_url if socials_for_subscriber else '#', "send_to": s.send_to, "year": year} send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context), diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index dd4dea5b..33ebe29c 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -33,30 +33,30 @@ {% endif %} -
+
{{ description | safe }}
- +
{% if not image_url is None %} -
+
{% endif %} From 3bf65472d91d6360ddb6494e8dd2338bc50d38f8 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 7 Oct 2019 16:31:50 +0300 Subject: [PATCH 209/319] Auto fuzziness with match query --- apps/search_indexes/views.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index a4f37006..8d126ab7 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -6,10 +6,9 @@ from django_elasticsearch_dsl_drf.filter_backends import ( GeoSpatialFilteringFilterBackend ) from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet - -from utils.pagination import ProjectPageNumberPagination from search_indexes import serializers, filters from search_indexes.documents import EstablishmentDocument, NewsDocument +from utils.pagination import ProjectPageNumberPagination class NewsDocumentViewSet(BaseDocumentViewSet): @@ -27,11 +26,11 @@ class NewsDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, ] - search_fields = ( - 'title', - 'subtitle', - 'description', - ) + search_fields = { + 'title': {'fuzziness': 'auto'}, + 'subtitle': {'fuzziness': 'auto'}, + 'description': {'fuzziness': 'auto'}, + } translated_search_fields = ( 'title', 'subtitle', @@ -65,10 +64,10 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): GeoSpatialFilteringFilterBackend, ] - search_fields = ( - 'name', - 'description', - ) + search_fields = { + 'name': {'fuzziness': 'auto'}, + 'description': {'fuzziness': 'auto'}, + } translated_search_fields = ( 'description', ) From 7925febc77d103c54c2431d77f3a1b56b378dc5d 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: Tue, 8 Oct 2019 09:57:47 +0300 Subject: [PATCH 210/319] Models role --- apps/account/migrations/0009_role_userrole.py | 47 +++++++++++++++++++ apps/account/models.py | 22 +++++++++ .../migrations/0003_auto_20191004_0928.py | 17 +++++++ 3 files changed, 86 insertions(+) create mode 100644 apps/account/migrations/0009_role_userrole.py create mode 100644 apps/timetable/migrations/0003_auto_20191004_0928.py diff --git a/apps/account/migrations/0009_role_userrole.py b/apps/account/migrations/0009_role_userrole.py new file mode 100644 index 00000000..f69cb34f --- /dev/null +++ b/apps/account/migrations/0009_role_userrole.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.4 on 2019-10-08 06:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0010_auto_20190904_0711'), + ('account', '0008_auto_20190912_1325'), + ] + + operations = [ + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('role', models.CharField(choices=[(0, 'Guest'), (1, 'Standard user'), (2, 'Content page manager'), (3, 'Comments moderator')], max_length=250, verbose_name='Role')), + ('is_list', models.BooleanField(default=True, verbose_name='list')), + ('is_create', models.BooleanField(default=False, verbose_name='create')), + ('is_update', models.BooleanField(default=False, verbose_name='update')), + ('is_delete', models.BooleanField(default=False, verbose_name='delete')), + ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='UserRole', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('role', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='account.Role', verbose_name='Role')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 81ade4fc..86d9bf8c 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from authorization.models import Application +from location.models import Country from utils.models import GMTokenGenerator from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.tokens import GMRefreshToken @@ -195,3 +196,24 @@ class User(AbstractUser): return render_to_string( template_name=settings.CHANGE_EMAIL_TEMPLATE, context=context) + + +class Role(ProjectBaseMixin): + ROLE_CHOICES =( + (0, 'Guest'), + (1, 'Standard user'), + (2, 'Content page manager'), + (3, 'Comments moderator'), + ) + role = models.CharField(verbose_name=_('Role'), max_length=250, + choices=ROLE_CHOICES, null=False, blank=False) + country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) + is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) + is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) + is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) + is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False) + + +class UserRole(ProjectBaseMixin): + user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) + role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) \ No newline at end of file diff --git a/apps/timetable/migrations/0003_auto_20191004_0928.py b/apps/timetable/migrations/0003_auto_20191004_0928.py new file mode 100644 index 00000000..6e82c679 --- /dev/null +++ b/apps/timetable/migrations/0003_auto_20191004_0928.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-04 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0002_auto_20190919_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='timetable', + options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, + ), + ] From e66a6bcbfaa2f527793d48f57fbf4dade4e0c6dc 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: Tue, 8 Oct 2019 10:18:51 +0300 Subject: [PATCH 211/319] Fix models --- apps/account/admin.py | 10 ++++++++++ apps/account/migrations/0009_role_userrole.py | 4 ++-- apps/account/models.py | 8 +++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/account/admin.py b/apps/account/admin.py index 8429952f..12f2ef03 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -6,6 +6,16 @@ from django.utils.translation import ugettext_lazy as _ from account import models +@admin.register(models.Role) +class RoleAdmin(admin.ModelAdmin): + list_display = ['role', 'country', 'is_list', 'is_create', 'is_update', 'is_delete'] + + +@admin.register(models.UserRole) +class UserRoleAdmin(admin.ModelAdmin): + list_display = ['user', 'role'] + + @admin.register(models.User) class UserAdmin(BaseUserAdmin): """User model admin settings.""" diff --git a/apps/account/migrations/0009_role_userrole.py b/apps/account/migrations/0009_role_userrole.py index f69cb34f..e162bfe5 100644 --- a/apps/account/migrations/0009_role_userrole.py +++ b/apps/account/migrations/0009_role_userrole.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 06:54 +# Generated by Django 2.2.4 on 2019-10-08 07:17 from django.conf import settings from django.db import migrations, models @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('role', models.CharField(choices=[(0, 'Guest'), (1, 'Standard user'), (2, 'Content page manager'), (3, 'Comments moderator')], max_length=250, verbose_name='Role')), + ('role', models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator')], verbose_name='Role')), ('is_list', models.BooleanField(default=True, verbose_name='list')), ('is_create', models.BooleanField(default=False, verbose_name='create')), ('is_update', models.BooleanField(default=False, verbose_name='update')), diff --git a/apps/account/models.py b/apps/account/models.py index 86d9bf8c..960860cd 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -200,13 +200,11 @@ class User(AbstractUser): class Role(ProjectBaseMixin): ROLE_CHOICES =( - (0, 'Guest'), (1, 'Standard user'), - (2, 'Content page manager'), - (3, 'Comments moderator'), + (2, 'Comments moderator'), ) - role = models.CharField(verbose_name=_('Role'), max_length=250, - choices=ROLE_CHOICES, null=False, blank=False) + role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, + null=False, blank=False) country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) From 861daf8871dc057a079fa423a999f8192d347037 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 8 Oct 2019 14:39:42 +0300 Subject: [PATCH 212/319] update search fields --- apps/search_indexes/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 8d126ab7..50c32fc7 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -27,7 +27,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): ] search_fields = { - 'title': {'fuzziness': 'auto'}, + 'title': {'fuzziness': 'auto:3,4'}, 'subtitle': {'fuzziness': 'auto'}, 'description': {'fuzziness': 'auto'}, } @@ -65,7 +65,8 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): ] search_fields = { - 'name': {'fuzziness': 'auto'}, + 'name': {'fuzziness': 'auto:3,4'}, + 'name_translated': {'fuzziness': 'auto:3,4'}, 'description': {'fuzziness': 'auto'}, } translated_search_fields = ( From eeba02c2aea27b37306b841548f6ca2a0c368be3 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: Tue, 8 Oct 2019 15:26:39 +0300 Subject: [PATCH 213/319] test role --- apps/account/admin.py | 2 +- .../migrations/0010_auto_20191008_0751.py | 29 +++++++++++++++++++ apps/account/models.py | 8 ++--- apps/account/permissions.py | 0 apps/comment/serializers/back.py | 11 +++++++ apps/comment/urls/back.py | 9 ++++++ apps/comment/views/back.py | 0 7 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 apps/account/migrations/0010_auto_20191008_0751.py create mode 100644 apps/account/permissions.py create mode 100644 apps/comment/serializers/back.py create mode 100644 apps/comment/urls/back.py create mode 100644 apps/comment/views/back.py diff --git a/apps/account/admin.py b/apps/account/admin.py index 12f2ef03..a89e6693 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -8,7 +8,7 @@ from account import models @admin.register(models.Role) class RoleAdmin(admin.ModelAdmin): - list_display = ['role', 'country', 'is_list', 'is_create', 'is_update', 'is_delete'] + list_display = ['role', 'country'] @admin.register(models.UserRole) diff --git a/apps/account/migrations/0010_auto_20191008_0751.py b/apps/account/migrations/0010_auto_20191008_0751.py new file mode 100644 index 00000000..289f8643 --- /dev/null +++ b/apps/account/migrations/0010_auto_20191008_0751.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.4 on 2019-10-08 07:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_role_userrole'), + ] + + operations = [ + migrations.RemoveField( + model_name='role', + name='is_create', + ), + migrations.RemoveField( + model_name='role', + name='is_delete', + ), + migrations.RemoveField( + model_name='role', + name='is_list', + ), + migrations.RemoveField( + model_name='role', + name='is_update', + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 960860cd..c7de88e3 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -206,10 +206,10 @@ class Role(ProjectBaseMixin): role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) - is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) - is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) - is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) - is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False) + # is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) + # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) + # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) + # is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False) class UserRole(ProjectBaseMixin): diff --git a/apps/account/permissions.py b/apps/account/permissions.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/comment/serializers/back.py b/apps/comment/serializers/back.py new file mode 100644 index 00000000..a491168d --- /dev/null +++ b/apps/comment/serializers/back.py @@ -0,0 +1,11 @@ +"""Comment app common serializers.""" +from comment import models +from rest_framework import serializers +from utils.serializers import ProjectModelSerializer + + +class CommentBaseSerializer(ProjectModelSerializer): + + class Meta: + model = models.Comment + fields = ('id', 'text', 'mark', 'user') \ No newline at end of file diff --git a/apps/comment/urls/back.py b/apps/comment/urls/back.py new file mode 100644 index 00000000..6141ceed --- /dev/null +++ b/apps/comment/urls/back.py @@ -0,0 +1,9 @@ +"""Web urlpaths.""" +from comment.urls.common import urlpatterns as common_urlpatterns + +app_name = 'comment' + +urlpatterns_api = [] + +urlpatterns = common_urlpatterns + \ + urlpatterns_api diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py new file mode 100644 index 00000000..e69de29b From d321866dc21b45eb2205930cd4a01098f7f3e132 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 8 Oct 2019 15:38:35 +0300 Subject: [PATCH 214/319] gm-192: in progress --- apps/account/views/web.py | 3 +- apps/establishment/admin.py | 22 +++-- ...ishmenttag_establishmenttypetagcategory.py | 39 ++++++++ apps/establishment/models.py | 62 +++++++++++- apps/establishment/serializers/back.py | 17 ++++ apps/establishment/serializers/common.py | 64 ++++++++----- apps/establishment/urls/back.py | 6 ++ apps/establishment/urls/web.py | 2 +- apps/establishment/views/back.py | 94 ++++++++++++++++++- apps/establishment/views/web.py | 36 +------ apps/main/admin.py | 16 ---- apps/main/models.py | 1 - .../search_indexes/documents/establishment.py | 23 ++--- apps/tag/__init__.py | 0 apps/tag/admin.py | 12 +++ apps/tag/apps.py | 7 ++ apps/tag/migrations/0001_initial.py | 44 +++++++++ apps/tag/migrations/__init__.py | 0 apps/tag/models.py | 51 ++++++++++ apps/tag/serializers.py | 44 +++++++++ apps/tag/tests.py | 3 + apps/tag/urls.py | 10 ++ apps/tag/views.py | 18 ++++ project/settings/base.py | 1 + project/urls/back.py | 6 +- 25 files changed, 477 insertions(+), 104 deletions(-) create mode 100644 apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py create mode 100644 apps/tag/__init__.py create mode 100644 apps/tag/admin.py create mode 100644 apps/tag/apps.py create mode 100644 apps/tag/migrations/0001_initial.py create mode 100644 apps/tag/migrations/__init__.py create mode 100644 apps/tag/models.py create mode 100644 apps/tag/serializers.py create mode 100644 apps/tag/tests.py create mode 100644 apps/tag/urls.py create mode 100644 apps/tag/views.py diff --git a/apps/account/views/web.py b/apps/account/views/web.py index 897c955e..9f2ebcfd 100644 --- a/apps/account/views/web.py +++ b/apps/account/views/web.py @@ -40,8 +40,7 @@ class PasswordResetConfirmView(JWTGenericViewMixin): queryset = models.User.objects.active() def get_object(self): - """Override get_object method - """ + """Override get_object method""" queryset = self.filter_queryset(self.get_queryset()) uidb64 = self.kwargs.get('uidb64') diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index f95dd5c8..a40475b0 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from comment.models import Comment from establishment import models -from main.models import Award, MetaDataContent +from main.models import Award from review import models as review_models @@ -24,11 +24,6 @@ class AwardInline(GenericTabularInline): extra = 0 -class MetaDataContentInline(GenericTabularInline): - model = MetaDataContent - extra = 0 - - class ContactPhoneInline(admin.TabularInline): """Contact phone inline admin.""" model = models.ContactPhone @@ -56,8 +51,7 @@ class EstablishmentAdmin(admin.ModelAdmin): """Establishment admin.""" list_display = ['id', '__str__', 'image_tag', ] inlines = [ - AwardInline, MetaDataContentInline, - ContactPhoneInline, ContactEmailInline, + AwardInline, ContactPhoneInline, ContactEmailInline, ReviewInline, CommentInline] @@ -84,4 +78,14 @@ class MenuAdmin(admin.ModelAdmin): """Get user's short name.""" return obj.category_translated - category_translated.short_description = _('category') \ No newline at end of file + category_translated.short_description = _('category') + + +@admin.register(models.EstablishmentTypeTagCategory) +class EstablishmentTypeTagCategory(admin.ModelAdmin): + """EstablishmentTypeTagCategory admin.""" + + +@admin.register(models.EstablishmentTag) +class EstablishmentTag(admin.ModelAdmin): + """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py new file mode 100644 index 00000000..6efaa57a --- /dev/null +++ b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.4 on 2019-10-08 07:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ('establishment', '0031_establishment_slug'), + ] + + operations = [ + migrations.CreateModel( + name='EstablishmentTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), + ('tag_category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category')), + ], + options={ + 'verbose_name': 'establishment type tag categories', + 'verbose_name_plural': 'establishment type tag categories', + }, + ), + migrations.CreateModel( + name='EstablishmentTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='establishment.Establishment', verbose_name='establishment')), + ('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.Tag', verbose_name='tag')), + ], + options={ + 'verbose_name': 'establishment tag', + 'verbose_name_plural': 'establishment tags', + }, + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5c2a0ff0..9ce4eebc 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -76,11 +76,13 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address').prefetch_related( - models.Prefetch('tags', - MetaDataContent.objects.select_related( - 'metadata__category')) - ) + return self.select_related('address') + # todo: fix this + # return self.select_related('address').prefetch_related( + # models.Prefetch('tags', + # MetaDataContent.objects.select_related( + # 'metadata__category')) + # ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -546,3 +548,53 @@ class SocialNetwork(models.Model): def __str__(self): return self.title + + +class EstablishmentTagQuerySet(models.QuerySet): + """Establishment tag QuerySet.""" + + def by_country_code(self, code): + """Return establishment tags by establishment country code.""" + return self.filter(establishment__address__city__country__code=code) + + +class EstablishmentTag(models.Model): + """Establishment tag model.""" + tag = models.ForeignKey('tag.Tag', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name=_('tag')) + establishment = models.ForeignKey('establishment.Establishment', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name=_('establishment')) + objects = EstablishmentTagQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment tag') + verbose_name_plural = _('establishment tags') + + +class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): + """EstablishmentTypeTagCategory QuerySet.""" + + def by_country_code(self, code): + """Return establishment tags by country code""" + return self.filter(tag_category__country__code=code) + + +class EstablishmentTypeTagCategory(models.Model): + """Tag categories based on establishment type.""" + establishment_type = models.ForeignKey(EstablishmentType, + on_delete=models.SET_NULL, null=True, + related_name='tag_categories', + verbose_name=_('establishment type')) + tag_category = models.ForeignKey('tag.TagCategory', + on_delete=models.SET_NULL, null=True, + related_name='est_type_tag_categories', + verbose_name=_('tag category')) + objects = EstablishmentTypeTagCategoryQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment type tag categories') + verbose_name_plural = _('establishment type tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index d0c70b2f..b15ff51f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from tag.serializers import TagBaseSerializer from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, @@ -6,6 +7,7 @@ from establishment.serializers import ( EstablishmentTypeSerializer) from utils.decorators import with_base_attributes +from utils.serializers import TranslatedField from main.models import Currency @@ -74,6 +76,21 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): ] +class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTypeTagCategories.""" + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + ] + + class SocialNetworkSerializers(serializers.ModelSerializer): """Social network serializers.""" class Meta: diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index f09c8200..6c12360b 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -6,8 +6,8 @@ from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites from location.serializers import AddressBaseSerializer -from main.models import MetaDataContent -from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer +from main.serializers import AwardSerializer, CurrencySerializer +from tag import models as tag_models from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions @@ -89,25 +89,32 @@ class MenuRUDSerializers(ProjectModelSerializer): class EstablishmentTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentType model.""" - name_translated = serializers.CharField(allow_null=True) + name_translated = TranslatedField() class Meta: """Meta class.""" model = models.EstablishmentType - fields = ('id', 'name_translated') + fields = ('id', 'name', 'name_translated') + extra_kwargs = { + 'name': {'write_only': True} + } class EstablishmentSubTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentSubType models.""" - name_translated = serializers.CharField(allow_null=True) + name_translated = TranslatedField() class Meta: """Meta class.""" model = models.EstablishmentSubType - fields = ('id', 'name_translated') + fields = ('id', 'name', 'name_translated', 'establishment_type') + extra_kwargs = { + 'name': {'write_only': True}, + 'establishment_type': {'write_only': True} + } class ReviewSerializer(serializers.ModelSerializer): @@ -138,14 +145,42 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'position_translated', 'awards', 'priority') +class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): + """Serializer for establishment category tags.""" + label_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = tag_models.TagCategory + fields = [ + 'id', + 'label_translated', + ] + + +class EstablishmentTagListSerializer(serializers.ModelSerializer): + """Serializer for establishment tags.""" + label_translated = TranslatedField(source='tag.label_translated') + category = EstablishmentCategoryTagListSerializer(source='tag.category') + + class Meta: + """Meta class.""" + model = tag_models.Tag + fields = [ + 'id', + 'label_translated', + 'category', + ] + + class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" preview_image = serializers.URLField(source='preview_image_url') slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() - tags = MetaDataContentSerializer(many=True) in_favorites = serializers.BooleanField(allow_null=True) + tags = EstablishmentTagListSerializer(many=True) class Meta: """Meta class.""" @@ -305,18 +340,3 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): 'content_object': validated_data.pop('establishment') }) return super().create(validated_data) - - -class EstablishmentTagListSerializer(serializers.ModelSerializer): - """List establishment tag serializer.""" - id = serializers.IntegerField(source='metadata.id') - label_translated = serializers.CharField( - source='metadata.label_translated', read_only=True, allow_null=True) - - class Meta: - """Meta class.""" - model = MetaDataContent - fields = [ - 'id', - 'label_translated', - ] diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index dca5fb55..b04a6843 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,6 +14,8 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), + path('/tags/categories/', views.EstablishmentTagCategoryListView.as_view(), + name='tag-category-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -26,4 +28,8 @@ urlpatterns = [ path('emails//', views.EmailRUDView.as_view(), name='emails-rud'), path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), + path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), + path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), + path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), + path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), ] \ No newline at end of file diff --git a/apps/establishment/urls/web.py b/apps/establishment/urls/web.py index b732d171..b4d1942d 100644 --- a/apps/establishment/urls/web.py +++ b/apps/establishment/urls/web.py @@ -4,4 +4,4 @@ from establishment.urls.common import urlpatterns as common_urlpatterns urlpatterns = [] -urlpatterns.extend(common_urlpatterns) \ No newline at end of file +urlpatterns.extend(common_urlpatterns) diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 5cba8255..64887fb3 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,9 +1,9 @@ """Establishment app views.""" - +from django.shortcuts import get_object_or_404 from rest_framework import generics -from establishment import models -from establishment import serializers +from establishment import models, serializers +from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer class EstablishmentMixinViews: @@ -25,6 +25,62 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.EstablishmentRUDSerializer +class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment schedule RUD view""" + serializer_class = ScheduleRUDSerializer + + def get_object(self): + """ + Returns the object the view is displaying. + """ + establishment_pk = self.kwargs['pk'] + schedule_id = self.kwargs['schedule_id'] + + establishment = get_object_or_404(klass=models.Establishment.objects.all(), + pk=establishment_pk) + schedule = get_object_or_404(klass=establishment.schedule, + id=schedule_id) + + # May raise a permission denied + self.check_object_permissions(self.request, establishment) + self.check_object_permissions(self.request, schedule) + + return schedule + + +class EstablishmentScheduleCreateView(generics.CreateAPIView): + """Establishment schedule Create view""" + serializer_class = ScheduleCreateSerializer + + +class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): + """View for establishment tag categories.""" + serializer_class = serializers.EstablishmentTagCategoryListSerializer + pagination_class = None + + def get_object(self): + """ + Returns the object the view is displaying. + """ + queryset = super(EstablishmentTagCategoryListView, self).get_queryset() + + # Perform the lookup filtering. + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + obj = get_object_or_404(queryset, **filter_kwargs) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + + return obj + + def get_queryset(self): + """Overridden get_queryset method.""" + establishment = self.get_object() + return establishment.establishment_type.tag_categories.all() + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -100,3 +156,35 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.EmployeeBackSerializers queryset = models.Employee.objects.all() + + +class EstablishmentTypeListCreateView(generics.ListCreateAPIView): + """Establishment type list/create view.""" + serializer_class = serializers.EstablishmentTypeSerializer + queryset = models.EstablishmentType.objects.all() + pagination_class = None + + +class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment type retrieve/update/destroy view.""" + serializer_class = serializers.EstablishmentTypeSerializer + queryset = models.EstablishmentType.objects.all() + + +class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): + """Establishment subtype list/create view.""" + serializer_class = serializers.EstablishmentSubTypeSerializer + queryset = models.EstablishmentSubType.objects.all() + pagination_class = None + + +class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment subtype retrieve/update/destroy view.""" + serializer_class = serializers.EstablishmentSubTypeSerializer + queryset = models.EstablishmentSubType.objects.all() + + +class EstablishmentTagListCreateView(generics.ListCreateAPIView): + """Establishment tag list/create view.""" + serializer_class = serializers.EstablishmentTagListSerializer + queryset = models.EstablishmentTag.objects.all() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8f5d2a26..d65ede11 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -9,7 +9,6 @@ from establishment import filters from establishment import models, serializers from main import methods from main.models import MetaDataContent -from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.pagination import EstablishmentPortionPagination @@ -19,9 +18,10 @@ class EstablishmentMixinView: permission_classes = (permissions.AllowAny,) def get_queryset(self): - """Overrided method 'get_queryset'.""" - return models.Establishment.objects.published().with_base_related().\ - annotate_in_favorites(self.request.user) + """Overridden method 'get_queryset'.""" + return models.Establishment.objects.published() \ + .with_base_related() \ + .annotate_in_favorites(self.request.user) class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): @@ -187,31 +187,3 @@ class EstablishmentTagListView(generics.ListAPIView): return MetaDataContent.objects.by_content_type(app_label='establishment', model='establishment')\ .distinct('metadata__label') - - -class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): - """Establishment schedule RUD view""" - serializer_class = ScheduleRUDSerializer - - def get_object(self): - """ - Returns the object the view is displaying. - """ - establishment_pk = self.kwargs['pk'] - schedule_id = self.kwargs['schedule_id'] - - establishment = get_object_or_404(klass=models.Establishment.objects.all(), - pk=establishment_pk) - schedule = get_object_or_404(klass=establishment.schedule, - id=schedule_id) - - # May raise a permission denied - self.check_object_permissions(self.request, establishment) - self.check_object_permissions(self.request, schedule) - - return schedule - - -class EstablishmentScheduleCreateView(generics.CreateAPIView): - """Establishment schedule Create view""" - serializer_class = ScheduleCreateSerializer diff --git a/apps/main/admin.py b/apps/main/admin.py index bdbfe46e..f14a3470 100644 --- a/apps/main/admin.py +++ b/apps/main/admin.py @@ -25,22 +25,6 @@ class AwardAdmin(admin.ModelAdmin): # list_display_links = ['id', '__str__'] -@admin.register(models.MetaData) -class MetaDataAdmin(admin.ModelAdmin): - """MetaData admin.""" - - -@admin.register(models.MetaDataCategory) -class MetaDataCategoryAdmin(admin.ModelAdmin): - """MetaData admin.""" - list_display = ['id', 'country', 'content_type'] - - -@admin.register(models.MetaDataContent) -class MetaDataContentAdmin(admin.ModelAdmin): - """MetaDataContent admin""" - - @admin.register(models.Currency) class CurrencContentAdmin(admin.ModelAdmin): """CurrencContent admin""" diff --git a/apps/main/models.py b/apps/main/models.py index fa6cf7d1..df04fd8a 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -353,7 +353,6 @@ class Carousel(models.Model): return self.content_object.establishment_type.name_translated - class Page(models.Model): """Page model.""" diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 2d43154e..3a06d4ed 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -30,17 +30,18 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES) }, multi=True) - tags = fields.ObjectField( - properties={ - 'id': fields.IntegerField(attr='metadata.id'), - 'label': fields.ObjectField(attr='metadata.label_indexing', - properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='metadata.category', - properties={ - 'id': fields.IntegerField(), - }) - }, - multi=True) + # todo: need to fix + # tags = fields.ObjectField( + # properties={ + # 'id': fields.IntegerField(attr='metadata.id'), + # 'label': fields.ObjectField(attr='metadata.label_indexing', + # properties=OBJECT_FIELD_PROPERTIES), + # 'category': fields.ObjectField(attr='metadata.category', + # properties={ + # 'id': fields.IntegerField(), + # }) + # }, + # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/__init__.py b/apps/tag/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/admin.py b/apps/tag/admin.py new file mode 100644 index 00000000..ea7f9394 --- /dev/null +++ b/apps/tag/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from .models import Tag, TagCategory + + +@admin.register(Tag) +class TagAdmin(admin.ModelAdmin): + """Admin model for model Tag.""" + + +@admin.register(TagCategory) +class TagCategoryAdmin(admin.ModelAdmin): + """Admin model for model TagCategory.""" diff --git a/apps/tag/apps.py b/apps/tag/apps.py new file mode 100644 index 00000000..a1cce249 --- /dev/null +++ b/apps/tag/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class TagConfig(AppConfig): + name = 'tag' + verbose_name = _('tag') diff --git a/apps/tag/migrations/0001_initial.py b/apps/tag/migrations/0001_initial.py new file mode 100644 index 00000000..1c146570 --- /dev/null +++ b/apps/tag/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.4 on 2019-10-08 07:47 + +from django.db import migrations, models +import django.db.models.deletion +import utils.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('location', '0010_auto_20190904_0711'), + ] + + operations = [ + migrations.CreateModel( + name='TagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='label')), + ('public', models.BooleanField(default=False)), + ('country', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country')), + ], + options={ + 'verbose_name': 'tag category', + 'verbose_name_plural': 'tag categories', + }, + bases=(utils.models.TranslatedFieldsMixin, models.Model), + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='label')), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.TagCategory', verbose_name='category')), + ], + options={ + 'verbose_name': 'tag', + 'verbose_name_plural': 'tags', + }, + bases=(utils.models.TranslatedFieldsMixin, models.Model), + ), + ] diff --git a/apps/tag/migrations/__init__.py b/apps/tag/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/models.py b/apps/tag/models.py new file mode 100644 index 00000000..c269d64e --- /dev/null +++ b/apps/tag/models.py @@ -0,0 +1,51 @@ +"""Tag app models.""" +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from configuration.models import TranslationSettings +from utils.models import TJSONField, TranslatedFieldsMixin + + +class Tag(TranslatedFieldsMixin, models.Model): + """Tag model.""" + label = TJSONField( + _('label'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + category = models.ForeignKey('TagCategory', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name='category') + + class Meta: + verbose_name = _('tag') + verbose_name_plural = _('tags') + + def __str__(self): + label = 'None' + lang = TranslationSettings.get_solo().default_language + if self.label and lang in self.label: + label = self.label[lang] + return f'id:{self.id}-{label}' + + +class TagCategory(TranslatedFieldsMixin, models.Model): + """Tag base category model.""" + label = TJSONField( + _('label'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + country = models.ForeignKey('location.Country', + on_delete=models.SET_NULL, null=True, + default=None) + + public = models.BooleanField(default=False) + + class Meta: + verbose_name = _('tag category') + verbose_name_plural = _('tag categories') + + def __str__(self): + label = 'None' + lang = TranslationSettings.get_solo().default_language + if self.label and lang in self.label: + label = self.label[lang] + return f'id:{self.id}-{label}' diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py new file mode 100644 index 00000000..cf783910 --- /dev/null +++ b/apps/tag/serializers.py @@ -0,0 +1,44 @@ +"""Tag serializers.""" +from rest_framework import serializers +from . import models +from utils.serializers import TranslatedField + + +class TagBaseSerializer(serializers.ModelSerializer): + """Serializer for model Tag.""" + label_translated = TranslatedField() + + class Meta: + model = models.Tag + fields = [ + 'id', + 'label', + 'label_translated', + 'category' + ] + extra_kwargs = { + 'label': {'write_only': True}, + 'category': {'write_only': True} + } + + +class TagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for model TagCategory.""" + label_translated = TranslatedField() + country_translated = TranslatedField(source='country.name_translated') + + class Meta: + """Meta class.""" + model = models.TagCategory + fields = [ + 'id', + 'label', + 'label_translated', + 'country', + 'country_translated', + 'public', + ] + extra_kwargs = { + 'label': {'write_only': True}, + 'country': {'write_only': True}, + } diff --git a/apps/tag/tests.py b/apps/tag/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/tag/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/tag/urls.py b/apps/tag/urls.py new file mode 100644 index 00000000..0d0b86d2 --- /dev/null +++ b/apps/tag/urls.py @@ -0,0 +1,10 @@ +"""Urlconf for app tag.""" +from django.urls import path +from . import views + +app_name = 'tag' + +urlpatterns = [ + path('', views.TagListCreateView.as_view(), name='list-create'), + path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), +] diff --git a/apps/tag/views.py b/apps/tag/views.py new file mode 100644 index 00000000..ab944ba7 --- /dev/null +++ b/apps/tag/views.py @@ -0,0 +1,18 @@ +"""Tag views.""" +from rest_framework import generics + +from . import serializers, models + + +class TagListCreateView(generics.ListCreateAPIView): + """List/create tag view.""" + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBaseSerializer + pagination_class = None + + +class TagCategoryListCreateView(generics.ListCreateAPIView): + """List/create tag category view.""" + queryset = models.TagCategory.objects.all() + serializer_class = serializers.TagCategoryBaseSerializer + pagination_class = None diff --git a/project/settings/base.py b/project/settings/base.py index cec6a4d1..8f02f52d 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -72,6 +72,7 @@ PROJECT_APPS = [ 'comment.apps.CommentConfig', 'favorites.apps.FavoritesConfig', 'rating.apps.RatingConfig', + 'tag.apps.TagConfig', ] EXTERNAL_APPS = [ diff --git a/project/urls/back.py b/project/urls/back.py index 7b4146eb..2a9657db 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,5 +7,7 @@ urlpatterns = [ namespace='gallery')), path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), - path('news/', include('news.urls.back')) -] \ No newline at end of file + path('news/', include('news.urls.back')), + path('tags/', include(('tag.urls', 'tag'), + namespace='tag')) +] From 69d02e7a07eb573d03f6dea0210379677ac2067a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 10:05:19 +0300 Subject: [PATCH 215/319] fix auth --- apps/utils/authentication.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/utils/authentication.py b/apps/utils/authentication.py index 044d6d75..e8375ffe 100644 --- a/apps/utils/authentication.py +++ b/apps/utils/authentication.py @@ -23,14 +23,24 @@ class GMJWTAuthentication(JWTAuthentication): """ def authenticate(self, request): - token = get_token_from_cookies(request) - if token is None: + try: + token = get_token_from_cookies(request) + # Return non-authorized user if token not in cookies + assert token + + raw_token = self.get_raw_token(token) + # Return non-authorized user if cant get raw token + assert raw_token + + validated_token = self.get_validated_token(raw_token) + user = self.get_user(validated_token) + + # Check record in DB + token_is_valid = user.access_tokens.valid() \ + .by_jti(jti=validated_token.payload.get('jti')) + assert token_is_valid.exists() + except: + # Return non-authorized user if token is invalid or raised an error when run checks. return None - - raw_token = self.get_raw_token(token) - if raw_token is None: - return None - - validated_token = self.get_validated_token(raw_token) - - return self.get_user(validated_token), None + else: + return user, None From b57303898082028e32346b20669dc5d958db0930 Mon Sep 17 00:00:00 2001 From: "e.stoyushko" Date: Wed, 9 Oct 2019 09:46:23 +0000 Subject: [PATCH 216/319] Update tasks.py --- apps/news/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index c2b63b48..7ff4d504 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -4,10 +4,9 @@ from celery import shared_task from django.core.mail import send_mail from notification.models import Subscriber from news import models -from django.template.loader import render_to_string +from django.template.loader import render_to_string, get_template from django.conf import settings from smtplib import SMTPException -from django.template.loader import get_template from django.core.validators import EMPTY_VALUES from main.models import SiteSettings From ca5f588466602381dc5a8bf0e0a8ff85f523c567 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 12:53:35 +0300 Subject: [PATCH 217/319] fix elastic document, fix prefetch and select related --- apps/authorization/tasks.py | 4 +-- ...ishmenttag_establishmenttypetagcategory.py | 28 +++++++--------- .../migrations/0033_auto_20191009_0715.py | 30 +++++++++++++++++ apps/establishment/models.py | 22 ++++++------- apps/establishment/serializers/back.py | 1 + .../search_indexes/documents/establishment.py | 32 +++++++++++-------- apps/tag/migrations/0001_initial.py | 2 +- project/settings/base.py | 6 ++-- 8 files changed, 78 insertions(+), 47 deletions(-) create mode 100644 apps/establishment/migrations/0033_auto_20191009_0715.py diff --git a/apps/authorization/tasks.py b/apps/authorization/tasks.py index c97fbbae..cb186142 100644 --- a/apps/authorization/tasks.py +++ b/apps/authorization/tasks.py @@ -1,10 +1,10 @@ """Authorization app celery tasks.""" import logging -from django.utils.translation import gettext_lazy as _ + from celery import shared_task +from django.utils.translation import gettext_lazy as _ from account import models as account_models -from smtplib import SMTPException logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py index 6efaa57a..ec9966d8 100644 --- a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py +++ b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:47 +# Generated by Django 2.2.4 on 2019-10-09 07:15 from django.db import migrations, models import django.db.models.deletion @@ -7,33 +7,29 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('tag', '0001_initial'), ('establishment', '0031_establishment_slug'), ] operations = [ - migrations.CreateModel( - name='EstablishmentTypeTagCategory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('establishment_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), - ('tag_category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category')), - ], - options={ - 'verbose_name': 'establishment type tag categories', - 'verbose_name_plural': 'establishment type tag categories', - }, - ), migrations.CreateModel( name='EstablishmentTag', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='establishment.Establishment', verbose_name='establishment')), - ('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.Tag', verbose_name='tag')), ], options={ 'verbose_name': 'establishment tag', 'verbose_name_plural': 'establishment tags', }, ), + migrations.CreateModel( + name='EstablishmentTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), + ], + options={ + 'verbose_name': 'establishment type tag categories', + 'verbose_name_plural': 'establishment type tag categories', + }, + ), ] diff --git a/apps/establishment/migrations/0033_auto_20191009_0715.py b/apps/establishment/migrations/0033_auto_20191009_0715.py new file mode 100644 index 00000000..5df367d6 --- /dev/null +++ b/apps/establishment/migrations/0033_auto_20191009_0715.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.4 on 2019-10-09 07:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ('establishment', '0032_establishmenttag_establishmenttypetagcategory'), + ] + + operations = [ + migrations.AddField( + model_name='establishmenttypetagcategory', + name='tag_category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category'), + ), + migrations.AddField( + model_name='establishmenttag', + name='establishment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='establishment.Establishment', verbose_name='establishment'), + ), + migrations.AddField( + model_name='establishmenttag', + name='tag', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.Tag', verbose_name='tag'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9ce4eebc..30ca88bd 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -15,6 +15,7 @@ from elasticsearch_dsl import Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection +from tag.models import Tag, TagCategory from main.models import Award, MetaDataContent from location.models import Address from review.models import Review @@ -76,13 +77,12 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address') - # todo: fix this - # return self.select_related('address').prefetch_related( - # models.Prefetch('tags', - # MetaDataContent.objects.select_related( - # 'metadata__category')) - # ) + return self.select_related('address', 'establishment_type').prefetch_related( + models.Prefetch('tags', + EstablishmentTag.objects.select_related('tag')), + models.Prefetch('establishment_type__tag_categories', + EstablishmentTypeTagCategory.objects.select_related('tag_category')), + ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -561,11 +561,11 @@ class EstablishmentTagQuerySet(models.QuerySet): class EstablishmentTag(models.Model): """Establishment tag model.""" tag = models.ForeignKey('tag.Tag', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tags', verbose_name=_('tag')) establishment = models.ForeignKey('establishment.Establishment', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tags', verbose_name=_('establishment')) objects = EstablishmentTagQuerySet.as_manager() @@ -586,11 +586,11 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): class EstablishmentTypeTagCategory(models.Model): """Tag categories based on establishment type.""" establishment_type = models.ForeignKey(EstablishmentType, - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tag_categories', verbose_name=_('establishment type')) tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='est_type_tag_categories', verbose_name=_('tag category')) objects = EstablishmentTypeTagCategoryQuerySet.as_manager() diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index b15ff51f..766806ad 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -78,6 +78,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id') label_translated = TranslatedField(source='tag_category.label_translated') tags = TagBaseSerializer(source='tag_category.tags', many=True) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 3a06d4ed..e2147fbc 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -21,27 +21,31 @@ class EstablishmentDocument(Document): properties={ 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', - properties=OBJECT_FIELD_PROPERTIES) + properties=OBJECT_FIELD_PROPERTIES), + 'tag_categories': fields.ObjectField(properties={ + 'tag_category': fields.ObjectField( + properties={ + 'id': fields.IntegerField() + } + ) + }), }) establishment_subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', - properties=OBJECT_FIELD_PROPERTIES) + properties={ + 'id': fields.IntegerField(), + }), + }, + multi=True) + tags = fields.ObjectField( + properties={ + 'tag': fields.ObjectField(properties={ + 'id': fields.IntegerField(), + }), }, multi=True) - # todo: need to fix - # tags = fields.ObjectField( - # properties={ - # 'id': fields.IntegerField(attr='metadata.id'), - # 'label': fields.ObjectField(attr='metadata.label_indexing', - # properties=OBJECT_FIELD_PROPERTIES), - # 'category': fields.ObjectField(attr='metadata.category', - # properties={ - # 'id': fields.IntegerField(), - # }) - # }, - # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/migrations/0001_initial.py b/apps/tag/migrations/0001_initial.py index 1c146570..543eb035 100644 --- a/apps/tag/migrations/0001_initial.py +++ b/apps/tag/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:47 +# Generated by Django 2.2.4 on 2019-10-09 07:15 from django.db import migrations, models import django.db.models.deletion diff --git a/project/settings/base.py b/project/settings/base.py index 8f02f52d..f04788d1 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -282,9 +282,9 @@ SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.yandex.ru' -EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' -EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' +EMAIL_HOST = 'smtp.mandrillapp.com' +EMAIL_HOST_USER = 'bbody@gaultmillau.fr' +EMAIL_HOST_PASSWORD = 'FQghjXmS1FmKmlZEpSg6TA' EMAIL_PORT = 587 # Django Rest Swagger From 2191a505a59b6f6202dfcb8bcb03786885e45826 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 14:11:53 +0300 Subject: [PATCH 218/319] refactored EstablishmentBase serializer --- apps/establishment/serializers/back.py | 18 ----------- apps/establishment/serializers/common.py | 38 +++++++++++++++++------- apps/establishment/urls/common.py | 1 - apps/establishment/views/back.py | 9 +++--- apps/establishment/views/web.py | 13 -------- 5 files changed, 33 insertions(+), 46 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 766806ad..d0c70b2f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,5 +1,4 @@ from rest_framework import serializers -from tag.serializers import TagBaseSerializer from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, @@ -7,7 +6,6 @@ from establishment.serializers import ( EstablishmentTypeSerializer) from utils.decorators import with_base_attributes -from utils.serializers import TranslatedField from main.models import Currency @@ -76,22 +74,6 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): ] -class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id') - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - ] - - class SocialNetworkSerializers(serializers.ModelSerializer): """Social network serializers.""" class Meta: diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 6c12360b..2cd4e429 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -1,17 +1,20 @@ """Establishment serializers.""" from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers + from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites from location.serializers import AddressBaseSerializer from main.serializers import AwardSerializer, CurrencySerializer -from tag import models as tag_models from review import models as review_models +from tag import models as tag_models +from tag.serializers import TagBaseSerializer from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions -from utils.serializers import TranslatedField, ProjectModelSerializer +from utils.serializers import ProjectModelSerializer +from utils.serializers import TranslatedField class ContactPhonesSerializer(serializers.ModelSerializer): @@ -86,6 +89,22 @@ class MenuRUDSerializers(ProjectModelSerializer): ] +class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id') + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + ] + + class EstablishmentTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentType model.""" @@ -158,18 +177,17 @@ class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): ] -class EstablishmentTagListSerializer(serializers.ModelSerializer): - """Serializer for establishment tags.""" - label_translated = TranslatedField(source='tag.label_translated') - category = EstablishmentCategoryTagListSerializer(source='tag.category') +class EstablishmentTagSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTag.""" + id = serializers.IntegerField(source='tag.id') + label_translated = serializers.CharField(source='tag.label_translated') class Meta: """Meta class.""" - model = tag_models.Tag + model = models.EstablishmentTag fields = [ 'id', - 'label_translated', - 'category', + 'label_translated' ] @@ -180,7 +198,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) - tags = EstablishmentTagListSerializer(many=True) + tags = EstablishmentTagSerializer(many=True) class Meta: """Meta class.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 1e9225d6..5d7df146 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,6 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 64887fb3..32ee2805 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -184,7 +184,8 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentSubType.objects.all() -class EstablishmentTagListCreateView(generics.ListCreateAPIView): - """Establishment tag list/create view.""" - serializer_class = serializers.EstablishmentTagListSerializer - queryset = models.EstablishmentTag.objects.all() +# todo: next task +# class EstablishmentTagListCreateView(generics.CreateAPIView): +# """Establishment tag list/create view.""" +# serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer +# queryset = models.EstablishmentTag.objects.all() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index d65ede11..b4ef4f13 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -174,16 +174,3 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() if v is not None}) return qs - - -class EstablishmentTagListView(generics.ListAPIView): - """List view for establishment tags.""" - serializer_class = serializers.EstablishmentTagListSerializer - permission_classes = (permissions.AllowAny,) - pagination_class = None - - def get_queryset(self): - """Override get_queryset method""" - return MetaDataContent.objects.by_content_type(app_label='establishment', - model='establishment')\ - .distinct('metadata__label') From 0cfc48e537aa44529c6737c8a3ab3728d815a600 Mon Sep 17 00:00:00 2001 From: "e.stoyushko" Date: Wed, 9 Oct 2019 11:34:14 +0000 Subject: [PATCH 219/319] Update admin.py --- apps/booking/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/booking/admin.py b/apps/booking/admin.py index 59e56238..3eb9d180 100644 --- a/apps/booking/admin.py +++ b/apps/booking/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import models +from booking.models import models @admin.register(models.Booking) From f3eda60868364a723ae88512b449aa0539b053d2 Mon Sep 17 00:00:00 2001 From: "e.stoyushko" Date: Wed, 9 Oct 2019 11:43:09 +0000 Subject: [PATCH 220/319] Update urls.py --- apps/booking/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/booking/urls.py b/apps/booking/urls.py index a86178ff..900ec8cb 100644 --- a/apps/booking/urls.py +++ b/apps/booking/urls.py @@ -7,8 +7,8 @@ app = 'booking' urlpatterns = [ path('/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'), path('/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'), - path('', views.UpdatePendingBooking.as_view(), name='update-pending-booking'), - path('/cancel', views.CancelBooking.as_view(), name='cancel-existing-booking'), + path('/', views.UpdatePendingBooking.as_view(), name='update-pending-booking'), + path('/cancel/', views.CancelBooking.as_view(), name='cancel-existing-booking'), path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'), - path('retrieve/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'), + path('retrieve//', views.GetBookingById.as_view(), name='retrieves-booking-by-id'), ] From 48ca13803eb8a6398519c7ca597ea2ea0ffc94ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 9 Oct 2019 14:44:01 +0300 Subject: [PATCH 221/319] Pre test permission comment --- apps/account/serializers/back.py | 21 ++++++++ apps/account/tests/tests_back.py | 86 ++++++++++++++++++++++++++++++++ apps/account/urls/back.py | 12 +++++ apps/account/views/back.py | 13 +++++ apps/comment/permissions.py | 28 +++++++++++ apps/comment/serializers/back.py | 4 +- apps/comment/tests.py | 58 ++++++++++++++++++++- apps/comment/urls/back.py | 14 +++--- apps/comment/views/back.py | 16 ++++++ project/urls/back.py | 4 +- 10 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 apps/account/serializers/back.py create mode 100644 apps/account/tests/tests_back.py create mode 100644 apps/account/urls/back.py create mode 100644 apps/account/views/back.py create mode 100644 apps/comment/permissions.py diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py new file mode 100644 index 00000000..c1a1c6d4 --- /dev/null +++ b/apps/account/serializers/back.py @@ -0,0 +1,21 @@ +"""Back account serializers""" +from rest_framework import serializers +from account import models + + +class RoleSerializer(serializers.ModelSerializer): + class Meta: + model = models.Role + fields = [ + 'role', + 'country' + ] + + +class UserRoleSerializer(serializers.ModelSerializer): + class Meta: + model = models.UserRole + fields = [ + 'user', + 'role' + ] \ No newline at end of file diff --git a/apps/account/tests/tests_back.py b/apps/account/tests/tests_back.py new file mode 100644 index 00000000..56c0cd3a --- /dev/null +++ b/apps/account/tests/tests_back.py @@ -0,0 +1,86 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from http.cookies import SimpleCookie +from location.models import Country +from account.models import Role, User, UserRole + +class RoleTests(APITestCase): + def setUp(self): + self.data = get_tokens_for_user() + self.client.cookies = SimpleCookie( + {'access_token': self.data['tokens'].get('access_token'), + 'refresh_token': self.data['tokens'].get('access_token')}) + + def test_role_get(self): + url = reverse('back:account:role-list-create') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_role_post(self): + url = reverse('back:account:role-list-create') + country = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000 + ) + country.save() + + data = { + "role": 2, + "country": country.pk + } + response = self.client.post(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + +class UserRoleTests(APITestCase): + def setUp(self): + self.data = get_tokens_for_user() + self.client.cookies = SimpleCookie( + {'access_token': self.data['tokens'].get('access_token'), + 'refresh_token': self.data['tokens'].get('access_token')}) + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000 + ) + self.country_ru.save() + + self.country_en = Country.objects.create( + name='{"en-GB":"England"}', + code='25', + low_price=15, + high_price=150000 + ) + self.country_en.save() + + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.user_test = User.objects.create_user(username='test', + email='testemail@mail.com', + password='passwordtest') + + def test_user_role_post(self): + url = reverse('back:account:user-role-list-create') + # userRole = UserRole.objects.create( + # user=self.user_test, + # role=self.role + # ) + # userRole.save() + + data = { + "user": self.user_test.id, + "role": self.role.id + } + + response = self.client.post(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py new file mode 100644 index 00000000..ee2e4148 --- /dev/null +++ b/apps/account/urls/back.py @@ -0,0 +1,12 @@ +"""Back account URLs""" +from django.urls import path + +from account.views import back as views + +app_name = 'account' + +urlpatterns = [ + path('role/', views.RoleLstView.as_view(), name='role-list-create'), + path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), + +] diff --git a/apps/account/views/back.py b/apps/account/views/back.py new file mode 100644 index 00000000..8799f915 --- /dev/null +++ b/apps/account/views/back.py @@ -0,0 +1,13 @@ +from rest_framework import generics +from account.serializers import back as serializers +from account import models + + +class RoleLstView(generics.ListCreateAPIView): + serializer_class = serializers.RoleSerializer + queryset = models.Role.objects.all() + + +class UserRoleLstView(generics.ListCreateAPIView): + serializer_class = serializers.UserRoleSerializer + queryset = models.Role.objects.all() \ No newline at end of file diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py new file mode 100644 index 00000000..aa57eaca --- /dev/null +++ b/apps/comment/permissions.py @@ -0,0 +1,28 @@ +from rest_framework import permissions +from account.models import UserRole, Role, User + + +class IsCommentModerator(permissions.BasePermission): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Instance must have an attribute named `user`. + role = Role.objects.get(role=2) # 'Comments moderator' + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user == request.user and is_access: + return True + + # User is super-user? + if User.objects.filter(pk=request.user.pk).exists(): + return True + + return False + diff --git a/apps/comment/serializers/back.py b/apps/comment/serializers/back.py index a491168d..d0cd47c8 100644 --- a/apps/comment/serializers/back.py +++ b/apps/comment/serializers/back.py @@ -1,11 +1,9 @@ """Comment app common serializers.""" from comment import models from rest_framework import serializers -from utils.serializers import ProjectModelSerializer -class CommentBaseSerializer(ProjectModelSerializer): - +class CommentBaseSerializer(serializers.ModelSerializer): class Meta: model = models.Comment fields = ('id', 'text', 'mark', 'user') \ No newline at end of file diff --git a/apps/comment/tests.py b/apps/comment/tests.py index a39b155a..09287225 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1 +1,57 @@ -# Create your tests here. +from rest_framework.test import APITestCase +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from http.cookies import SimpleCookie +from location.models import Country +from account.models import Role, User, UserRole + + +class CommentModeratorPermissionTests(APITestCase): + def setUp(self): + self.data = get_tokens_for_user() + + self.client.cookies = SimpleCookie( + {'access_token': self.data['tokens'].get('access_token'), + 'refresh_token': self.data['tokens'].get('access_token')}) + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000 + ) + self.country_ru.save() + + self.country_en = Country.objects.create( + name='{"en-GB":"England"}', + code='25', + low_price=15, + high_price=150000 + ) + self.country_en.save() + + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.moderator = User.objects.create_user(username='moderator', + email='moderator@mail.com', + password='passwordmoderator') + + self.userRole = UserRole.objects.create( + user=self.moderator, + role=self.role + ) + self.userRole.save() + + tokens = User.create_jwt_tokens(self.moderator) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + def test_permission(self): + self.assertTrue(True) \ No newline at end of file diff --git a/apps/comment/urls/back.py b/apps/comment/urls/back.py index 6141ceed..a1f2e010 100644 --- a/apps/comment/urls/back.py +++ b/apps/comment/urls/back.py @@ -1,9 +1,11 @@ -"""Web urlpaths.""" -from comment.urls.common import urlpatterns as common_urlpatterns +"""Back comment URLs""" +from django.urls import path + +from comment.views import back as views app_name = 'comment' -urlpatterns_api = [] - -urlpatterns = common_urlpatterns + \ - urlpatterns_api +urlpatterns = [ + path('', views.CommentLstView.as_view(), name='comment-list-create'), + path('/', views.CommentRUDView.as_view(), name='comment-crud'), +] diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index e69de29b..1420ebc2 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -0,0 +1,16 @@ +from rest_framework import generics, permissions +from comment.serializers import back as serializers +from comment import models +from comment.permissions import IsCommentModerator + + +class CommentLstView(generics.ListCreateAPIView): + serializer_class = serializers.CommentBaseSerializer + queryset = models.Comment.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly,] + + +class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): + serializer_class = serializers.CommentBaseSerializer + queryset = models.Comment.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsCommentModerator] \ No newline at end of file diff --git a/project/urls/back.py b/project/urls/back.py index 7b4146eb..59758c66 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,5 +7,7 @@ urlpatterns = [ namespace='gallery')), path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), - path('news/', include('news.urls.back')) + path('news/', include('news.urls.back')), + path('account/', include('account.urls.back')), + path('comment/', include('comment.urls.back')), ] \ No newline at end of file From 31f8b5abd12ad2a6b195cc1c1ed21a97eb7168e7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:19:46 +0300 Subject: [PATCH 222/319] refactored base settings --- 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 cec6a4d1..2a6f4aea 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -275,15 +275,15 @@ SMS_CODE_SHOW = False # SMSC Settings SMS_SERVICE = 'http://smsc.ru/sys/send.php' -SMS_LOGIN = 'GM2019' -SMS_PASSWORD = '}#6%Qe7CYG7n' +SMS_LOGIN = os.environ.get('SMS_LOGIN') +SMS_PASSWORD = os.environ.get('SMS_PASSWORD') SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.yandex.ru' -EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' -EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' +EMAIL_HOST = 'smtp.mandrillapp.com' +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_PORT = 587 # Django Rest Swagger From e9c4d62b582349a09f4e17e80f33ae241f088abc Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 9 Oct 2019 15:26:31 +0300 Subject: [PATCH 223/319] remove bin/manage --- bin/manage | 3 --- 1 file changed, 3 deletions(-) delete mode 100755 bin/manage diff --git a/bin/manage b/bin/manage deleted file mode 100755 index 62d0ec94..00000000 --- a/bin/manage +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker-compose run --rm gm_app python manage.py "$@" \ No newline at end of file From f5a45fccf1d726dd0d1c69ae65d23a0fc04b4cd6 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 9 Oct 2019 15:31:58 +0300 Subject: [PATCH 224/319] Fix migration --- apps/booking/migrations/0002_auto_20191003_1601.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/booking/migrations/0002_auto_20191003_1601.py b/apps/booking/migrations/0002_auto_20191003_1601.py index cf868196..7f3814cd 100644 --- a/apps/booking/migrations/0002_auto_20191003_1601.py +++ b/apps/booking/migrations/0002_auto_20191003_1601.py @@ -10,7 +10,11 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( + migrations.RemoveField( + model_name='booking', + name='restaurant_id', + ), + migrations.AddField( model_name='booking', name='restaurant_id', field=models.TextField(default=None, verbose_name='booking service establishment id'), From 40570a07463fdba1785c72036f4aee568374af24 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:36:43 +0300 Subject: [PATCH 225/319] Revert "remove duplicate migration" This reverts commit 93894db7 --- .../migrations/0003_auto_20191002_0729.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py new file mode 100644 index 00000000..16196f74 --- /dev/null +++ b/apps/timetable/migrations/0003_auto_20191002_0729.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-02 07:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0002_auto_20190919_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='timetable', + options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, + ), + ] From a77b7f9c10651bdd63065d6846711df789fd42b7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:42:46 +0300 Subject: [PATCH 226/319] Revert "remove duplicate migration" This reverts commit 40570a07 --- .../migrations/0003_auto_20191002_0729.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py deleted file mode 100644 index 16196f74..00000000 --- a/apps/timetable/migrations/0003_auto_20191002_0729.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-02 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] From 7ed976dec0a906b604abaaebcb620ccb93968389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 9 Oct 2019 16:28:14 +0300 Subject: [PATCH 227/319] Test --- apps/account/tests/tests_back.py | 5 -- apps/comment/permissions.py | 9 ++-- apps/comment/tests.py | 83 +++++++++++++++++++++++++++++--- apps/comment/urls/back.py | 2 +- apps/comment/views/back.py | 3 +- 5 files changed, 85 insertions(+), 17 deletions(-) diff --git a/apps/account/tests/tests_back.py b/apps/account/tests/tests_back.py index 56c0cd3a..8adc6b35 100644 --- a/apps/account/tests/tests_back.py +++ b/apps/account/tests/tests_back.py @@ -71,11 +71,6 @@ class UserRoleTests(APITestCase): def test_user_role_post(self): url = reverse('back:account:user-role-list-create') - # userRole = UserRole.objects.create( - # user=self.user_test, - # role=self.role - # ) - # userRole.save() data = { "user": self.user_test.id, diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py index aa57eaca..09860c2c 100644 --- a/apps/comment/permissions.py +++ b/apps/comment/permissions.py @@ -14,14 +14,17 @@ class IsCommentModerator(permissions.BasePermission): if request.method in permissions.SAFE_METHODS: return True + if obj.user == request.user: + return True + # Instance must have an attribute named `user`. role = Role.objects.get(role=2) # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() - if obj.user == request.user and is_access: + if obj.user != request.user and is_access: return True - # User is super-user? - if User.objects.filter(pk=request.user.pk).exists(): + super_user=User.objects.filter(pk=request.user.pk, is_superuser=True).exists() + if super_user: return True return False diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 09287225..0b053cb8 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -2,18 +2,15 @@ from rest_framework.test import APITestCase from rest_framework import status from authorization.tests.tests_authorization import get_tokens_for_user from django.urls import reverse +from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie from location.models import Country from account.models import Role, User, UserRole +from comment.models import Comment class CommentModeratorPermissionTests(APITestCase): def setUp(self): - self.data = get_tokens_for_user() - - self.client.cookies = SimpleCookie( - {'access_token': self.data['tokens'].get('access_token'), - 'refresh_token': self.data['tokens'].get('access_token')}) self.country_ru = Country.objects.create( name='{"ru-RU":"Russia"}', @@ -47,11 +44,83 @@ class CommentModeratorPermissionTests(APITestCase): ) self.userRole.save() + content_type = ContentType.objects.get(app_label='location', model='country') + + self.user_test = get_tokens_for_user() + self.comment = Comment.objects.create(text='Test comment', mark=1, + user=self.user_test["user"], + object_id= self.country_ru.pk, + content_type_id=content_type.id + ) + self.comment.save() + + def test_get(self): + url = reverse('back:comment:comment-crud', kwargs={"id": 1}) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_moderator(self): + url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + tokens = User.create_jwt_tokens(self.moderator) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": self.moderator.id + } + + response = self.client.put(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_other_user(self): + url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + other_user = User.objects.create_user(username='test', + email='test@mail.com', + password='passwordtest') + + tokens = User.create_jwt_tokens(other_user) self.client.cookies = SimpleCookie( {'access_token': tokens.get('access_token'), 'refresh_token': tokens.get('access_token')}) - def test_permission(self): - self.assertTrue(True) \ No newline at end of file + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": other_user.id + } + + response = self.client.put(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_put_super_user(self): + url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + super_user = User.objects.create_user(username='super', + email='super@mail.com', + password='passwordtestsuper', + is_superuser=True) + + tokens = User.create_jwt_tokens(super_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": super_user.id + } + + response = self.client.put(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + diff --git a/apps/comment/urls/back.py b/apps/comment/urls/back.py index a1f2e010..214eab48 100644 --- a/apps/comment/urls/back.py +++ b/apps/comment/urls/back.py @@ -7,5 +7,5 @@ app_name = 'comment' urlpatterns = [ path('', views.CommentLstView.as_view(), name='comment-list-create'), - path('/', views.CommentRUDView.as_view(), name='comment-crud'), + path('/', views.CommentRUDView.as_view(), name='comment-crud'), ] diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 1420ebc2..16450d03 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -13,4 +13,5 @@ class CommentLstView(generics.ListCreateAPIView): class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsCommentModerator] \ No newline at end of file + permission_classes = [permissions.IsAuthenticatedOrReadOnly,IsCommentModerator] + lookup_field = 'id' From 01dd7283234f719170f9ef2e5f28fe1e9ed26034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 9 Oct 2019 16:44:29 +0300 Subject: [PATCH 228/319] Refactor --- apps/comment/tests.py | 22 +++++-------------- .../migrations/0004_merge_20191009_1341.py | 14 ++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 apps/timetable/migrations/0004_merge_20191009_1341.py diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 0b053cb8..2157933e 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -20,14 +20,6 @@ class CommentModeratorPermissionTests(APITestCase): ) self.country_ru.save() - self.country_en = Country.objects.create( - name='{"en-GB":"England"}', - code='25', - low_price=15, - high_price=150000 - ) - self.country_en.save() - self.role = Role.objects.create( role=2, country=self.country_ru @@ -53,15 +45,13 @@ class CommentModeratorPermissionTests(APITestCase): content_type_id=content_type.id ) self.comment.save() + self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) def test_get(self): - url = reverse('back:comment:comment-crud', kwargs={"id": 1}) - response = self.client.get(url, format='json') + response = self.client.get(self.url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_put_moderator(self): - url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - tokens = User.create_jwt_tokens(self.moderator) self.client.cookies = SimpleCookie( {'access_token': tokens.get('access_token'), @@ -74,11 +64,10 @@ class CommentModeratorPermissionTests(APITestCase): "user": self.moderator.id } - response = self.client.put(url, data=data, format='json') + response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_put_other_user(self): - url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) other_user = User.objects.create_user(username='test', email='test@mail.com', password='passwordtest') @@ -96,11 +85,10 @@ class CommentModeratorPermissionTests(APITestCase): "user": other_user.id } - response = self.client.put(url, data=data, format='json') + response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_put_super_user(self): - url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) super_user = User.objects.create_user(username='super', email='super@mail.com', password='passwordtestsuper', @@ -119,7 +107,7 @@ class CommentModeratorPermissionTests(APITestCase): "user": super_user.id } - response = self.client.put(url, data=data, format='json') + response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/timetable/migrations/0004_merge_20191009_1341.py b/apps/timetable/migrations/0004_merge_20191009_1341.py new file mode 100644 index 00000000..22d83ca3 --- /dev/null +++ b/apps/timetable/migrations/0004_merge_20191009_1341.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-09 13:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0003_auto_20191004_0928'), + ('timetable', '0003_auto_20191003_0943'), + ] + + operations = [ + ] From 365f39ce651f61e3c79ddf936d9a405fd22d05ad Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 9 Oct 2019 17:23:49 +0300 Subject: [PATCH 229/319] News email detail link fix --- project/templates/news/news_email.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index b3782f5f..d14bd898 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -36,7 +36,7 @@
{{ description | safe }}
- +
From 074a5ec5ea7be3450ec89b79c7b278c8a6a09464 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 9 Oct 2019 18:25:40 +0300 Subject: [PATCH 230/319] refactor news tags --- .../migrations/0021_auto_20191009_1408.py | 24 ++++++++++ apps/news/models.py | 7 +-- apps/news/serializers.py | 4 +- apps/news/urls/web.py | 2 + apps/news/views.py | 16 +++++++ .../tag/migrations/0002_auto_20191009_1408.py | 27 +++++++++++ apps/tag/models.py | 47 ++++++++++++------- apps/tag/serializers.py | 21 +++++++-- apps/tag/views.py | 5 +- project/urls/back.py | 6 +-- project/urls/web.py | 3 +- 11 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 apps/news/migrations/0021_auto_20191009_1408.py create mode 100644 apps/tag/migrations/0002_auto_20191009_1408.py diff --git a/apps/news/migrations/0021_auto_20191009_1408.py b/apps/news/migrations/0021_auto_20191009_1408.py new file mode 100644 index 00000000..81a4d7fa --- /dev/null +++ b/apps/news/migrations/0021_auto_20191009_1408.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('news', '0020_remove_news_author'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='tags', + field=models.ManyToManyField(related_name='news', to='tag.Tag', verbose_name='Tags'), + ), + migrations.AddField( + model_name='newstype', + name='tag_categories', + field=models.ManyToManyField(related_name='news_types', to='tag.TagCategory'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 6e91b912..8a6a89f4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -12,6 +12,8 @@ class NewsType(models.Model): """NewsType model.""" name = models.CharField(_('name'), max_length=250) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='news_types') class Meta: """Meta class.""" @@ -133,8 +135,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): country = models.ForeignKey('location.Country', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('country')) - tags = generic.GenericRelation(to='main.MetaDataContent') - + tags = models.ManyToManyField('tag.Tag', related_name='news', + verbose_name=_('Tags')) ratings = generic.GenericRelation(Rating) objects = NewsQuerySet.as_manager() @@ -163,4 +165,3 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def same_theme(self): return self.__class__.objects.same_theme(self)[:3] - diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c473be1d..6f0b73b6 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -3,8 +3,8 @@ from rest_framework import serializers from account.serializers.common import UserBaseSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer -from main.serializers import MetaDataContentSerializer from news import models +from tag.serializers import TagBaseSerializer from utils.serializers import TranslatedField, ProjectModelSerializer @@ -27,7 +27,7 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) - tags = MetaDataContentSerializer(read_only=True, many=True) + tags = TagBaseSerializer(read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 80fcf072..0671f5f6 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -7,5 +7,7 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), path('types/', views.NewsTypeListView.as_view(), name='type'), + path('types//tags/', views.NewsTypeTagsView.as_view(), + name='type-tags'), path('slug//', views.NewsDetailView.as_view(), name='rud'), ] diff --git a/apps/news/views.py b/apps/news/views.py index 61a57251..1a845a7b 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,7 +1,10 @@ """News app views.""" +from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating +from tag.serializers import TagCategoryDetailSerializer + class NewsMixinView: """News mixin.""" @@ -34,6 +37,7 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() + class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" @@ -43,6 +47,18 @@ class NewsTypeListView(generics.ListAPIView): serializer_class = serializers.NewsTypeSerializer +class NewsTypeTagsView(generics.ListAPIView): + """Resource to get a list of tags for a news type.""" + + pagination_class = None + permission_classes = (permissions.AllowAny, ) + serializer_class = TagCategoryDetailSerializer + + def get_queryset(self): + news_type = get_object_or_404(models.NewsType, pk=self.kwargs.get('pk')) + return news_type.tag_categories.with_related() + + class NewsBackOfficeMixinView: """News back office mixin view.""" diff --git a/apps/tag/migrations/0002_auto_20191009_1408.py b/apps/tag/migrations/0002_auto_20191009_1408.py new file mode 100644 index 00000000..472d9596 --- /dev/null +++ b/apps/tag/migrations/0002_auto_20191009_1408.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='tag', + options={'verbose_name': 'Tag', 'verbose_name_plural': 'Tags'}, + ), + migrations.AlterModelOptions( + name='tagcategory', + options={'verbose_name': 'Tag category', 'verbose_name_plural': 'Tag categories'}, + ), + migrations.AlterField( + model_name='tag', + name='category', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tags', to='tag.TagCategory', verbose_name='Category'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index c269d64e..b552bbcb 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -1,24 +1,25 @@ """Tag app models.""" from django.db import models from django.utils.translation import gettext_lazy as _ - from configuration.models import TranslationSettings from utils.models import TJSONField, TranslatedFieldsMixin class Tag(TranslatedFieldsMixin, models.Model): """Tag model.""" - label = TJSONField( - _('label'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') - category = models.ForeignKey('TagCategory', - on_delete=models.SET_NULL, null=True, - related_name='tags', - verbose_name='category') + + label = TJSONField(blank=True, null=True, default=None, + verbose_name=_('label'), + help_text='{"en-GB":"some text"}') + category = models.ForeignKey('TagCategory', on_delete=models.PROTECT, + null=True, related_name='tags', + verbose_name=_('Category')) class Meta: - verbose_name = _('tag') - verbose_name_plural = _('tags') + """Meta class.""" + + verbose_name = _('Tag') + verbose_name_plural = _('Tags') def __str__(self): label = 'None' @@ -28,20 +29,34 @@ class Tag(TranslatedFieldsMixin, models.Model): return f'id:{self.id}-{label}' +class TagCategoryQuerySet(models.QuerySet): + """Extended queryset for TagCategory model.""" + + def by_news_type(self, news_type): + return self.filter(news_types=news_type) + + def with_related(self): + return self.select_related('country').prefetch_related('tags') + + class TagCategory(TranslatedFieldsMixin, models.Model): """Tag base category model.""" - label = TJSONField( - _('label'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') + + label = TJSONField(blank=True, null=True, default=None, + verbose_name=_('label'), + help_text='{"en-GB":"some text"}') country = models.ForeignKey('location.Country', on_delete=models.SET_NULL, null=True, default=None) - public = models.BooleanField(default=False) + objects = TagCategoryQuerySet.as_manager() + class Meta: - verbose_name = _('tag category') - verbose_name_plural = _('tag categories') + """Meta class.""" + + verbose_name = _('Tag category') + verbose_name_plural = _('Tag categories') def __str__(self): label = 'None' diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index cf783910..ecb1b68d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,14 +1,17 @@ """Tag serializers.""" from rest_framework import serializers -from . import models +from tag import models from utils.serializers import TranslatedField class TagBaseSerializer(serializers.ModelSerializer): """Serializer for model Tag.""" + label_translated = TranslatedField() class Meta: + """Meta class.""" + model = models.Tag fields = [ 'id', @@ -24,21 +27,33 @@ class TagBaseSerializer(serializers.ModelSerializer): class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" + label_translated = TranslatedField() country_translated = TranslatedField(source='country.name_translated') class Meta: """Meta class.""" + model = models.TagCategory - fields = [ + fields = ( 'id', 'label', 'label_translated', 'country', 'country_translated', 'public', - ] + ) extra_kwargs = { 'label': {'write_only': True}, 'country': {'write_only': True}, } + + +class TagCategoryDetailSerializer(TagCategoryBaseSerializer): + + tags = TagBaseSerializer(many=True) + + class Meta(TagCategoryBaseSerializer.Meta): + """Meta class.""" + + fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) diff --git a/apps/tag/views.py b/apps/tag/views.py index ab944ba7..5bf4d045 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,11 +1,11 @@ """Tag views.""" from rest_framework import generics - -from . import serializers, models +from tag import serializers, models class TagListCreateView(generics.ListCreateAPIView): """List/create tag view.""" + queryset = models.Tag.objects.all() serializer_class = serializers.TagBaseSerializer pagination_class = None @@ -13,6 +13,7 @@ class TagListCreateView(generics.ListCreateAPIView): class TagCategoryListCreateView(generics.ListCreateAPIView): """List/create tag category view.""" + queryset = models.TagCategory.objects.all() serializer_class = serializers.TagCategoryBaseSerializer pagination_class = None diff --git a/project/urls/back.py b/project/urls/back.py index 2a9657db..77e573d0 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -3,11 +3,9 @@ from django.urls import path, include app_name = 'back' urlpatterns = [ - path('gallery/', include(('gallery.urls', 'gallery'), - namespace='gallery')), + path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('tags/', include(('tag.urls', 'tag'), - namespace='tag')) + path('tags/', include(('tag.urls', 'tag'), namespace='tag')) ] diff --git a/project/urls/web.py b/project/urls/web.py index 5bf538f3..a4e68317 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -23,7 +23,8 @@ urlpatterns = [ path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), path('news/', include('news.urls.web')), - path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), + path('notifications/', include(('notification.urls.web', "notification"), + namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), path('main/', include('main.urls')), From bd7ba611fc399ff5b6dd9eda0f3d8269f2616b79 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 10 Oct 2019 14:28:42 +0300 Subject: [PATCH 231/319] Fix imports issue --- apps/establishment/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 43bc2f94..4b4cbbea 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,7 +11,7 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from elasticsearch_dsl import Q +from elasticsearch_dsl import Q as Elastic_Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection @@ -103,8 +103,8 @@ class EstablishmentQuerySet(models.QuerySet): """Search text via ElasticSearch.""" from search_indexes.documents import EstablishmentDocument search = EstablishmentDocument.search().filter( - Q('match', name=value) | - Q('match', **{f'description.{locale}': value}) + Elastic_Q('match', name=value) | + Elastic_Q('match', **{f'description.{locale}': value}) ).execute() ids = [result.meta.id for result in search] return self.filter(id__in=ids) From bb41f00451cfceffcf9f3a98962ae6d2cb5ada9a Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 10 Oct 2019 14:42:42 +0300 Subject: [PATCH 232/319] hide es search --- apps/establishment/models.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4b4cbbea..fe81d9ef 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,7 +11,6 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from elasticsearch_dsl import Q as Elastic_Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection @@ -99,15 +98,15 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() - def es_search(self, value, locale=None): - """Search text via ElasticSearch.""" - from search_indexes.documents import EstablishmentDocument - search = EstablishmentDocument.search().filter( - Elastic_Q('match', name=value) | - Elastic_Q('match', **{f'description.{locale}': value}) - ).execute() - ids = [result.meta.id for result in search] - return self.filter(id__in=ids) + # def es_search(self, value, locale=None): + # """Search text via ElasticSearch.""" + # from search_indexes.documents import EstablishmentDocument + # search = EstablishmentDocument.search().filter( + # Elastic_Q('match', name=value) | + # Elastic_Q('match', **{f'description.{locale}': value}) + # ).execute() + # ids = [result.meta.id for result in search] + # return self.filter(id__in=ids) def by_country_code(self, code): """Return establishments by country code""" From 74460b30600b00c532c11eda02f2dcd92d0e094d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 10 Oct 2019 14:28:42 +0300 Subject: [PATCH 233/319] Fix imports issue --- apps/establishment/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5c2a0ff0..690eb8a2 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,7 +11,7 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from elasticsearch_dsl import Q +from elasticsearch_dsl import Q as Elastic_Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection @@ -103,8 +103,8 @@ class EstablishmentQuerySet(models.QuerySet): """Search text via ElasticSearch.""" from search_indexes.documents import EstablishmentDocument search = EstablishmentDocument.search().filter( - Q('match', name=value) | - Q('match', **{f'description.{locale}': value}) + Elastic_Q('match', name=value) | + Elastic_Q('match', **{f'description.{locale}': value}) ).execute() ids = [result.meta.id for result in search] return self.filter(id__in=ids) From 2aa51b93d3d2b72ab72e9cbb40ad3192cd153e29 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 10 Oct 2019 14:42:42 +0300 Subject: [PATCH 234/319] hide es search --- apps/establishment/models.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 690eb8a2..5290f368 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,7 +11,6 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from elasticsearch_dsl import Q as Elastic_Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection @@ -99,15 +98,15 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() - def es_search(self, value, locale=None): - """Search text via ElasticSearch.""" - from search_indexes.documents import EstablishmentDocument - search = EstablishmentDocument.search().filter( - Elastic_Q('match', name=value) | - Elastic_Q('match', **{f'description.{locale}': value}) - ).execute() - ids = [result.meta.id for result in search] - return self.filter(id__in=ids) + # def es_search(self, value, locale=None): + # """Search text via ElasticSearch.""" + # from search_indexes.documents import EstablishmentDocument + # search = EstablishmentDocument.search().filter( + # Elastic_Q('match', name=value) | + # Elastic_Q('match', **{f'description.{locale}': value}) + # ).execute() + # ids = [result.meta.id for result in search] + # return self.filter(id__in=ids) def by_country_code(self, code): """Return establishments by country code""" From 56c0927a52458f306ac1091294774a59fb02721c 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, 10 Oct 2019 14:58:19 +0300 Subject: [PATCH 235/319] Add country control --- .../migrations/0002_comment_language.py | 20 +++++++++++++++++++ apps/comment/models.py | 3 ++- apps/comment/permissions.py | 6 +++--- .../migrations/0011_country_language.py | 19 ++++++++++++++++++ apps/location/models.py | 3 ++- 5 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 apps/comment/migrations/0002_comment_language.py create mode 100644 apps/location/migrations/0011_country_language.py diff --git a/apps/comment/migrations/0002_comment_language.py b/apps/comment/migrations/0002_comment_language.py new file mode 100644 index 00000000..4b0b9ab7 --- /dev/null +++ b/apps/comment/migrations/0002_comment_language.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-10-10 11:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0003_auto_20190901_1032'), + ('comment', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='language', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='translation.Language', verbose_name='Locale'), + ), + ] diff --git a/apps/comment/models.py b/apps/comment/models.py index fe781d4d..0193055d 100644 --- a/apps/comment/models.py +++ b/apps/comment/models.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from account.models import User from utils.models import ProjectBaseMixin from utils.querysets import ContentTypeQuerySetMixin - +from translation.models import Language class CommentQuerySet(ContentTypeQuerySetMixin): """QuerySets for Comment model.""" @@ -41,6 +41,7 @@ class Comment(ProjectBaseMixin): content_object = generic.GenericForeignKey('content_type', 'object_id') objects = CommentQuerySet.as_manager() + language = models.ForeignKey(Language, verbose_name=_('Locale'), on_delete=models.SET_NULL, null=True) class Meta: """Meta class""" diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py index 09860c2c..3bfb2b3c 100644 --- a/apps/comment/permissions.py +++ b/apps/comment/permissions.py @@ -13,12 +13,12 @@ class IsCommentModerator(permissions.BasePermission): # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True - + # user owner is user request if obj.user == request.user: return True - # Instance must have an attribute named `user`. - role = Role.objects.get(role=2) # 'Comments moderator' + # Must have role + role = Role.objects.filter(role=2, country__language=obj.language).first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() if obj.user != request.user and is_access: return True diff --git a/apps/location/migrations/0011_country_language.py b/apps/location/migrations/0011_country_language.py new file mode 100644 index 00000000..9a6b4e8a --- /dev/null +++ b/apps/location/migrations/0011_country_language.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-10 11:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0003_auto_20190901_1032'), + ('location', '0010_auto_20190904_0711'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='language', + field=models.ManyToManyField(to='translation.Language', verbose_name='Language'), + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index 1cab1815..b11b0ad5 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -6,7 +6,7 @@ from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField - +from translation.models import Language class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): """Country model.""" @@ -18,6 +18,7 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): code = models.CharField(max_length=255, unique=True, verbose_name=_('Code')) low_price = models.IntegerField(default=25, verbose_name=_('Low price')) high_price = models.IntegerField(default=50, verbose_name=_('High price')) + language = models.ManyToManyField(Language, verbose_name=_('Language')) class Meta: """Meta class.""" From f83b735eb321306dfa6a710e3f820bb12d14dcab Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 10 Oct 2019 15:10:43 +0300 Subject: [PATCH 236/319] changed files --- .../migrations/0034_merge_20191009_1457.py | 14 ++++++ apps/establishment/models.py | 27 ++++++++++++ apps/establishment/serializers/back.py | 5 +-- apps/establishment/serializers/common.py | 44 +++++++++++++++++-- apps/establishment/urls/back.py | 4 ++ apps/establishment/views/back.py | 30 ++++++++++++- 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 apps/establishment/migrations/0034_merge_20191009_1457.py diff --git a/apps/establishment/migrations/0034_merge_20191009_1457.py b/apps/establishment/migrations/0034_merge_20191009_1457.py new file mode 100644 index 00000000..945860f7 --- /dev/null +++ b/apps/establishment/migrations/0034_merge_20191009_1457.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0033_auto_20191009_0715'), + ('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'), + ] + + operations = [ + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 74348f5d..c248ca5d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -479,6 +479,7 @@ class ContactEmail(models.Model): def __str__(self): return f'{self.email}' + # # class Wine(TranslatedFieldsMixin, models.Model): # """Wine model.""" @@ -586,6 +587,11 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): """Return establishment tags by country code""" return self.filter(tag_category__country__code=code) + def with_base_related(self): + """Return with related relations.""" + # return self.select_related('tags', 'establishment_type') + return self.select_related('establishment_type', 'tag_category') + class EstablishmentTypeTagCategory(models.Model): """Tag categories based on establishment type.""" @@ -602,3 +608,24 @@ class EstablishmentTypeTagCategory(models.Model): class Meta: verbose_name = _('establishment type tag categories') verbose_name_plural = _('establishment type tag categories') + + +# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): +# """QuerySet for tag categories based on establishment subtype.""" +# +# +# class EstablishmentSubTypeTagCategory(models.Model): +# """Tag categories based on establishment subtype.""" +# establishment_subtype = models.ForeignKey(EstablishmentSubType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment subtype')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_subtype_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment subtype tag categories') +# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 8bd09e85..33d2e812 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,13 +1,12 @@ from rest_framework import serializers + from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentTypeSerializer) - -from utils.decorators import with_base_attributes - from main.models import Currency +from utils.decorators import with_base_attributes class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 2cd4e429..ef21e4fb 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -89,11 +89,11 @@ class MenuRUDSerializers(ProjectModelSerializer): ] -class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): +class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id') + id = serializers.IntegerField(source='tag_category.id', read_only=True) label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True) + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) class Meta: """Meta class.""" @@ -102,7 +102,21 @@ class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): 'id', 'label_translated', 'tags', + 'establishment_type', + 'tag_category', ] + extra_kwargs = { + 'establishment_type': {'write_only': True}, + 'tag_category': {'write_only': True}, + } + + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs class EstablishmentTypeSerializer(serializers.ModelSerializer): @@ -136,6 +150,30 @@ class EstablishmentSubTypeSerializer(serializers.ModelSerializer): } +# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentSubTypeTagCategory +# fields = [ +# 'establishment_subtype', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_subtype': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentSubTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_subtype'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs + + class ReviewSerializer(serializers.ModelSerializer): """Serializer for model Review.""" text_translated = serializers.CharField(read_only=True) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index b04a6843..ad10ea1a 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -30,6 +30,10 @@ urlpatterns = [ path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), + path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), + name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), + # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + # name='subtype-attach-tag-category'), ] \ No newline at end of file diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 32ee2805..7d0949be 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,8 +1,9 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics +from rest_framework import generics, status from establishment import models, serializers +from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer @@ -55,7 +56,7 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTagCategoryListSerializer + serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer pagination_class = None def get_object(self): @@ -81,6 +82,31 @@ class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPI return establishment.establishment_type.tag_categories.all() +class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): + """Attach tag category to establishment type.""" + serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer + + def get_queryset(self): + """Override get_queryset method.""" + return models.EstablishmentTypeTagCategory.objects.with_base_related() + + def post(self, request, *args, **kwargs): + """Overridden post-method.""" + super(EstablishmentTypeAttachTagCategoryView, self).post(request) + return Response(status=status.HTTP_200_OK) + + +# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment subtype.""" +# queryset = models.EstablishmentSubTypeTagCategory.objects.all() +# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers From ab5a66afe2e579c4b6ae88ca0e85874dcd5938fe 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, 10 Oct 2019 15:20:13 +0300 Subject: [PATCH 237/319] Test change --- apps/comment/permissions.py | 13 ++++++++----- apps/comment/tests.py | 13 +++++++++++-- .../migrations/0012_auto_20191010_1205.py | 18 ++++++++++++++++++ apps/location/models.py | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 apps/location/migrations/0012_auto_20191010_1205.py diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py index 3bfb2b3c..9e0ab58b 100644 --- a/apps/comment/permissions.py +++ b/apps/comment/permissions.py @@ -13,17 +13,20 @@ class IsCommentModerator(permissions.BasePermission): # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True - # user owner is user request - if obj.user == request.user: - return True + # # user owner is user request + # if obj.user == request.user: + # return True # Must have role - role = Role.objects.filter(role=2, country__language=obj.language).first() # 'Comments moderator' + # ,country__languages__id=obj.language_id + role = Role.objects.filter(role=2, + country__languages__id=obj.language_id)\ + .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() if obj.user != request.user and is_access: return True - super_user=User.objects.filter(pk=request.user.pk, is_superuser=True).exists() + super_user = User.objects.filter(pk=request.user.pk, is_superuser=True).exists() if super_user: return True diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 2157933e..949ba597 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -7,17 +7,25 @@ from http.cookies import SimpleCookie from location.models import Country from account.models import Role, User, UserRole from comment.models import Comment +from translation.models import Language class CommentModeratorPermissionTests(APITestCase): def setUp(self): + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + self.country_ru = Country.objects.create( name='{"ru-RU":"Russia"}', code='23', low_price=15, - high_price=150000 + high_price=150000, ) + self.country_ru.languages.add(self.lang) self.country_ru.save() self.role = Role.objects.create( @@ -42,7 +50,8 @@ class CommentModeratorPermissionTests(APITestCase): self.comment = Comment.objects.create(text='Test comment', mark=1, user=self.user_test["user"], object_id= self.country_ru.pk, - content_type_id=content_type.id + content_type_id=content_type.id, + language=self.lang ) self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) diff --git a/apps/location/migrations/0012_auto_20191010_1205.py b/apps/location/migrations/0012_auto_20191010_1205.py new file mode 100644 index 00000000..a2de2917 --- /dev/null +++ b/apps/location/migrations/0012_auto_20191010_1205.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-10 12:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0011_country_language'), + ] + + operations = [ + migrations.RenameField( + model_name='country', + old_name='language', + new_name='languages', + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index b11b0ad5..f52f9fee 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -18,7 +18,7 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): code = models.CharField(max_length=255, unique=True, verbose_name=_('Code')) low_price = models.IntegerField(default=25, verbose_name=_('Low price')) high_price = models.IntegerField(default=50, verbose_name=_('High price')) - language = models.ManyToManyField(Language, verbose_name=_('Language')) + languages = models.ManyToManyField(Language, verbose_name=_('Languages')) class Meta: """Meta class.""" From b83efb7cda31913714c89a78da158be3cb92bbe0 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, 10 Oct 2019 15:23:44 +0300 Subject: [PATCH 238/319] Fix migrations --- ...y_language.py => 0011_country_languages.py} | 6 +++--- .../migrations/0012_auto_20191010_1205.py | 18 ------------------ 2 files changed, 3 insertions(+), 21 deletions(-) rename apps/location/migrations/{0011_country_language.py => 0011_country_languages.py} (77%) delete mode 100644 apps/location/migrations/0012_auto_20191010_1205.py diff --git a/apps/location/migrations/0011_country_language.py b/apps/location/migrations/0011_country_languages.py similarity index 77% rename from apps/location/migrations/0011_country_language.py rename to apps/location/migrations/0011_country_languages.py index 9a6b4e8a..629f76bc 100644 --- a/apps/location/migrations/0011_country_language.py +++ b/apps/location/migrations/0011_country_languages.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-10 11:34 +# Generated by Django 2.2.4 on 2019-10-10 12:22 from django.db import migrations, models @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='country', - name='language', - field=models.ManyToManyField(to='translation.Language', verbose_name='Language'), + name='languages', + field=models.ManyToManyField(to='translation.Language', verbose_name='Languages'), ), ] diff --git a/apps/location/migrations/0012_auto_20191010_1205.py b/apps/location/migrations/0012_auto_20191010_1205.py deleted file mode 100644 index a2de2917..00000000 --- a/apps/location/migrations/0012_auto_20191010_1205.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-10 12:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('location', '0011_country_language'), - ] - - operations = [ - migrations.RenameField( - model_name='country', - old_name='language', - new_name='languages', - ), - ] From b390b1af26d707092bf9d6e9c18168baddcecfb0 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, 10 Oct 2019 16:00:23 +0300 Subject: [PATCH 239/319] Fix --- apps/comment/permissions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py index 9e0ab58b..9dacced4 100644 --- a/apps/comment/permissions.py +++ b/apps/comment/permissions.py @@ -14,11 +14,10 @@ class IsCommentModerator(permissions.BasePermission): if request.method in permissions.SAFE_METHODS: return True # # user owner is user request - # if obj.user == request.user: - # return True + if obj.user == request.user: + return True # Must have role - # ,country__languages__id=obj.language_id role = Role.objects.filter(role=2, country__languages__id=obj.language_id)\ .first() # 'Comments moderator' From c920628574ec58c9929dc5791bc2c5724a8b3224 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 10 Oct 2019 17:42:29 +0300 Subject: [PATCH 240/319] Don't change email until user confirms it --- .../migrations/0009_user_unconfirmed_email.py | 18 ++++++++++++++++++ apps/account/models.py | 3 +++ apps/account/serializers/common.py | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 apps/account/migrations/0009_user_unconfirmed_email.py diff --git a/apps/account/migrations/0009_user_unconfirmed_email.py b/apps/account/migrations/0009_user_unconfirmed_email.py new file mode 100644 index 00000000..6aaf3a6a --- /dev/null +++ b/apps/account/migrations/0009_user_unconfirmed_email.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-10 14:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0008_auto_20190912_1325'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='unconfirmed_email', + field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='unconfirmed email'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 81ade4fc..17df54ae 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -60,6 +60,7 @@ class User(AbstractUser): blank=True, null=True, default=None) email = models.EmailField(_('email address'), blank=True, null=True, default=None) + unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None) email_confirmed = models.BooleanField(_('email status'), default=False) newsletter = models.NullBooleanField(default=True) @@ -112,6 +113,8 @@ class User(AbstractUser): def confirm_email(self): """Method to confirm user email address""" + self.email = self.unconfirmed_email + self.unconfirmed_email = None self.email_confirmed = True self.save() diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index b68aca7d..ad232eae 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -61,9 +61,12 @@ class UserSerializer(serializers.ModelSerializer): def update(self, instance, validated_data): """Override update method""" + old_email = instance.email instance = super().update(instance, validated_data) if 'email' in validated_data: instance.email_confirmed = False + instance.email = old_email + instance.unconfirmed_email = validated_data['email'] instance.save() # Send verification link on user email for change email address if settings.USE_CELERY: From 71528d8a2896894b72cc2194752a9e45eec4d61f Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 10 Oct 2019 20:49:35 +0300 Subject: [PATCH 241/319] Password reset confirmation mechanics --- .../0010_user_password_confirmed.py | 18 +++++++++++++ apps/account/models.py | 20 ++++++++++++++ apps/account/serializers/web.py | 13 ++++++++++ apps/account/tasks.py | 11 ++++++++ apps/account/urls/common.py | 1 + apps/account/views/common.py | 26 +++++++++++++++++++ apps/authorization/serializers/common.py | 4 +-- apps/utils/models.py | 7 +++-- project/settings/base.py | 1 + .../account/password_confirm_email.html | 11 ++++++++ 10 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 apps/account/migrations/0010_user_password_confirmed.py create mode 100644 project/templates/account/password_confirm_email.html diff --git a/apps/account/migrations/0010_user_password_confirmed.py b/apps/account/migrations/0010_user_password_confirmed.py new file mode 100644 index 00000000..5f369f3c --- /dev/null +++ b/apps/account/migrations/0010_user_password_confirmed.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-10 17:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_user_unconfirmed_email'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='password_confirmed', + field=models.BooleanField(default=True, verbose_name='is new password confirmed'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 17df54ae..b4023816 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -63,6 +63,7 @@ class User(AbstractUser): unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None) email_confirmed = models.BooleanField(_('email status'), default=False) newsletter = models.NullBooleanField(default=True) + password_confirmed = models.BooleanField(_('is new password confirmed'), default=True, null=False) EMAIL_FIELD = 'email' USERNAME_FIELD = 'username' @@ -118,6 +119,10 @@ class User(AbstractUser): self.email_confirmed = True self.save() + def confirm_password(self): + self.password_confirmed = True + self.save() + def approve(self): """Set user is_active status to True""" self.is_active = True @@ -152,6 +157,11 @@ class User(AbstractUser): """Make a token for finish signup.""" return password_token_generator.make_token(self) + @property + def confirm_password_token(self): + """Make a token for new password confirmation """ + return GMTokenGenerator(purpose=GMTokenGenerator.CONFIRM_PASSWORD).make_token(self) + @property def get_user_uidb64(self): """Get base64 value for user by primary key identifier""" @@ -181,6 +191,16 @@ class User(AbstractUser): template_name=settings.RESETTING_TOKEN_TEMPLATE, context=context) + def confirm_password_template(self, country_code): + """Get confirm password template""" + context = {'token': self.confirm_password_token, + 'country_code': country_code} + context.update(self.base_template) + return render_to_string( + template_name=settings.CONFIRM_PASSWORD_TEMPLATE, + context=context, + ) + def confirm_email_template(self, country_code): """Get confirm email template""" context = {'token': self.confirm_email_token, diff --git a/apps/account/serializers/web.py b/apps/account/serializers/web.py index 8be73afa..f87c34b8 100644 --- a/apps/account/serializers/web.py +++ b/apps/account/serializers/web.py @@ -1,8 +1,10 @@ """Serializers for account web""" from django.contrib.auth import password_validation as password_validators +from django.conf import settings from rest_framework import serializers from account import models +from account import tasks from utils import exceptions as utils_exceptions from utils.methods import username_validator @@ -67,5 +69,16 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer): """Override update method""" # Update user password from instance instance.set_password(validated_data.get('password')) + instance.password_confirmed = False instance.save() + if settings.USE_CELERY: + tasks.send_reset_password_confirm.delay( + user_id=instance, + country_code=self.context.get('request').country_code, + ) + else: + tasks.send_reset_password_confirm( + user_id=instance, + country_code=self.context.get('request').country_code, + ) return instance diff --git a/apps/account/tasks.py b/apps/account/tasks.py index 03a231b3..3729c40c 100644 --- a/apps/account/tasks.py +++ b/apps/account/tasks.py @@ -22,6 +22,17 @@ def send_reset_password_email(user_id, country_code): f'DETAIL: Exception occurred for reset password: ' f'{user_id}') +@shared_task +def send_reset_password_confirm(user: models.User, country_code): + """ Send email to user for applying new password. """ + try: + user.send_email(subject=_('New password confirmation'), + message=user.confirm_password_template(country_code)) + except: + logger.error(f'METHOD_NAME: {send_reset_password_confirm.__name__}\n' + f'DETAIL: Exception occured for new passwordconfirmation', + f'{user.id}') + @shared_task def confirm_new_email_address(user_id, country_code): diff --git a/apps/account/urls/common.py b/apps/account/urls/common.py index 4ea2af66..a440c5bf 100644 --- a/apps/account/urls/common.py +++ b/apps/account/urls/common.py @@ -8,6 +8,7 @@ app_name = 'account' urlpatterns = [ path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'), path('change-password/', views.ChangePasswordView.as_view(), name='change-password'), + path('change-password-confirm///', views.ConfirmPasswordView.as_view(), name='change-password'), path('email/confirm/', views.SendConfirmationEmailView.as_view(), name='send-confirm-email'), path('email/confirm///', views.ConfirmEmailView.as_view(), name='confirm-email'), ] diff --git a/apps/account/views/common.py b/apps/account/views/common.py index d29ce2bb..cb0d84d7 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -91,6 +91,32 @@ class ConfirmEmailView(JWTGenericViewMixin): else: raise utils_exceptions.UserNotFoundError() +class ConfirmPasswordView(JWTGenericViewMixin): + """View for applying newly set password""" + + permission_classes = (permissions.AllowAny,) + + def get(self, request, *args, **kwargs): + uidb64 = kwargs.get('uidb64') + token = kwargs.get('token') + uid = force_text(urlsafe_base64_decode(uidb64)) + user_qs = models.User.objects.filter(pk=uid) + if user_qs.exists(): + user = user_qs.first() + if not GMTokenGenerator(GMTokenGenerator.CONFIRM_PASSWORD).check_token( + user, token): + raise utils_exceptions.NotValidTokenError() + user.confirm_password() + tokens = user.create_jwt_tokens() + return self._put_cookies_in_response( + cookies=self._put_data_in_cookies( + access_token=tokens.get('access_token'), + refresh_token=tokens.get('refresh_token')), + response=Response(status=status.HTTP_200_OK)) + else: + raise utils_exceptions.UserNotFoundError() + + # Firebase Cloud Messaging class FCMDeviceViewSet(generics.GenericAPIView): diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index ed68ba9f..6be76a00 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -108,8 +108,8 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, """Override validate method""" username_or_email = attrs.pop('username_or_email') password = attrs.pop('password') - user_qs = account_models.User.objects.filter(Q(username=username_or_email) | - Q(email=username_or_email)) + user_qs = account_models.User.objects.filter(password_confirmed=True)\ + .filter(Q(username=username_or_email) | Q(email=username_or_email)) if not user_qs.exists(): raise utils_exceptions.WrongAuthCredentials() else: diff --git a/apps/utils/models.py b/apps/utils/models.py index 4e6df35e..e5a39895 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -258,12 +258,14 @@ class GMTokenGenerator(PasswordResetTokenGenerator): RESET_PASSWORD = 1 CHANGE_PASSWORD = 2 CONFIRM_EMAIL = 3 + CONFIRM_PASSWORD = 4 TOKEN_CHOICES = ( CHANGE_EMAIL, RESET_PASSWORD, CHANGE_PASSWORD, - CONFIRM_EMAIL + CONFIRM_EMAIL, + CONFIRM_PASSWORD, ) def __init__(self, purpose: int): @@ -279,7 +281,8 @@ class GMTokenGenerator(PasswordResetTokenGenerator): self.purpose == self.CONFIRM_EMAIL: fields.extend([str(user.email_confirmed), str(user.email)]) elif self.purpose == self.RESET_PASSWORD or \ - self.purpose == self.CHANGE_PASSWORD: + self.purpose == self.CHANGE_PASSWORD or \ + self.purpose == self.CONFIRM_PASSWORD: fields.append(str(user.password)) return fields diff --git a/project/settings/base.py b/project/settings/base.py index 06f83811..7d9b8c05 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -405,6 +405,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1 # TEMPLATES RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' CHANGE_EMAIL_TEMPLATE = 'account/change_email.html' +CONFIRM_PASSWORD_TEMPLATE = 'account/password_confirm_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html' NEWS_EMAIL_TEMPLATE = "news/news_email.html" diff --git a/project/templates/account/password_confirm_email.html b/project/templates/account/password_confirm_email.html new file mode 100644 index 00000000..29f27afb --- /dev/null +++ b/project/templates/account/password_confirm_email.html @@ -0,0 +1,11 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}Confirm a password reset for your user account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following page:" %} + +https://{{ country_code }}.{{ domain_uri }}/confirm-new-password/{{ uidb64 }}/{{ token }}/ + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} +{% endautoescape %} \ No newline at end of file From a25d8460285a4f4e78e3fc66005f4b044b16f4c4 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 10 Oct 2019 21:02:56 +0300 Subject: [PATCH 242/319] fixed bug with duplicated records in similar establishments --- apps/establishment/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5290f368..1ff82e72 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,8 +14,8 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection -from main.models import Award, MetaDataContent from location.models import Address +from main.models import Award, MetaDataContent from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -180,7 +180,8 @@ class EstablishmentQuerySet(models.QuerySet): return self.filter(id__in=subquery_filter_by_distance) \ .annotate_intermediate_public_mark() \ .annotate_mark_similarity(mark=establishment.public_mark) \ - .order_by('mark_similarity') + .order_by('mark_similarity') \ + .distinct('mark_similarity', 'id') else: return self.none() From 114d159449647edd41a96638e7fc6da8dffb4c85 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 11 Oct 2019 13:46:08 +0300 Subject: [PATCH 243/319] fix tests --- apps/account/serializers/web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/account/serializers/web.py b/apps/account/serializers/web.py index f87c34b8..dd8ccec8 100644 --- a/apps/account/serializers/web.py +++ b/apps/account/serializers/web.py @@ -73,12 +73,12 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer): instance.save() if settings.USE_CELERY: tasks.send_reset_password_confirm.delay( - user_id=instance, + user=instance, country_code=self.context.get('request').country_code, ) else: tasks.send_reset_password_confirm( - user_id=instance, + user=instance, country_code=self.context.get('request').country_code, ) return instance From 0816a847ac6940d58e40c1785a023a0692ff31ea Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 11 Oct 2019 14:29:01 +0300 Subject: [PATCH 244/319] Some booking fixes --- apps/booking/models/services.py | 2 +- apps/booking/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py index fd685548..a3ee17ea 100644 --- a/apps/booking/models/services.py +++ b/apps/booking/models/services.py @@ -141,7 +141,7 @@ class LastableService(AbstractBookingService): super().check_whether_booking_available(restaurant_id, date) url = f'{self.url}v1/restaurant/{restaurant_id}/offers' r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies) - response = json.loads(r.content)['data'] + response = json.loads(r.content).get('data') if not status.is_success(r.status_code) or not response: return False self.response = response diff --git a/apps/booking/views.py b/apps/booking/views.py index 245dbf05..dfa64bb9 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -35,9 +35,9 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): response = { 'available': is_booking_available, - 'type': service.service, + 'type': service.service if service else None, } - response.update({'details': service.response} if service.response else {}) + response.update({'details': service.response} if service and service.response else {}) return Response(data=response, status=200) From a6c56a4bf1d2c468a890fdc6b0effa114c6ab86b 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, 11 Oct 2019 14:49:32 +0300 Subject: [PATCH 245/319] Fix and refactor --- apps/account/admin.py | 1 - ...userrole.py => 0009_auto_20191011_1123.py} | 13 +++---- .../migrations/0010_auto_20191008_0751.py | 29 ---------------- apps/account/models.py | 34 +++++++++++-------- apps/comment/permissions.py | 15 +++----- apps/comment/views/back.py | 4 ++- apps/location/models.py | 1 + .../migrations/0003_auto_20191004_0928.py | 17 ---------- .../migrations/0004_merge_20191009_1341.py | 14 -------- 9 files changed, 36 insertions(+), 92 deletions(-) rename apps/account/migrations/{0009_role_userrole.py => 0009_auto_20191011_1123.py} (80%) delete mode 100644 apps/account/migrations/0010_auto_20191008_0751.py delete mode 100644 apps/timetable/migrations/0003_auto_20191004_0928.py delete mode 100644 apps/timetable/migrations/0004_merge_20191009_1341.py diff --git a/apps/account/admin.py b/apps/account/admin.py index a89e6693..3b247289 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -2,7 +2,6 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.translation import ugettext_lazy as _ - from account import models diff --git a/apps/account/migrations/0009_role_userrole.py b/apps/account/migrations/0009_auto_20191011_1123.py similarity index 80% rename from apps/account/migrations/0009_role_userrole.py rename to apps/account/migrations/0009_auto_20191011_1123.py index e162bfe5..f1ec87f9 100644 --- a/apps/account/migrations/0009_role_userrole.py +++ b/apps/account/migrations/0009_auto_20191011_1123.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:17 +# Generated by Django 2.2.4 on 2019-10-11 11:23 from django.conf import settings from django.db import migrations, models @@ -9,7 +9,7 @@ import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('location', '0010_auto_20190904_0711'), + ('location', '0011_country_languages'), ('account', '0008_auto_20190912_1325'), ] @@ -21,10 +21,6 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('role', models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator')], verbose_name='Role')), - ('is_list', models.BooleanField(default=True, verbose_name='list')), - ('is_create', models.BooleanField(default=False, verbose_name='create')), - ('is_update', models.BooleanField(default=False, verbose_name='update')), - ('is_delete', models.BooleanField(default=False, verbose_name='delete')), ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country')), ], options={ @@ -44,4 +40,9 @@ class Migration(migrations.Migration): 'abstract': False, }, ), + migrations.AddField( + model_name='user', + name='roles', + field=models.ManyToManyField(through='account.UserRole', to='account.Role', verbose_name='Roles'), + ), ] diff --git a/apps/account/migrations/0010_auto_20191008_0751.py b/apps/account/migrations/0010_auto_20191008_0751.py deleted file mode 100644 index 289f8643..00000000 --- a/apps/account/migrations/0010_auto_20191008_0751.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0009_role_userrole'), - ] - - operations = [ - migrations.RemoveField( - model_name='role', - name='is_create', - ), - migrations.RemoveField( - model_name='role', - name='is_delete', - ), - migrations.RemoveField( - model_name='role', - name='is_list', - ), - migrations.RemoveField( - model_name='role', - name='is_update', - ), - ] diff --git a/apps/account/models.py b/apps/account/models.py index c7de88e3..206f5e02 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -19,6 +19,24 @@ from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.tokens import GMRefreshToken +class Role(ProjectBaseMixin): + """Base Role model.""" + STANDARD_USER = 1 + COMMENTS_MODERATOR = 2 + + ROLE_CHOICES =( + (STANDARD_USER, 'Standard user'), + (COMMENTS_MODERATOR, 'Comments moderator'), + ) + role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, + null=False, blank=False) + country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) + # is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) + # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) + # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) + # is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False) + + class UserManager(BaseUserManager): """Extended manager for User model.""" @@ -68,6 +86,7 @@ class User(AbstractUser): USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] + roles = models.ManyToManyField(Role, verbose_name=_('Roles'), through='UserRole') objects = UserManager.from_queryset(UserQuerySet)() class Meta: @@ -198,20 +217,7 @@ class User(AbstractUser): context=context) -class Role(ProjectBaseMixin): - ROLE_CHOICES =( - (1, 'Standard user'), - (2, 'Comments moderator'), - ) - role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, - null=False, blank=False) - country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) - # is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) - # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) - # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) - # is_delete = models.BooleanField(verbose_name=_('delete'), default=False, null=False) - - class UserRole(ProjectBaseMixin): + """UserRole model.""" user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) \ No newline at end of file diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py index 9dacced4..6d691c07 100644 --- a/apps/comment/permissions.py +++ b/apps/comment/permissions.py @@ -2,7 +2,7 @@ from rest_framework import permissions from account.models import UserRole, Role, User -class IsCommentModerator(permissions.BasePermission): +class IsCommentModerator(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. @@ -11,23 +11,18 @@ class IsCommentModerator(permissions.BasePermission): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. - if request.method in permissions.SAFE_METHODS: - return True - # # user owner is user request - if obj.user == request.user: + if request.method in permissions.SAFE_METHODS or \ + obj.user == request.user or request.user.is_superuser: return True # Must have role - role = Role.objects.filter(role=2, + role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, country__languages__id=obj.language_id)\ .first() # 'Comments moderator' + is_access = UserRole.objects.filter(user=request.user, role=role).exists() if obj.user != request.user and is_access: return True - super_user = User.objects.filter(pk=request.user.pk, is_superuser=True).exists() - if super_user: - return True - return False diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 16450d03..77edfa97 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -5,13 +5,15 @@ from comment.permissions import IsCommentModerator class CommentLstView(generics.ListCreateAPIView): + """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() permission_classes = [permissions.IsAuthenticatedOrReadOnly,] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): + """Comment RUD view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly,IsCommentModerator] + permission_classes = [IsCommentModerator] lookup_field = 'id' diff --git a/apps/location/models.py b/apps/location/models.py index f52f9fee..2298c28e 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField from translation.models import Language + class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): """Country model.""" diff --git a/apps/timetable/migrations/0003_auto_20191004_0928.py b/apps/timetable/migrations/0003_auto_20191004_0928.py deleted file mode 100644 index 6e82c679..00000000 --- a/apps/timetable/migrations/0003_auto_20191004_0928.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-04 09:28 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] diff --git a/apps/timetable/migrations/0004_merge_20191009_1341.py b/apps/timetable/migrations/0004_merge_20191009_1341.py deleted file mode 100644 index 22d83ca3..00000000 --- a/apps/timetable/migrations/0004_merge_20191009_1341.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-09 13:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0003_auto_20191004_0928'), - ('timetable', '0003_auto_20191003_0943'), - ] - - operations = [ - ] From ddb3eec6790cad55810233d948c1ac0db0e80236 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 15:41:31 +0300 Subject: [PATCH 246/319] finished endpoint to get tags by establishment type and subtype --- apps/establishment/admin.py | 9 +- .../0035_establishmentsubtypetagcategory.py | 27 +++ apps/establishment/models.py | 43 ++--- apps/establishment/serializers/back.py | 6 +- apps/establishment/serializers/common.py | 170 ++++++++++-------- apps/establishment/urls/back.py | 6 +- apps/establishment/views/back.py | 45 +++-- apps/establishment/views/web.py | 2 +- 8 files changed, 185 insertions(+), 123 deletions(-) create mode 100644 apps/establishment/migrations/0035_establishmentsubtypetagcategory.py diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index a40475b0..1f200f41 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -82,10 +82,15 @@ class MenuAdmin(admin.ModelAdmin): @admin.register(models.EstablishmentTypeTagCategory) -class EstablishmentTypeTagCategory(admin.ModelAdmin): +class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): + """EstablishmentTypeTagCategory admin.""" + + +@admin.register(models.EstablishmentSubTypeTagCategory) +class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): """EstablishmentTypeTagCategory admin.""" @admin.register(models.EstablishmentTag) -class EstablishmentTag(admin.ModelAdmin): +class EstablishmentTagAdmin(admin.ModelAdmin): """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py b/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py new file mode 100644 index 00000000..6a85fca7 --- /dev/null +++ b/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-10-11 10:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('establishment', '0034_merge_20191009_1457'), + ] + + operations = [ + migrations.CreateModel( + name='EstablishmentSubTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_subtype', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentSubType', verbose_name='establishment subtype')), + ('tag_category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_subtype_tag_categories', to='tag.TagCategory', verbose_name='tag category')), + ], + options={ + 'verbose_name': 'establishment subtype tag categories', + 'verbose_name_plural': 'establishment subtype tag categories', + }, + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c248ca5d..39b750fa 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -589,7 +589,6 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): def with_base_related(self): """Return with related relations.""" - # return self.select_related('tags', 'establishment_type') return self.select_related('establishment_type', 'tag_category') @@ -610,22 +609,26 @@ class EstablishmentTypeTagCategory(models.Model): verbose_name_plural = _('establishment type tag categories') -# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): -# """QuerySet for tag categories based on establishment subtype.""" -# -# -# class EstablishmentSubTypeTagCategory(models.Model): -# """Tag categories based on establishment subtype.""" -# establishment_subtype = models.ForeignKey(EstablishmentSubType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment subtype')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_subtype_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment subtype tag categories') -# verbose_name_plural = _('establishment subtype tag categories') +class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): + """QuerySet for tag categories based on establishment subtype.""" + + def with_base_related(self): + """Return queryset with base related.""" + return self.select_related('establishment_subtype', 'tag_category') + + +class EstablishmentSubTypeTagCategory(models.Model): + """Tag categories based on establishment subtype.""" + establishment_subtype = models.ForeignKey(EstablishmentSubType, + on_delete=models.CASCADE, + related_name='tag_categories', + verbose_name=_('establishment subtype')) + tag_category = models.ForeignKey('tag.TagCategory', + on_delete=models.CASCADE, + related_name='est_subtype_tag_categories', + verbose_name=_('tag category')) + objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment subtype tag categories') + verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 33d2e812..e164b44a 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -4,7 +4,7 @@ from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeSerializer) + EstablishmentTypeBaseSerializer) from main.models import Currency from utils.decorators import with_base_attributes @@ -20,7 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) - type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) + type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) class Meta: model = models.Establishment @@ -54,7 +54,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=False, many=True, ) emails = ContactEmailsSerializer(read_only=False, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=False, many=True, ) - type = EstablishmentTypeSerializer(source='establishment_type') + type = EstablishmentTypeBaseSerializer(source='establishment_type') class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index ef21e4fb..eec51466 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -89,6 +89,57 @@ class MenuRUDSerializers(ProjectModelSerializer): ] +class ReviewSerializer(serializers.ModelSerializer): + """Serializer for model Review.""" + text_translated = serializers.CharField(read_only=True) + + class Meta: + """Meta class.""" + model = review_models.Review + fields = ( + 'text_translated', + ) + + +class EstablishmentTypeBaseSerializer(serializers.ModelSerializer): + """Serializer for EstablishmentType model.""" + name_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = models.EstablishmentType + fields = [ + 'id', + 'name', + 'name_translated', + 'use_subtypes' + ] + extra_kwargs = { + 'name': {'write_only': True}, + 'use_subtypes': {'write_only': True}, + } + + +class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): + """Serializer for EstablishmentSubType models.""" + + name_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = models.EstablishmentSubType + fields = [ + 'id', + 'name', + 'name_translated', + 'establishment_type' + ] + extra_kwargs = { + 'name': {'write_only': True}, + 'establishment_type': {'write_only': True} + } + + class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" id = serializers.IntegerField(source='tag_category.id', read_only=True) @@ -119,71 +170,62 @@ class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): return attrs -class EstablishmentTypeSerializer(serializers.ModelSerializer): - """Serializer for EstablishmentType model.""" - - name_translated = TranslatedField() +class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id', read_only=True) + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) class Meta: """Meta class.""" - - model = models.EstablishmentType - fields = ('id', 'name', 'name_translated') + model = models.EstablishmentSubTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + 'establishment_subtype', + 'tag_category', + ] extra_kwargs = { - 'name': {'write_only': True} + 'establishment_subtype': {'write_only': True}, + 'tag_category': {'write_only': True}, } + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs -class EstablishmentSubTypeSerializer(serializers.ModelSerializer): - """Serializer for EstablishmentSubType models.""" - name_translated = TranslatedField() +class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): + """Extended serializer for EstablishmentSubType model with tags.""" + tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - class Meta: + class Meta(EstablishmentSubTypeBaseSerializer.Meta): + """Meta class""" + fields = [ + 'id', + 'name_translated', + 'tag_categories' + ] + + +class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): + """Tags by establishment type""" + tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) + subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') + + class Meta(EstablishmentTypeBaseSerializer.Meta): """Meta class.""" - - model = models.EstablishmentSubType - fields = ('id', 'name', 'name_translated', 'establishment_type') - extra_kwargs = { - 'name': {'write_only': True}, - 'establishment_type': {'write_only': True} - } - - -# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentSubTypeTagCategory -# fields = [ -# 'establishment_subtype', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_subtype': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentSubTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_subtype'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs - - -class ReviewSerializer(serializers.ModelSerializer): - """Serializer for model Review.""" - text_translated = serializers.CharField(read_only=True) - - class Meta: - """Meta class.""" - model = review_models.Review - fields = ( - 'text_translated', - ) + fields = [ + 'id', + 'name_translated', + 'tag_categories', + 'subtypes', + ] class EstablishmentEmployeeSerializer(serializers.ModelSerializer): @@ -202,19 +244,6 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'position_translated', 'awards', 'priority') -class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): - """Serializer for establishment category tags.""" - label_translated = TranslatedField() - - class Meta: - """Meta class.""" - model = tag_models.TagCategory - fields = [ - 'id', - 'label_translated', - ] - - class EstablishmentTagSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTag.""" id = serializers.IntegerField(source='tag.id') @@ -262,8 +291,8 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer): description_translated = TranslatedField() image = serializers.URLField(source='image_url') - type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishment_subtypes') + type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) + subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes') awards = AwardSerializer(many=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) phones = ContactPhonesSerializer(read_only=True, many=True) @@ -396,3 +425,4 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): 'content_object': validated_data.pop('establishment') }) return super().create(validated_data) + diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index ad10ea1a..e0489783 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,7 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/tags/categories/', views.EstablishmentTagCategoryListView.as_view(), + path('/tags/', views.EstablishmentTagCategoryListView.as_view(), name='tag-category-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), @@ -34,6 +34,6 @@ urlpatterns = [ name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - # name='subtype-attach-tag-category'), + path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + name='subtype-attach-tag-category'), ] \ No newline at end of file diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 7d0949be..84855982 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -54,9 +54,9 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): +class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.RetrieveAPIView): """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer + serializer_class = serializers.EstablishmentTagsByType pagination_class = None def get_object(self): @@ -74,12 +74,7 @@ class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPI # May raise a permission denied self.check_object_permissions(self.request, obj) - return obj - - def get_queryset(self): - """Overridden get_queryset method.""" - establishment = self.get_object() - return establishment.establishment_type.tag_categories.all() + return obj.establishment_type class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): @@ -96,17 +91,6 @@ class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): return Response(status=status.HTTP_200_OK) -# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment subtype.""" -# queryset = models.EstablishmentSubTypeTagCategory.objects.all() -# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) - - class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -186,31 +170,44 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentTypeListCreateView(generics.ListCreateAPIView): """Establishment type list/create view.""" - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() pagination_class = None class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment type retrieve/update/destroy view.""" - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" - serializer_class = serializers.EstablishmentSubTypeSerializer + serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() pagination_class = None class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment subtype retrieve/update/destroy view.""" - serializer_class = serializers.EstablishmentSubTypeSerializer + serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() -# todo: next task +class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): + """Attach tag category to establishment subtype.""" + serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer + + def get_queryset(self): + """Override get_queryset method.""" + return models.EstablishmentSubTypeTagCategory.objects.with_base_related() + + def post(self, request, *args, **kwargs): + """Overridden post-method.""" + super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) + return Response(status=status.HTTP_200_OK) + + # class EstablishmentTagListCreateView(generics.CreateAPIView): # """Establishment tag list/create view.""" # serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index b4ef4f13..f164ec73 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -84,7 +84,7 @@ class EstablishmentTypeListView(generics.ListAPIView): """Resource for getting a list of establishment types.""" permission_classes = (permissions.AllowAny,) - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() From 700695cdcc0182c16f310f95d583dca27ba0898c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 11 Oct 2019 15:57:59 +0300 Subject: [PATCH 247/319] Change route for changed email confirmation --- project/templates/account/change_email.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/templates/account/change_email.html b/project/templates/account/change_email.html index 0a257ed6..6b74d970 100644 --- a/project/templates/account/change_email.html +++ b/project/templates/account/change_email.html @@ -3,7 +3,7 @@ {% trans "Please go to the following page for confirmation new email address:" %} -https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/ +https://{{ country_code }}.{{ domain_uri }}/change-email-confirm/{{ uidb64 }}/{{ token }}/ {% trans "Thanks for using our site!" %} From f441896d6a122bce23cf38b65a14c87920775593 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, 11 Oct 2019 16:00:23 +0300 Subject: [PATCH 248/319] Migrate data --- apps/location/migrations/0012_data_migrate.py | 20 + apps/location/migrations/migrate_lang.sql | 390 ++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 apps/location/migrations/0012_data_migrate.py create mode 100644 apps/location/migrations/migrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py new file mode 100644 index 00000000..8b638404 --- /dev/null +++ b/apps/location/migrations/0012_data_migrate.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-09-01 10:32 + +from django.db import migrations, connection +import os + + +class Migration(migrations.Migration): + def load_data_from_sql(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + dependencies = [ + ('location', '0011_country_languages'), + ] + + operations = [ + migrations.RunPython(load_data_from_sql), + ] diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql new file mode 100644 index 00000000..409e7bf3 --- /dev/null +++ b/apps/location/migrations/migrate_lang.sql @@ -0,0 +1,390 @@ +delete from comment_comment as cc; +delete from location_country_languages; +delete from translation_language as tl; +delete from location_country as lc; + +CREATE TABLE public.codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +-- Permissions + +ALTER TABLE public.codelang OWNER TO postgres; +GRANT ALL ON TABLE public.codelang TO postgres; + + +INSERT INTO public.codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO public.codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO public.codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO public.codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO public.codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO public.codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO public.codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO public.codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO public.codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO public.codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO public.codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO public.codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO public.codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO public.codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO public.codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO public.codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO public.codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO public.codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO public.codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO public.codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO public.codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO public.codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO public.codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO public.codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE public.country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + +--delete from location_country as lc + +INSERT INTO public.location_country +(code, "name", low_price, high_price, created, modified) +select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country), + 0 as low_price, + 100 as high_price, + now() as created, + now() as modified +from +( + select + distinct c.country + from country_code c +) t +; + +commit; + +--delete from translation_language as tl; + +INSERT INTO public.translation_language +(title, locale) +select + t.country as title, + t.code as locale +from +( + select + distinct c.country, c.code + from country_code c +) t +; + +commit; + + +--delete from location_country_languages + +INSERT INTO public.location_country_languages +(country_id, language_id) +select lc.id as country_id, + l.id as language_id +from location_country as lc +join ( + select tl.*, '"'||tl.title||'"' as country + from translation_language as tl +) l on l.country = (lc."name"::json->'en-GB')::text +; + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file From e9ad418976a6f371dbb7f32955b71f5f560c8d62 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, 11 Oct 2019 16:57:59 +0300 Subject: [PATCH 249/319] Revert migrate location --- apps/location/migrations/0012_data_migrate.py | 12 +- apps/location/migrations/remigrate_lang.sql | 394 ++++++++++++++++++ 2 files changed, 403 insertions(+), 3 deletions(-) create mode 100644 apps/location/migrations/remigrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index 8b638404..d32454bf 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -1,20 +1,26 @@ -# Generated by Django 2.2.4 on 2019-09-01 10:32 - from django.db import migrations, connection import os class Migration(migrations.Migration): + def load_data_from_sql(apps, schema_editor): file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') sql_statement = open(file_path).read() with connection.cursor() as c: c.execute(sql_statement) + def revert_data(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + dependencies = [ ('location', '0011_country_languages'), ] operations = [ - migrations.RunPython(load_data_from_sql), + migrations.RunPython(load_data_from_sql, revert_data), + # migrations.RunPython(load_data_from_sql, revert_data), ] diff --git a/apps/location/migrations/remigrate_lang.sql b/apps/location/migrations/remigrate_lang.sql new file mode 100644 index 00000000..910dfcfa --- /dev/null +++ b/apps/location/migrations/remigrate_lang.sql @@ -0,0 +1,394 @@ +CREATE TABLE public.codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +-- Permissions + +ALTER TABLE public.codelang OWNER TO postgres; +GRANT ALL ON TABLE public.codelang TO postgres; + + +INSERT INTO public.codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO public.codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO public.codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO public.codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO public.codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO public.codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO public.codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO public.codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO public.codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO public.codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO public.codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO public.codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO public.codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO public.codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO public.codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO public.codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO public.codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO public.codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO public.codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO public.codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO public.codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO public.codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO public.codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO public.codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE public.country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + + +delete from location_country_languages as lcl +where lcl.country_id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; +commit; + + +delete from location_country as lcl +where lcl.id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; + +commit; + + +delete from translation_language tl +where tl.id in +( + SELECT tl.id + FROM + ( + select + distinct c.country, c.code + from country_code c + ) t + JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country +); + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file From 1f44777a15b23391f5c2329ea40ffe3023a5ca14 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, 11 Oct 2019 17:04:55 +0300 Subject: [PATCH 250/319] Fix --- apps/location/migrations/0012_data_migrate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index d32454bf..511990db 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -22,5 +22,4 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(load_data_from_sql, revert_data), - # migrations.RunPython(load_data_from_sql, revert_data), ] From cb762ce746dbfa20cb3f9c5afafee26c3dbbde51 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 18:26:13 +0300 Subject: [PATCH 251/319] added param to attr for Establoishment model and small refactoring, boost queries --- .../migrations/0036_auto_20191011_1356.py | 18 ++++ apps/establishment/models.py | 7 ++ apps/establishment/serializers/back.py | 98 ++++++++++++++++++- apps/establishment/serializers/common.py | 59 ----------- apps/establishment/urls/back.py | 5 +- apps/establishment/views/back.py | 11 +-- 6 files changed, 130 insertions(+), 68 deletions(-) create mode 100644 apps/establishment/migrations/0036_auto_20191011_1356.py diff --git a/apps/establishment/migrations/0036_auto_20191011_1356.py b/apps/establishment/migrations/0036_auto_20191011_1356.py new file mode 100644 index 00000000..c2eb2e4e --- /dev/null +++ b/apps/establishment/migrations/0036_auto_20191011_1356.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-11 13:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0035_establishmentsubtypetagcategory'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='establishment_subtypes', + field=models.ManyToManyField(blank=True, related_name='subtype_establishment', to='establishment.EstablishmentSubType', verbose_name='subtype'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 39b750fa..169590ff 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -23,6 +23,10 @@ from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) +class EstablishmentTypeQuerySet(models.QuerySet): + """QuerySet for model EstablishmentType.""" + + # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): """Establishment type model.""" @@ -82,6 +86,8 @@ class EstablishmentQuerySet(models.QuerySet): EstablishmentTag.objects.select_related('tag')), models.Prefetch('establishment_type__tag_categories', EstablishmentTypeTagCategory.objects.select_related('tag_category')), + models.Prefetch('establishment_type__establishmentsubtype_set', + EstablishmentSubType.objects.prefetch_related('tag_categories')), ) def with_extended_related(self): @@ -257,6 +263,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): on_delete=models.PROTECT, verbose_name=_('type')) establishment_subtypes = models.ManyToManyField(EstablishmentSubType, + blank=True, related_name='subtype_establishment', verbose_name=_('subtype')) address = models.ForeignKey(Address, blank=True, null=True, default=None, diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index e164b44a..21f63e17 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,12 +1,16 @@ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer) + EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer, + EstablishmentTypeTagCategoryBaseSerializer) from main.models import Currency +from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes +from utils.serializers import TranslatedField class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -141,3 +145,95 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'name' ] + +class EstablishmentTagCreateSerializer(serializers.ModelSerializer): + """Serializer for model EstablishmentTag.""" + class Meta: + model = models.EstablishmentTag + fields = [ + 'tag', + 'establishment' + ] + extra_kwargs = { + 'tag': {'write_only': True}, + 'establishment': {'write_only': True}, + } + + def validate(self, attrs): + """Validate method.""" + establishment = attrs.get('establishment') + tag = attrs.get('tag') + + # Check if tag is already added to establishment. + if establishment.tags.filter(tag=tag).exists(): + raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) + + # Сhecking tag availability for establishment type. + if not establishment.establishment_type.use_subtypes: + qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) + else: + # Сhecking tag availability for establishment subtype. + qs = establishment.establishment_type.tag_categories.filter( + establishmentsubtype_set__tag_category=tag.category) + if not qs.exists(): + raise serializers.ValidationError( + detail={'detail': _('Tag is not available for this establishment type|subtype.')}) + return attrs + + +class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id', read_only=True) + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentSubTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + 'establishment_subtype', + 'tag_category', + ] + extra_kwargs = { + 'establishment_subtype': {'write_only': True}, + 'tag_category': {'write_only': True}, + } + + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs + + +class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): + """Extended serializer for EstablishmentSubType model with tags.""" + tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) + + class Meta(EstablishmentSubTypeBaseSerializer.Meta): + """Meta class""" + fields = [ + 'id', + 'name_translated', + 'tag_categories' + ] + + +class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): + """Tags by establishment type""" + tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) + subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') + + class Meta(EstablishmentTypeBaseSerializer.Meta): + """Meta class.""" + fields = [ + 'id', + 'name_translated', + 'tag_categories', + 'subtypes', + ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index eec51466..1789da31 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -9,7 +9,6 @@ from favorites.models import Favorites from location.serializers import AddressBaseSerializer from main.serializers import AwardSerializer, CurrencySerializer from review import models as review_models -from tag import models as tag_models from tag.serializers import TagBaseSerializer from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions @@ -170,64 +169,6 @@ class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): return attrs -class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentSubTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_subtype', - 'tag_category', - ] - extra_kwargs = { - 'establishment_subtype': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs - - -class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): - """Extended serializer for EstablishmentSubType model with tags.""" - tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - - class Meta(EstablishmentSubTypeBaseSerializer.Meta): - """Meta class""" - fields = [ - 'id', - 'name_translated', - 'tag_categories' - ] - - -class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): - """Tags by establishment type""" - tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') - - class Meta(EstablishmentTypeBaseSerializer.Meta): - """Meta class.""" - fields = [ - 'id', - 'name_translated', - 'tag_categories', - 'subtypes', - ] - - class EstablishmentEmployeeSerializer(serializers.ModelSerializer): """Serializer for actual employees.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index e0489783..c21aecc8 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,8 +14,9 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/tags/', views.EstablishmentTagCategoryListView.as_view(), + path('/type/tags/', views.EstablishmentTagCategoryListView.as_view(), name='tag-category-list'), + path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -36,4 +37,4 @@ urlpatterns = [ path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), name='subtype-attach-tag-category'), -] \ No newline at end of file +] diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 84855982..fd61485d 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -91,6 +91,11 @@ class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): return Response(status=status.HTTP_200_OK) +class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): + """Attach tag to establishment.""" + serializer_class = serializers.EstablishmentTagCreateSerializer + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -206,9 +211,3 @@ class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): """Overridden post-method.""" super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) return Response(status=status.HTTP_200_OK) - - -# class EstablishmentTagListCreateView(generics.CreateAPIView): -# """Establishment tag list/create view.""" -# serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer -# queryset = models.EstablishmentTag.objects.all() From 47993a7155c8a682c4ec9132ff32dcb273416aef Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 10:22:17 +0300 Subject: [PATCH 252/319] added endpoint ot get list of tag categories with tags by establishment type --- apps/establishment/urls/back.py | 1 + apps/establishment/views/back.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index c21aecc8..f1afe1df 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -30,6 +30,7 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), + path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), name='type-attach-tag-category'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index fd61485d..09bf2492 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -186,6 +186,13 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() +class EstablishmentTypeTagListView(generics.ListAPIView): + """List of tags with categories by establishment type.""" + serializer_class = serializers.EstablishmentTagsByType + queryset = models.EstablishmentType.objects.all() + pagination_class = None + + class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer From 9e237d465711b6f890ee878266828acf9a6b3394 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 11:40:03 +0300 Subject: [PATCH 253/319] removed endpoint to display a list of tags by establishment id, added instead filter to filter tags by establishment id --- apps/account/views/common.py | 1 + apps/establishment/filters.py | 14 ++++++++++++++ apps/establishment/urls/back.py | 2 -- apps/establishment/views/back.py | 28 ++++------------------------ 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/apps/account/views/common.py b/apps/account/views/common.py index cb0d84d7..0a9a4d98 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -91,6 +91,7 @@ class ConfirmEmailView(JWTGenericViewMixin): else: raise utils_exceptions.UserNotFoundError() + class ConfirmPasswordView(JWTGenericViewMixin): """View for applying newly set password""" diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 51b207dc..13d951b6 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -26,3 +26,17 @@ class EstablishmentFilter(filters.FilterSet): if value not in EMPTY_VALUES: return queryset.search(value, locale=self.request.locale) return queryset + + +class EstablishmentTypeTagFilter(filters.FilterSet): + """Establishment tag filter set.""" + + type_id = filters.NumberFilter(field_name='id') + + class Meta: + """Meta class.""" + + model = models.EstablishmentType + fields = ( + 'type_id', + ) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index f1afe1df..3db81dce 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,8 +14,6 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/type/tags/', views.EstablishmentTagCategoryListView.as_view(), - name='tag-category-list'), path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), 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 09bf2492..a5e2ff47 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,10 +1,11 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, status +from rest_framework import generics, status, permissions from establishment import models, serializers from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer +from establishment.filters import EstablishmentTypeTagFilter class EstablishmentMixinViews: @@ -54,29 +55,6 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.RetrieveAPIView): - """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTagsByType - pagination_class = None - - def get_object(self): - """ - Returns the object the view is displaying. - """ - queryset = super(EstablishmentTagCategoryListView, self).get_queryset() - - # Perform the lookup filtering. - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - - filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - obj = get_object_or_404(queryset, **filter_kwargs) - - # May raise a permission denied - self.check_object_permissions(self.request, obj) - - return obj.establishment_type - - class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): """Attach tag category to establishment type.""" serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer @@ -190,6 +168,8 @@ class EstablishmentTypeTagListView(generics.ListAPIView): """List of tags with categories by establishment type.""" serializer_class = serializers.EstablishmentTagsByType queryset = models.EstablishmentType.objects.all() + filter_class = EstablishmentTypeTagFilter + permission_classes = (permissions.AllowAny, ) pagination_class = None From 469f7bdf4cb506c8a317e3a127a96bff73cdc0e1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 12:24:26 +0300 Subject: [PATCH 254/319] added prefetch select related --- apps/establishment/models.py | 15 +++++++++++++++ apps/establishment/views/back.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 77992f9d..195f029d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -16,6 +16,7 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from location.models import Address from main.models import Award +from tag.models import Tag, TagCategory from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -24,6 +25,18 @@ from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, class EstablishmentTypeQuerySet(models.QuerySet): """QuerySet for model EstablishmentType.""" + def with_base_related(self): + """Return QuerySet with base related.""" + return self.prefetch_related( + models.Prefetch('tag_categories', + EstablishmentTypeTagCategory.objects.select_related('tag_category')), + models.Prefetch('establishmentsubtype_set', + EstablishmentSubType.objects.prefetch_related( + models.Prefetch( + 'tag_categories', + EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) + ) + # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): @@ -35,6 +48,8 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): help_text='{"en-GB":"some text"}') use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + objects = EstablishmentTypeQuerySet.as_manager() + class Meta: """Meta class.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index a5e2ff47..543dbd3e 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -167,7 +167,7 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentTypeTagListView(generics.ListAPIView): """List of tags with categories by establishment type.""" serializer_class = serializers.EstablishmentTagsByType - queryset = models.EstablishmentType.objects.all() + queryset = models.EstablishmentType.objects.with_base_related() filter_class = EstablishmentTypeTagFilter permission_classes = (permissions.AllowAny, ) pagination_class = None From b3eac3666b6cfa59f6667f668560d2ec9c6a9f52 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, 14 Oct 2019 14:48:13 +0300 Subject: [PATCH 255/319] Standart user, guest. --- .../migrations/0011_merge_20191014_0839.py | 14 +++ apps/comment/permissions.py | 28 ----- apps/comment/tests.py | 103 +---------------- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 63 ++++++++++- apps/utils/tests/__init__.py | 0 apps/utils/tests/tests_json_field.py | 37 +++++++ apps/utils/tests/tests_permissions.py | 104 ++++++++++++++++++ .../{tests.py => tests/tests_translated.py} | 38 ------- 9 files changed, 220 insertions(+), 169 deletions(-) create mode 100644 apps/account/migrations/0011_merge_20191014_0839.py delete mode 100644 apps/comment/permissions.py create mode 100644 apps/utils/tests/__init__.py create mode 100644 apps/utils/tests/tests_json_field.py create mode 100644 apps/utils/tests/tests_permissions.py rename apps/utils/{tests.py => tests/tests_translated.py} (80%) diff --git a/apps/account/migrations/0011_merge_20191014_0839.py b/apps/account/migrations/0011_merge_20191014_0839.py new file mode 100644 index 00000000..653f39b7 --- /dev/null +++ b/apps/account/migrations/0011_merge_20191014_0839.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-14 08:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191011_1123'), + ('account', '0010_user_password_confirmed'), + ] + + operations = [ + ] diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py deleted file mode 100644 index 6d691c07..00000000 --- a/apps/comment/permissions.py +++ /dev/null @@ -1,28 +0,0 @@ -from rest_framework import permissions -from account.models import UserRole, Role, User - - -class IsCommentModerator(permissions.IsAuthenticatedOrReadOnly): - """ - Object-level permission to only allow owners of an object to edit it. - Assumes the model instance has an `owner` attribute. - """ - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request, - # so we'll always allow GET, HEAD or OPTIONS requests. - if request.method in permissions.SAFE_METHODS or \ - obj.user == request.user or request.user.is_superuser: - return True - - # Must have role - role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country__languages__id=obj.language_id)\ - .first() # 'Comments moderator' - - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - if obj.user != request.user and is_access: - return True - - return False - diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 949ba597..6c6c3d6a 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1,64 +1,12 @@ -from rest_framework.test import APITestCase from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie -from location.models import Country -from account.models import Role, User, UserRole -from comment.models import Comment -from translation.models import Language +from account.models import User +from utils.tests.tests_permissions import BasePermissionTests -class CommentModeratorPermissionTests(APITestCase): +class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): - - self.lang = Language.objects.create( - title='Russia', - locale='ru-RU' - ) - self.lang.save() - - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, - ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() - - self.role = Role.objects.create( - role=2, - country=self.country_ru - ) - self.role.save() - - self.moderator = User.objects.create_user(username='moderator', - email='moderator@mail.com', - password='passwordmoderator') - - self.userRole = UserRole.objects.create( - user=self.moderator, - role=self.role - ) - self.userRole.save() - - content_type = ContentType.objects.get(app_label='location', model='country') - - self.user_test = get_tokens_for_user() - self.comment = Comment.objects.create(text='Test comment', mark=1, - user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, - language=self.lang - ) - self.comment.save() - self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - - def test_get(self): - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + super().setUp() def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) @@ -76,48 +24,5 @@ class CommentModeratorPermissionTests(APITestCase): response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_put_other_user(self): - other_user = User.objects.create_user(username='test', - email='test@mail.com', - password='passwordtest') - - tokens = User.create_jwt_tokens(other_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": other_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_put_super_user(self): - super_user = User.objects.create_user(username='super', - email='super@mail.com', - password='passwordtestsuper', - is_superuser=True) - - tokens = User.create_jwt_tokens(super_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": super_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 77edfa97..7f066f30 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from comment.serializers import back as serializers from comment import models -from comment.permissions import IsCommentModerator +from utils.permissions import IsCommentModerator class CommentLstView(generics.ListCreateAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 09b24ecd..2d406850 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -1,12 +1,13 @@ """Project custom permissions""" -from rest_framework.permissions import BasePermission +from rest_framework import permissions from rest_framework_simplejwt.tokens import AccessToken +from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken -class IsAuthenticatedAndTokenIsValid(BasePermission): +class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): """ Check if user has a valid token and authenticated """ @@ -24,7 +25,7 @@ class IsAuthenticatedAndTokenIsValid(BasePermission): return False -class IsRefreshTokenValid(BasePermission): +class IsRefreshTokenValid(permissions.BasePermission): """ Check if user has a valid refresh token and authenticated """ @@ -38,3 +39,59 @@ class IsRefreshTokenValid(BasePermission): return refresh_token_qs.exists() else: return False + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS or \ + obj.user == request.user or request.user.is_superuser: + return True + return False + + +class IsGuest(permissions.IsAuthenticatedOrReadOnly): + """ + Object-level permission to only allow owners of an object to edit it. + """ + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + + return False + + +class IsStandardUser(IsGuest): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request + if super().has_object_permission(request, view, obj) or\ + obj.user == request.user or request.user.is_superuser: + return True + return False + + +class IsCommentModerator(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + + if super().has_object_permission(request, view, obj): + return True + + # Must have role + role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, + country__languages__id=obj.language_id)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + + return False diff --git a/apps/utils/tests/__init__.py b/apps/utils/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/utils/tests/tests_json_field.py b/apps/utils/tests/tests_json_field.py new file mode 100644 index 00000000..c0207def --- /dev/null +++ b/apps/utils/tests/tests_json_field.py @@ -0,0 +1,37 @@ +from django.test import TestCase +from translation.models import Language +from django.core import exceptions +from utils.serializers import validate_tjson + + +class ValidJSONTest(TestCase): + + def test_valid_json(self): + lang = Language.objects.create(title='English', locale='en-GB') + lang.save() + + data = 'str' + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_json') + + data = { + "string": "value" + } + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_translated_keys') + + data = { + "en-GB": "English" + } + + try: + validate_tjson(data) + self.assertTrue(True) + except exceptions.ValidationError: + self.assert_(False, "Test json translated FAILED") \ No newline at end of file diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py new file mode 100644 index 00000000..1e1e0e81 --- /dev/null +++ b/apps/utils/tests/tests_permissions.py @@ -0,0 +1,104 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from django.contrib.contenttypes.models import ContentType +from http.cookies import SimpleCookie +from location.models import Country +from account.models import Role, User, UserRole +from comment.models import Comment +from translation.models import Language + + +class BasePermissionTests(APITestCase): + def setUp(self): + + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.moderator = User.objects.create_user(username='moderator', + email='moderator@mail.com', + password='passwordmoderator') + + self.userRole = UserRole.objects.create( + user=self.moderator, + role=self.role + ) + self.userRole.save() + + content_type = ContentType.objects.get(app_label='location', model='country') + + self.user_test = get_tokens_for_user() + self.comment = Comment.objects.create(text='Test comment', mark=1, + user=self.user_test["user"], + object_id= self.country_ru.pk, + content_type_id=content_type.id, + language=self.lang + ) + self.comment.save() + self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + + def test_get(self): + response = self.client.get(self.url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_other_user(self): + other_user = User.objects.create_user(username='test', + email='test@mail.com', + password='passwordtest') + + tokens = User.create_jwt_tokens(other_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": other_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_put_super_user(self): + super_user = User.objects.create_user(username='super', + email='super@mail.com', + password='passwordtestsuper', + is_superuser=True) + + tokens = User.create_jwt_tokens(super_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": super_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/tests.py b/apps/utils/tests/tests_translated.py similarity index 80% rename from apps/utils/tests.py rename to apps/utils/tests/tests_translated.py index 0eaf343d..8f5caaaf 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests/tests_translated.py @@ -8,11 +8,6 @@ from http.cookies import SimpleCookie from account.models import User from news.models import News, NewsType -from django.test import TestCase -from translation.models import Language -from django.core import exceptions -from .serializers import validate_tjson - from establishment.models import Establishment, EstablishmentType, Employee @@ -125,36 +120,3 @@ class BaseAttributeTests(BaseTestCase): employee.refresh_from_db() self.assertEqual(modify_user, employee.modified_by) self.assertEqual(self.user, employee.created_by) - - -class ValidJSONTest(TestCase): - - def test_valid_json(self): - lang = Language.objects.create(title='English', locale='en-GB') - lang.save() - - data = 'str' - - with self.assertRaises(exceptions.ValidationError) as err: - validate_tjson(data) - - self.assertEqual(err.exception.code, 'invalid_json') - - data = { - "string": "value" - } - - with self.assertRaises(exceptions.ValidationError) as err: - validate_tjson(data) - - self.assertEqual(err.exception.code, 'invalid_translated_keys') - - data = { - "en-GB": "English" - } - - try: - validate_tjson(data) - self.assertTrue(True) - except exceptions.ValidationError: - self.assert_(False, "Test json translated FAILED") \ No newline at end of file From 7b4553663cee20a5147ec54a193a85705167b24c 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, 14 Oct 2019 15:45:41 +0300 Subject: [PATCH 256/319] Fix --- apps/account/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 24cc6d95..eb95fac1 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -24,7 +24,7 @@ class Role(ProjectBaseMixin): STANDARD_USER = 1 COMMENTS_MODERATOR = 2 - ROLE_CHOICES =( + ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), ) @@ -243,4 +243,4 @@ class User(AbstractUser): class UserRole(ProjectBaseMixin): """UserRole model.""" user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) - role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) \ No newline at end of file + role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) From 0656e2ae3299e665732ba246d9b923e3bc4dba76 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, 14 Oct 2019 15:48:31 +0300 Subject: [PATCH 257/319] Confirmed email for user standard --- apps/utils/permissions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2d406850..50bf78d2 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -68,7 +68,8 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request if super().has_object_permission(request, view, obj) or\ - obj.user == request.user or request.user.is_superuser: + (obj.user == request.user and obj.user.email_confirmed) \ + or request.user.is_superuser: return True return False From 6fa0051c93b6965523f8bf731e48e4c76d26f3cf Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 14 Oct 2019 15:53:40 +0300 Subject: [PATCH 258/319] Revert "Password reset confirmation mechanics" This reverts commit 71528d8 --- apps/account/models.py | 20 -------------- apps/account/serializers/web.py | 13 ---------- apps/account/tasks.py | 11 -------- apps/account/urls/common.py | 1 - apps/account/views/common.py | 26 ------------------- apps/authorization/serializers/common.py | 4 +-- apps/utils/models.py | 7 ++--- project/settings/base.py | 1 - .../account/password_confirm_email.html | 11 -------- 9 files changed, 4 insertions(+), 90 deletions(-) delete mode 100644 project/templates/account/password_confirm_email.html diff --git a/apps/account/models.py b/apps/account/models.py index 24cc6d95..2d7ad8f2 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -82,7 +82,6 @@ class User(AbstractUser): unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None) email_confirmed = models.BooleanField(_('email status'), default=False) newsletter = models.NullBooleanField(default=True) - password_confirmed = models.BooleanField(_('is new password confirmed'), default=True, null=False) EMAIL_FIELD = 'email' USERNAME_FIELD = 'username' @@ -139,10 +138,6 @@ class User(AbstractUser): self.email_confirmed = True self.save() - def confirm_password(self): - self.password_confirmed = True - self.save() - def approve(self): """Set user is_active status to True""" self.is_active = True @@ -177,11 +172,6 @@ class User(AbstractUser): """Make a token for finish signup.""" return password_token_generator.make_token(self) - @property - def confirm_password_token(self): - """Make a token for new password confirmation """ - return GMTokenGenerator(purpose=GMTokenGenerator.CONFIRM_PASSWORD).make_token(self) - @property def get_user_uidb64(self): """Get base64 value for user by primary key identifier""" @@ -211,16 +201,6 @@ class User(AbstractUser): template_name=settings.RESETTING_TOKEN_TEMPLATE, context=context) - def confirm_password_template(self, country_code): - """Get confirm password template""" - context = {'token': self.confirm_password_token, - 'country_code': country_code} - context.update(self.base_template) - return render_to_string( - template_name=settings.CONFIRM_PASSWORD_TEMPLATE, - context=context, - ) - def confirm_email_template(self, country_code): """Get confirm email template""" context = {'token': self.confirm_email_token, diff --git a/apps/account/serializers/web.py b/apps/account/serializers/web.py index dd8ccec8..8be73afa 100644 --- a/apps/account/serializers/web.py +++ b/apps/account/serializers/web.py @@ -1,10 +1,8 @@ """Serializers for account web""" from django.contrib.auth import password_validation as password_validators -from django.conf import settings from rest_framework import serializers from account import models -from account import tasks from utils import exceptions as utils_exceptions from utils.methods import username_validator @@ -69,16 +67,5 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer): """Override update method""" # Update user password from instance instance.set_password(validated_data.get('password')) - instance.password_confirmed = False instance.save() - if settings.USE_CELERY: - tasks.send_reset_password_confirm.delay( - user=instance, - country_code=self.context.get('request').country_code, - ) - else: - tasks.send_reset_password_confirm( - user=instance, - country_code=self.context.get('request').country_code, - ) return instance diff --git a/apps/account/tasks.py b/apps/account/tasks.py index 3729c40c..03a231b3 100644 --- a/apps/account/tasks.py +++ b/apps/account/tasks.py @@ -22,17 +22,6 @@ def send_reset_password_email(user_id, country_code): f'DETAIL: Exception occurred for reset password: ' f'{user_id}') -@shared_task -def send_reset_password_confirm(user: models.User, country_code): - """ Send email to user for applying new password. """ - try: - user.send_email(subject=_('New password confirmation'), - message=user.confirm_password_template(country_code)) - except: - logger.error(f'METHOD_NAME: {send_reset_password_confirm.__name__}\n' - f'DETAIL: Exception occured for new passwordconfirmation', - f'{user.id}') - @shared_task def confirm_new_email_address(user_id, country_code): diff --git a/apps/account/urls/common.py b/apps/account/urls/common.py index a440c5bf..4ea2af66 100644 --- a/apps/account/urls/common.py +++ b/apps/account/urls/common.py @@ -8,7 +8,6 @@ app_name = 'account' urlpatterns = [ path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'), path('change-password/', views.ChangePasswordView.as_view(), name='change-password'), - path('change-password-confirm///', views.ConfirmPasswordView.as_view(), name='change-password'), path('email/confirm/', views.SendConfirmationEmailView.as_view(), name='send-confirm-email'), path('email/confirm///', views.ConfirmEmailView.as_view(), name='confirm-email'), ] diff --git a/apps/account/views/common.py b/apps/account/views/common.py index cb0d84d7..d29ce2bb 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -91,32 +91,6 @@ class ConfirmEmailView(JWTGenericViewMixin): else: raise utils_exceptions.UserNotFoundError() -class ConfirmPasswordView(JWTGenericViewMixin): - """View for applying newly set password""" - - permission_classes = (permissions.AllowAny,) - - def get(self, request, *args, **kwargs): - uidb64 = kwargs.get('uidb64') - token = kwargs.get('token') - uid = force_text(urlsafe_base64_decode(uidb64)) - user_qs = models.User.objects.filter(pk=uid) - if user_qs.exists(): - user = user_qs.first() - if not GMTokenGenerator(GMTokenGenerator.CONFIRM_PASSWORD).check_token( - user, token): - raise utils_exceptions.NotValidTokenError() - user.confirm_password() - tokens = user.create_jwt_tokens() - return self._put_cookies_in_response( - cookies=self._put_data_in_cookies( - access_token=tokens.get('access_token'), - refresh_token=tokens.get('refresh_token')), - response=Response(status=status.HTTP_200_OK)) - else: - raise utils_exceptions.UserNotFoundError() - - # Firebase Cloud Messaging class FCMDeviceViewSet(generics.GenericAPIView): diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 6be76a00..ed68ba9f 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -108,8 +108,8 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin, """Override validate method""" username_or_email = attrs.pop('username_or_email') password = attrs.pop('password') - user_qs = account_models.User.objects.filter(password_confirmed=True)\ - .filter(Q(username=username_or_email) | Q(email=username_or_email)) + user_qs = account_models.User.objects.filter(Q(username=username_or_email) | + Q(email=username_or_email)) if not user_qs.exists(): raise utils_exceptions.WrongAuthCredentials() else: diff --git a/apps/utils/models.py b/apps/utils/models.py index e5a39895..4e6df35e 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -258,14 +258,12 @@ class GMTokenGenerator(PasswordResetTokenGenerator): RESET_PASSWORD = 1 CHANGE_PASSWORD = 2 CONFIRM_EMAIL = 3 - CONFIRM_PASSWORD = 4 TOKEN_CHOICES = ( CHANGE_EMAIL, RESET_PASSWORD, CHANGE_PASSWORD, - CONFIRM_EMAIL, - CONFIRM_PASSWORD, + CONFIRM_EMAIL ) def __init__(self, purpose: int): @@ -281,8 +279,7 @@ class GMTokenGenerator(PasswordResetTokenGenerator): self.purpose == self.CONFIRM_EMAIL: fields.extend([str(user.email_confirmed), str(user.email)]) elif self.purpose == self.RESET_PASSWORD or \ - self.purpose == self.CHANGE_PASSWORD or \ - self.purpose == self.CONFIRM_PASSWORD: + self.purpose == self.CHANGE_PASSWORD: fields.append(str(user.password)) return fields diff --git a/project/settings/base.py b/project/settings/base.py index 7d9b8c05..06f83811 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -405,7 +405,6 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1 # TEMPLATES RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' CHANGE_EMAIL_TEMPLATE = 'account/change_email.html' -CONFIRM_PASSWORD_TEMPLATE = 'account/password_confirm_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html' NEWS_EMAIL_TEMPLATE = "news/news_email.html" diff --git a/project/templates/account/password_confirm_email.html b/project/templates/account/password_confirm_email.html deleted file mode 100644 index 29f27afb..00000000 --- a/project/templates/account/password_confirm_email.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n %}{% autoescape off %} -{% blocktrans %}Confirm a password reset for your user account at {{ site_name }}.{% endblocktrans %} - -{% trans "Please go to the following page:" %} - -https://{{ country_code }}.{{ domain_uri }}/confirm-new-password/{{ uidb64 }}/{{ token }}/ - -{% trans "Thanks for using our site!" %} - -{% blocktrans %}The {{ site_name }} team{% endblocktrans %} -{% endautoescape %} \ No newline at end of file From 8eaf611d7e6231a43245331c6d8574991f601650 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 14 Oct 2019 16:02:02 +0300 Subject: [PATCH 259/319] Reverting migration --- .../migrations/0011_merge_20191014_1258.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/account/migrations/0011_merge_20191014_1258.py diff --git a/apps/account/migrations/0011_merge_20191014_1258.py b/apps/account/migrations/0011_merge_20191014_1258.py new file mode 100644 index 00000000..cee0a824 --- /dev/null +++ b/apps/account/migrations/0011_merge_20191014_1258.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-14 12:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191011_1123'), + ('account', '0010_user_password_confirmed'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='password_confirmed', + ), + ] From fb4cf25dcef162164e2d917cb7df5ab55e3aa93c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 14 Oct 2019 16:43:59 +0300 Subject: [PATCH 260/319] Fix empty email after creating account --- apps/account/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 2d7ad8f2..5052969e 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -133,8 +133,9 @@ class User(AbstractUser): def confirm_email(self): """Method to confirm user email address""" - self.email = self.unconfirmed_email - self.unconfirmed_email = None + if self.unconfirmed_email is not None: + self.email = self.unconfirmed_email + self.unconfirmed_email = None self.email_confirmed = True self.save() From 1bd3cc91703bf26fd98707f8256820c59c2f741a 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, 14 Oct 2019 17:32:34 +0300 Subject: [PATCH 261/319] 1 --- apps/account/models.py | 5 ++++- apps/utils/permissions.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index eb95fac1..4f227b56 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -23,14 +23,17 @@ class Role(ProjectBaseMixin): """Base Role model.""" STANDARD_USER = 1 COMMENTS_MODERATOR = 2 + COUNTRY_ADMIN = 3 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), + (COUNTRY_ADMIN, 'Country admin'), ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) - country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) + country = models.ForeignKey(Country, verbose_name=_('Country'), + null=True, blank=True, on_delete=models.SET_NULL) # is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 50bf78d2..47689ed5 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -54,7 +54,7 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): Object-level permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: + if request.method in permissions.SAFE_METHODS or request.user.is_superuser: return True return False @@ -68,8 +68,7 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request if super().has_object_permission(request, view, obj) or\ - (obj.user == request.user and obj.user.email_confirmed) \ - or request.user.is_superuser: + (obj.user == request.user and obj.user.email_confirmed): return True return False @@ -96,3 +95,18 @@ class IsCommentModerator(IsStandardUser): return True return False + + +class IsCountryAdmin(IsGuest): + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + + # Must have role + role = Role.objects.filter(role=Role.COUNTRY_ADMIN).first() # 'Country admin' + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + + if super().has_object_permission(request, view, obj) and is_access: + return True + + return False From 01a32f66e83a83e40af14aa0caa823f582d335ce 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, 14 Oct 2019 17:38:37 +0300 Subject: [PATCH 262/319] Fix location migrate sql --- apps/location/migrations/migrate_lang.sql | 62 ++++++++++----------- apps/location/migrations/remigrate_lang.sql | 56 +++++++++---------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 409e7bf3..25364e64 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -3,18 +3,18 @@ delete from location_country_languages; delete from translation_language as tl; delete from location_country as lc; -CREATE TABLE public.codelang ( +CREATE TABLE codelang ( code varchar(100) NULL, country varchar(10000) NULL ); -- Permissions -ALTER TABLE public.codelang OWNER TO postgres; -GRANT ALL ON TABLE public.codelang TO postgres; +ALTER TABLE codelang OWNER TO postgres; +GRANT ALL ON TABLE codelang TO postgres; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('af','Afrikaans') ,('af-ZA','Afrikaans (South Africa)') ,('ar','Arabic') @@ -26,7 +26,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ar-JO','Arabic (Jordan)') ,('ar-KW','Arabic (Kuwait)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('ar-LB','Arabic (Lebanon)') ,('ar-LY','Arabic (Libya)') ,('ar-MA','Arabic (Morocco)') @@ -38,7 +38,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ar-YE','Arabic (Yemen)') ,('az','Azeri (Latin)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('az-AZ','Azeri (Latin) (Azerbaijan)') ,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') ,('be','Belarusian') @@ -50,7 +50,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ca-ES','Catalan (Spain)') ,('cs','Czech') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('cs-CZ','Czech (Czech Republic)') ,('cy','Welsh') ,('cy-GB','Welsh (United Kingdom)') @@ -62,7 +62,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('de-DE','German (Germany)') ,('de-LI','German (Liechtenstein)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('de-LU','German (Luxembourg)') ,('dv','Divehi') ,('dv-MV','Divehi (Maldives)') @@ -74,7 +74,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('en-CA','English (Canada)') ,('en-CB','English (Caribbean)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('en-GB','English (United Kingdom)') ,('en-IE','English (Ireland)') ,('en-JM','English (Jamaica)') @@ -86,7 +86,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('en-ZW','English (Zimbabwe)') ,('eo','Esperanto') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es','Spanish') ,('es-AR','Spanish (Argentina)') ,('es-BO','Spanish (Bolivia)') @@ -98,7 +98,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('es-ES','Spanish (Castilian)') ,('es-ES','Spanish (Spain)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es-GT','Spanish (Guatemala)') ,('es-HN','Spanish (Honduras)') ,('es-MX','Spanish (Mexico)') @@ -110,7 +110,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('es-SV','Spanish (El Salvador)') ,('es-UY','Spanish (Uruguay)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es-VE','Spanish (Venezuela)') ,('et','Estonian') ,('et-EE','Estonian (Estonia)') @@ -122,7 +122,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('fi-FI','Finnish (Finland)') ,('fo','Faroese') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('fo-FO','Faroese (Faroe Islands)') ,('fr','French') ,('fr-BE','French (Belgium)') @@ -134,7 +134,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('gl','Galician') ,('gl-ES','Galician (Spain)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('gu','Gujarati') ,('gu-IN','Gujarati (India)') ,('he','Hebrew') @@ -146,7 +146,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('hr-HR','Croatian (Croatia)') ,('hu','Hungarian') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('hu-HU','Hungarian (Hungary)') ,('hy','Armenian') ,('hy-AM','Armenian (Armenia)') @@ -158,7 +158,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('it-CH','Italian (Switzerland)') ,('it-IT','Italian (Italy)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('ja','Japanese') ,('ja-JP','Japanese (Japan)') ,('ka','Georgian') @@ -170,7 +170,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ko','Korean') ,('ko-KR','Korean (Korea)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('kok','Konkani') ,('kok-IN','Konkani (India)') ,('ky','Kyrgyz') @@ -182,7 +182,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('mi','Maori') ,('mi-NZ','Maori (New Zealand)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('mk','FYRO Macedonian') ,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') ,('mn','Mongolian') @@ -194,7 +194,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ms-MY','Malay (Malaysia)') ,('mt','Maltese') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('mt-MT','Maltese (Malta)') ,('nb','Norwegian (Bokm?l)') ,('nb-NO','Norwegian (Bokm?l) (Norway)') @@ -206,7 +206,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ns-ZA','Northern Sotho (South Africa)') ,('pa','Punjabi') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('pa-IN','Punjabi (India)') ,('pl','Polish') ,('pl-PL','Polish (Poland)') @@ -218,7 +218,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('qu','Quechua') ,('qu-BO','Quechua (Bolivia)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('qu-EC','Quechua (Ecuador)') ,('qu-PE','Quechua (Peru)') ,('ro','Romanian') @@ -230,7 +230,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('se','Sami (Northern)') ,('se-FI','Sami (Northern) (Finland)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('se-FI','Sami (Skolt) (Finland)') ,('se-FI','Sami (Inari) (Finland)') ,('se-NO','Sami (Northern) (Norway)') @@ -242,7 +242,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('sk','Slovak') ,('sk-SK','Slovak (Slovakia)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('sl','Slovenian') ,('sl-SI','Slovenian (Slovenia)') ,('sq','Albanian') @@ -254,7 +254,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('sv','Swedish') ,('sv-FI','Swedish (Finland)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('sv-SE','Swedish (Sweden)') ,('sw','Swahili') ,('sw-KE','Swahili (Kenya)') @@ -266,7 +266,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('te-IN','Telugu (India)') ,('th','Thai') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('th-TH','Thai (Thailand)') ,('tl','Tagalog') ,('tl-PH','Tagalog (Philippines)') @@ -278,7 +278,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('tt-RU','Tatar (Russia)') ,('ts','Tsonga') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('uk','Ukrainian') ,('uk-UA','Ukrainian (Ukraine)') ,('ur','Urdu') @@ -290,7 +290,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('vi-VN','Vietnamese (Viet Nam)') ,('xh','Xhosa') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('xh-ZA','Xhosa (South Africa)') ,('zh','Chinese') ,('zh-CN','Chinese (S)') @@ -304,7 +304,7 @@ INSERT INTO public.codelang (code,country) VALUES /***************************/ -- Manual migrate -CREATE TABLE public.country_code ( +CREATE TABLE country_code ( code varchar(100) NULL, country varchar(10000) NULL ); @@ -332,7 +332,7 @@ commit; --delete from location_country as lc -INSERT INTO public.location_country +INSERT INTO location_country (code, "name", low_price, high_price, created, modified) select lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, @@ -353,7 +353,7 @@ commit; --delete from translation_language as tl; -INSERT INTO public.translation_language +INSERT INTO translation_language (title, locale) select t.country as title, @@ -371,7 +371,7 @@ commit; --delete from location_country_languages -INSERT INTO public.location_country_languages +INSERT INTO location_country_languages (country_id, language_id) select lc.id as country_id, l.id as language_id diff --git a/apps/location/migrations/remigrate_lang.sql b/apps/location/migrations/remigrate_lang.sql index 910dfcfa..35033cdb 100644 --- a/apps/location/migrations/remigrate_lang.sql +++ b/apps/location/migrations/remigrate_lang.sql @@ -1,15 +1,15 @@ -CREATE TABLE public.codelang ( +CREATE TABLE codelang ( code varchar(100) NULL, country varchar(10000) NULL ); -- Permissions -ALTER TABLE public.codelang OWNER TO postgres; -GRANT ALL ON TABLE public.codelang TO postgres; +ALTER TABLE codelang OWNER TO postgres; +GRANT ALL ON TABLE codelang TO postgres; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('af','Afrikaans') ,('af-ZA','Afrikaans (South Africa)') ,('ar','Arabic') @@ -21,7 +21,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ar-JO','Arabic (Jordan)') ,('ar-KW','Arabic (Kuwait)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('ar-LB','Arabic (Lebanon)') ,('ar-LY','Arabic (Libya)') ,('ar-MA','Arabic (Morocco)') @@ -33,7 +33,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ar-YE','Arabic (Yemen)') ,('az','Azeri (Latin)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('az-AZ','Azeri (Latin) (Azerbaijan)') ,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') ,('be','Belarusian') @@ -45,7 +45,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ca-ES','Catalan (Spain)') ,('cs','Czech') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('cs-CZ','Czech (Czech Republic)') ,('cy','Welsh') ,('cy-GB','Welsh (United Kingdom)') @@ -57,7 +57,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('de-DE','German (Germany)') ,('de-LI','German (Liechtenstein)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('de-LU','German (Luxembourg)') ,('dv','Divehi') ,('dv-MV','Divehi (Maldives)') @@ -69,7 +69,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('en-CA','English (Canada)') ,('en-CB','English (Caribbean)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('en-GB','English (United Kingdom)') ,('en-IE','English (Ireland)') ,('en-JM','English (Jamaica)') @@ -81,7 +81,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('en-ZW','English (Zimbabwe)') ,('eo','Esperanto') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es','Spanish') ,('es-AR','Spanish (Argentina)') ,('es-BO','Spanish (Bolivia)') @@ -93,7 +93,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('es-ES','Spanish (Castilian)') ,('es-ES','Spanish (Spain)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es-GT','Spanish (Guatemala)') ,('es-HN','Spanish (Honduras)') ,('es-MX','Spanish (Mexico)') @@ -105,7 +105,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('es-SV','Spanish (El Salvador)') ,('es-UY','Spanish (Uruguay)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('es-VE','Spanish (Venezuela)') ,('et','Estonian') ,('et-EE','Estonian (Estonia)') @@ -117,7 +117,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('fi-FI','Finnish (Finland)') ,('fo','Faroese') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('fo-FO','Faroese (Faroe Islands)') ,('fr','French') ,('fr-BE','French (Belgium)') @@ -129,7 +129,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('gl','Galician') ,('gl-ES','Galician (Spain)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('gu','Gujarati') ,('gu-IN','Gujarati (India)') ,('he','Hebrew') @@ -141,7 +141,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('hr-HR','Croatian (Croatia)') ,('hu','Hungarian') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('hu-HU','Hungarian (Hungary)') ,('hy','Armenian') ,('hy-AM','Armenian (Armenia)') @@ -153,7 +153,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('it-CH','Italian (Switzerland)') ,('it-IT','Italian (Italy)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('ja','Japanese') ,('ja-JP','Japanese (Japan)') ,('ka','Georgian') @@ -165,7 +165,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ko','Korean') ,('ko-KR','Korean (Korea)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('kok','Konkani') ,('kok-IN','Konkani (India)') ,('ky','Kyrgyz') @@ -177,7 +177,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('mi','Maori') ,('mi-NZ','Maori (New Zealand)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('mk','FYRO Macedonian') ,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') ,('mn','Mongolian') @@ -189,7 +189,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ms-MY','Malay (Malaysia)') ,('mt','Maltese') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('mt-MT','Maltese (Malta)') ,('nb','Norwegian (Bokm?l)') ,('nb-NO','Norwegian (Bokm?l) (Norway)') @@ -201,7 +201,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('ns-ZA','Northern Sotho (South Africa)') ,('pa','Punjabi') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('pa-IN','Punjabi (India)') ,('pl','Polish') ,('pl-PL','Polish (Poland)') @@ -213,7 +213,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('qu','Quechua') ,('qu-BO','Quechua (Bolivia)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('qu-EC','Quechua (Ecuador)') ,('qu-PE','Quechua (Peru)') ,('ro','Romanian') @@ -225,7 +225,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('se','Sami (Northern)') ,('se-FI','Sami (Northern) (Finland)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('se-FI','Sami (Skolt) (Finland)') ,('se-FI','Sami (Inari) (Finland)') ,('se-NO','Sami (Northern) (Norway)') @@ -237,7 +237,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('sk','Slovak') ,('sk-SK','Slovak (Slovakia)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('sl','Slovenian') ,('sl-SI','Slovenian (Slovenia)') ,('sq','Albanian') @@ -249,7 +249,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('sv','Swedish') ,('sv-FI','Swedish (Finland)') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('sv-SE','Swedish (Sweden)') ,('sw','Swahili') ,('sw-KE','Swahili (Kenya)') @@ -261,7 +261,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('te-IN','Telugu (India)') ,('th','Thai') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('th-TH','Thai (Thailand)') ,('tl','Tagalog') ,('tl-PH','Tagalog (Philippines)') @@ -273,7 +273,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('tt-RU','Tatar (Russia)') ,('ts','Tsonga') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('uk','Ukrainian') ,('uk-UA','Ukrainian (Ukraine)') ,('ur','Urdu') @@ -285,7 +285,7 @@ INSERT INTO public.codelang (code,country) VALUES ,('vi-VN','Vietnamese (Viet Nam)') ,('xh','Xhosa') ; -INSERT INTO public.codelang (code,country) VALUES +INSERT INTO codelang (code,country) VALUES ('xh-ZA','Xhosa (South Africa)') ,('zh','Chinese') ,('zh-CN','Chinese (S)') @@ -299,7 +299,7 @@ INSERT INTO public.codelang (code,country) VALUES /***************************/ -- Manual migrate -CREATE TABLE public.country_code ( +CREATE TABLE country_code ( code varchar(100) NULL, country varchar(10000) NULL ); From 8d2d5b87ac83446d86e6a9cd0721f088790bf163 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, 14 Oct 2019 17:56:51 +0300 Subject: [PATCH 263/319] Fix migrate SQL --- apps/location/migrations/migrate_lang.sql | 3 +++ apps/location/migrations/remigrate_lang.sql | 2 ++ 2 files changed, 5 insertions(+) diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 25364e64..5df451fa 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -1,3 +1,6 @@ +SET search_path TO gm, public; + + delete from comment_comment as cc; delete from location_country_languages; delete from translation_language as tl; diff --git a/apps/location/migrations/remigrate_lang.sql b/apps/location/migrations/remigrate_lang.sql index 35033cdb..94eb79d4 100644 --- a/apps/location/migrations/remigrate_lang.sql +++ b/apps/location/migrations/remigrate_lang.sql @@ -1,3 +1,5 @@ +SET search_path TO gm, public; + CREATE TABLE codelang ( code varchar(100) NULL, country varchar(10000) NULL From 275c5b467c45bfa97351927b487b9a83a2e8aa59 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, 14 Oct 2019 18:15:17 +0300 Subject: [PATCH 264/319] fixa --- apps/location/migrations/migrate_lang.sql | 11 ----------- apps/location/migrations/remigrate_lang.sql | 5 ----- 2 files changed, 16 deletions(-) diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 5df451fa..11c93573 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -1,21 +1,10 @@ SET search_path TO gm, public; - -delete from comment_comment as cc; -delete from location_country_languages; -delete from translation_language as tl; -delete from location_country as lc; - CREATE TABLE codelang ( code varchar(100) NULL, country varchar(10000) NULL ); --- Permissions - -ALTER TABLE codelang OWNER TO postgres; -GRANT ALL ON TABLE codelang TO postgres; - INSERT INTO codelang (code,country) VALUES ('af','Afrikaans') diff --git a/apps/location/migrations/remigrate_lang.sql b/apps/location/migrations/remigrate_lang.sql index 94eb79d4..160ac93e 100644 --- a/apps/location/migrations/remigrate_lang.sql +++ b/apps/location/migrations/remigrate_lang.sql @@ -5,11 +5,6 @@ CREATE TABLE codelang ( country varchar(10000) NULL ); --- Permissions - -ALTER TABLE codelang OWNER TO postgres; -GRANT ALL ON TABLE codelang TO postgres; - INSERT INTO codelang (code,country) VALUES ('af','Afrikaans') From 2b35d3473b5c4f1be8a2bb557cc74a71f44df613 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 18:23:03 +0300 Subject: [PATCH 265/319] fix settings --- 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 06f83811..5c32f263 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -283,15 +283,15 @@ SMS_CODE_SHOW = False # SMSC Settings SMS_SERVICE = 'http://smsc.ru/sys/send.php' -SMS_LOGIN = 'GM2019' -SMS_PASSWORD = '}#6%Qe7CYG7n' +SMS_LOGIN = os.environ.get('SMS_LOGIN') +SMS_PASSWORD = os.environ.get('SMS_PASSWORD') SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.yandex.ru' -EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' -EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' +EMAIL_HOST = 'smtp.mandrillapp.com' +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_PORT = 587 # Django Rest Swagger From 43531d88dc7ebd897737363f131eec88fb22927e 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: Tue, 15 Oct 2019 10:05:31 +0300 Subject: [PATCH 266/319] Country id --- .../migrations/0003_auto_20191015_0704.py | 24 +++++++++++++++++++ apps/comment/models.py | 8 ++++++- apps/utils/permissions.py | 2 +- apps/utils/tests/tests_permissions.py | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 apps/comment/migrations/0003_auto_20191015_0704.py diff --git a/apps/comment/migrations/0003_auto_20191015_0704.py b/apps/comment/migrations/0003_auto_20191015_0704.py new file mode 100644 index 00000000..09296253 --- /dev/null +++ b/apps/comment/migrations/0003_auto_20191015_0704.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-15 07:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('comment', '0002_comment_language'), + ] + + operations = [ + migrations.RemoveField( + model_name='comment', + name='language', + ), + migrations.AddField( + model_name='comment', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'), + ), + ] diff --git a/apps/comment/models.py b/apps/comment/models.py index 0193055d..08ae2cde 100644 --- a/apps/comment/models.py +++ b/apps/comment/models.py @@ -7,6 +7,8 @@ from account.models import User from utils.models import ProjectBaseMixin from utils.querysets import ContentTypeQuerySetMixin from translation.models import Language +from location.models import Country + class CommentQuerySet(ContentTypeQuerySetMixin): """QuerySets for Comment model.""" @@ -41,7 +43,11 @@ class Comment(ProjectBaseMixin): content_object = generic.GenericForeignKey('content_type', 'object_id') objects = CommentQuerySet.as_manager() - language = models.ForeignKey(Language, verbose_name=_('Locale'), on_delete=models.SET_NULL, null=True) + country = models.ForeignKey(Country, verbose_name=_('Country'), + on_delete=models.SET_NULL, null=True) + # language = models.ForeignKey(Language, verbose_name=_('Locale'), + # on_delete=models.SET_NULL, null=True + # ) class Meta: """Meta class""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 47689ed5..f0da64e4 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -103,7 +103,7 @@ class IsCountryAdmin(IsGuest): # Read permissions are allowed to any request. # Must have role - role = Role.objects.filter(role=Role.COUNTRY_ADMIN).first() # 'Country admin' + role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id).first() # 'Country admin' is_access = UserRole.objects.filter(user=request.user, role=role).exists() if super().has_object_permission(request, view, obj) and is_access: diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index 1e1e0e81..f68f71d6 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -51,7 +51,7 @@ class BasePermissionTests(APITestCase): user=self.user_test["user"], object_id= self.country_ru.pk, content_type_id=content_type.id, - language=self.lang + country=self.country_ru ) self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) From 1764cfc08b17f6c36f5cdd85d87a8b9487e2bb1f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 15 Oct 2019 12:52:21 +0300 Subject: [PATCH 267/319] added urlpath to get establishment tags to all platform --- apps/establishment/urls/common.py | 1 + project/urls/back.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 5d7df146..18b768c0 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,6 +7,7 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), + path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/project/urls/back.py b/project/urls/back.py index 7cfcd038..eb049b9c 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,7 +7,7 @@ urlpatterns = [ path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('tags/', include(('tag.urls', 'tag'), namespace='tag')) + path('tags/', include(('tag.urls', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] From ff7df815f44aaca0092477e8ad8c2efaf56b07c9 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: Tue, 15 Oct 2019 15:20:44 +0300 Subject: [PATCH 268/319] Fix country_id --- .../account/migrations/0012_merge_20191015_0708.py | 14 ++++++++++++++ apps/comment/models.py | 3 --- apps/utils/permissions.py | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 apps/account/migrations/0012_merge_20191015_0708.py diff --git a/apps/account/migrations/0012_merge_20191015_0708.py b/apps/account/migrations/0012_merge_20191015_0708.py new file mode 100644 index 00000000..91dba02e --- /dev/null +++ b/apps/account/migrations/0012_merge_20191015_0708.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 07:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0011_merge_20191014_0839'), + ('account', '0011_merge_20191014_1258'), + ] + + operations = [ + ] diff --git a/apps/comment/models.py b/apps/comment/models.py index 08ae2cde..55c7802e 100644 --- a/apps/comment/models.py +++ b/apps/comment/models.py @@ -45,9 +45,6 @@ class Comment(ProjectBaseMixin): objects = CommentQuerySet.as_manager() country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.SET_NULL, null=True) - # language = models.ForeignKey(Language, verbose_name=_('Locale'), - # on_delete=models.SET_NULL, null=True - # ) class Meta: """Meta class""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f0da64e4..3b408b31 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -87,7 +87,7 @@ class IsCommentModerator(IsStandardUser): # Must have role role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country__languages__id=obj.language_id)\ + country_id=obj.country_id)\ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() From 9acef83894da6573ad991fb4b8c72dc2cf2f0a96 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: Tue, 15 Oct 2019 15:23:42 +0300 Subject: [PATCH 269/319] Add country admin --- apps/utils/permissions.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 3b408b31..dec3b848 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -73,7 +73,28 @@ class IsStandardUser(IsGuest): return False -class IsCommentModerator(IsStandardUser): +class IsCountryAdmin(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + if super().has_object_permission(request, view, obj): + return True + + # Must have role + role = Role.objects.filter(role=Role.COUNTRY_ADMIN, + country_id=obj.country_id) \ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + return False + + +class IsCommentModerator(IsCountryAdmin): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. From 322cfcd89d21570f2982bf0f83c5692cd5c50ba7 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: Tue, 15 Oct 2019 15:33:28 +0300 Subject: [PATCH 270/319] Fix test --- apps/comment/tests.py | 85 ++++++++++++++++++++++++++- apps/utils/permissions.py | 15 ----- apps/utils/tests/tests_permissions.py | 79 ------------------------- 3 files changed, 82 insertions(+), 97 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 6c6c3d6a..8cbcee88 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1,13 +1,46 @@ -from rest_framework import status -from http.cookies import SimpleCookie -from account.models import User from utils.tests.tests_permissions import BasePermissionTests +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from django.contrib.contenttypes.models import ContentType +from http.cookies import SimpleCookie +from account.models import Role, User, UserRole +from comment.models import Comment class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): super().setUp() + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.moderator = User.objects.create_user(username='moderator', + email='moderator@mail.com', + password='passwordmoderator') + + self.userRole = UserRole.objects.create( + user=self.moderator, + role=self.role + ) + self.userRole.save() + + content_type = ContentType.objects.get(app_label='location', model='country') + + self.user_test = get_tokens_for_user() + self.comment = Comment.objects.create(text='Test comment', mark=1, + user=self.user_test["user"], + object_id= self.country_ru.pk, + content_type_id=content_type.id, + country=self.country_ru + ) + self.comment.save() + self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + + def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) self.client.cookies = SimpleCookie( @@ -24,5 +57,51 @@ class CommentModeratorPermissionTests(BasePermissionTests): response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_get(self): + response = self.client.get(self.url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_other_user(self): + other_user = User.objects.create_user(username='test', + email='test@mail.com', + password='passwordtest') + + tokens = User.create_jwt_tokens(other_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": other_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_put_super_user(self): + super_user = User.objects.create_user(username='super', + email='super@mail.com', + password='passwordtestsuper', + is_superuser=True) + + tokens = User.create_jwt_tokens(super_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": super_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index dec3b848..2754a3c5 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -116,18 +116,3 @@ class IsCommentModerator(IsCountryAdmin): return True return False - - -class IsCountryAdmin(IsGuest): - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request. - - # Must have role - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id).first() # 'Country admin' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - - if super().has_object_permission(request, view, obj) and is_access: - return True - - return False diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index f68f71d6..ccb202ab 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -1,12 +1,5 @@ from rest_framework.test import APITestCase -from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType -from http.cookies import SimpleCookie from location.models import Country -from account.models import Role, User, UserRole -from comment.models import Comment from translation.models import Language @@ -28,77 +21,5 @@ class BasePermissionTests(APITestCase): self.country_ru.languages.add(self.lang) self.country_ru.save() - self.role = Role.objects.create( - role=2, - country=self.country_ru - ) - self.role.save() - self.moderator = User.objects.create_user(username='moderator', - email='moderator@mail.com', - password='passwordmoderator') - self.userRole = UserRole.objects.create( - user=self.moderator, - role=self.role - ) - self.userRole.save() - - content_type = ContentType.objects.get(app_label='location', model='country') - - self.user_test = get_tokens_for_user() - self.comment = Comment.objects.create(text='Test comment', mark=1, - user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, - country=self.country_ru - ) - self.comment.save() - self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - - def test_get(self): - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_put_other_user(self): - other_user = User.objects.create_user(username='test', - email='test@mail.com', - password='passwordtest') - - tokens = User.create_jwt_tokens(other_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": other_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_put_super_user(self): - super_user = User.objects.create_user(username='super', - email='super@mail.com', - password='passwordtestsuper', - is_superuser=True) - - tokens = User.create_jwt_tokens(super_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": super_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) From 60cdec99e02a9b0cbab04110517cebe4cfe80772 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 17:17:03 +0300 Subject: [PATCH 271/319] refactor tags --- apps/establishment/admin.py | 28 +-- .../migrations/0037_auto_20191015_1404.py | 54 +++++ apps/establishment/models.py | 202 ++++++++++-------- apps/establishment/serializers/back.py | 182 ++++++++-------- apps/establishment/serializers/common.py | 84 ++++---- apps/establishment/urls/back.py | 12 +- apps/establishment/urls/common.py | 2 +- apps/establishment/views/back.py | 70 +++--- apps/search_indexes/documents/news.py | 18 +- apps/tag/filters.py | 48 +++++ apps/tag/models.py | 12 +- apps/tag/serializers.py | 46 ++-- apps/tag/urls/__init__.py | 0 apps/tag/{urls.py => urls/back.py} | 10 +- apps/tag/urls/web.py | 16 ++ apps/tag/views.py | 28 ++- project/settings/base.py | 2 +- project/urls/back.py | 2 +- project/urls/web.py | 1 + 19 files changed, 494 insertions(+), 323 deletions(-) create mode 100644 apps/establishment/migrations/0037_auto_20191015_1404.py create mode 100644 apps/tag/filters.py create mode 100644 apps/tag/urls/__init__.py rename apps/tag/{urls.py => urls/back.py} (53%) create mode 100644 apps/tag/urls/web.py diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 1f200f41..8a5e57de 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -80,17 +80,17 @@ class MenuAdmin(admin.ModelAdmin): category_translated.short_description = _('category') - -@admin.register(models.EstablishmentTypeTagCategory) -class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): - """EstablishmentTypeTagCategory admin.""" - - -@admin.register(models.EstablishmentSubTypeTagCategory) -class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): - """EstablishmentTypeTagCategory admin.""" - - -@admin.register(models.EstablishmentTag) -class EstablishmentTagAdmin(admin.ModelAdmin): - """EstablishmentTag admin.""" +# +# @admin.register(models.EstablishmentTypeTagCategory) +# class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): +# """EstablishmentTypeTagCategory admin.""" +# +# +# @admin.register(models.EstablishmentSubTypeTagCategory) +# class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): +# """EstablishmentTypeTagCategory admin.""" +# +# +# @admin.register(models.EstablishmentTag) +# class EstablishmentTagAdmin(admin.ModelAdmin): +# """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0037_auto_20191015_1404.py b/apps/establishment/migrations/0037_auto_20191015_1404.py new file mode 100644 index 00000000..971970e2 --- /dev/null +++ b/apps/establishment/migrations/0037_auto_20191015_1404.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.4 on 2019-10-15 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('establishment', '0036_auto_20191011_1356'), + ] + + operations = [ + migrations.RemoveField( + model_name='establishmenttag', + name='establishment', + ), + migrations.RemoveField( + model_name='establishmenttag', + name='tag', + ), + migrations.RemoveField( + model_name='establishmenttypetagcategory', + name='establishment_type', + ), + migrations.RemoveField( + model_name='establishmenttypetagcategory', + name='tag_category', + ), + migrations.AddField( + model_name='establishment', + name='tags', + field=models.ManyToManyField(related_name='establishments', to='tag.Tag', verbose_name='Tag'), + ), + migrations.AddField( + model_name='establishmentsubtype', + name='tag_categories', + field=models.ManyToManyField(related_name='establishment_subtypes', to='tag.TagCategory', verbose_name='Tag'), + ), + migrations.AddField( + model_name='establishmenttype', + name='tag_categories', + field=models.ManyToManyField(related_name='establishment_types', to='tag.TagCategory', verbose_name='Tag'), + ), + migrations.DeleteModel( + name='EstablishmentSubTypeTagCategory', + ), + migrations.DeleteModel( + name='EstablishmentTag', + ), + migrations.DeleteModel( + name='EstablishmentTypeTagCategory', + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 195f029d..14d7e02b 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -27,15 +27,16 @@ class EstablishmentTypeQuerySet(models.QuerySet): def with_base_related(self): """Return QuerySet with base related.""" - return self.prefetch_related( - models.Prefetch('tag_categories', - EstablishmentTypeTagCategory.objects.select_related('tag_category')), - models.Prefetch('establishmentsubtype_set', - EstablishmentSubType.objects.prefetch_related( - models.Prefetch( - 'tag_categories', - EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) - ) + return self + # return self.prefetch_related( + # models.Prefetch('tag_categories', + # EstablishmentTypeTagCategory.objects.select_related('tag_category')), + # models.Prefetch('establishmentsubtype_set', + # EstablishmentSubType.objects.prefetch_related( + # models.Prefetch( + # 'tag_categories', + # EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) + # ) # todo: establishment type&subtypes check @@ -47,6 +48,9 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='establishment_types', + verbose_name=_('Tag')) objects = EstablishmentTypeQuerySet.as_manager() @@ -75,6 +79,9 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): establishment_type = models.ForeignKey(EstablishmentType, on_delete=models.CASCADE, verbose_name=_('Type')) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='establishment_subtypes', + verbose_name=_('Tag')) objects = EstablishmentSubTypeManager() @@ -94,14 +101,15 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address', 'establishment_type').prefetch_related( - models.Prefetch('tags', - EstablishmentTag.objects.select_related('tag')), - models.Prefetch('establishment_type__tag_categories', - EstablishmentTypeTagCategory.objects.select_related('tag_category')), - models.Prefetch('establishment_type__establishmentsubtype_set', - EstablishmentSubType.objects.prefetch_related('tag_categories')), - ) + return self + # return self.select_related('address', 'establishment_type').prefetch_related( + # models.Prefetch('tags', + # EstablishmentTag.objects.select_related('tag')), + # models.Prefetch('establishment_type__tag_categories', + # EstablishmentTypeTagCategory.objects.select_related('tag_category')), + # models.Prefetch('establishment_type__establishmentsubtype_set', + # EstablishmentSubType.objects.prefetch_related('tag_categories')), + # ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -320,7 +328,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') - tags = generic.GenericRelation(to='main.MetaDataContent') + # todo: remove after data merge + # tags = generic.GenericRelation(to='main.MetaDataContent') + tags = models.ManyToManyField('tag.Tag', related_name='establishments', + verbose_name=_('Tag')) + old_tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') favorites = generic.GenericRelation(to='favorites.Favorites') @@ -575,81 +587,81 @@ class SocialNetwork(models.Model): def __str__(self): return self.title +# +# class EstablishmentTagQuerySet(models.QuerySet): +# """Establishment tag QuerySet.""" +# +# def by_country_code(self, code): +# """Return establishment tags by establishment country code.""" +# return self.filter(establishment__address__city__country__code=code) -class EstablishmentTagQuerySet(models.QuerySet): - """Establishment tag QuerySet.""" - - def by_country_code(self, code): - """Return establishment tags by establishment country code.""" - return self.filter(establishment__address__city__country__code=code) - - -class EstablishmentTag(models.Model): - """Establishment tag model.""" - tag = models.ForeignKey('tag.Tag', - on_delete=models.CASCADE, - related_name='tags', - verbose_name=_('tag')) - establishment = models.ForeignKey('establishment.Establishment', - on_delete=models.CASCADE, - related_name='tags', - verbose_name=_('establishment')) - objects = EstablishmentTagQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment tag') - verbose_name_plural = _('establishment tags') - - -class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): - """EstablishmentTypeTagCategory QuerySet.""" - - def by_country_code(self, code): - """Return establishment tags by country code""" - return self.filter(tag_category__country__code=code) - - def with_base_related(self): - """Return with related relations.""" - return self.select_related('establishment_type', 'tag_category') - - -class EstablishmentTypeTagCategory(models.Model): - """Tag categories based on establishment type.""" - establishment_type = models.ForeignKey(EstablishmentType, - on_delete=models.CASCADE, - related_name='tag_categories', - verbose_name=_('establishment type')) - tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.CASCADE, - related_name='est_type_tag_categories', - verbose_name=_('tag category')) - objects = EstablishmentTypeTagCategoryQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment type tag categories') - verbose_name_plural = _('establishment type tag categories') - - -class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): - """QuerySet for tag categories based on establishment subtype.""" - - def with_base_related(self): - """Return queryset with base related.""" - return self.select_related('establishment_subtype', 'tag_category') - - -class EstablishmentSubTypeTagCategory(models.Model): - """Tag categories based on establishment subtype.""" - establishment_subtype = models.ForeignKey(EstablishmentSubType, - on_delete=models.CASCADE, - related_name='tag_categories', - verbose_name=_('establishment subtype')) - tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.CASCADE, - related_name='est_subtype_tag_categories', - verbose_name=_('tag category')) - objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment subtype tag categories') - verbose_name_plural = _('establishment subtype tag categories') +# +# class EstablishmentTag(models.Model): +# """Establishment tag model.""" +# tag = models.ForeignKey('tag.Tag', +# on_delete=models.CASCADE, +# related_name='tags', +# verbose_name=_('tag')) +# establishment = models.ForeignKey('establishment.Establishment', +# on_delete=models.CASCADE, +# related_name='tags', +# verbose_name=_('establishment')) +# objects = EstablishmentTagQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment tag') +# verbose_name_plural = _('establishment tags') +# +# +# class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): +# """EstablishmentTypeTagCategory QuerySet.""" +# +# def by_country_code(self, code): +# """Return establishment tags by country code""" +# return self.filter(tag_category__country__code=code) +# +# def with_base_related(self): +# """Return with related relations.""" +# return self.select_related('establishment_type', 'tag_category') +# +# +# class EstablishmentTypeTagCategory(models.Model): +# """Tag categories based on establishment type.""" +# establishment_type = models.ForeignKey(EstablishmentType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment type')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_type_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment type tag categories') +# verbose_name_plural = _('establishment type tag categories') +# +# +# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): +# """QuerySet for tag categories based on establishment subtype.""" +# +# def with_base_related(self): +# """Return queryset with base related.""" +# return self.select_related('establishment_subtype', 'tag_category') +# +# +# class EstablishmentSubTypeTagCategory(models.Model): +# """Tag categories based on establishment subtype.""" +# establishment_subtype = models.ForeignKey(EstablishmentSubType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment subtype')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_subtype_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment subtype tag categories') +# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 21f63e17..dca25763 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -5,8 +5,8 @@ from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer, - EstablishmentTypeTagCategoryBaseSerializer) + EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer) + # EstablishmentTypeTagCategoryBaseSerializer) from main.models import Currency from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes @@ -146,94 +146,94 @@ class EmployeeBackSerializers(serializers.ModelSerializer): ] -class EstablishmentTagCreateSerializer(serializers.ModelSerializer): - """Serializer for model EstablishmentTag.""" - class Meta: - model = models.EstablishmentTag - fields = [ - 'tag', - 'establishment' - ] - extra_kwargs = { - 'tag': {'write_only': True}, - 'establishment': {'write_only': True}, - } - - def validate(self, attrs): - """Validate method.""" - establishment = attrs.get('establishment') - tag = attrs.get('tag') - - # Check if tag is already added to establishment. - if establishment.tags.filter(tag=tag).exists(): - raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) - - # Сhecking tag availability for establishment type. - if not establishment.establishment_type.use_subtypes: - qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) - else: - # Сhecking tag availability for establishment subtype. - qs = establishment.establishment_type.tag_categories.filter( - establishmentsubtype_set__tag_category=tag.category) - if not qs.exists(): - raise serializers.ValidationError( - detail={'detail': _('Tag is not available for this establishment type|subtype.')}) - return attrs +# class EstablishmentTagCreateSerializer(serializers.ModelSerializer): +# """Serializer for model EstablishmentTag.""" +# class Meta: +# model = models.EstablishmentTag +# fields = [ +# 'tag', +# 'establishment' +# ] +# extra_kwargs = { +# 'tag': {'write_only': True}, +# 'establishment': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Validate method.""" +# establishment = attrs.get('establishment') +# tag = attrs.get('tag') +# +# # Check if tag is already added to establishment. +# if establishment.tags.filter(tag=tag).exists(): +# raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) +# +# # Сhecking tag availability for establishment type. +# if not establishment.establishment_type.use_subtypes: +# qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) +# else: +# # Сhecking tag availability for establishment subtype. +# qs = establishment.establishment_type.tag_categories.filter( +# establishmentsubtype_set__tag_category=tag.category) +# if not qs.exists(): +# raise serializers.ValidationError( +# detail={'detail': _('Tag is not available for this establishment type|subtype.')}) +# return attrs -class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentSubTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_subtype', - 'tag_category', - ] - extra_kwargs = { - 'establishment_subtype': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs - - -class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): - """Extended serializer for EstablishmentSubType model with tags.""" - tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - - class Meta(EstablishmentSubTypeBaseSerializer.Meta): - """Meta class""" - fields = [ - 'id', - 'name_translated', - 'tag_categories' - ] - - -class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): - """Tags by establishment type""" - tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') - - class Meta(EstablishmentTypeBaseSerializer.Meta): - """Meta class.""" - fields = [ - 'id', - 'name_translated', - 'tag_categories', - 'subtypes', - ] +# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" +# id = serializers.IntegerField(source='tag_category.id', read_only=True) +# label_translated = TranslatedField(source='tag_category.label_translated') +# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentSubTypeTagCategory +# fields = [ +# 'id', +# 'label_translated', +# 'tags', +# 'establishment_subtype', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_subtype': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_type'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs +# +# +# class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): +# """Extended serializer for EstablishmentSubType model with tags.""" +# tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) +# +# class Meta(EstablishmentSubTypeBaseSerializer.Meta): +# """Meta class""" +# fields = [ +# 'id', +# 'name_translated', +# 'tag_categories' +# ] +# +# +# class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): +# """Tags by establishment type""" +# tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) +# subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') +# +# class Meta(EstablishmentTypeBaseSerializer.Meta): +# """Meta class.""" +# fields = [ +# 'id', +# 'name_translated', +# 'tag_categories', +# 'subtypes', +# ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 1789da31..7c659c9b 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -139,34 +139,34 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): } -class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_type', - 'tag_category', - ] - extra_kwargs = { - 'establishment_type': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs +# class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentTypeTagCategories.""" +# id = serializers.IntegerField(source='tag_category.id', read_only=True) +# label_translated = TranslatedField(source='tag_category.label_translated') +# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentTypeTagCategory +# fields = [ +# 'id', +# 'label_translated', +# 'tags', +# 'establishment_type', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_type': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_type'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs class EstablishmentEmployeeSerializer(serializers.ModelSerializer): @@ -184,19 +184,19 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): model = models.Employee fields = ('id', 'name', 'position_translated', 'awards', 'priority') - -class EstablishmentTagSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTag.""" - id = serializers.IntegerField(source='tag.id') - label_translated = serializers.CharField(source='tag.label_translated') - - class Meta: - """Meta class.""" - model = models.EstablishmentTag - fields = [ - 'id', - 'label_translated' - ] +# +# class EstablishmentTagSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentTag.""" +# id = serializers.IntegerField(source='tag.id') +# label_translated = serializers.CharField(source='tag.label_translated') +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentTag +# fields = [ +# 'id', +# 'label_translated' +# ] class EstablishmentBaseSerializer(ProjectModelSerializer): @@ -206,7 +206,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) - tags = EstablishmentTagSerializer(many=True) + tags = TagBaseSerializer(read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index 3db81dce..cdd4d183 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,7 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), + # path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -28,12 +28,12 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), - path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), + # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), - path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), - name='type-attach-tag-category'), + # path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), + # name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - name='subtype-attach-tag-category'), + # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + # name='subtype-attach-tag-category'), ] diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 18b768c0..65b796ae 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,7 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), + # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 543dbd3e..f79707d9 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -55,24 +55,24 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): - """Attach tag category to establishment type.""" - serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer - - def get_queryset(self): - """Override get_queryset method.""" - return models.EstablishmentTypeTagCategory.objects.with_base_related() - - def post(self, request, *args, **kwargs): - """Overridden post-method.""" - super(EstablishmentTypeAttachTagCategoryView, self).post(request) - return Response(status=status.HTTP_200_OK) +# class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment type.""" +# serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer +# +# def get_queryset(self): +# """Override get_queryset method.""" +# return models.EstablishmentTypeTagCategory.objects.with_base_related() +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) -class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): - """Attach tag to establishment.""" - serializer_class = serializers.EstablishmentTagCreateSerializer - +# class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): +# """Attach tag to establishment.""" +# serializer_class = serializers.EstablishmentTagCreateSerializer +# class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" @@ -164,13 +164,13 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() -class EstablishmentTypeTagListView(generics.ListAPIView): - """List of tags with categories by establishment type.""" - serializer_class = serializers.EstablishmentTagsByType - queryset = models.EstablishmentType.objects.with_base_related() - filter_class = EstablishmentTypeTagFilter - permission_classes = (permissions.AllowAny, ) - pagination_class = None +# class EstablishmentTypeTagListView(generics.ListAPIView): +# """List of tags with categories by establishment type.""" +# serializer_class = serializers.EstablishmentTagsByType +# queryset = models.EstablishmentType.objects.with_base_related() +# filter_class = EstablishmentTypeTagFilter +# permission_classes = (permissions.AllowAny, ) +# pagination_class = None class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): @@ -186,15 +186,15 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentSubType.objects.all() -class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): - """Attach tag category to establishment subtype.""" - serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer - - def get_queryset(self): - """Override get_queryset method.""" - return models.EstablishmentSubTypeTagCategory.objects.with_base_related() - - def post(self, request, *args, **kwargs): - """Overridden post-method.""" - super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) - return Response(status=status.HTTP_200_OK) +# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment subtype.""" +# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer +# +# def get_queryset(self): +# """Override get_queryset method.""" +# return models.EstablishmentSubTypeTagCategory.objects.with_base_related() +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 6e0974d8..62049c05 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,15 +24,15 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - tags = fields.ObjectField( - properties={ - 'id': fields.IntegerField(attr='metadata.id'), - 'label': fields.ObjectField(attr='metadata.label_indexing', - properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='metadata.category', - properties={'id': fields.IntegerField()}) - }, - multi=True) + # tags = fields.ObjectField( + # properties={ + # 'id': fields.IntegerField(attr='metadata.id'), + # 'label': fields.ObjectField(attr='metadata.label_indexing', + # properties=OBJECT_FIELD_PROPERTIES), + # 'category': fields.ObjectField(attr='metadata.category', + # properties={'id': fields.IntegerField()}) + # }, + # multi=True) class Django: diff --git a/apps/tag/filters.py b/apps/tag/filters.py new file mode 100644 index 00000000..95f56b53 --- /dev/null +++ b/apps/tag/filters.py @@ -0,0 +1,48 @@ +"""Tag app filters.""" +from django_filters import rest_framework as filters +from tag import models + + +class TagCategoryFilterSet(filters.FilterSet): + """TagCategory filterset.""" + + # Object type choices + NEWS = 'news' + ESTABLISHMENT = 'establishment' + + TYPE_CHOICES = ( + (NEWS, 'News'), + (ESTABLISHMENT, 'Establishment'), + ) + + type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, + method='filter_by_type') + + # Establishment type choices + RESTAURANT = 'restaurant' + + ESTABLISHMENT_TYPE_CHOICES = ( + (RESTAURANT, 'restaurant'), + ) + + establishment_type = filters.MultipleChoiceFilter( + choices=ESTABLISHMENT_TYPE_CHOICES, + method='filter_by_establishment_type') + + class Meta: + """Meta class.""" + + model = models.TagCategory + fields = ('type', + 'establishment_type', ) + + def filter_by_type(self, queryset, name, value): + if self.NEWS in value: + queryset = queryset.for_news() + if self.ESTABLISHMENT in value: + queryset = queryset.for_establishments() + return queryset + + # todo: filter by establishment type + def filter_by_establishment_type(self, queryset, name, value): + return queryset.for_establishments() diff --git a/apps/tag/models.py b/apps/tag/models.py index b552bbcb..381e552d 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -32,8 +32,16 @@ class Tag(TranslatedFieldsMixin, models.Model): class TagCategoryQuerySet(models.QuerySet): """Extended queryset for TagCategory model.""" - def by_news_type(self, news_type): - return self.filter(news_types=news_type) + def with_base_related(self): + return self.prefetch_related('tags') + + def for_news(self): + return self.filter(news_types__isnull=True) + + def for_establishments(self): + return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | + models.Q(est_type_tag_categories__isnull=True)).\ + distinct() def with_related(self): return self.select_related('country').prefetch_related('tags') diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index ecb1b68d..895fe2a1 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -13,23 +13,29 @@ class TagBaseSerializer(serializers.ModelSerializer): """Meta class.""" model = models.Tag - fields = [ + fields = ( 'id', - 'label', 'label_translated', + ) + + +class TagBackOfficeSerializer(TagBaseSerializer): + """Serializer for Tag model for Back office users.""" + + class Meta(TagBaseSerializer.Meta): + """Meta class.""" + + fields = TagBaseSerializer.Meta.fields + ( + 'label', 'category' - ] - extra_kwargs = { - 'label': {'write_only': True}, - 'category': {'write_only': True} - } + ) class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" label_translated = TranslatedField() - country_translated = TranslatedField(source='country.name_translated') + tags = TagBaseSerializer(many=True, read_only=True) class Meta: """Meta class.""" @@ -37,23 +43,29 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): model = models.TagCategory fields = ( 'id', - 'label', 'label_translated', - 'country', - 'country_translated', - 'public', + 'tags' ) - extra_kwargs = { - 'label': {'write_only': True}, - 'country': {'write_only': True}, - } class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - tags = TagBaseSerializer(many=True) class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) + + +class TagCategoryBackOfficeDetailSerializer(TagCategoryDetailSerializer): + + country_translated = TranslatedField(source='country.name_translated') + + class Meta(TagCategoryDetailSerializer.Meta): + """Meta class.""" + + fields = TagCategoryDetailSerializer.Meta.fields + ( + 'news_types', + 'country', + 'country_translated', + ) diff --git a/apps/tag/urls/__init__.py b/apps/tag/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/urls.py b/apps/tag/urls/back.py similarity index 53% rename from apps/tag/urls.py rename to apps/tag/urls/back.py index 0d0b86d2..9f03bb45 100644 --- a/apps/tag/urls.py +++ b/apps/tag/urls/back.py @@ -1,10 +1,16 @@ """Urlconf for app tag.""" from django.urls import path -from . import views +from rest_framework.routers import SimpleRouter +from tag import views app_name = 'tag' +router = SimpleRouter() +router.register(r'', views.TagViewSet) + + urlpatterns = [ - path('', views.TagListCreateView.as_view(), name='list-create'), path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), ] + +urlpatterns += router.urls diff --git a/apps/tag/urls/web.py b/apps/tag/urls/web.py new file mode 100644 index 00000000..c99253eb --- /dev/null +++ b/apps/tag/urls/web.py @@ -0,0 +1,16 @@ +"""Tag app urlpatterns web users.""" +from rest_framework.routers import SimpleRouter +from tag import views + + +app_name = 'tag' + +router = SimpleRouter() +router.register(r'categories', views.TagCategoryViewSet) + +urlpatterns = [ + +] + +urlpatterns += router.urls + diff --git a/apps/tag/views.py b/apps/tag/views.py index 5bf4d045..2bc9d8d2 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,19 +1,33 @@ """Tag views.""" -from rest_framework import generics -from tag import serializers, models +from rest_framework import generics, viewsets, mixins +from tag import filters, models, serializers +from rest_framework import permissions -class TagListCreateView(generics.ListCreateAPIView): +class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, + viewsets.GenericViewSet): """List/create tag view.""" - queryset = models.Tag.objects.all() - serializer_class = serializers.TagBaseSerializer pagination_class = None + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBackOfficeSerializer class TagCategoryListCreateView(generics.ListCreateAPIView): """List/create tag category view.""" - queryset = models.TagCategory.objects.all() - serializer_class = serializers.TagCategoryBaseSerializer pagination_class = None + permission_classes = (permissions.AllowAny, ) + queryset = models.TagCategory.objects.all() + serializer_class = serializers.TagCategoryBackOfficeDetailSerializer + + +class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): + """ViewSet for TagCategory model.""" + + filterset_class = filters.TagCategoryFilterSet + pagination_class = None + permission_classes = (permissions.AllowAny, ) + queryset = models.TagCategory.objects.with_base_related() + serializer_class = serializers.TagCategoryBaseSerializer diff --git a/project/settings/base.py b/project/settings/base.py index 4618887d..1de0c109 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -222,8 +222,8 @@ REST_FRAMEWORK = { 'COERCE_DECIMAL_TO_STRING': False, 'DEFAULT_AUTHENTICATION_CLASSES': ( # JWT - 'utils.authentication.GMJWTAuthentication', 'rest_framework.authentication.SessionAuthentication', + # 'utils.authentication.GMJWTAuthentication', ), 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],), diff --git a/project/urls/back.py b/project/urls/back.py index eb049b9c..389c6f51 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,7 +7,7 @@ urlpatterns = [ path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('tags/', include(('tag.urls', 'tag'), namespace='tag')), + path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] diff --git a/project/urls/web.py b/project/urls/web.py index 89debab2..5bd67207 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -30,6 +30,7 @@ urlpatterns = [ path('location/', include('location.urls.web')), path('main/', include('main.urls')), path('recipes/', include('recipe.urls.web')), + path('tags/', include('tag.urls.web')), path('translation/', include('translation.urls')), path('comments/', include('comment.urls.web')), path('favorites/', include('favorites.urls')), From c94ad3f017e24680ef9b483a0b49ea5139ff99f5 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 17:48:01 +0300 Subject: [PATCH 272/319] comment some code --- apps/search_indexes/documents/establishment.py | 14 +++++++------- apps/tag/models.py | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index e2147fbc..5d45c45b 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -39,13 +39,13 @@ class EstablishmentDocument(Document): }), }, multi=True) - tags = fields.ObjectField( - properties={ - 'tag': fields.ObjectField(properties={ - 'id': fields.IntegerField(), - }), - }, - multi=True) + # tags = fields.ObjectField( + # properties={ + # 'tag': fields.ObjectField(properties={ + # 'id': fields.IntegerField(), + # }), + # }, + # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/models.py b/apps/tag/models.py index 381e552d..32cbfa05 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -39,9 +39,10 @@ class TagCategoryQuerySet(models.QuerySet): return self.filter(news_types__isnull=True) def for_establishments(self): - return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | - models.Q(est_type_tag_categories__isnull=True)).\ - distinct() + return self + # return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | + # models.Q(est_type_tag_categories__isnull=True)).\ + # distinct() def with_related(self): return self.select_related('country').prefetch_related('tags') From 42ae1cbb8773636dbbd956e2742c18360496c6cd Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:13:37 +0300 Subject: [PATCH 273/319] fix tagcategory queryset --- apps/tag/models.py | 13 +++++++++---- apps/tag/serializers.py | 2 +- apps/tag/views.py | 13 ++----------- project/urls/back.py | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index 32cbfa05..3deed7d9 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -33,16 +33,21 @@ class TagCategoryQuerySet(models.QuerySet): """Extended queryset for TagCategory model.""" def with_base_related(self): + """Select related objects.""" return self.prefetch_related('tags') def for_news(self): + """Select tag categories for news.""" return self.filter(news_types__isnull=True) def for_establishments(self): - return self - # return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | - # models.Q(est_type_tag_categories__isnull=True)).\ - # distinct() + """Select tag categories for establishments.""" + return self.filter(models.Q(establishment_types__isnull=False) | + models.Q(establishment_subtypes__isnull=False)) + + def with_tags(self, switcher=True): + """Filter by existing tags.""" + return self.filter(tags__isnull=not switcher) def with_related(self): return self.select_related('country').prefetch_related('tags') diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 895fe2a1..946206f4 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -49,7 +49,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - + """Detailed serializer for TagCategory model.""" class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" diff --git a/apps/tag/views.py b/apps/tag/views.py index 2bc9d8d2..622ec849 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,5 +1,5 @@ """Tag views.""" -from rest_framework import generics, viewsets, mixins +from rest_framework import viewsets, mixins from tag import filters, models, serializers from rest_framework import permissions @@ -14,20 +14,11 @@ class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, serializer_class = serializers.TagBackOfficeSerializer -class TagCategoryListCreateView(generics.ListCreateAPIView): - """List/create tag category view.""" - - pagination_class = None - permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.all() - serializer_class = serializers.TagCategoryBackOfficeDetailSerializer - - class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" filterset_class = filters.TagCategoryFilterSet pagination_class = None permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.with_base_related() + queryset = models.TagCategory.objects.with_tags().with_base_related() serializer_class = serializers.TagCategoryBaseSerializer diff --git a/project/urls/back.py b/project/urls/back.py index 389c6f51..5d221932 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,7 +7,7 @@ urlpatterns = [ path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), + # path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] From 9d4c483f514554dd1adc504e2b4d1f47350d46b3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:25:09 +0300 Subject: [PATCH 274/319] clean models (tags) --- apps/establishment/admin.py | 15 ---- apps/establishment/models.py | 109 +---------------------- apps/establishment/serializers/back.py | 99 +------------------- apps/establishment/serializers/common.py | 44 --------- apps/establishment/urls/back.py | 6 -- apps/establishment/urls/common.py | 1 - apps/establishment/views/back.py | 46 +--------- apps/news/admin.py | 2 +- apps/news/urls/web.py | 2 - apps/news/views.py | 13 --- apps/tag/models.py | 3 - apps/tag/serializers.py | 15 +--- 12 files changed, 8 insertions(+), 347 deletions(-) diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 8a5e57de..50c21b90 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -79,18 +79,3 @@ class MenuAdmin(admin.ModelAdmin): return obj.category_translated category_translated.short_description = _('category') - -# -# @admin.register(models.EstablishmentTypeTagCategory) -# class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): -# """EstablishmentTypeTagCategory admin.""" -# -# -# @admin.register(models.EstablishmentSubTypeTagCategory) -# class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): -# """EstablishmentTypeTagCategory admin.""" -# -# -# @admin.register(models.EstablishmentTag) -# class EstablishmentTagAdmin(admin.ModelAdmin): -# """EstablishmentTag admin.""" diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 14d7e02b..c93bbf89 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -16,29 +16,11 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from location.models import Address from main.models import Award -from tag.models import Tag, TagCategory from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) -class EstablishmentTypeQuerySet(models.QuerySet): - """QuerySet for model EstablishmentType.""" - - def with_base_related(self): - """Return QuerySet with base related.""" - return self - # return self.prefetch_related( - # models.Prefetch('tag_categories', - # EstablishmentTypeTagCategory.objects.select_related('tag_category')), - # models.Prefetch('establishmentsubtype_set', - # EstablishmentSubType.objects.prefetch_related( - # models.Prefetch( - # 'tag_categories', - # EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) - # ) - - # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): """Establishment type model.""" @@ -52,8 +34,6 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): related_name='establishment_types', verbose_name=_('Tag')) - objects = EstablishmentTypeQuerySet.as_manager() - class Meta: """Meta class.""" @@ -101,15 +81,8 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self - # return self.select_related('address', 'establishment_type').prefetch_related( - # models.Prefetch('tags', - # EstablishmentTag.objects.select_related('tag')), - # models.Prefetch('establishment_type__tag_categories', - # EstablishmentTypeTagCategory.objects.select_related('tag_category')), - # models.Prefetch('establishment_type__establishmentsubtype_set', - # EstablishmentSubType.objects.prefetch_related('tag_categories')), - # ) + return self.select_related('address', 'establishment_type').\ + prefetch_related('tags') def with_extended_related(self): return self.select_related('establishment_type').\ @@ -587,81 +560,3 @@ class SocialNetwork(models.Model): def __str__(self): return self.title -# -# class EstablishmentTagQuerySet(models.QuerySet): -# """Establishment tag QuerySet.""" -# -# def by_country_code(self, code): -# """Return establishment tags by establishment country code.""" -# return self.filter(establishment__address__city__country__code=code) - -# -# class EstablishmentTag(models.Model): -# """Establishment tag model.""" -# tag = models.ForeignKey('tag.Tag', -# on_delete=models.CASCADE, -# related_name='tags', -# verbose_name=_('tag')) -# establishment = models.ForeignKey('establishment.Establishment', -# on_delete=models.CASCADE, -# related_name='tags', -# verbose_name=_('establishment')) -# objects = EstablishmentTagQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment tag') -# verbose_name_plural = _('establishment tags') -# -# -# class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): -# """EstablishmentTypeTagCategory QuerySet.""" -# -# def by_country_code(self, code): -# """Return establishment tags by country code""" -# return self.filter(tag_category__country__code=code) -# -# def with_base_related(self): -# """Return with related relations.""" -# return self.select_related('establishment_type', 'tag_category') -# -# -# class EstablishmentTypeTagCategory(models.Model): -# """Tag categories based on establishment type.""" -# establishment_type = models.ForeignKey(EstablishmentType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment type')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_type_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment type tag categories') -# verbose_name_plural = _('establishment type tag categories') -# -# -# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): -# """QuerySet for tag categories based on establishment subtype.""" -# -# def with_base_related(self): -# """Return queryset with base related.""" -# return self.select_related('establishment_subtype', 'tag_category') -# -# -# class EstablishmentSubTypeTagCategory(models.Model): -# """Tag categories based on establishment subtype.""" -# establishment_subtype = models.ForeignKey(EstablishmentSubType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment subtype')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_subtype_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment subtype tag categories') -# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index dca25763..59725710 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,16 +1,12 @@ -from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer) - # EstablishmentTypeTagCategoryBaseSerializer) + EstablishmentTypeBaseSerializer) from main.models import Currency -from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes -from utils.serializers import TranslatedField class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -144,96 +140,3 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'user', 'name' ] - - -# class EstablishmentTagCreateSerializer(serializers.ModelSerializer): -# """Serializer for model EstablishmentTag.""" -# class Meta: -# model = models.EstablishmentTag -# fields = [ -# 'tag', -# 'establishment' -# ] -# extra_kwargs = { -# 'tag': {'write_only': True}, -# 'establishment': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Validate method.""" -# establishment = attrs.get('establishment') -# tag = attrs.get('tag') -# -# # Check if tag is already added to establishment. -# if establishment.tags.filter(tag=tag).exists(): -# raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) -# -# # Сhecking tag availability for establishment type. -# if not establishment.establishment_type.use_subtypes: -# qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) -# else: -# # Сhecking tag availability for establishment subtype. -# qs = establishment.establishment_type.tag_categories.filter( -# establishmentsubtype_set__tag_category=tag.category) -# if not qs.exists(): -# raise serializers.ValidationError( -# detail={'detail': _('Tag is not available for this establishment type|subtype.')}) -# return attrs - - -# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" -# id = serializers.IntegerField(source='tag_category.id', read_only=True) -# label_translated = TranslatedField(source='tag_category.label_translated') -# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentSubTypeTagCategory -# fields = [ -# 'id', -# 'label_translated', -# 'tags', -# 'establishment_subtype', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_subtype': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_type'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs -# -# -# class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): -# """Extended serializer for EstablishmentSubType model with tags.""" -# tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) -# -# class Meta(EstablishmentSubTypeBaseSerializer.Meta): -# """Meta class""" -# fields = [ -# 'id', -# 'name_translated', -# 'tag_categories' -# ] -# -# -# class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): -# """Tags by establishment type""" -# tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) -# subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') -# -# class Meta(EstablishmentTypeBaseSerializer.Meta): -# """Meta class.""" -# fields = [ -# 'id', -# 'name_translated', -# 'tag_categories', -# 'subtypes', -# ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7c659c9b..389d0d1c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -139,36 +139,6 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): } -# class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentTypeTagCategories.""" -# id = serializers.IntegerField(source='tag_category.id', read_only=True) -# label_translated = TranslatedField(source='tag_category.label_translated') -# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentTypeTagCategory -# fields = [ -# 'id', -# 'label_translated', -# 'tags', -# 'establishment_type', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_type': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_type'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs - - class EstablishmentEmployeeSerializer(serializers.ModelSerializer): """Serializer for actual employees.""" @@ -184,20 +154,6 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): model = models.Employee fields = ('id', 'name', 'position_translated', 'awards', 'priority') -# -# class EstablishmentTagSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentTag.""" -# id = serializers.IntegerField(source='tag.id') -# label_translated = serializers.CharField(source='tag.label_translated') -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentTag -# fields = [ -# 'id', -# 'label_translated' -# ] - class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index cdd4d183..6a12e792 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,6 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - # path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -28,12 +27,7 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), - # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), - # path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), - # name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - # name='subtype-attach-tag-category'), ] diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 65b796ae..5d7df146 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,6 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index f79707d9..d87baf6d 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,11 +1,9 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, status, permissions +from rest_framework import generics from establishment import models, serializers -from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer -from establishment.filters import EstablishmentTypeTagFilter class EstablishmentMixinViews: @@ -55,25 +53,6 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -# class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment type.""" -# serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer -# -# def get_queryset(self): -# """Override get_queryset method.""" -# return models.EstablishmentTypeTagCategory.objects.with_base_related() -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) - - -# class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): -# """Attach tag to establishment.""" -# serializer_class = serializers.EstablishmentTagCreateSerializer -# - class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -164,15 +143,6 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() -# class EstablishmentTypeTagListView(generics.ListAPIView): -# """List of tags with categories by establishment type.""" -# serializer_class = serializers.EstablishmentTagsByType -# queryset = models.EstablishmentType.objects.with_base_related() -# filter_class = EstablishmentTypeTagFilter -# permission_classes = (permissions.AllowAny, ) -# pagination_class = None - - class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer @@ -184,17 +154,3 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment subtype retrieve/update/destroy view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() - - -# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment subtype.""" -# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer -# -# def get_queryset(self): -# """Override get_queryset method.""" -# return models.EstablishmentSubTypeTagCategory.objects.with_base_related() -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) diff --git a/apps/news/admin.py b/apps/news/admin.py index 77ea8388..5d7f79f0 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin - from news import models from .tasks import send_email_with_news + @admin.register(models.NewsType) class NewsTypeAdmin(admin.ModelAdmin): """News type admin.""" diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 0671f5f6..80fcf072 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -7,7 +7,5 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), path('types/', views.NewsTypeListView.as_view(), name='type'), - path('types//tags/', views.NewsTypeTagsView.as_view(), - name='type-tags'), path('slug//', views.NewsDetailView.as_view(), name='rud'), ] diff --git a/apps/news/views.py b/apps/news/views.py index 1a845a7b..b167324e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -3,7 +3,6 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating -from tag.serializers import TagCategoryDetailSerializer class NewsMixinView: @@ -47,18 +46,6 @@ class NewsTypeListView(generics.ListAPIView): serializer_class = serializers.NewsTypeSerializer -class NewsTypeTagsView(generics.ListAPIView): - """Resource to get a list of tags for a news type.""" - - pagination_class = None - permission_classes = (permissions.AllowAny, ) - serializer_class = TagCategoryDetailSerializer - - def get_queryset(self): - news_type = get_object_or_404(models.NewsType, pk=self.kwargs.get('pk')) - return news_type.tag_categories.with_related() - - class NewsBackOfficeMixinView: """News back office mixin view.""" diff --git a/apps/tag/models.py b/apps/tag/models.py index 3deed7d9..4fa04f8e 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -49,9 +49,6 @@ class TagCategoryQuerySet(models.QuerySet): """Filter by existing tags.""" return self.filter(tags__isnull=not switcher) - def with_related(self): - return self.select_related('country').prefetch_related('tags') - class TagCategory(TranslatedFieldsMixin, models.Model): """Tag base category model.""" diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 946206f4..e4ecf25d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -48,23 +48,14 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): ) -class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - """Detailed serializer for TagCategory model.""" - - class Meta(TagCategoryBaseSerializer.Meta): - """Meta class.""" - - fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) - - -class TagCategoryBackOfficeDetailSerializer(TagCategoryDetailSerializer): +class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): country_translated = TranslatedField(source='country.name_translated') - class Meta(TagCategoryDetailSerializer.Meta): + class Meta(TagBaseSerializer.Meta): """Meta class.""" - fields = TagCategoryDetailSerializer.Meta.fields + ( + fields = TagCategoryBaseSerializer.Meta.fields + ( 'news_types', 'country', 'country_translated', From bde81b3700b16f6950b1d490e446ce040aab3110 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:32:16 +0300 Subject: [PATCH 275/319] update ES documents --- apps/search_indexes/documents/establishment.py | 16 +++++++++------- apps/search_indexes/documents/news.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d45c45b..6a00f0f1 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -39,13 +39,15 @@ class EstablishmentDocument(Document): }), }, multi=True) - # tags = fields.ObjectField( - # properties={ - # 'tag': fields.ObjectField(properties={ - # 'id': fields.IntegerField(), - # }), - # }, - # multi=True) + tags = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'category': fields.ObjectField(attr='category', + properties={'id': fields.IntegerField()}) + }, + multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 62049c05..25eec31d 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,15 +24,15 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - # tags = fields.ObjectField( - # properties={ - # 'id': fields.IntegerField(attr='metadata.id'), - # 'label': fields.ObjectField(attr='metadata.label_indexing', - # properties=OBJECT_FIELD_PROPERTIES), - # 'category': fields.ObjectField(attr='metadata.category', - # properties={'id': fields.IntegerField()}) - # }, - # multi=True) + tags = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'category': fields.ObjectField(attr='category', + properties={'id': fields.IntegerField()}) + }, + multi=True) class Django: From 3a54b70093df293e9b893a0de8efda80f79557fa Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:43:08 +0300 Subject: [PATCH 276/319] update tag on document index --- apps/search_indexes/documents/establishment.py | 2 -- apps/search_indexes/documents/news.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 6a00f0f1..ca1da993 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -44,8 +44,6 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(attr='id'), 'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='category', - properties={'id': fields.IntegerField()}) }, multi=True) address = fields.ObjectField( diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 25eec31d..99071e53 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -29,8 +29,6 @@ class NewsDocument(Document): 'id': fields.IntegerField(attr='id'), 'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='category', - properties={'id': fields.IntegerField()}) }, multi=True) From ccf6e12e3f949658d32342682170e70d578a65a0 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:52:32 +0300 Subject: [PATCH 277/319] remove tag category from establishment type --- apps/search_indexes/documents/establishment.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index ca1da993..c30d4c58 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -22,13 +22,6 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'tag_categories': fields.ObjectField(properties={ - 'tag_category': fields.ObjectField( - properties={ - 'id': fields.IntegerField() - } - ) - }), }) establishment_subtypes = fields.ObjectField( properties={ From 86692019d1983770e1add6a634511acca33fe970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 10:14:23 +0300 Subject: [PATCH 278/319] Add Country admin to views --- apps/account/models.py | 2 ++ apps/account/permissions.py | 0 apps/comment/views/back.py | 4 +-- apps/establishment/models.py | 7 +++++ apps/establishment/views/back.py | 3 ++ apps/establishment/views/web.py | 2 +- apps/location/models.py | 4 +++ apps/location/views/back.py | 14 ++++++---- apps/news/tests.py | 32 +++++++++++++++++++++- apps/news/views.py | 2 ++ apps/utils/permissions.py | 47 ++++++++++++++++++++++++-------- 11 files changed, 97 insertions(+), 20 deletions(-) delete mode 100644 apps/account/permissions.py diff --git a/apps/account/models.py b/apps/account/models.py index 63486f96..96927c1e 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -24,11 +24,13 @@ class Role(ProjectBaseMixin): STANDARD_USER = 1 COMMENTS_MODERATOR = 2 COUNTRY_ADMIN = 3 + CONTENT_PAGE_MANAGER = 4 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), + (CONTENT_PAGE_MANAGER, 'Content page manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/account/permissions.py b/apps/account/permissions.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 7f066f30..2895fdbe 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from comment.serializers import back as serializers from comment import models -from utils.permissions import IsCommentModerator +from utils.permissions import IsCommentModerator, IsCountryAdmin class CommentLstView(generics.ListCreateAPIView): @@ -15,5 +15,5 @@ class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsCommentModerator] + permission_classes = [IsCountryAdmin|IsCommentModerator] lookup_field = 'id' diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 2fc63bd5..c97562cc 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -382,6 +382,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( field_name='vintage_year') + @property + def country_id(self): + """ + Return Country object of establishment location + """ + return self.address.city.country.id + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 5cba8255..fe9633ab 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -4,6 +4,7 @@ from rest_framework import generics from establishment import models from establishment import serializers +from utils.permissions import IsCountryAdmin class EstablishmentMixinViews: @@ -18,11 +19,13 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer + permission_classes = [IsCountryAdmin] class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer + permission_classes = [IsCountryAdmin] class MenuListCreateView(generics.ListCreateAPIView): diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8f5d2a26..36f2a027 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -11,7 +11,7 @@ from main import methods from main.models import MetaDataContent from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.pagination import EstablishmentPortionPagination - +from utils.permissions import IsCountryAdmin class EstablishmentMixinView: """Establishment mixin.""" diff --git a/apps/location/models.py b/apps/location/models.py index 2298c28e..7f797811 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -112,6 +112,10 @@ class Address(models.Model): return {'lat': self.latitude, 'lon': self.longitude} + @property + def country_id(self): + return self.city.country_id + # todo: Make recalculate price levels @receiver(post_save, sender=Country) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index ce6589ed..5c028545 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -3,50 +3,54 @@ from rest_framework import generics from location import models, serializers from location.views import common - +from utils.permissions import IsCountryAdmin # Address class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() + permission_classes = [IsCountryAdmin] class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() + permission_classes = [IsCountryAdmin] # City class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CitySerializer - + permission_classes = [IsCountryAdmin] class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer + permission_classes = [IsCountryAdmin] # Region class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" serializer_class = serializers.RegionSerializer - + permission_classes = [IsCountryAdmin] class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve view for model Region""" serializer_class = serializers.RegionSerializer - + permission_classes = [IsCountryAdmin] # Country class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView): """List/Create view for model Country.""" serializer_class = serializers.CountryBackSerializer pagination_class = None - + permission_classes = [IsCountryAdmin] class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer + permission_classes = [IsCountryAdmin] \ No newline at end of file diff --git a/apps/news/tests.py b/apps/news/tests.py index 2e24ac45..27ede62c 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -1,3 +1,4 @@ +from django.urls import reverse from http.cookies import SimpleCookie from rest_framework.test import APITestCase @@ -6,7 +7,8 @@ from datetime import datetime, timedelta from news.models import NewsType, News from account.models import User - +from translation.models import Language +from location.models import Country # Create your tests here. @@ -27,7 +29,20 @@ class BaseTestCase(APITestCase): playlist=1, start=datetime.now() + timedelta(hours=-2), end=datetime.now() + timedelta(hours=2), state=News.PUBLISHED, slug='test-news-slug',) + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() class NewsTestCase(BaseTestCase): @@ -50,3 +65,18 @@ class NewsTestCase(BaseTestCase): def test_news_type_list(self): response = self.client.get("/api/web/news/types/") self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_back_detail_put(self): + # retrieve-update-destroy + url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id}) + data = { + 'id': self.test_news.id, + 'description': {"en-GB": "Description test news!"}, + 'slug': self.test_news.slug, + 'start': self.test_news.start, + 'playlist': self.test_news.playlist, + 'news_type_id':self.test_news.news_type_id, + 'country_id': self.country_ru.id + } + response = self.client.put(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/apps/news/views.py b/apps/news/views.py index 61a57251..b5273a5e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -2,6 +2,7 @@ from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating +from utils.permissions import IsCountryAdmin class NewsMixinView: """News mixin.""" @@ -57,6 +58,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeBaseSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer + permission_classes = [IsCountryAdmin] def get_serializer_class(self): """Override serializer class.""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2754a3c5..e2a2b80a 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -67,9 +67,34 @@ class IsStandardUser(IsGuest): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request - if super().has_object_permission(request, view, obj) or\ - (obj.user == request.user and obj.user.email_confirmed): + if obj.user == request.user and obj.user.email_confirmed: return True + + if super().has_object_permission(request, view, obj): + return True + + return False + + +class IsContentPageManager(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, + country_id=obj.country_id)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + + if super().has_object_permission(request, view, obj): + return True + return False @@ -80,17 +105,18 @@ class IsCountryAdmin(IsStandardUser): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. - if super().has_object_permission(request, view, obj): - return True - - # Must have role role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id) \ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: return True + + if super().has_object_permission(request, view, obj): + return True + return False @@ -102,17 +128,16 @@ class IsCommentModerator(IsCountryAdmin): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. - - if super().has_object_permission(request, view, obj): - return True - - # Must have role role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, country_id=obj.country_id)\ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: return True + if super().has_object_permission(request, view, obj): + return True + return False From 04a6bbea191d522d30c4714f1bfc776030646b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 10:43:06 +0300 Subject: [PATCH 279/319] Add content page manager --- apps/news/views.py | 5 +++-- apps/utils/permissions.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/news/views.py b/apps/news/views.py index b5273a5e..105b9064 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -2,7 +2,7 @@ from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating -from utils.permissions import IsCountryAdmin +from utils.permissions import IsCountryAdmin, IsContentPageManager class NewsMixinView: """News mixin.""" @@ -58,7 +58,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeBaseSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsContentPageManager] def get_serializer_class(self): """Override serializer class.""" @@ -76,6 +76,7 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView, """Resource for detailed information about news for back-office users.""" serializer_class = serializers.NewsBackOfficeDetailSerializer + permission_classes = [IsCountryAdmin|IsContentPageManager] def get(self, request, pk, *args, **kwargs): add_rating(remote_addr=request.META.get('REMOTE_ADDR'), diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index e2a2b80a..f1c2f46d 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -120,7 +120,7 @@ class IsCountryAdmin(IsStandardUser): return False -class IsCommentModerator(IsCountryAdmin): +class IsCommentModerator(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. From 3271a6fed99ab72f1cd4def10754419a28c55225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 11:02:26 +0300 Subject: [PATCH 280/319] Establishment manager --- apps/products/urls/back.py | 0 apps/products/views/back.py | 0 apps/utils/permissions.py | 5 ++++- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 apps/products/urls/back.py create mode 100644 apps/products/views/back.py diff --git a/apps/products/urls/back.py b/apps/products/urls/back.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/products/views/back.py b/apps/products/views/back.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f1c2f46d..ff10e117 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -75,7 +75,6 @@ class IsStandardUser(IsGuest): return False - class IsContentPageManager(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. @@ -141,3 +140,7 @@ class IsCommentModerator(IsStandardUser): return True return False + + +class IsEstablishmentManager(IsStandardUser): + pass From 9fd89bf812a508cdb1e7819fc67db0d48b6bd762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 11:40:51 +0300 Subject: [PATCH 281/319] Add establishment manager role --- apps/account/admin.py | 2 +- .../migrations/0013_auto_20191016_0810.py | 30 +++++++++++++++++++ apps/account/models.py | 7 ++++- apps/utils/permissions.py | 15 +++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 apps/account/migrations/0013_auto_20191016_0810.py diff --git a/apps/account/admin.py b/apps/account/admin.py index 3b247289..651e5a5a 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -12,7 +12,7 @@ class RoleAdmin(admin.ModelAdmin): @admin.register(models.UserRole) class UserRoleAdmin(admin.ModelAdmin): - list_display = ['user', 'role'] + list_display = ['user', 'role', 'establishment'] @admin.register(models.User) diff --git a/apps/account/migrations/0013_auto_20191016_0810.py b/apps/account/migrations/0013_auto_20191016_0810.py new file mode 100644 index 00000000..72955cee --- /dev/null +++ b/apps/account/migrations/0013_auto_20191016_0810.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.4 on 2019-10-16 08:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'), + ('account', '0012_merge_20191015_0708'), + ] + + operations = [ + migrations.AddField( + model_name='userrole', + name='establishment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'), + ), + migrations.AlterField( + model_name='role', + name='country', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'), + ), + migrations.AlterField( + model_name='role', + name='role', + field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager')], verbose_name='Role'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 96927c1e..17bbd66a 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -13,6 +13,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 location.models import Country from utils.models import GMTokenGenerator from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin @@ -25,12 +26,14 @@ class Role(ProjectBaseMixin): COMMENTS_MODERATOR = 2 COUNTRY_ADMIN = 3 CONTENT_PAGE_MANAGER = 4 + ESTABLISHMENT_MANAGER = 5 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), - (CONTENT_PAGE_MANAGER, 'Content page manager') + (CONTENT_PAGE_MANAGER, 'Content page manager'), + (ESTABLISHMENT_MANAGER, 'Establishment manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) @@ -230,3 +233,5 @@ class UserRole(ProjectBaseMixin): """UserRole model.""" user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) + establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'), + on_delete=models.SET_NULL, null=True, blank=True) \ No newline at end of file diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index ff10e117..9c703f50 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -143,4 +143,17 @@ class IsCommentModerator(IsStandardUser): class IsEstablishmentManager(IsStandardUser): - pass + + def has_object_permission(self, request, view, obj): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.establishment_id).exists() + if is_access: + return True + + if super().has_object_permission(request, view, obj): + return True + + return False From 5ad3776fe30ee761842176c8ec43da6052135bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 12:14:13 +0300 Subject: [PATCH 282/319] establishment manager --- apps/establishment/models.py | 9 ++++++++- apps/establishment/views/back.py | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c97562cc..7938ae5a 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -385,10 +385,17 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def country_id(self): """ - Return Country object of establishment location + Return Country id of establishment location """ return self.address.city.country.id + @property + def establishment_id(self): + """ + Return establishment id of establishment location + """ + return self.id + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index fe9633ab..591fcc36 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics from establishment import models from establishment import serializers -from utils.permissions import IsCountryAdmin +from utils.permissions import IsCountryAdmin, IsEstablishmentManager class EstablishmentMixinViews: @@ -19,25 +19,27 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsEstablishmentManager] class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsEstablishmentManager] class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() + permission_classes = [IsEstablishmentManager] class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() + permission_classes = [IsEstablishmentManager] class SocialListCreateView(generics.ListCreateAPIView): @@ -45,12 +47,14 @@ class SocialListCreateView(generics.ListCreateAPIView): serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class SocialRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() + permission_classes = [IsEstablishmentManager] class PlateListCreateView(generics.ListCreateAPIView): @@ -58,12 +62,14 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() + permission_classes = [IsEstablishmentManager] class PhonesListCreateView(generics.ListCreateAPIView): @@ -71,12 +77,14 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() + permission_classes = [IsEstablishmentManager] class EmailListCreateView(generics.ListCreateAPIView): @@ -84,12 +92,14 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() + permission_classes = [IsEstablishmentManager] class EmployeeListCreateView(generics.ListCreateAPIView): From 766fdcdad59402bd944a2fbeaf5d523cb6989345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 15:18:31 +0300 Subject: [PATCH 283/319] Add reviewer manager --- apps/account/models.py | 4 +++- apps/utils/permissions.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index 17bbd66a..559d3ef8 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -27,13 +27,15 @@ class Role(ProjectBaseMixin): COUNTRY_ADMIN = 3 CONTENT_PAGE_MANAGER = 4 ESTABLISHMENT_MANAGER = 5 + REVIEWER_MANGER = 6 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), (CONTENT_PAGE_MANAGER, 'Content page manager'), - (ESTABLISHMENT_MANAGER, 'Establishment manager') + (ESTABLISHMENT_MANAGER, 'Establishment manager'), + (REVIEWER_MANGER, 'Reviewer manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 9c703f50..0450e4d3 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -157,3 +157,15 @@ class IsEstablishmentManager(IsStandardUser): return True return False + + +class IsReviewerManager(IsStandardUser): + def has_object_permission(self, request, view, obj): + access_models=[""] + + role = Role.objects.filter(role=Role.REVIEWER_MANGER)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role) + return False + From 32de6f7705aae63f33bf50391c37d54f54183a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 15:54:15 +0300 Subject: [PATCH 284/319] Readme --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 62dc8dc5..3cb1c6b9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # gm-backend +## Build + +1. ``git clone ssh://git@gl.id-east.ru:222/gm/gm-backend.git`` +1. ``cd ./gm-backend`` +1. ``git checkout develop`` +1. ``docker-compose build`` +1. First start database: ``docker-compose up db`` +1. ``docker-compose up -d`` +### Migrate data + +1.Connect to container with django ``docker exec -it gm-backend_gm_app_1 bash`` + +#### In docker container(django) + +1. Migrate ``python manage.py migrate`` +1. Create super-user ``python manage.py createsuperuser`` + +Backend is available at localhost:8000 or 0.0.0.0:8000 + +URL for admin http://0.0.0.0:8000/admin +URL for swagger http://0.0.0.0:8000/docs/ +URL for redocs http://0.0.0.0:8000/redocs/ + +## Start and stop backend containers + +Demonize start ``docker-compose up -d`` +Stop ``docker-compose down`` +Stop and remove volumes ``docker-compose down -v`` \ No newline at end of file From 5176a455c1c4a9dde1c40af9397390ded946e12c Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 17 Oct 2019 07:03:48 +0000 Subject: [PATCH 285/319] Added method for getting coordinates and city by ip --- apps/main/methods.py | 22 ++++++++++++++++------ apps/main/urls.py | 1 + apps/main/views.py | 23 +++++++++++++++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/apps/main/methods.py b/apps/main/methods.py index 67da3480..845a99a4 100644 --- a/apps/main/methods.py +++ b/apps/main/methods.py @@ -1,8 +1,10 @@ """Main app methods.""" import logging +from typing import Tuple, Optional from django.conf import settings from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception +from geoip2.models import City from main import models @@ -39,17 +41,16 @@ def determine_country_code(ip_addr): return country_code -def determine_coordinates(ip_addr): - longitude, latitude = None, None +def determine_coordinates(ip_addr: str) -> Tuple[Optional[float], Optional[float]]: if ip_addr: try: geoip = GeoIP2() - longitude, latitude = geoip.coords(ip_addr) + return geoip.coords(ip_addr) except GeoIP2Exception as ex: - logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}') + logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}') except Exception as ex: - logger.error(f'GEOIP Base exception: {ex}') - return longitude, latitude + logger.warning(f'GEOIP Base exception: {ex}') + return None, None def determine_user_site_url(country_code): @@ -73,3 +74,12 @@ def determine_user_site_url(country_code): return site.site_url +def determine_user_city(ip_addr: str) -> Optional[City]: + try: + geoip = GeoIP2() + return geoip.city(ip_addr) + except GeoIP2Exception as ex: + logger.warning(f'GEOIP Exception: {ex}. ip: {ip_addr}') + except Exception as ex: + logger.warning(f'GEOIP Base exception: {ex}') + return None diff --git a/apps/main/urls.py b/apps/main/urls.py index a74c0b49..12a98c84 100644 --- a/apps/main/urls.py +++ b/apps/main/urls.py @@ -6,6 +6,7 @@ app = 'main' urlpatterns = [ path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'), + path('determine-location/', views.DetermineLocation.as_view(), name='determine-location'), path('sites/', views.SiteListView.as_view(), name='site-list'), path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings'), path('awards/', views.AwardView.as_view(), name='awards_list'), diff --git a/apps/main/views.py b/apps/main/views.py index d7d1fa2c..ee06160e 100644 --- a/apps/main/views.py +++ b/apps/main/views.py @@ -3,6 +3,7 @@ from rest_framework import generics, permissions from rest_framework.response import Response from main import methods, models, serializers from utils.serializers import EmptySerializer +from django.http import Http404 class DetermineSiteView(generics.GenericAPIView): @@ -18,6 +19,22 @@ class DetermineSiteView(generics.GenericAPIView): return Response(data={'url': url}) +class DetermineLocation(generics.GenericAPIView): + """Determine user's location.""" + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + + def get(self, request, *args, **kwargs): + user_ip = methods.get_user_ip(request) + longitude, latitude = methods.determine_coordinates(user_ip) + city = methods.determine_user_city(user_ip) + if longitude and latitude and city: + return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) + else: + raise Http404 + + class SiteSettingsView(generics.RetrieveAPIView): """Site settings View.""" @@ -34,6 +51,8 @@ class SiteListView(generics.ListAPIView): permission_classes = (permissions.AllowAny,) queryset = models.SiteSettings.objects.with_country() serializer_class = serializers.SiteSerializer + + # # class FeatureViewMixin: # """Feature view mixin.""" @@ -76,7 +95,7 @@ class AwardView(generics.ListAPIView): """Awards list view.""" serializer_class = serializers.AwardSerializer queryset = models.Award.objects.all() - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) class AwardRetrieveView(generics.RetrieveAPIView): @@ -90,5 +109,5 @@ class CarouselListView(generics.ListAPIView): """Return list of carousel items.""" queryset = models.Carousel.objects.all() serializer_class = serializers.CarouselListSerializer - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) pagination_class = None From e2c27bd2e1e7db6ed7e2c6092c050320c140c276 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 13:40:52 +0300 Subject: [PATCH 286/319] switched broker storage from rabbtimq to redis --- project/settings/base.py | 5 ++++- project/settings/local.py | 7 +++++-- requirements/base.txt | 9 ++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/project/settings/base.py b/project/settings/base.py index 5c32f263..f89eff8d 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -317,7 +317,10 @@ REDOC_SETTINGS = { } # CELERY -BROKER_URL = 'amqp://rabbitmq:5672' +# RabbitMQ +# BROKER_URL = 'amqp://rabbitmq:5672' +# Redis +BROKER_URL = 'redis://base:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL CELERY_ACCEPT_CONTENT = ['application/json'] diff --git a/project/settings/local.py b/project/settings/local.py index a644e9b8..31fe88c2 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -6,7 +6,7 @@ ALLOWED_HOSTS = ['*', ] SEND_SMS = False SMS_CODE_SHOW = True -USE_CELERY = False +USE_CELERY = True SCHEMA_URI = 'http' DEFAULT_SUBDOMAIN = 'www' @@ -14,7 +14,10 @@ SITE_DOMAIN_URI = 'testserver.com:8000' DOMAIN_URI = '0.0.0.0:8000' # CELERY -BROKER_URL = 'amqp://rabbitmq:5672' +# RabbitMQ +# BROKER_URL = 'amqp://rabbitmq:5672' +# Redis +BROKER_URL = 'redis://redis:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL diff --git a/requirements/base.txt b/requirements/base.txt index 716d21c6..5c734322 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -15,8 +15,6 @@ djangorestframework==3.9.4 markdown django-filter==2.1.0 djangorestframework-xml -celery -amqp>=2.4.0 geoip2==2.9.0 django-phonenumber-field[phonenumbers]==2.1.0 @@ -33,4 +31,9 @@ djangorestframework-simplejwt==4.3.0 django-elasticsearch-dsl>=7.0.0,<8.0.0 django-elasticsearch-dsl-drf==0.20.2 -sentry-sdk==0.11.2 \ No newline at end of file +sentry-sdk==0.11.2 + +# temp solution +redis==3.2.0 +amqp>=2.4.0 +celery==4.3.0rc2 \ No newline at end of file From 8066d5607b0f324dae374a5effd5291bc13ef0d0 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 13:41:40 +0300 Subject: [PATCH 287/319] comment all rabbitmq dependencies added redis to docker compose file --- docker-compose.yml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7c4e49d2..3b446101 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: - "5436:5432" volumes: - gm-db:/var/lib/postgresql/data/ + elasticsearch: image: elasticsearch:7.3.1 volumes: @@ -27,11 +28,18 @@ services: - discovery.type=single-node - xpack.security.enabled=false - # RabbitMQ - rabbitmq: - image: rabbitmq:latest + # Redis + redis: + image: redis:2.8.23 ports: - - "5672:5672" + - "6379:6379" + + # RabbitMQ + #rabbitmq: + # image: rabbitmq:latest + # ports: + # - "5672:5672" + # Celery worker: build: . @@ -47,7 +55,9 @@ services: - .:/code links: - db - - rabbitmq +# - rabbitmq + - redis + worker_beat: build: . command: ./run_celery_beat.sh @@ -62,7 +72,8 @@ services: - .:/code links: - db - - rabbitmq +# - rabbitmq + - redis # App: G&M gm_app: build: . @@ -76,7 +87,8 @@ services: - DB_PASSWORD=postgres depends_on: - db - - rabbitmq +# - rabbitmq + - redis - worker - worker_beat - elasticsearch From 8a363498f55946ad82f2257ec1c230cedf53eb37 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 14:28:39 +0300 Subject: [PATCH 288/319] change broker url --- project/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/base.py b/project/settings/base.py index f89eff8d..040cbbad 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -320,7 +320,7 @@ REDOC_SETTINGS = { # RabbitMQ # BROKER_URL = 'amqp://rabbitmq:5672' # Redis -BROKER_URL = 'redis://base:6379/1' +BROKER_URL = 'redis://localhost:6379/1' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL CELERY_ACCEPT_CONTENT = ['application/json'] From 19b3502f582601adbed20e1a1272c2197a4a9fa3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 17 Oct 2019 14:34:31 +0300 Subject: [PATCH 289/319] revert auth backends --- project/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/base.py b/project/settings/base.py index 1de0c109..4618887d 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -222,8 +222,8 @@ REST_FRAMEWORK = { 'COERCE_DECIMAL_TO_STRING': False, 'DEFAULT_AUTHENTICATION_CLASSES': ( # JWT + 'utils.authentication.GMJWTAuthentication', 'rest_framework.authentication.SessionAuthentication', - # 'utils.authentication.GMJWTAuthentication', ), 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],), From c80b53a0fca1dffbc8b369a39cfffc43600a333e 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, 17 Oct 2019 15:04:08 +0300 Subject: [PATCH 290/319] reviewer manager --- apps/comment/tests.py | 2 +- apps/utils/permissions.py | 98 +++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 8cbcee88..9b060f4e 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -59,7 +59,7 @@ class CommentModeratorPermissionTests(BasePermissionTests): def test_get(self): response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_put_other_user(self): other_user = User.objects.create_user(username='test', diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 0450e4d3..4cfabee8 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -53,11 +53,16 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS or request.user.is_superuser: - return True + def has_permission(self, request, view): + return request.user.is_authenticated - return False + def has_object_permission(self, request, view, obj): + + rules = [ + request.user.is_superuser, + request.method in permissions.SAFE_METHODS + ] + return any(rules) class IsStandardUser(IsGuest): @@ -67,34 +72,32 @@ class IsStandardUser(IsGuest): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request - if obj.user == request.user and obj.user.email_confirmed: - return True + rules = [ + obj.user == request.user and obj.user.email_confirmed, + super().has_object_permission(request, view, obj) + ] - if super().has_object_permission(request, view, obj): - return True + return any(rules) - return False class IsContentPageManager(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. + role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, country_id=obj.country_id)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists() and + obj.user != request.user, + super().has_object_permission(request, view, obj) + ] + return any(rules) class IsCountryAdmin(IsStandardUser): @@ -108,15 +111,13 @@ class IsCountryAdmin(IsStandardUser): country_id=obj.country_id) \ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() + rules = [ + obj.user != request.user and + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_object_permission(request, view, obj), + ] - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + return any(rules) class IsCommentModerator(IsStandardUser): @@ -124,22 +125,18 @@ class IsCommentModerator(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, country_id=obj.country_id)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists() and + obj.user != request.user, + super().has_object_permission(request, view, obj) + ] + return any(rules) class IsEstablishmentManager(IsStandardUser): @@ -148,24 +145,27 @@ class IsEstablishmentManager(IsStandardUser): role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.establishment_id).exists() - if is_access: - return True + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.establishment_id).exists(), + super().has_object_permission(request, view, obj) + ] - if super().has_object_permission(request, view, obj): - return True - - return False + return any(rules) class IsReviewerManager(IsStandardUser): + def has_object_permission(self, request, view, obj): - access_models=[""] - role = Role.objects.filter(role=Role.REVIEWER_MANGER)\ - .first() # 'Comments moderator' + role = Role.objects.filter(role=Role.REVIEWER_MANGER, + country_id=obj.country_id)\ + .first() - is_access = UserRole.objects.filter(user=request.user, role=role) - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_object_permission(request, view, obj) + ] + + return any(rules) From 9661de481075b94978f8093d4126a727b016e7bf Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 17 Oct 2019 15:17:29 +0300 Subject: [PATCH 291/319] Establishment types --- .../0038_establishmenttype_index_name.py | 34 +++++++++++++++++++ apps/establishment/models.py | 14 ++++++++ apps/search_indexes/signals.py | 16 +++------ apps/tag/filters.py | 18 ++++------ apps/tag/models.py | 4 +++ apps/tag/views.py | 3 +- 6 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 apps/establishment/migrations/0038_establishmenttype_index_name.py diff --git a/apps/establishment/migrations/0038_establishmenttype_index_name.py b/apps/establishment/migrations/0038_establishmenttype_index_name.py new file mode 100644 index 00000000..657dbe42 --- /dev/null +++ b/apps/establishment/migrations/0038_establishmenttype_index_name.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.4 on 2019-10-16 11:33 + +from django.db import migrations, models + + +def fill_establishment_type(apps, schemaeditor): + import ipdb; ipdb.set_trace() + EstablishmentType = apps.get_model('establishment', 'EstablishmentType') + for n, et in enumerate(EstablishmentType.objects.all()): + et.index_name = f'Type {n}' + et.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0037_auto_20191015_1404'), + ] + + operations = [ + migrations.AddField( + model_name='establishmenttype', + name='index_name', + field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), + ), + migrations.RunPython(fill_establishment_type, migrations.RunPython.noop), + migrations.AlterField( + model_name='establishmenttype', + name='index_name', + field=models.CharField(choices=[('restaurant', 'Restaurant'), ('artisan', 'Artisan'), + ('producer', 'Producer')], db_index=True, max_length=50, + unique=True, verbose_name='Index name'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c93bbf89..7466414c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -27,8 +27,22 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): STR_FIELD_NAME = 'name' + # INDEX NAME CHOICES + RESTAURANT = 'restaurant' + ARTISAN = 'artisan' + PRODUCER = 'producer' + + INDEX_NAME_TYPES = ( + (RESTAURANT, _('Restaurant')), + (ARTISAN, _('Artisan')), + (PRODUCER, _('Producer')), + ) + name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, + unique=True, db_index=True, + verbose_name=_('Index name')) use_subtypes = models.BooleanField(_('Use subtypes'), default=True) tag_categories = models.ManyToManyField('tag.TagCategory', related_name='establishment_types', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index f7520b57..0f6a071f 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -40,12 +40,8 @@ def update_document(sender, **kwargs): for establishment in establishments: registry.update(establishment) - if app_label == 'main': - if model_name == 'metadata': - establishments = Establishment.objects.filter(tags__metadata=instance) - for establishment in establishments: - registry.update(establishment) - if model_name == 'metadatacontent': + if app_label == 'tag': + if model_name == 'tag': establishments = Establishment.objects.filter(tags=instance) for establishment in establishments: registry.update(establishment) @@ -70,12 +66,8 @@ def update_news(sender, **kwargs): for news in qs: registry.update(news) - if app_label == 'main': - if model_name == 'metadata': - qs = News.objects.filter(tags__metadata=instance) - for news in qs: - registry.update(news) - if model_name == 'metadatacontent': + if app_label == 'tag': + if model_name == 'tag': qs = News.objects.filter(tags=instance) for news in qs: registry.update(news) diff --git a/apps/tag/filters.py b/apps/tag/filters.py index 95f56b53..8816f820 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -1,5 +1,6 @@ """Tag app filters.""" from django_filters import rest_framework as filters +from establishment.models import EstablishmentType from tag import models @@ -18,16 +19,9 @@ class TagCategoryFilterSet(filters.FilterSet): type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, method='filter_by_type') - # Establishment type choices - RESTAURANT = 'restaurant' - - ESTABLISHMENT_TYPE_CHOICES = ( - (RESTAURANT, 'restaurant'), - ) - - establishment_type = filters.MultipleChoiceFilter( - choices=ESTABLISHMENT_TYPE_CHOICES, - method='filter_by_establishment_type') + establishment_type = filters.ChoiceFilter( + choices=EstablishmentType.INDEX_NAME_TYPES, + method='by_establishment_type') class Meta: """Meta class.""" @@ -44,5 +38,5 @@ class TagCategoryFilterSet(filters.FilterSet): return queryset # todo: filter by establishment type - def filter_by_establishment_type(self, queryset, name, value): - return queryset.for_establishments() + def by_establishment_type(self, queryset, name, value): + return queryset.by_establishment_type(value) diff --git a/apps/tag/models.py b/apps/tag/models.py index 4fa04f8e..d8d32639 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -45,6 +45,10 @@ class TagCategoryQuerySet(models.QuerySet): return self.filter(models.Q(establishment_types__isnull=False) | models.Q(establishment_subtypes__isnull=False)) + def by_establishment_type(self, index_name): + """Filter by establishment type index name.""" + return self.filter(establishment_types__index_name=index_name) + def with_tags(self, switcher=True): """Filter by existing tags.""" return self.filter(tags__isnull=not switcher) diff --git a/apps/tag/views.py b/apps/tag/views.py index 622ec849..ea83f3d5 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -20,5 +20,6 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): filterset_class = filters.TagCategoryFilterSet pagination_class = None permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.with_tags().with_base_related() + queryset = models.TagCategory.objects.with_tags().with_base_related().\ + distinct() serializer_class = serializers.TagCategoryBaseSerializer From 5a058c14f4e2276847a53cf1a82569c347906323 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, 17 Oct 2019 15:26:26 +0300 Subject: [PATCH 292/319] Add back for review --- apps/review/migrations/0004_review_country.py | 20 +++++++++++++++++++ apps/review/models.py | 3 +++ apps/review/serializers/back.py | 18 +++++++++++++++++ apps/review/urls/back.py | 11 ++++++++++ apps/review/views/back.py | 19 ++++++++++++++++++ project/urls/back.py | 1 + 6 files changed, 72 insertions(+) create mode 100644 apps/review/migrations/0004_review_country.py create mode 100644 apps/review/serializers/back.py create mode 100644 apps/review/urls/back.py create mode 100644 apps/review/views/back.py diff --git a/apps/review/migrations/0004_review_country.py b/apps/review/migrations/0004_review_country.py new file mode 100644 index 00000000..1d4173e0 --- /dev/null +++ b/apps/review/migrations/0004_review_country.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-10-17 12:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('review', '0003_review_text'), + ] + + operations = [ + migrations.AddField( + model_name='review', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='country', to='location.Country', verbose_name='Country'), + ), + ] diff --git a/apps/review/models.py b/apps/review/models.py index 9d3a39c4..4c7f3385 100644 --- a/apps/review/models.py +++ b/apps/review/models.py @@ -65,6 +65,9 @@ class Review(BaseAttributes, TranslatedFieldsMixin): validators=[MinValueValidator(1900), MaxValueValidator(2100)]) + country = models.ForeignKey('location.Country', on_delete=models.CASCADE, + related_name='country', verbose_name=_('Country'), + null=True) objects = ReviewQuerySet.as_manager() class Meta: diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py new file mode 100644 index 00000000..3e816394 --- /dev/null +++ b/apps/review/serializers/back.py @@ -0,0 +1,18 @@ +"""Review app common serializers.""" +from review import models +from rest_framework import serializers + + +class ReviewBaseSerializer(serializers.ModelSerializer): + class Meta: + model = models.Review + fields = ('id', + 'reviewer', + 'text', + 'language', + 'status', + 'child', + 'published_at', + 'vintage', + 'country' + ) \ No newline at end of file diff --git a/apps/review/urls/back.py b/apps/review/urls/back.py new file mode 100644 index 00000000..84ca49f3 --- /dev/null +++ b/apps/review/urls/back.py @@ -0,0 +1,11 @@ +"""Back review URLs""" +from django.urls import path + +from review.views import back as views + +app_name = 'review' + +urlpatterns = [ + path('', views.ReviewLstView.as_view(), name='review-list-create'), + path('/', views.ReviewRUDView.as_view(), name='review-crud'), +] diff --git a/apps/review/views/back.py b/apps/review/views/back.py new file mode 100644 index 00000000..a09f8fd9 --- /dev/null +++ b/apps/review/views/back.py @@ -0,0 +1,19 @@ +from rest_framework import generics, permissions +from review.serializers import back as serializers +from review import models +from utils.permissions import IsReviewerManager + + +class ReviewLstView(generics.ListCreateAPIView): + """Comment list create view.""" + serializer_class = serializers.ReviewBaseSerializer + queryset = models.Review.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly,] + + +class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): + """Comment RUD view.""" + serializer_class = serializers.ReviewBaseSerializer + queryset = models.Review.objects.all() + permission_classes = [IsReviewerManager] + lookup_field = 'id' diff --git a/project/urls/back.py b/project/urls/back.py index 59758c66..eb713c97 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -10,4 +10,5 @@ urlpatterns = [ path('news/', include('news.urls.back')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), + path('review/', include('review.urls.back')), ] \ No newline at end of file From c012f969afbedf204f47886b6e2594f55eb729b5 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, 17 Oct 2019 16:17:17 +0300 Subject: [PATCH 293/319] Add RestaurantReviewer role --- apps/account/models.py | 4 +++- apps/utils/permissions.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index 559d3ef8..a9f739bd 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -28,6 +28,7 @@ class Role(ProjectBaseMixin): CONTENT_PAGE_MANAGER = 4 ESTABLISHMENT_MANAGER = 5 REVIEWER_MANGER = 6 + RESTAURANT_REVIEWER = 7 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -35,7 +36,8 @@ class Role(ProjectBaseMixin): (COUNTRY_ADMIN, 'Country admin'), (CONTENT_PAGE_MANAGER, 'Content page manager'), (ESTABLISHMENT_MANAGER, 'Establishment manager'), - (REVIEWER_MANGER, 'Reviewer manager') + (REVIEWER_MANGER, 'Reviewer manager'), + (RESTAURANT_REVIEWER, 'Restaurant reviewer') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 4cfabee8..f979dca7 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -1,4 +1,6 @@ """Project custom permissions""" +from django.contrib.contenttypes.models import ContentType + from rest_framework import permissions from rest_framework_simplejwt.tokens import AccessToken @@ -169,3 +171,23 @@ class IsReviewerManager(IsStandardUser): return any(rules) + +class IsRestaurantReviewer(IsStandardUser): + + def has_object_permission(self, request, view, obj): + + content_type = ContentType.objects.get(app_lable='establishment', + model='establishment') + + role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER, + country=obj.country_id).first() + + rules = [ + obj.content_type_id == content_type.id and + UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.object_id + ).exists(), + super().has_object_permission(request, view, obj) + ] + + return any(rules) From 64ac59ee99e36a6c2c8b67bbd5f5a0a7c997b2d3 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, 17 Oct 2019 16:20:25 +0300 Subject: [PATCH 294/319] Add permission to view ReviewRUDView --- apps/review/views/back.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/review/views/back.py b/apps/review/views/back.py index a09f8fd9..2b4288d2 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from review.serializers import back as serializers from review import models -from utils.permissions import IsReviewerManager +from utils.permissions import IsReviewerManager, IsRestaurantReviewer class ReviewLstView(generics.ListCreateAPIView): @@ -15,5 +15,5 @@ class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.ReviewBaseSerializer queryset = models.Review.objects.all() - permission_classes = [IsReviewerManager] + permission_classes = [IsReviewerManager|IsRestaurantReviewer] lookup_field = 'id' From 1ff571b7670fcd190ac9b105b75ebc5d7fdc5c1b Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 17 Oct 2019 13:29:29 +0000 Subject: [PATCH 295/319] Feature/mobile features --- apps/location/urls/web.py | 3 +- apps/main/migrations/0017_feature_route.py | 19 ++++++ apps/main/migrations/0018_feature_source.py | 18 ++++++ apps/main/models.py | 54 +++++++++------- apps/main/serializers.py | 6 +- apps/main/urls.py | 15 ----- apps/main/urls/__init__.py | 0 apps/main/urls/common.py | 12 ++++ apps/main/urls/mobile.py | 11 ++++ apps/main/urls/web.py | 11 ++++ apps/main/views/__init__.py | 0 apps/main/{views.py => views/common.py} | 69 ++++++--------------- apps/main/views/mobile.py | 16 +++++ apps/main/views/web.py | 38 ++++++++++++ project/urls/mobile.py | 1 + project/urls/web.py | 2 +- 16 files changed, 185 insertions(+), 90 deletions(-) create mode 100644 apps/main/migrations/0017_feature_route.py create mode 100644 apps/main/migrations/0018_feature_source.py delete mode 100644 apps/main/urls.py create mode 100644 apps/main/urls/__init__.py create mode 100644 apps/main/urls/common.py create mode 100644 apps/main/urls/mobile.py create mode 100644 apps/main/urls/web.py create mode 100644 apps/main/views/__init__.py rename apps/main/{views.py => views/common.py} (72%) create mode 100644 apps/main/views/mobile.py create mode 100644 apps/main/views/web.py diff --git a/apps/location/urls/web.py b/apps/location/urls/web.py index cac89037..e86a3992 100644 --- a/apps/location/urls/web.py +++ b/apps/location/urls/web.py @@ -1,7 +1,6 @@ """Location app web urlconf.""" from location.urls.common import urlpatterns as common_urlpatterns - urlpatterns = [] -urlpatterns.extend(common_urlpatterns) \ No newline at end of file +urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/migrations/0017_feature_route.py b/apps/main/migrations/0017_feature_route.py new file mode 100644 index 00000000..0b0f46bb --- /dev/null +++ b/apps/main/migrations/0017_feature_route.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-07 14:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0016_merge_20190919_0954'), + ] + + operations = [ + migrations.AddField( + model_name='feature', + name='route', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Page'), + ), + ] diff --git a/apps/main/migrations/0018_feature_source.py b/apps/main/migrations/0018_feature_source.py new file mode 100644 index 00000000..cf94cad2 --- /dev/null +++ b/apps/main/migrations/0018_feature_source.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-07 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0017_feature_route'), + ] + + operations = [ + migrations.AddField( + model_name='feature', + name='source', + field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index fa6cf7d1..46598257 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -1,19 +1,23 @@ """Main app models.""" +from typing import Iterable + from django.conf import settings from django.contrib.contenttypes import fields as generic +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import JSONField from django.db import models +from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from django.contrib.contenttypes.models import ContentType from advertisement.models import Advertisement +from configuration.models import TranslationSettings from location.models import Country from main import methods from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, - TranslatedFieldsMixin, ImageMixin) + TranslatedFieldsMixin, ImageMixin, PlatformMixin) from utils.querysets import ContentTypeQuerySetMixin -from configuration.models import TranslationSettings + # # @@ -109,7 +113,6 @@ class SiteSettingsQuerySet(models.QuerySet): class SiteSettings(ProjectBaseMixin): - subdomain = models.CharField(max_length=255, db_index=True, unique=True, verbose_name=_('Subdomain')) country = models.OneToOneField(Country, on_delete=models.PROTECT, @@ -150,7 +153,8 @@ class SiteSettings(ProjectBaseMixin): @property def published_sitefeatures(self): - return self.sitefeature_set.filter(published=True) + return self.sitefeature_set\ + .filter(Q(published=True) and Q(feature__source__in=[PlatformMixin.WEB, PlatformMixin.ALL])) @property def site_url(self): @@ -159,11 +163,27 @@ class SiteSettings(ProjectBaseMixin): domain=settings.SITE_DOMAIN_URI) -class Feature(ProjectBaseMixin): +class Page(models.Model): + """Page model.""" + + page_name = models.CharField(max_length=255, unique=True) + advertisements = models.ManyToManyField(Advertisement) + + class Meta: + """Meta class.""" + verbose_name = _('Page') + verbose_name_plural = _('Pages') + + def __str__(self): + return f'{self.page_name}' + + +class Feature(ProjectBaseMixin, PlatformMixin): """Feature model.""" slug = models.CharField(max_length=255, unique=True) priority = models.IntegerField(unique=True, null=True, default=None) + route = models.ForeignKey(Page, on_delete=models.PROTECT, null=True, default=None) site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature') class Meta: @@ -181,6 +201,12 @@ class SiteFeatureQuerySet(models.QuerySet): def published(self, switcher=True): return self.filter(published=switcher) + def by_country_code(self, country_code: str): + return self.filter(site_settings__country__code=country_code) + + def by_sources(self, sources: Iterable[int]): + return self.filter(feature__source__in=sources) + class SiteFeature(ProjectBaseMixin): """SiteFeature model.""" @@ -351,19 +377,3 @@ class Carousel(models.Model): def model_name(self): if hasattr(self.content_object, 'establishment_type'): return self.content_object.establishment_type.name_translated - - - -class Page(models.Model): - """Page model.""" - - page_name = models.CharField(max_length=255, unique=True) - advertisements = models.ManyToManyField(Advertisement) - - class Meta: - """Meta class.""" - verbose_name = _('Page') - verbose_name_plural = _('Pages') - - def __str__(self): - return f'{self.page_name}' diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 03ea73e6..d425016d 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -25,6 +25,8 @@ class SiteFeatureSerializer(serializers.ModelSerializer): id = serializers.IntegerField(source='feature.id') slug = serializers.CharField(source='feature.slug') priority = serializers.IntegerField(source='feature.priority') + route = serializers.CharField(source='feature.route.page_name') + source = serializers.IntegerField(source='feature.source') class Meta: """Meta class.""" @@ -32,7 +34,9 @@ class SiteFeatureSerializer(serializers.ModelSerializer): fields = ('main', 'id', 'slug', - 'priority' + 'priority', + 'route', + 'source' ) diff --git a/apps/main/urls.py b/apps/main/urls.py deleted file mode 100644 index 12a98c84..00000000 --- a/apps/main/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Main app urls.""" -from django.urls import path -from main import views - -app = 'main' - -urlpatterns = [ - path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'), - path('determine-location/', views.DetermineLocation.as_view(), name='determine-location'), - path('sites/', views.SiteListView.as_view(), name='site-list'), - path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings'), - path('awards/', views.AwardView.as_view(), name='awards_list'), - path('awards//', views.AwardRetrieveView.as_view(), name='awards_retrieve'), - path('carousel/', views.CarouselListView.as_view(), name='carousel-list'), -] \ No newline at end of file diff --git a/apps/main/urls/__init__.py b/apps/main/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/main/urls/common.py b/apps/main/urls/common.py new file mode 100644 index 00000000..bac16add --- /dev/null +++ b/apps/main/urls/common.py @@ -0,0 +1,12 @@ +"""Main app urls.""" +from django.urls import path +from main.views.common import * + +app = 'main' + +common_urlpatterns = [ + path('awards/', AwardView.as_view(), name='awards_list'), + path('awards//', AwardRetrieveView.as_view(), name='awards_retrieve'), + path('carousel/', CarouselListView.as_view(), name='carousel-list'), +path('determine-location/', DetermineLocation.as_view(), name='determine-location') +] diff --git a/apps/main/urls/mobile.py b/apps/main/urls/mobile.py new file mode 100644 index 00000000..b0383d4e --- /dev/null +++ b/apps/main/urls/mobile.py @@ -0,0 +1,11 @@ +from main.urls.common import common_urlpatterns + +from django.urls import path + +from main.views.mobile import FeaturesView + +urlpatterns = [ + path('features/', FeaturesView.as_view(), name='features'), +] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/urls/web.py b/apps/main/urls/web.py new file mode 100644 index 00000000..2126b0c0 --- /dev/null +++ b/apps/main/urls/web.py @@ -0,0 +1,11 @@ +from main.urls.common import common_urlpatterns +from django.urls import path + +from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView + +urlpatterns = [ + path('determine-site/', DetermineSiteView.as_view(), name='determine-site'), + path('sites/', SiteListView.as_view(), name='site-list'), + path('site-settings//', SiteSettingsView.as_view(), name='site-settings'), ] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/views/__init__.py b/apps/main/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/main/views.py b/apps/main/views/common.py similarity index 72% rename from apps/main/views.py rename to apps/main/views/common.py index ee06160e..e6fd3444 100644 --- a/apps/main/views.py +++ b/apps/main/views/common.py @@ -1,56 +1,9 @@ """Main app views.""" +from django.http import Http404 from rest_framework import generics, permissions from rest_framework.response import Response + from main import methods, models, serializers -from utils.serializers import EmptySerializer -from django.http import Http404 - - -class DetermineSiteView(generics.GenericAPIView): - """Determine user's site.""" - - permission_classes = (permissions.AllowAny,) - serializer_class = EmptySerializer - - def get(self, request, *args, **kwargs): - user_ip = methods.get_user_ip(request) - country_code = methods.determine_country_code(user_ip) - url = methods.determine_user_site_url(country_code) - return Response(data={'url': url}) - - -class DetermineLocation(generics.GenericAPIView): - """Determine user's location.""" - - permission_classes = (permissions.AllowAny,) - serializer_class = EmptySerializer - - def get(self, request, *args, **kwargs): - user_ip = methods.get_user_ip(request) - longitude, latitude = methods.determine_coordinates(user_ip) - city = methods.determine_user_city(user_ip) - if longitude and latitude and city: - return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) - else: - raise Http404 - - -class SiteSettingsView(generics.RetrieveAPIView): - """Site settings View.""" - - lookup_field = 'subdomain' - permission_classes = (permissions.AllowAny,) - queryset = models.SiteSettings.objects.all() - serializer_class = serializers.SiteSettingsSerializer - - -class SiteListView(generics.ListAPIView): - """Site settings View.""" - - pagination_class = None - permission_classes = (permissions.AllowAny,) - queryset = models.SiteSettings.objects.with_country() - serializer_class = serializers.SiteSerializer # @@ -89,6 +42,7 @@ class SiteListView(generics.ListAPIView): # class SiteFeaturesRUDView(SiteFeaturesViewMixin, # generics.RetrieveUpdateDestroyAPIView): # """Site features RUD.""" +from utils.serializers import EmptySerializer class AwardView(generics.ListAPIView): @@ -111,3 +65,20 @@ class CarouselListView(generics.ListAPIView): serializer_class = serializers.CarouselListSerializer permission_classes = (permissions.AllowAny,) pagination_class = None + + +class DetermineLocation(generics.GenericAPIView): + """Determine user's location.""" + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + + def get(self, request, *args, **kwargs): + user_ip = methods.get_user_ip(request) + longitude, latitude = methods.determine_coordinates(user_ip) + city = methods.determine_user_city(user_ip) + if longitude and latitude and city: + return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) + else: + raise Http404 + diff --git a/apps/main/views/mobile.py b/apps/main/views/mobile.py new file mode 100644 index 00000000..b992dbb8 --- /dev/null +++ b/apps/main/views/mobile.py @@ -0,0 +1,16 @@ +from rest_framework import generics, permissions + +from main import models, serializers +from utils.models import PlatformMixin + + +class FeaturesView(generics.ListAPIView): + pagination_class = None + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.SiteFeatureSerializer + + def get_queryset(self): + return models.SiteFeature.objects\ + .prefetch_related('feature', 'feature__route') \ + .by_country_code(self.request.country_code) \ + .by_sources([PlatformMixin.ALL, PlatformMixin.MOBILE]) diff --git a/apps/main/views/web.py b/apps/main/views/web.py new file mode 100644 index 00000000..e1dc32ef --- /dev/null +++ b/apps/main/views/web.py @@ -0,0 +1,38 @@ +from typing import Iterable + +from rest_framework import generics, permissions + +from utils.serializers import EmptySerializer +from rest_framework.response import Response +from main import methods, models, serializers + + +class DetermineSiteView(generics.GenericAPIView): + """Determine user's site.""" + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + + def get(self, request, *args, **kwargs): + user_ip = methods.get_user_ip(request) + country_code = methods.determine_country_code(user_ip) + url = methods.determine_user_site_url(country_code) + return Response(data={'url': url}) + + +class SiteSettingsView(generics.RetrieveAPIView): + """Site settings View.""" + + lookup_field = 'subdomain' + permission_classes = (permissions.AllowAny,) + queryset = models.SiteSettings.objects.all() + serializer_class = serializers.SiteSettingsSerializer + + +class SiteListView(generics.ListAPIView): + """Site settings View.""" + + pagination_class = None + permission_classes = (permissions.AllowAny,) + queryset = models.SiteSettings.objects.with_country() + serializer_class = serializers.SiteSerializer diff --git a/project/urls/mobile.py b/project/urls/mobile.py index 0bcbd31c..0bea5305 100644 --- a/project/urls/mobile.py +++ b/project/urls/mobile.py @@ -4,6 +4,7 @@ app_name = 'mobile' urlpatterns = [ path('establishments/', include('establishment.urls.mobile')), + path('main/', include('main.urls.mobile')) # path('account/', include('account.urls.web')), # path('advertisement/', include('advertisement.urls.web')), # path('collection/', include('collection.urls.web')), diff --git a/project/urls/web.py b/project/urls/web.py index 59c08a6f..9c05e1a6 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -27,7 +27,7 @@ urlpatterns = [ path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), - path('main/', include('main.urls')), + path('main/', include('main.urls.web')), path('recipes/', include('recipe.urls.web')), path('translation/', include('translation.urls')), path('comments/', include('comment.urls.web')), From 2e2ed19118a6f2018621758ada99e4ca812e89f6 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, 18 Oct 2019 12:14:10 +0300 Subject: [PATCH 296/319] Fix establishment tests --- apps/establishment/models.py | 6 ++- apps/establishment/tests.py | 84 ++++++++++++++++++++++++++++-------- apps/utils/permissions.py | 12 ++++-- apps/utils/serializers.py | 4 +- 4 files changed, 81 insertions(+), 25 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7938ae5a..4aa37ebe 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -387,7 +387,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ Return Country id of establishment location """ - return self.address.city.country.id + return self.address.country_id @property def establishment_id(self): @@ -529,6 +529,10 @@ class Plate(TranslatedFieldsMixin, models.Model): menu = models.ForeignKey( 'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE) + @property + def establishment_id(self): + return self.menu.establishment.id + class Meta: verbose_name = _('plate') verbose_name_plural = _('plates') diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 39e28861..9eb8c987 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -7,10 +7,11 @@ from main.models import Currency from establishment.models import Establishment, EstablishmentType, Menu # Create your tests here. from translation.models import Language +from account.models import Role, UserRole +from location.models import Country, Address, City, Region class BaseTestCase(APITestCase): - def setUp(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' @@ -27,10 +28,49 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") # Create lang object - Language.objects.create( + self.lang = Language.objects.create( title='English', locale='en-GB' ) + self.lang.save() + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.region = Region.objects.create(name='Moscow area', code='01', + country=self.country_ru) + self.region.save() + + self.city = City.objects.create(name='Mosocow', code='01', + region=self.region, country=self.country_ru) + self.city.save() + + self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya', + number=2, postal_code='010100') + self.address.save() + + self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER) + self.role.save() + + self.establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id, + is_publish=True, + slug="test", + address=self.address + ) + + self.establishment.save() + + self.user_role = UserRole.objects.create(user=self.user, role=self.role, + establishment=self.establishment) + self.user_role.save() class EstablishmentBTests(BaseTestCase): @@ -43,25 +83,25 @@ class EstablishmentBTests(BaseTestCase): 'name': 'Test establishment', 'type_id': self.establishment_type.id, 'is_publish': True, - 'slug': 'test-establishment-slug', + 'slug': 'test-establishment-slug' } response = self.client.post('/api/back/establishments/', data=data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - establishment = response.json() - - response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json') + 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/{establishment["id"]}/', data=update_data) + 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/{establishment["id"]}/', format='json') + response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', + format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -96,39 +136,45 @@ class EmployeeTests(BaseTestCase): class ChildTestCase(BaseTestCase): def setUp(self): super().setUp() - self.establishment = Establishment.objects.create( - name="Test establishment", - establishment_type_id=self.establishment_type.id, - is_publish=True, - slug="test" - ) - # Test childs class EmailTests(ChildTestCase): - def test_email_CRUD(self): + def setUp(self): + super().setUp() + + def test_get(self): response = self.client.get('/api/back/establishments/emails/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_post(self): data = { 'email': "test@test.com", 'establishment': self.establishment.id } response = self.client.post('/api/back/establishments/emails/', data=data) + self.id_email = response.json()['id'] self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get('/api/back/establishments/emails/1/', format='json') + def test_get_by_pk(self): + self.test_post() + response = self.client.get(f'/api/back/establishments/emails/{self.id_email}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_patch(self): + self.test_post() + update_data = { 'email': 'testnew@test.com' } - response = self.client.patch('/api/back/establishments/emails/1/', data=update_data) + response = self.client.patch(f'/api/back/establishments/emails/{self.id_email}/', + data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete('/api/back/establishments/emails/1/') + def test_email_CRUD(self): + self.test_post() + response = self.client.delete(f'/api/back/establishments/emails/{self.id_email}/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f979dca7..17179483 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -75,10 +75,15 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request rules = [ - obj.user == request.user and obj.user.email_confirmed, super().has_object_permission(request, view, obj) ] + if hasattr(obj, 'user'): + rules = [ + obj.user == request.user and obj.user.email_confirmed, + super().has_object_permission(request, view, obj) + ] + return any(rules) @@ -114,7 +119,7 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - obj.user != request.user and + # obj.user != request.user and UserRole.objects.filter(user=request.user, role=role).exists(), super().has_object_permission(request, view, obj), ] @@ -149,7 +154,8 @@ class IsEstablishmentManager(IsStandardUser): rules = [ UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.establishment_id).exists(), + establishment_id=obj.establishment_id + ).exists(), super().has_object_permission(request, view, obj) ] diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 2b2282d1..90efea00 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -33,8 +33,8 @@ def validate_tjson(value): code='invalid_json', params={'value': value}, ) - lang_count = Language.objects.filter(locale__in=value.keys()).count() - if lang_count != len(value.keys()): + is_lang = Language.objects.filter(locale__in=value.keys()).exists() + if not is_lang: raise exceptions.ValidationError( 'invalid_translated_keys', code='invalid_translated_keys', From c44f8c39c1ab6db474500d39a3c0a79519e97952 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, 18 Oct 2019 13:05:42 +0300 Subject: [PATCH 297/319] Fix tests news --- apps/news/tests.py | 44 +++++++++++++++++++++++++++------------ apps/utils/permissions.py | 5 ++--- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/apps/news/tests.py b/apps/news/tests.py index 27ede62c..dd256bac 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -6,7 +6,7 @@ from rest_framework import status from datetime import datetime, timedelta from news.models import NewsType, News -from account.models import User +from account.models import User, Role, UserRole from translation.models import Language from location.models import Country # Create your tests here. @@ -24,11 +24,7 @@ class BaseTestCase(APITestCase): self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) self.test_news_type = NewsType.objects.create(name="Test news type") - self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, - news_type=self.test_news_type, description={"en-GB": "Description test news"}, - playlist=1, start=datetime.now() + timedelta(hours=-2), - end=datetime.now() + timedelta(hours=2), - state=News.PUBLISHED, slug='test-news-slug',) + self.lang = Language.objects.create( title='Russia', locale='ru-RU' @@ -44,16 +40,41 @@ class BaseTestCase(APITestCase): self.country_ru.languages.add(self.lang) self.country_ru.save() -class NewsTestCase(BaseTestCase): + role = Role.objects.create( + role=Role.CONTENT_PAGE_MANAGER, + country=self.country_ru + ) + role.save() - def test_news_list(self): + user_role = UserRole.objects.create( + user=self.user, + role=role + ) + user_role.save() + + self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, + title={"en-GB": "Test news"}, + news_type=self.test_news_type, + description={"en-GB": "Description test news"}, + playlist=1, start=datetime.now() + timedelta(hours=-2), + end=datetime.now() + timedelta(hours=2), + state=News.PUBLISHED, slug='test-news-slug', + country=self.country_ru) + +class NewsTestCase(BaseTestCase): + def setUp(self): + super().setUp() + + def test_web_news(self): response = self.client.get("/api/web/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_web_detail(self): response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get("/api/web/news/types/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_news_back_detail(self): response = self.client.get(f"/api/back/news/{self.test_news.id}/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -62,10 +83,6 @@ class NewsTestCase(BaseTestCase): response = self.client.get("/api/back/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_type_list(self): - response = self.client.get("/api/web/news/types/") - self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_back_detail_put(self): # retrieve-update-destroy url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id}) @@ -78,5 +95,6 @@ class NewsTestCase(BaseTestCase): 'news_type_id':self.test_news.news_type_id, 'country_id': self.country_ru.id } + response = self.client.put(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 17179483..45d978a0 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -100,8 +100,8 @@ class IsContentPageManager(IsStandardUser): .first() # 'Comments moderator' rules = [ - UserRole.objects.filter(user=request.user, role=role).exists() and - obj.user != request.user, + UserRole.objects.filter(user=request.user, role=role).exists(), + # and obj.user != request.user, super().has_object_permission(request, view, obj) ] return any(rules) @@ -119,7 +119,6 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - # obj.user != request.user and UserRole.objects.filter(user=request.user, role=role).exists(), super().has_object_permission(request, view, obj), ] From 5772558c4b8da5fa19b902ddf5afee9930ba9dfc 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, 18 Oct 2019 16:04:21 +0300 Subject: [PATCH 298/319] Fix --- apps/location/tests.py | 26 +++++++++++++++++++------- apps/location/views/back.py | 5 +++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index f68ba56b..331a9e2b 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -5,8 +5,8 @@ from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from location.models import City, Region, Country - +from location.models import City, Region, Country, Language +from account.models import Role, UserRole class BaseTestCase(APITestCase): @@ -20,27 +20,39 @@ class BaseTestCase(APITestCase): # get tokens + # self.user.is_superuser = True + # self.user.save() + tokkens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + + # role = Role.objects.create(role=Role.COUNTRY_ADMIN) + class CountryTests(BaseTestCase): + def setUp(self): + super().setUp() def test_country_CRUD(self): - response = self.client.get('/api/back/location/countries/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = { - 'name': 'Test country', + 'name': {"ru-RU":"Russia"}, 'code': 'test' } - response = self.client.post('/api/back/location/countries/', data=data, format='json') response_data = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) + response = self.client.get('/api/back/location/countries/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get(f'/api/back/location/countries/{response_data["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 5c028545..3a2739b2 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -43,14 +43,15 @@ class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIVie serializer_class = serializers.RegionSerializer permission_classes = [IsCountryAdmin] + # Country -class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView): +class CountryListCreateView(generics.ListCreateAPIView): """List/Create view for model Country.""" serializer_class = serializers.CountryBackSerializer pagination_class = None permission_classes = [IsCountryAdmin] -class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView): +class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer permission_classes = [IsCountryAdmin] \ No newline at end of file From 7be7315cef145ced8352e93a456aa70e57b2fad8 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 18 Oct 2019 18:00:57 +0300 Subject: [PATCH 299/319] added filters and endpoint to get winery list --- apps/establishment/filters.py | 12 ++++++ .../0038_establishmenttype_index_name.py | 5 ++- .../0039_establishmentsubtype_index_name.py | 35 +++++++++++++++++ apps/establishment/models.py | 38 +++++++++++++++++++ apps/establishment/urls/common.py | 1 + apps/establishment/views/web.py | 11 ++++++ 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 apps/establishment/migrations/0039_establishmentsubtype_index_name.py diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 13d951b6..20ece644 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -10,6 +10,10 @@ class EstablishmentFilter(filters.FilterSet): tag_id = filters.NumberFilter(field_name='tags__metadata__id',) award_id = filters.NumberFilter(field_name='awards__id',) search = filters.CharFilter(method='search_text') + est_type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES, + method='by_type') + est_subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES, + method='by_subtype') class Meta: """Meta class.""" @@ -19,6 +23,8 @@ class EstablishmentFilter(filters.FilterSet): 'tag_id', 'award_id', 'search', + 'est_type', + 'est_subtype', ) def search_text(self, queryset, name, value): @@ -27,6 +33,12 @@ class EstablishmentFilter(filters.FilterSet): return queryset.search(value, locale=self.request.locale) return queryset + def by_type(self, queryset, name, value): + return queryset.by_type(value) + + def by_subtype(self, queryset, name, value): + return queryset.by_subtype(value) + class EstablishmentTypeTagFilter(filters.FilterSet): """Establishment tag filter set.""" diff --git a/apps/establishment/migrations/0038_establishmenttype_index_name.py b/apps/establishment/migrations/0038_establishmenttype_index_name.py index 657dbe42..5f9d5879 100644 --- a/apps/establishment/migrations/0038_establishmenttype_index_name.py +++ b/apps/establishment/migrations/0038_establishmenttype_index_name.py @@ -3,8 +3,9 @@ from django.db import migrations, models -def fill_establishment_type(apps, schemaeditor): - import ipdb; ipdb.set_trace() +def fill_establishment_type(apps, schema_editor): + # We can't import the Person model directly as it may be a newer + # version than this migration expects. We use the historical version. EstablishmentType = apps.get_model('establishment', 'EstablishmentType') for n, et in enumerate(EstablishmentType.objects.all()): et.index_name = f'Type {n}' diff --git a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py new file mode 100644 index 00000000..5473600a --- /dev/null +++ b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.4 on 2019-10-18 13:47 + +from django.db import migrations, models + + +def fill_establishment_subtype(apps, schema_editor): + # We can't import the Person model directly as it may be a newer + # version than this migration expects. We use the historical version. + EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType') + for n, et in enumerate(EstablishmentSubType.objects.all()): + et.index_name = f'Type {n}' + et.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0038_establishmenttype_index_name'), + ] + + operations = [ + migrations.AddField( + model_name='establishmentsubtype', + name='index_name', + field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), + ), + migrations.RunPython(fill_establishment_subtype), + migrations.AlterField( + model_name='establishmentsubtype', + name='index_name', + field=models.CharField(choices=[('winery', 'Winery'), ], db_index=True, max_length=50, + unique=True, verbose_name='Index name'), + ), + + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7466414c..d69c0395 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -68,8 +68,18 @@ class EstablishmentSubTypeManager(models.Manager): class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): """Establishment type model.""" + # INDEX NAME CHOICES + WINERY = 'winery' + + INDEX_NAME_TYPES = ( + (WINERY, _('Winery')), + ) + name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, + unique=True, db_index=True, + verbose_name=_('Index name')) establishment_type = models.ForeignKey(EstablishmentType, on_delete=models.CASCADE, verbose_name=_('Type')) @@ -104,6 +114,9 @@ class EstablishmentQuerySet(models.QuerySet): 'phones').\ prefetch_actual_employees() + def with_type_related(self): + return self.prefetch_related('establishment_subtypes') + def search(self, value, locale=None): """Search text in JSON fields.""" if locale is not None: @@ -251,6 +264,31 @@ class EstablishmentQuerySet(models.QuerySet): kwargs = {unit: radius} return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs))) + def artisans(self): + """Return artisans.""" + return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN) + + def producers(self): + """Return producers.""" + return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER) + + def restaurants(self): + """Return restaurants.""" + return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT) + + def wineries(self): + """Return wineries.""" + return self.producers().filter( + establishment_subtypes__index_name=EstablishmentSubType.WINERY) + + def by_type(self, value): + """Return QuerySet with type by value.""" + return self.filter(establishment_type__index_name=value) + + def by_subtype(self, value): + """Return QuerySet with subtype by value.""" + return self.filter(establishment_subtypes__index_name=value) + class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """Establishment model.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 5d7df146..8d9453c1 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -9,6 +9,7 @@ urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), + # path('wineries/', views.WineriesListView.as_view(), name='wineries-list'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), path('slug//similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug//comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index f164ec73..cd83fed5 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -174,3 +174,14 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() if v is not None}) return qs + + +# Wineries +# todo: find out about difference between subtypes data +# class WineriesListView(EstablishmentListView): +# """Return list establishments with type Wineries""" +# +# def get_queryset(self): +# """Overridden get_queryset method.""" +# qs = super(WineriesListView, self).get_queryset() +# return qs.with_type_related().wineries() From 21e3f76f5a24847490894aefbb206f41dc4c9658 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 18 Oct 2019 18:02:24 +0300 Subject: [PATCH 300/319] Backoffice Tag&TagCategory --- .../tag/migrations/0003_auto_20191018_0758.py | 19 +++ apps/tag/models.py | 6 +- apps/tag/serializers.py | 104 ++++++++++++++++- apps/tag/urls/back.py | 11 +- apps/tag/views.py | 108 ++++++++++++++++-- .../migrations/0004_auto_20191018_0832.py | 18 +++ apps/translation/models.py | 2 +- apps/utils/exceptions.py | 23 +++- project/urls/back.py | 10 +- 9 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 apps/tag/migrations/0003_auto_20191018_0758.py create mode 100644 apps/translation/migrations/0004_auto_20191018_0832.py diff --git a/apps/tag/migrations/0003_auto_20191018_0758.py b/apps/tag/migrations/0003_auto_20191018_0758.py new file mode 100644 index 00000000..3814d05a --- /dev/null +++ b/apps/tag/migrations/0003_auto_20191018_0758.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-18 07:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ] + + operations = [ + migrations.AlterField( + model_name='tag', + name='category', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.TagCategory', verbose_name='Category'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index d8d32639..85d86e74 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -11,7 +11,7 @@ class Tag(TranslatedFieldsMixin, models.Model): label = TJSONField(blank=True, null=True, default=None, verbose_name=_('label'), help_text='{"en-GB":"some text"}') - category = models.ForeignKey('TagCategory', on_delete=models.PROTECT, + category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) @@ -36,6 +36,10 @@ class TagCategoryQuerySet(models.QuerySet): """Select related objects.""" return self.prefetch_related('tags') + def with_extended_related(self): + """Select related objects.""" + return self.select_related('country') + def for_news(self): """Select tag categories for news.""" return self.filter(news_types__isnull=True) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index e4ecf25d..8c994d8d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,6 +1,11 @@ """Tag serializers.""" from rest_framework import serializers +from establishment.models import (Establishment, EstablishmentType, + EstablishmentSubType) +from news.models import News, NewsType from tag import models +from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, + RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -49,14 +54,109 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): + """Tag Category detail serializer for back-office users.""" country_translated = TranslatedField(source='country.name_translated') - class Meta(TagBaseSerializer.Meta): + class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" fields = TagCategoryBaseSerializer.Meta.fields + ( - 'news_types', + 'label', 'country', 'country_translated', ) + + +class TagBindObjectSerializer(serializers.Serializer): + """Serializer for binding tag category and objects""" + + ESTABLISHMENT = 'establishment' + NEWS = 'news' + + TYPE_CHOICES = ( + (ESTABLISHMENT, 'Establishment type'), + (NEWS, 'News type'), + ) + + type = serializers.ChoiceField(TYPE_CHOICES) + object_id = serializers.IntegerField() + + def validate(self, attrs): + obj_type = attrs.get('type') + obj_id = attrs.get('object_id') + request = self.context.get('request') + view = self.context.get('view') + tag = view.get_object() + attrs['tag'] = tag + if obj_type == self.ESTABLISHMENT: + establishment = Establishment.objects.filter(pk=obj_id).first() + if not establishment: + raise BindingObjectNotFound() + if request.method == 'POST' and tag.establishments.filter( + pk=establishment.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag.establishments.filter( + pk=establishment.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment + elif obj_type == self.NEWS: + news = News.objects.filter(pk=obj_id).first() + if not news: + raise BindingObjectNotFound() + if request.method == 'POST' and tag.news.filter(pk=news.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag.news.filter( + pk=news.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = news + return attrs + + +class TagCategoryBindObjectSerializer(serializers.Serializer): + """Serializer for binding tag category and objects""" + + ESTABLISHMENT_TYPE = 'establishment_type' + NEWS_TYPE = 'news_type' + + TYPE_CHOICES = ( + (ESTABLISHMENT_TYPE, 'Establishment type'), + (NEWS_TYPE, 'News type'), + ) + + type = serializers.ChoiceField(TYPE_CHOICES) + object_id = serializers.IntegerField() + + def validate(self, attrs): + obj_type = attrs.get('type') + obj_id = attrs.get('object_id') + view = self.context.get('view') + tag_category = view.get_object() + attrs['tag_category'] = tag_category + request = self.context.get('request') + + if obj_type == self.ESTABLISHMENT_TYPE: + establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ + first() + if not establishment_type: + raise BindingObjectNotFound() + if request.method == 'POST' and tag_category.establishment_types.\ + filter(pk=establishment_type.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag_category.\ + establishment_types.filter(pk=establishment_type.pk).\ + exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment_type + elif obj_type == self.NEWS: + news_type = NewsType.objects.filter(pk=obj_id).first() + if not news_type: + raise BindingObjectNotFound() + if request.method == 'POST' and tag_category.news_types.\ + filter(pk=news_type.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag_category.news_types.\ + filter(pk=news_type.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = news_type + return attrs diff --git a/apps/tag/urls/back.py b/apps/tag/urls/back.py index 9f03bb45..03733297 100644 --- a/apps/tag/urls/back.py +++ b/apps/tag/urls/back.py @@ -1,16 +1,11 @@ """Urlconf for app tag.""" -from django.urls import path from rest_framework.routers import SimpleRouter from tag import views app_name = 'tag' router = SimpleRouter() -router.register(r'', views.TagViewSet) +router.register(r'categories', views.TagCategoryBackOfficeViewSet) +router.register(r'', views.TagBackOfficeViewSet) - -urlpatterns = [ - path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), -] - -urlpatterns += router.urls +urlpatterns = router.urls diff --git a/apps/tag/views.py b/apps/tag/views.py index ea83f3d5..2a0ff0f5 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,19 +1,12 @@ """Tag views.""" -from rest_framework import viewsets, mixins +from rest_framework import viewsets, mixins, status +from rest_framework.decorators import action +from rest_framework.response import Response from tag import filters, models, serializers from rest_framework import permissions -class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, - mixins.UpdateModelMixin, mixins.DestroyModelMixin, - viewsets.GenericViewSet): - """List/create tag view.""" - - pagination_class = None - queryset = models.Tag.objects.all() - serializer_class = serializers.TagBackOfficeSerializer - - +# User`s views & viewsets class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" @@ -23,3 +16,96 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): queryset = models.TagCategory.objects.with_tags().with_base_related().\ distinct() serializer_class = serializers.TagCategoryBaseSerializer + + +# BackOffice user`s views & viewsets +class BindObjectMixin: + """Bind object mixin.""" + + def get_serializer_class(self): + if self.action == 'bind_object': + return self.bind_object_serializer_class + return self.serializer_class + + def perform_binding(self, serializer): + raise NotImplemented + + def perform_unbinding(self, serializer): + raise NotImplemented + + @action(methods=['post', 'delete'], detail=True, url_path='bind-object') + def bind_object(self, request, pk=None): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + if request.method == 'POST': + self.perform_binding(serializer) + return Response(serializer.data, status=status.HTTP_201_CREATED) + elif request.method == 'DELETE': + self.perform_unbinding(serializer) + return Response(status=status.HTTP_204_NO_CONTENT) + + +class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, + BindObjectMixin, viewsets.GenericViewSet): + """List/create tag view.""" + + pagination_class = None + permission_classes = (permissions.IsAuthenticated, ) + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBackOfficeSerializer + bind_object_serializer_class = serializers.TagBindObjectSerializer + + def perform_binding(self, serializer): + data = serializer.validated_data + tag = data.pop('tag') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: + tag.establishments.add(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS: + tag.news.add(related_object) + + def perform_unbinding(self, serializer): + data = serializer.validated_data + tag = data.pop('tag') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: + tag.establishments.remove(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS: + tag.news.remove(related_object) + + +class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + mixins.RetrieveModelMixin, + BindObjectMixin, + TagCategoryViewSet): + """ViewSet for TagCategory model for BackOffice users.""" + + permission_classes = (permissions.IsAuthenticated, ) + queryset = TagCategoryViewSet.queryset.with_extended_related() + serializer_class = serializers.TagCategoryBackOfficeDetailSerializer + bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer + + def perform_binding(self, serializer): + data = serializer.validated_data + tag_category = data.pop('tag_category') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: + tag_category.establishment_types.add(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: + tag_category.news_types.add(related_object) + + def perform_unbinding(self, serializer): + data = serializer.validated_data + tag_category = data.pop('tag_category') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: + tag_category.establishment_types.remove(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: + tag_category.news_types.remove(related_object) diff --git a/apps/translation/migrations/0004_auto_20191018_0832.py b/apps/translation/migrations/0004_auto_20191018_0832.py new file mode 100644 index 00000000..d2d26a2b --- /dev/null +++ b/apps/translation/migrations/0004_auto_20191018_0832.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-18 08:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0003_auto_20190901_1032'), + ] + + operations = [ + migrations.AlterField( + model_name='language', + name='locale', + field=models.CharField(max_length=10, unique=True, verbose_name='Locale identifier'), + ), + ] diff --git a/apps/translation/models.py b/apps/translation/models.py index 42530965..bc9fbfbf 100644 --- a/apps/translation/models.py +++ b/apps/translation/models.py @@ -22,7 +22,7 @@ class Language(models.Model): title = models.CharField(max_length=255, verbose_name=_('Language title')) - locale = models.CharField(max_length=10, + locale = models.CharField(max_length=10, unique=True, verbose_name=_('Locale identifier')) objects = LanguageQuerySet.as_manager() diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py index 440f4ed4..37786ce7 100644 --- a/apps/utils/exceptions.py +++ b/apps/utils/exceptions.py @@ -1,5 +1,5 @@ from django.utils.translation import gettext_lazy as _ -from rest_framework import exceptions, status +from rest_framework import exceptions, serializers, status class ProjectBaseException(exceptions.APIException): @@ -142,3 +142,24 @@ class PasswordResetRequestExistedError(exceptions.APIException): """ status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Password reset request is already exists and valid.') + + +class ObjectAlreadyAdded(serializers.ValidationError): + """ + The exception must be thrown if the object has already been added to the + list. + """ + + default_detail = _('Object has already been added.') + + +class BindingObjectNotFound(serializers.ValidationError): + """The exception must be thrown if the object not found.""" + + default_detail = _('Binding object not found.') + + +class RemovedBindingObjectNotFound(serializers.ValidationError): + """The exception must be thrown if the object not found.""" + + default_detail = _('Removed binding object not found.') diff --git a/project/urls/back.py b/project/urls/back.py index 5d221932..206d359c 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -3,11 +3,11 @@ from django.urls import path, include app_name = 'back' urlpatterns = [ - path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), - path('establishments/', include('establishment.urls.back')), - path('location/', include('location.urls.back')), - path('news/', include('news.urls.back')), - # path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), + path('establishments/', include('establishment.urls.back')), + path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), + path('location/', include('location.urls.back')), + path('news/', include('news.urls.back')), + path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From 1cd586662ac52e85ce268b5d2dc5e48989af8751 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 10:12:30 +0300 Subject: [PATCH 301/319] fix tagcategory binding serializer --- apps/tag/serializers.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 8c994d8d..6ee55c84 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -83,12 +83,15 @@ class TagBindObjectSerializer(serializers.Serializer): object_id = serializers.IntegerField() def validate(self, attrs): + view = self.context.get('view') + request = self.context.get('request') + obj_type = attrs.get('type') obj_id = attrs.get('object_id') - request = self.context.get('request') - view = self.context.get('view') + tag = view.get_object() attrs['tag'] = tag + if obj_type == self.ESTABLISHMENT: establishment = Establishment.objects.filter(pk=obj_id).first() if not establishment: @@ -128,12 +131,14 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): object_id = serializers.IntegerField() def validate(self, attrs): + view = self.context.get('view') + request = self.context.get('request') + obj_type = attrs.get('type') obj_id = attrs.get('object_id') - view = self.context.get('view') + tag_category = view.get_object() attrs['tag_category'] = tag_category - request = self.context.get('request') if obj_type == self.ESTABLISHMENT_TYPE: establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ @@ -148,7 +153,7 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment_type - elif obj_type == self.NEWS: + elif obj_type == self.NEWS_TYPE: news_type = NewsType.objects.filter(pk=obj_id).first() if not news_type: raise BindingObjectNotFound() From c8b3f922e9200e8188ba5844df85ab6a9045ce06 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, 21 Oct 2019 11:08:30 +0300 Subject: [PATCH 302/319] Fix location tests --- apps/location/tests.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index 331a9e2b..0770227a 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -6,6 +6,7 @@ from rest_framework import status from http.cookies import SimpleCookie from location.models import City, Region, Country, Language +from django.contrib.gis.geos import Point from account.models import Role, UserRole class BaseTestCase(APITestCase): @@ -34,6 +35,23 @@ class BaseTestCase(APITestCase): ) self.lang.save() + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.role = Role.objects.create(role=Role.COUNTRY_ADMIN, + country=self.country_ru) + self.role.save() + + self.user_role = UserRole.objects.create(user=self.user, role=self.role) + + self.user_role.save() + # role = Role.objects.create(role=Role.COUNTRY_ADMIN) @@ -154,6 +172,7 @@ class AddressTests(BaseTestCase): def setUp(self): super().setUp() + self.country = Country.objects.create( name=json.dumps({"en-GB": "Test country"}), code="test" @@ -172,6 +191,13 @@ class AddressTests(BaseTestCase): country=self.country ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + def test_address_CRUD(self): response = self.client.get('/api/back/location/addresses/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -179,10 +205,8 @@ class AddressTests(BaseTestCase): data = { 'city_id': self.city.id, 'number': '+79999999', - "coordinates": { - "latitude": 37.0625, - "longitude": -95.677068 - }, + "latitude": 37.0625, + "longitude": -95.677068, "geo_lon": -95.677068, "geo_lat": 37.0625 } From 1332a8f65a3fefefaa241423193c11b6d2ba0bb9 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Mon, 21 Oct 2019 08:10:32 +0000 Subject: [PATCH 303/319] Login validation error message fix --- project/locale/ru/LC_MESSAGES/django.po | 3091 +++++++++++++++++++++++ 1 file changed, 3091 insertions(+) create mode 100644 project/locale/ru/LC_MESSAGES/django.po diff --git a/project/locale/ru/LC_MESSAGES/django.po b/project/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..8092593a --- /dev/null +++ b/project/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,3091 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-17 13:52+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" + +#: apps/account/admin.py:30 +msgid "Personal info" +msgstr "" + +#: apps/account/admin.py:34 +msgid "Subscription" +msgstr "" + +#: apps/account/admin.py:39 +msgid "Important dates" +msgstr "" + +#: apps/account/admin.py:40 +msgid "Permissions" +msgstr "" + +#: apps/account/admin.py:59 apps/location/models.py:18 +#: venv/lib/python3.6/site-packages/fcm_django/models.py:14 +msgid "Name" +msgstr "" + +#: apps/account/apps.py:7 +msgid "Account" +msgstr "" + +#: apps/account/forms.py:15 +msgid "The two password fields didn't match." +msgstr "" + +#: apps/account/forms.py:16 +msgid "Password already in use." +msgstr "" + +#: apps/account/forms.py:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:50 +msgid "New password" +msgstr "" + +#: apps/account/forms.py:25 +msgid "New password confirmation" +msgstr "" + +#: apps/account/models.py:31 apps/account/models.py:227 +msgid "Role" +msgstr "" + +#: apps/account/models.py:33 apps/location/models.py:28 apps/main/models.py:117 +msgid "Country" +msgstr "" + +#: apps/account/models.py:76 apps/news/models.py:126 apps/utils/models.py:194 +msgid "Image URL path" +msgstr "" + +#: apps/account/models.py:78 +msgid "Cropped image URL path" +msgstr "" + +#: apps/account/models.py:80 +msgid "email address" +msgstr "" + +#: apps/account/models.py:82 +msgid "unconfirmed email" +msgstr "" + +#: apps/account/models.py:83 +msgid "email status" +msgstr "" + +#: apps/account/models.py:90 +msgid "Roles" +msgstr "" + +#: apps/account/models.py:95 apps/account/models.py:226 +#: apps/comment/models.py:38 apps/establishment/models.py:435 +#: apps/favorites/models.py:23 apps/notification/models.py:79 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:30 +msgid "User" +msgstr "" + +#: apps/account/models.py:96 +msgid "Users" +msgstr "" + +#: apps/account/serializers/common.py:121 +msgid "Old password mismatch." +msgstr "" + +#: apps/account/serializers/common.py:124 apps/utils/exceptions.py:103 +msgid "Password is already in use" +msgstr "" + +#: apps/account/tasks.py:18 +msgid "Password resetting" +msgstr "" + +#: apps/account/tasks.py:31 apps/account/tasks.py:43 +msgid "Validate new email address" +msgstr "" + +#: apps/advertisement/apps.py:7 +msgid "advertisement" +msgstr "" + +#: apps/advertisement/models.py:15 +msgid "Ad URL" +msgstr "" + +#: apps/advertisement/models.py:16 +msgid "Block width" +msgstr "" + +#: apps/advertisement/models.py:17 +msgid "Block height" +msgstr "" + +#: apps/advertisement/models.py:18 +msgid "Block level" +msgstr "" + +#: apps/advertisement/models.py:22 apps/advertisement/models.py:23 +msgid "Advertisement" +msgstr "" + +#: apps/authorization/apps.py:8 +msgid "Authorization" +msgstr "" + +#: apps/authorization/models.py:86 +msgid "Expiration datetime" +msgstr "" + +#: apps/authorization/models.py:94 +msgid "Access token" +msgstr "" + +#: apps/authorization/models.py:95 +msgid "Access tokens" +msgstr "" + +#: apps/authorization/models.py:154 +msgid "Refresh token" +msgstr "" + +#: apps/authorization/models.py:155 +msgid "Refresh tokens" +msgstr "" + +#: apps/authorization/tasks.py:18 +msgid "Email confirmation" +msgstr "" + +#: apps/authorization/views/common.py:40 +msgid "Application is not found" +msgstr "" + +#: apps/authorization/views/common.py:50 +msgid "Not found an application with this source" +msgstr "" + +#: apps/booking/apps.py:7 apps/booking/models/models.py:66 +#: apps/booking/models/models.py:67 +msgid "Booking" +msgstr "" + +#: apps/booking/models/models.py:21 +msgid "Guestonline or Lastable" +msgstr "" + +#: apps/booking/models/models.py:22 +msgid "booking service establishment id" +msgstr "" + +#: apps/booking/models/models.py:23 +msgid "booking locale" +msgstr "" + +#: apps/booking/models/models.py:24 +msgid "external service pending booking" +msgstr "" + +#: apps/booking/models/models.py:25 +msgid "external service booking id" +msgstr "" + +#: apps/booking/models/models.py:28 +msgid "booking owner" +msgstr "" + +#: apps/collection/apps.py:7 apps/collection/models.py:80 +#: apps/collection/models.py:106 +msgid "collection" +msgstr "" + +#: apps/collection/models.py:17 apps/establishment/models.py:241 +#: apps/establishment/models.py:504 apps/location/models.py:34 +#: apps/location/models.py:55 apps/main/models.py:227 apps/main/models.py:278 +#: apps/news/models.py:14 +msgid "name" +msgstr "" + +#: apps/collection/models.py:26 +msgid "start" +msgstr "" + +#: apps/collection/models.py:27 +msgid "end" +msgstr "" + +#: apps/collection/models.py:54 +msgid "Ordinary" +msgstr "" + +#: apps/collection/models.py:55 +msgid "Pop" +msgstr "" + +#: apps/collection/models.py:60 +msgid "Collection type" +msgstr "" + +#: apps/collection/models.py:62 apps/establishment/models.py:280 +msgid "Publish status" +msgstr "" + +#: apps/collection/models.py:64 +msgid "Position on top" +msgstr "" + +#: apps/collection/models.py:66 apps/location/models.py:40 +#: apps/location/models.py:60 apps/main/models.py:226 apps/news/models.py:135 +msgid "country" +msgstr "" + +#: apps/collection/models.py:68 +msgid "collection block properties" +msgstr "" + +#: apps/collection/models.py:71 apps/establishment/models.py:245 +#: apps/establishment/models.py:507 apps/news/models.py:111 +msgid "description" +msgstr "" + +#: apps/collection/models.py:74 +msgid "Collection slug" +msgstr "" + +#: apps/collection/models.py:81 +msgid "collections" +msgstr "" + +#: apps/collection/models.py:99 +msgid "parent" +msgstr "" + +#: apps/collection/models.py:103 +msgid "advertorials" +msgstr "" + +#: apps/collection/models.py:112 +msgid "guide" +msgstr "" + +#: apps/collection/models.py:113 +msgid "guides" +msgstr "" + +#: apps/comment/apps.py:7 +msgid "comment" +msgstr "" + +#: apps/comment/apps.py:8 +msgid "comments" +msgstr "" + +#: apps/comment/models.py:32 +msgid "Comment text" +msgstr "" + +#: apps/comment/models.py:34 +msgid "Mark" +msgstr "" + +#: apps/comment/models.py:44 +msgid "Locale" +msgstr "" + +#: apps/comment/models.py:48 +msgid "Comment" +msgstr "" + +#: apps/comment/models.py:49 +msgid "Comments" +msgstr "" + +#: apps/configuration/apps.py:7 +msgid "configuration" +msgstr "" + +#: apps/configuration/models.py:9 +msgid "default language" +msgstr "" + +#: apps/establishment/admin.py:87 apps/establishment/models.py:529 +#: apps/main/models.py:248 +msgid "category" +msgstr "" + +#: apps/establishment/apps.py:8 apps/establishment/models.py:310 +#: apps/establishment/models.py:418 +msgid "Establishment" +msgstr "" + +#: apps/establishment/models.py:30 apps/establishment/models.py:54 +#: apps/establishment/models.py:391 apps/recipe/models.py:52 +msgid "Description" +msgstr "" + +#: apps/establishment/models.py:32 +msgid "Use subtypes" +msgstr "" + +#: apps/establishment/models.py:37 +msgid "Establishment type" +msgstr "" + +#: apps/establishment/models.py:38 +msgid "Establishment types" +msgstr "" + +#: apps/establishment/models.py:58 +msgid "Type" +msgstr "" + +#: apps/establishment/models.py:65 +msgid "Establishment subtype" +msgstr "" + +#: apps/establishment/models.py:66 +msgid "Establishment subtypes" +msgstr "" + +#: apps/establishment/models.py:70 +msgid "Establishment type is not use subtypes." +msgstr "" + +#: apps/establishment/models.py:242 +msgid "Transliterated name" +msgstr "" + +#: apps/establishment/models.py:249 +msgid "public mark" +msgstr "" + +#: apps/establishment/models.py:252 +msgid "toque number" +msgstr "" + +#: apps/establishment/models.py:256 +msgid "type" +msgstr "" + +#: apps/establishment/models.py:259 +msgid "subtype" +msgstr "" + +#: apps/establishment/models.py:262 apps/news/models.py:131 +msgid "address" +msgstr "" + +#: apps/establishment/models.py:265 +msgid "price level" +msgstr "" + +#: apps/establishment/models.py:267 +msgid "Web site URL" +msgstr "" + +#: apps/establishment/models.py:269 +msgid "Facebook URL" +msgstr "" + +#: apps/establishment/models.py:271 +msgid "Twitter URL" +msgstr "" + +#: apps/establishment/models.py:273 +msgid "Lafourchette URL" +msgstr "" + +#: apps/establishment/models.py:274 +msgid "guestonline id" +msgstr "" + +#: apps/establishment/models.py:276 +msgid "lastable id" +msgstr "" + +#: apps/establishment/models.py:279 +msgid "Booking URL" +msgstr "" + +#: apps/establishment/models.py:282 +msgid "Establishment schedule" +msgstr "" + +#: apps/establishment/models.py:289 +msgid "Transportation" +msgstr "" + +#: apps/establishment/models.py:293 +msgid "Collections" +msgstr "" + +#: apps/establishment/models.py:294 apps/news/models.py:128 +msgid "Preview image URL path" +msgstr "" + +#: apps/establishment/models.py:297 +msgid "Establishment slug" +msgstr "" + +#: apps/establishment/models.py:311 +msgid "Establishments" +msgstr "" + +#: apps/establishment/models.py:399 apps/establishment/models.py:425 +msgid "Position" +msgstr "" + +#: apps/establishment/models.py:400 +msgid "Positions" +msgstr "" + +#: apps/establishment/models.py:420 apps/establishment/models.py:445 +msgid "Employee" +msgstr "" + +#: apps/establishment/models.py:421 +msgid "From date" +msgstr "" + +#: apps/establishment/models.py:423 +msgid "To date" +msgstr "" + +#: apps/establishment/models.py:436 +msgid "Last name" +msgstr "" + +#: apps/establishment/models.py:446 +msgid "Employees" +msgstr "" + +#: apps/establishment/models.py:460 +msgid "contact phone" +msgstr "" + +#: apps/establishment/models.py:461 +msgid "contact phones" +msgstr "" + +#: apps/establishment/models.py:474 +msgid "contact email" +msgstr "" + +#: apps/establishment/models.py:475 +msgid "contact emails" +msgstr "" + +#: apps/establishment/models.py:510 +msgid "price" +msgstr "" + +#: apps/establishment/models.py:511 +msgid "is signature plate" +msgstr "" + +#: apps/establishment/models.py:513 apps/main/models.py:281 +msgid "currency" +msgstr "" + +#: apps/establishment/models.py:516 apps/establishment/models.py:536 +#: apps/establishment/models.py:537 +msgid "menu" +msgstr "" + +#: apps/establishment/models.py:519 +msgid "plate" +msgstr "" + +#: apps/establishment/models.py:520 +msgid "plates" +msgstr "" + +#: apps/establishment/models.py:532 apps/establishment/models.py:542 +msgid "establishment" +msgstr "" + +#: apps/establishment/models.py:544 apps/main/models.py:207 +#: apps/news/models.py:105 +msgid "title" +msgstr "" + +#: apps/establishment/models.py:545 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2234 +msgid "URL" +msgstr "" + +#: apps/establishment/models.py:548 +msgid "social network" +msgstr "" + +#: apps/establishment/models.py:549 +msgid "social networks" +msgstr "" + +#: apps/establishment/serializers/common.py:237 +#: apps/timetable/serialziers.py:47 +msgid "Establishment not found." +msgstr "" + +#: apps/establishment/serializers/common.py:288 +msgid "Object not found." +msgstr "" + +#: apps/favorites/apps.py:7 apps/favorites/models.py:32 +#: apps/favorites/models.py:33 +msgid "Favorites" +msgstr "" + +#: apps/gallery/apps.py:7 +msgid "gallery" +msgstr "" + +#: apps/gallery/models.py:12 +msgid "Image file" +msgstr "" + +#: apps/gallery/models.py:16 apps/utils/models.py:147 apps/utils/models.py:176 +#: apps/utils/models.py:207 +#: venv/lib/python3.6/site-packages/django/db/models/fields/files.py:360 +msgid "Image" +msgstr "" + +#: apps/gallery/models.py:17 +msgid "Images" +msgstr "" + +#: apps/location/admin.py:28 apps/main/apps.py:8 apps/main/models.py:191 +msgid "Main" +msgstr "" + +#: apps/location/admin.py:31 +msgid "Location" +msgstr "" + +#: apps/location/admin.py:34 +msgid "Address detail" +msgstr "" + +#: apps/location/apps.py:7 +msgid "location" +msgstr "" + +#: apps/location/models.py:19 +msgid "Code" +msgstr "" + +#: apps/location/models.py:20 +msgid "Low price" +msgstr "" + +#: apps/location/models.py:21 +msgid "High price" +msgstr "" + +#: apps/location/models.py:22 apps/translation/models.py:34 +msgid "Languages" +msgstr "" + +#: apps/location/models.py:27 +msgid "Countries" +msgstr "" + +#: apps/location/models.py:35 apps/location/models.py:56 +msgid "code" +msgstr "" + +#: apps/location/models.py:37 apps/location/models.py:58 +msgid "parent region" +msgstr "" + +#: apps/location/models.py:45 +msgid "regions" +msgstr "" + +#: apps/location/models.py:46 +msgid "region" +msgstr "" + +#: apps/location/models.py:63 apps/location/models.py:85 +msgid "postal code" +msgstr "" + +#: apps/location/models.py:63 apps/location/models.py:86 +msgid "Ex.: 350018" +msgstr "" + +#: apps/location/models.py:65 +msgid "is island" +msgstr "" + +#: apps/location/models.py:68 +msgid "cities" +msgstr "" + +#: apps/location/models.py:69 apps/location/models.py:78 +msgid "city" +msgstr "" + +#: apps/location/models.py:80 +msgid "street name 1" +msgstr "" + +#: apps/location/models.py:82 +msgid "street name 2" +msgstr "" + +#: apps/location/models.py:83 +msgid "number" +msgstr "" + +#: apps/location/models.py:88 +msgid "Coordinates" +msgstr "" + +#: apps/location/models.py:93 apps/location/models.py:94 +msgid "Address" +msgstr "" + +#: apps/location/serializers/common.py:120 +#: apps/location/serializers/common.py:125 +msgid "Invalid value" +msgstr "" + +#: apps/main/models.py:114 +msgid "Subdomain" +msgstr "" + +#: apps/main/models.py:119 +msgid "Default site" +msgstr "" + +#: apps/main/models.py:121 +msgid "Pinterest page URL" +msgstr "" + +#: apps/main/models.py:123 +msgid "Twitter page URL" +msgstr "" + +#: apps/main/models.py:125 +msgid "Facebook page URL" +msgstr "" + +#: apps/main/models.py:127 +msgid "Instagram page URL" +msgstr "" + +#: apps/main/models.py:129 +msgid "Contact email" +msgstr "" + +#: apps/main/models.py:131 +msgid "Config" +msgstr "" + +#: apps/main/models.py:133 +msgid "AD config" +msgstr "" + +#: apps/main/models.py:140 +msgid "Site setting" +msgstr "" + +#: apps/main/models.py:141 +msgid "Site settings" +msgstr "" + +#: apps/main/models.py:171 +msgid "Feature" +msgstr "" + +#: apps/main/models.py:172 +msgid "Features" +msgstr "" + +#: apps/main/models.py:190 apps/news/models.py:98 apps/recipe/models.py:42 +msgid "Published" +msgstr "" + +#: apps/main/models.py:198 +msgid "Site feature" +msgstr "" + +#: apps/main/models.py:199 +msgid "Site features" +msgstr "" + +#: apps/main/models.py:209 +msgid "vintage year" +msgstr "" + +#: apps/main/models.py:245 +msgid "label" +msgstr "" + +#: apps/main/models.py:251 apps/main/models.py:252 +msgid "metadata" +msgstr "" + +#: apps/main/models.py:282 +msgid "currencies" +msgstr "" + +#: apps/main/models.py:302 apps/main/models.py:303 +msgid "Carousel" +msgstr "" + +#: apps/main/models.py:365 apps/translation/models.py:49 +msgid "Page" +msgstr "" + +#: apps/main/models.py:366 +msgid "Pages" +msgstr "" + +#: apps/news/apps.py:7 apps/news/models.py:145 apps/news/models.py:146 +msgid "news" +msgstr "" + +#: apps/news/models.py:19 +msgid "news types" +msgstr "" + +#: apps/news/models.py:20 apps/news/models.py:103 +msgid "news type" +msgstr "" + +#: apps/news/models.py:96 apps/recipe/models.py:40 +msgid "Waiting" +msgstr "" + +#: apps/news/models.py:97 apps/recipe/models.py:41 +msgid "Hidden" +msgstr "" + +#: apps/news/models.py:99 apps/recipe/models.py:43 +msgid "Published exclusive" +msgstr "" + +#: apps/news/models.py:108 +msgid "subtitle" +msgstr "" + +#: apps/news/models.py:113 +msgid "Start" +msgstr "" + +#: apps/news/models.py:115 +msgid "End" +msgstr "" + +#: apps/news/models.py:117 +msgid "News slug" +msgstr "" + +#: apps/news/models.py:118 +msgid "playlist" +msgstr "" + +#: apps/news/models.py:120 apps/notification/models.py:89 +#: apps/recipe/models.py:55 +msgid "State" +msgstr "" + +#: apps/news/models.py:122 +msgid "Is highlighted" +msgstr "" + +#: apps/notification/apps.py:7 +msgid "notification" +msgstr "" + +#: apps/notification/models.py:73 +msgid "Unusable" +msgstr "" + +#: apps/notification/models.py:74 +msgid "Usable" +msgstr "" + +#: apps/notification/models.py:81 +msgid "Email" +msgstr "" + +#: apps/notification/models.py:83 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1891 +msgid "IP address" +msgstr "" + +#: apps/notification/models.py:85 +msgid "Country code" +msgstr "" + +#: apps/notification/models.py:87 apps/translation/models.py:26 +msgid "Locale identifier" +msgstr "" + +#: apps/notification/models.py:91 +msgid "Token" +msgstr "" + +#: apps/notification/models.py:98 +msgid "Subscriber" +msgstr "" + +#: apps/notification/models.py:99 +msgid "Subscribers" +msgstr "" + +#: apps/notification/serializers/common.py:29 +msgid "Does not match user email" +msgstr "" + +#: apps/notification/serializers/common.py:32 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:53 +msgid "This field is required." +msgstr "" + +#: apps/partner/apps.py:7 apps/partner/models.py:12 +msgid "partner" +msgstr "" + +#: apps/partner/models.py:9 +msgid "Partner URL" +msgstr "" + +#: apps/partner/models.py:13 +msgid "partners" +msgstr "" + +#: apps/products/apps.py:8 +msgid "products" +msgstr "" + +#: apps/rating/models.py:11 +msgid "ip" +msgstr "" + +#: apps/recipe/apps.py:8 +msgid "RecipeConfig" +msgstr "" + +#: apps/recipe/models.py:48 +msgid "Title" +msgstr "" + +#: apps/recipe/models.py:50 +msgid "Subtitle" +msgstr "" + +#: apps/recipe/models.py:57 +msgid "Author" +msgstr "" + +#: apps/recipe/models.py:58 apps/recipe/models.py:60 +msgid "Published at" +msgstr "" + +#: apps/recipe/models.py:61 apps/recipe/models.py:63 +msgid "Published scheduled at" +msgstr "" + +#: apps/recipe/models.py:70 +msgid "Recipe" +msgstr "" + +#: apps/recipe/models.py:71 +msgid "Recipes" +msgstr "" + +#: apps/review/apps.py:7 +msgid "reviews" +msgstr "" + +#: apps/review/models.py:37 +msgid "To investigate" +msgstr "" + +#: apps/review/models.py:38 +msgid "To review" +msgstr "" + +#: apps/review/models.py:39 +msgid "Ready" +msgstr "" + +#: apps/review/models.py:45 +msgid "Reviewer" +msgstr "" + +#: apps/review/models.py:47 +msgid "text" +msgstr "" + +#: apps/review/models.py:55 +msgid "Review language" +msgstr "" + +#: apps/review/models.py:60 +msgid "Child review" +msgstr "" + +#: apps/review/models.py:61 +msgid "Publish datetime" +msgstr "" + +#: apps/review/models.py:63 +msgid "Review published datetime" +msgstr "" + +#: apps/review/models.py:64 +msgid "Year of review" +msgstr "" + +#: apps/review/models.py:72 +msgid "Review" +msgstr "" + +#: apps/review/models.py:73 +msgid "Reviews" +msgstr "" + +#: apps/search_indexes/apps.py:7 +msgid "Search indexes" +msgstr "" + +#: apps/timetable/apps.py:7 +msgid "timetable" +msgstr "" + +#: apps/timetable/models.py:18 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Monday" +msgstr "" + +#: apps/timetable/models.py:19 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Tuesday" +msgstr "" + +#: apps/timetable/models.py:20 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Wednesday" +msgstr "" + +#: apps/timetable/models.py:21 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Thursday" +msgstr "" + +#: apps/timetable/models.py:22 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Friday" +msgstr "" + +#: apps/timetable/models.py:23 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:7 +msgid "Saturday" +msgstr "" + +#: apps/timetable/models.py:24 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:7 +msgid "Sunday" +msgstr "" + +#: apps/timetable/models.py:26 +msgid "Week day" +msgstr "" + +#: apps/timetable/models.py:28 +msgid "Lunch start time" +msgstr "" + +#: apps/timetable/models.py:29 +msgid "Lunch end time" +msgstr "" + +#: apps/timetable/models.py:30 +msgid "Dinner start time" +msgstr "" + +#: apps/timetable/models.py:31 +msgid "Dinner end time" +msgstr "" + +#: apps/timetable/models.py:32 +msgid "Opening time" +msgstr "" + +#: apps/timetable/models.py:33 +msgid "Closed time" +msgstr "" + +#: apps/timetable/models.py:37 +msgid "Timetable" +msgstr "" + +#: apps/timetable/models.py:38 +msgid "Timetables" +msgstr "" + +#: apps/translation/apps.py:7 +msgid "Translation" +msgstr "" + +#: apps/translation/models.py:24 +msgid "Language title" +msgstr "" + +#: apps/translation/models.py:33 +msgid "Language" +msgstr "" + +#: apps/translation/models.py:51 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2075 +msgid "Text" +msgstr "" + +#: apps/translation/models.py:59 apps/translation/models.py:60 +msgid "Site interface dictionary" +msgstr "" + +#: apps/utils/exceptions.py:8 +msgid "Bad request" +msgstr "" + +#: apps/utils/exceptions.py:27 +msgid "Service is temporarily unavailable" +msgstr "" + +#: apps/utils/exceptions.py:32 +msgid "User not found" +msgstr "" + +#: apps/utils/exceptions.py:38 +#, python-format +msgid "Unable to send message to mailbox %s" +msgstr "" + +#: apps/utils/exceptions.py:53 +#, python-format +msgid "Locale not found in database (%s)" +msgstr "" + +#: apps/utils/exceptions.py:68 +msgid "Wrong username" +msgstr "" + +#: apps/utils/exceptions.py:76 +msgid "Not valid token" +msgstr "" + +#: apps/utils/exceptions.py:83 +msgid "Not valid access token" +msgstr "" + +#: apps/utils/exceptions.py:90 +msgid "Not valid refresh token" +msgstr "" + +#: apps/utils/exceptions.py:95 +msgid "OAuth2 Error" +msgstr "" + +#: apps/utils/exceptions.py:111 +msgid "Email address is already confirmed" +msgstr "" + +#: apps/utils/exceptions.py:119 +msgid "Image invalid input." +msgstr "" + +#: apps/utils/exceptions.py:126 +msgid "Incorrect login or password." +msgstr "Неправильный логин или пароль." + +#: apps/utils/exceptions.py:135 +msgid "Item is already in favorites." +msgstr "" + +#: apps/utils/exceptions.py:144 +msgid "Password reset request is already exists and valid." +msgstr "" + +#: apps/utils/models.py:21 +msgid "Date created" +msgstr "" + +#: apps/utils/models.py:23 +msgid "Date updated" +msgstr "" + +#: apps/utils/models.py:126 +msgid "created by" +msgstr "" + +#: apps/utils/models.py:130 +msgid "modified by" +msgstr "" + +#: apps/utils/models.py:186 +msgid "SVG image" +msgstr "" + +#: apps/utils/models.py:219 +msgid "Mobile" +msgstr "" + +#: apps/utils/models.py:220 +msgid "Web" +msgstr "" + +#: apps/utils/models.py:221 +msgid "All" +msgstr "" + +#: apps/utils/models.py:224 +msgid "Source" +msgstr "" + +#: project/templates/account/change_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you want to change email address at " +"%(site_name)s." +msgstr "" + +#: project/templates/account/change_email.html:4 +msgid "Please go to the following page for confirmation new email address:" +msgstr "" + +#: project/templates/account/change_email.html:8 +#: project/templates/account/password_reset_email.html:8 +#: project/templates/authorization/confirm_email.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:10 +msgid "Thanks for using our site!" +msgstr "" + +#: project/templates/account/change_email.html:10 +#: project/templates/account/password_reset_email.html:10 +#: project/templates/authorization/confirm_email.html:9 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:12 +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +#: project/templates/account/password_reset_email.html:2 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you requested a password reset for your " +"user account at %(site_name)s." +msgstr "" + +#: project/templates/account/password_reset_email.html:4 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:4 +msgid "Please go to the following page and choose a new password:" +msgstr "" + +#: project/templates/authorization/confirm_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you trying to register new account at " +"%(site_name)s." +msgstr "" + +#: project/templates/authorization/confirm_email.html:4 +msgid "Please confirm your email address to complete the registration:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:4 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:6 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/app_index.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:163 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:22 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:32 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:14 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:15 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:12 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:7 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:7 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/object_history.html:6 +msgid "Home" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:7 +msgid "Server error" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:11 +msgid "Server error (500)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:14 +msgid "Server Error (500)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:15 +msgid "" +"There's been an error. It's been reported to the site administrators via " +"email and should be fixed shortly. Thanks for your patience." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:7 +msgid "Run the selected action" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:7 +msgid "Go" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:19 +msgid "Click here to select the objects across all pages" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:19 +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:21 +msgid "Clear selection" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/add_form.html:7 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/add_form.html:10 +msgid "Enter a username and password." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:31 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:75 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:143 +msgid "Change password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:65 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:58 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:31 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:65 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:31 +msgid "Please correct the errors below." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:50 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:60 +msgid "Password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:68 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:57 +msgid "Password (again)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:69 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:72 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:116 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:119 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base_site.html:9 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:31 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:36 +msgid "Django administration" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:81 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:52 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:58 +#, python-format +msgid "Models in the %(name)s application" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:87 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:93 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:143 +msgid "You don't have permission to edit anything." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:138 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:152 +msgid "Documentation" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:147 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:154 +msgid "Log out" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:173 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:57 +msgid "Close" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:39 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:65 +msgid "Add" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:114 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:116 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:18 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:14 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/object_history.html:9 +msgid "History" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:121 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:123 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/stacked.html:24 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/tabular.html:37 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:15 +msgid "View on site" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:47 +msgid "Filter" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:81 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:143 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:5 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:13 +msgid "Remove from sorting" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:16 +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:17 +msgid "Toggle sorting" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:25 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:34 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:36 +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:375 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 +msgid "Delete" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:34 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:48 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:62 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:70 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:72 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:74 +msgid "Yes, I'm sure" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:23 +msgid "Delete multiple objects" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:32 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:46 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:60 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(objects_name)s? All of the " +"following objects and their related items will be deleted:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/tabular.html:22 +msgid "Delete?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/includes/object_delete_summary.html:2 +msgid "Summary" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:18 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:21 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:22 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:31 +msgid "Apps" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:37 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:71 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:11 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:13 +msgid "Search" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:42 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:31 +msgid "Action" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:67 +msgid "View" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:69 +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:397 +msgid "Change" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:84 +msgid "Recent actions" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:89 +msgid "None available" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:115 +msgid "Unknown content" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/invalid_setup.html:5 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:21 +#, python-format +msgid "" +"You are authenticated as %(username)s, but are not authorized to access this " +"page. Would you like to login to a different account?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:85 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:23 +msgid "Log in" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:96 +msgid "Forgotten your password or username?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:29 +msgid "Date/time" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:45 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/pagination.html:19 +msgid "Show all" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/popup_response.html:3 +msgid "Popup closing..." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:9 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:16 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:23 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:4 +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:4 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:12 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:14 +msgid "Save as new" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:21 +msgid "Save and add another" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:26 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:28 +msgid "Save and continue editing" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/logged_out.html:18 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/logged_out.html:20 +msgid "Log in again" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:15 +msgid "Password change" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:20 +msgid "Your password was changed." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:37 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:43 +msgid "Old password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:66 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:46 +msgid "Change my password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:20 +msgid "Your password has been set. You may go ahead and log in now." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:10 +msgid "Password reset confirmation" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:24 +msgid "" +"Please enter your new password twice so we can verify you typed it in " +"correctly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:30 +msgid "New password:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:37 +msgid "Confirm password:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:55 +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:19 +msgid "" +"We've emailed you instructions for setting your password. You should be " +"receiving them shortly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:21 +msgid "" +"If you don't receive an email, please make sure you've entered the address " +"you registered with, and check your spam folder." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:8 +msgid "Your username, in case you've forgotten:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:23 +msgid "" +"Forgotten your password? Enter your email address below, and we'll email " +"instructions for setting a new one." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:29 +msgid "Email address:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:38 +msgid "Reset my password" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/messages/apps.py:7 +msgid "Messages" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/sitemaps/apps.py:7 +msgid "Site Maps" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/syndication/apps.py:7 +msgid "Syndication" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:45 +msgid "That page number is not an integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:47 +msgid "That page number is less than 1" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:52 +msgid "That page contains no results" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:31 +msgid "Enter a valid value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:102 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:658 +msgid "Enter a valid URL." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:154 +msgid "Enter a valid integer." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:165 +msgid "Enter a valid email address." +msgstr "" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: venv/lib/python3.6/site-packages/django/core/validators.py:239 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:246 +msgid "" +"Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:255 +#: venv/lib/python3.6/site-packages/django/core/validators.py:275 +msgid "Enter a valid IPv4 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:260 +#: venv/lib/python3.6/site-packages/django/core/validators.py:276 +msgid "Enter a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:270 +#: venv/lib/python3.6/site-packages/django/core/validators.py:274 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:304 +msgid "Enter only digits separated by commas." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:310 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:342 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:351 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:361 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:376 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:395 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:290 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:325 +msgid "Enter a number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:397 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:402 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:407 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:469 +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:521 +msgid "Null characters are not allowed." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/base.py:1162 +#: venv/lib/python3.6/site-packages/django/forms/models.py:756 +msgid "and" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/base.py:1164 +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:104 +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:105 +msgid "This field cannot be null." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:106 +msgid "This field cannot be blank." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:107 +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. +#. Eg: "Title must be unique for pub_date year" +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:111 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:128 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:899 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1766 +msgid "Integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:903 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1764 +#, python-format +msgid "'%(value)s' value must be an integer." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:978 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1844 +msgid "Big (8 byte) integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:990 +#, python-format +msgid "'%(value)s' value must be either True or False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:991 +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:993 +msgid "Boolean (Either True or False)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1034 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1098 +msgid "Comma-separated integers" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1147 +#, python-format +msgid "" +"'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1149 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1292 +#, python-format +msgid "" +"'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1152 +msgid "Date (without time)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1290 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1294 +#, python-format +msgid "" +"'%(value)s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1298 +msgid "Date (with time)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1446 +#, python-format +msgid "'%(value)s' value must be a decimal number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1448 +msgid "Decimal number" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1587 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." +"uuuuuu] format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1590 +msgid "Duration" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1640 +msgid "Email address" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1663 +msgid "File path" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1729 +#, python-format +msgid "'%(value)s' value must be a float." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1731 +msgid "Floating point number" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1860 +msgid "IPv4 address" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1971 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1972 +#, python-format +msgid "'%(value)s' value must be either None, True or False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1974 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2009 +msgid "Positive integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2022 +msgid "Positive small integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2036 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2068 +msgid "Small integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2103 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2105 +#, python-format +msgid "" +"'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2108 +msgid "Time" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2256 +msgid "Raw binary data" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2306 +#, python-format +msgid "'%(value)s' is not a valid UUID." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2308 +msgid "Universally unique identifier" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/files.py:221 +msgid "File" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:778 +#, python-format +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:780 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1007 +msgid "One-to-one relationship" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1057 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1058 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1100 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: venv/lib/python3.6/site-packages/django/forms/boundfield.py:146 +msgid ":?.!" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:245 +msgid "Enter a whole number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:396 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1126 +msgid "Enter a valid date." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:420 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1127 +msgid "Enter a valid time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:442 +msgid "Enter a valid date/time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:471 +msgid "Enter a valid duration." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:472 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:532 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:533 +msgid "No file was submitted." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:534 +msgid "The submitted file is empty." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:536 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:539 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:600 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:762 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:852 +#: venv/lib/python3.6/site-packages/django/forms/models.py:1270 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:853 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:968 +#: venv/lib/python3.6/site-packages/django/forms/models.py:1269 +msgid "Enter a list of values." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:969 +msgid "Enter a complete value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1185 +msgid "Enter a valid UUID." +msgstr "" + +#. Translators: This is the default suffix added to form field labels +#: venv/lib/python3.6/site-packages/django/forms/forms.py:86 +msgid ":" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/forms.py:212 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:91 +msgid "ManagementForm data is missing or has been tampered with" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:338 +#, python-format +msgid "Please submit %d or fewer forms." +msgid_plural "Please submit %d or fewer forms." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:345 +#, python-format +msgid "Please submit %d or more forms." +msgid_plural "Please submit %d or more forms." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:371 +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:373 +msgid "Order" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:751 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:755 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:761 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:770 +msgid "Please correct the duplicate values below." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1091 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1158 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1272 +#, python-format +msgid "\"%(pk)s\" is not a valid value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/utils.py:162 +#, python-format +msgid "" +"%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:395 +msgid "Clear" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:396 +msgid "Currently" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:711 +msgid "Unknown" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:712 +msgid "Yes" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:713 +msgid "No" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:788 +msgid "yes,no,maybe" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:817 +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:834 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:836 +#, python-format +msgid "%s KB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:838 +#, python-format +msgid "%s MB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:840 +#, python-format +msgid "%s GB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:842 +#, python-format +msgid "%s TB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:844 +#, python-format +msgid "%s PB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:62 +msgid "p.m." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:63 +msgid "a.m." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:68 +msgid "PM" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:69 +msgid "AM" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:150 +msgid "midnight" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:152 +msgid "noon" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Mon" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Tue" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Wed" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Thu" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Fri" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:11 +msgid "Sat" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:11 +msgid "Sun" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "January" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "February" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "August" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "September" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "October" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "November" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:16 +msgid "December" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "jan" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "feb" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "mar" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "apr" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "may" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "jun" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "jul" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "aug" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "sep" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "oct" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "nov" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "dec" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:23 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:24 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:25 +msgctxt "abbrev. month" +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:26 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:27 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:28 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:29 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:30 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:31 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:32 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:33 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:34 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:37 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:38 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:39 +msgctxt "alt. month" +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:40 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:41 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:42 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:43 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:44 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:45 +msgctxt "alt. month" +msgid "September" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:46 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:47 +msgctxt "alt. month" +msgid "November" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:48 +msgctxt "alt. month" +msgid "December" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/ipv6.py:8 +msgid "This is not a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/text.py:67 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/text.py:233 +msgid "or" +msgstr "" + +#. Translators: This string is used as a separator between list elements +#: venv/lib/python3.6/site-packages/django/utils/text.py:252 +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:83 +msgid ", " +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%d week" +msgid_plural "%d weeks" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:14 +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:72 +msgid "0 minutes" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:110 +msgid "Forbidden" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:111 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:115 +msgid "" +"You are seeing this message because this HTTPS site requires a 'Referer " +"header' to be sent by your Web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:120 +msgid "" +"If you have configured your browser to disable 'Referer' headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for 'same-" +"origin' requests." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:124 +msgid "" +"If you are using the tag or " +"including the 'Referrer-Policy: no-referrer' header, please remove them. The " +"CSRF protection requires the 'Referer' header to do strict referer checking. " +"If you're concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:132 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:137 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for 'same-origin' requests." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:142 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:41 +msgid "No year specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:61 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:111 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:208 +msgid "Date out of range" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:90 +msgid "No month specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:142 +msgid "No day specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:188 +msgid "No week specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:338 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:367 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:589 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:623 +#, python-format +msgid "Invalid date string '%(datestr)s' given format '%(format)s'" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/detail.py:54 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:67 +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:72 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:154 +#, python-format +msgid "Empty list and '%(class_name)s.allow_empty' is False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:40 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:42 +#, python-format +msgid "\"%(path)s\" does not exist" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:80 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:6 +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:345 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:367 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:368 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:383 +msgid "Django Documentation" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:384 +msgid "Topics, references, & how-to's" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:395 +msgid "Tutorial: A Polling App" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:396 +msgid "Get started with Django" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:407 +msgid "Django Community" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:408 +msgid "Connect, get help, or contribute" +msgstr "" + +#: venv/lib/python3.6/site-packages/easy_select2/forms.py:7 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:64 +#, python-format +msgid "Some messages were sent: %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:66 +#, python-format +msgid "All messages were sent: %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:72 +#, python-format +msgid "Some messages failed to send. %d devices were marked as inactive." +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:80 +msgid "Send test notification" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:85 +msgid "Send test notification in bulk" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:90 +msgid "Send test data message" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:96 +msgid "Send test data message in bulk" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:101 +msgid "Enable selected devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:106 +msgid "Disable selected devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/apps.py:7 +msgid "FCM Django" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/fields.py:52 +msgid "Enter a valid hexadecimal number" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:19 +msgid "Is active" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:20 +msgid "Inactive devices will not be sent notifications" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:25 +msgid "Creation date" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:161 +msgid "Device ID" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:162 +msgid "Unique device identifier" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:165 +msgid "Registration token" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:171 +#: venv/lib/python3.6/site-packages/fcm_django/models.py:255 +msgid "FCM device" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:256 +msgid "FCM devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1301 +#, python-format +msgid "Attempting to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1306 +#, python-format +msgid "Connected to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1324 +#, python-format +msgid "Unable to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:41 +msgid "Confidential" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:42 +msgid "Public" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:50 +msgid "Authorization code" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:51 +msgid "Implicit" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:52 +msgid "Resource owner password-based" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:53 +msgid "Client credentials" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:67 +msgid "Allowed URIs list, space separated" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:143 +#, python-brace-format +msgid "Unauthorized redirect scheme: {scheme}" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:148 +#, python-brace-format +msgid "redirect_uris cannot be empty with grant_type {grant_type}" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:166 +msgid "The access token is invalid." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:171 +msgid "The access token has expired." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:176 +msgid "The access token is valid but does not have enough scope." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 +msgid "Are you sure to delete the application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 +msgid "Cancel" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 +msgid "Client id" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 +msgid "Client secret" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 +msgid "Client type" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 +msgid "Authorization Grant Type" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 +msgid "Redirect Uris" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 +msgid "Go Back" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 +msgid "Edit" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 +msgid "Edit application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 +msgid "Your applications" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "No applications defined" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "Click here" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "if you want to register a new one" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 +msgid "Authorize" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 +msgid "Application requires following permissions" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 +msgid "Are you sure you want to delete this token?" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 +msgid "Tokens" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 +msgid "There are no authorized tokens yet." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/admin.py:53 +#, python-format +msgid "%(obj)s was changed successfully." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/admin.py:55 +msgid "You may edit it again below." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/templatetags/solo_tags.py:22 +#, python-format +msgid "" +"Templatetag requires the model dotted path: 'app_label.ModelName'. Received " +"'%s'." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/templatetags/solo_tags.py:28 +#, python-format +msgid "" +"Could not get the model name '%(model)s' from the application named '%(app)s'" +msgstr "" From f3389067360b70aea196c305b23048ca3b2aed0b 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, 21 Oct 2019 11:44:47 +0300 Subject: [PATCH 304/319] Fix location tests --- apps/location/models.py | 4 ++++ apps/location/tests.py | 25 +++++++++++++++++++++++++ apps/location/views/back.py | 4 +++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/location/models.py b/apps/location/models.py index 7f797811..2b7aa363 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -21,6 +21,10 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): high_price = models.IntegerField(default=50, verbose_name=_('High price')) languages = models.ManyToManyField(Language, verbose_name=_('Languages')) + @property + def country_id(self): + return self.id + class Meta: """Meta class.""" diff --git a/apps/location/tests.py b/apps/location/tests.py index 0770227a..edb719bd 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -59,15 +59,25 @@ class CountryTests(BaseTestCase): def setUp(self): super().setUp() + def test_country_CRUD(self): data = { 'name': {"ru-RU":"Russia"}, 'code': 'test' } + response = self.client.post('/api/back/location/countries/', data=data, format='json') response_data = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) + country = Country.objects.get(pk=response_data["id"]) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + response = self.client.get('/api/back/location/countries/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -94,6 +104,14 @@ class RegionTests(BaseTestCase): code="test" ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + + def test_region_CRUD(self): response = self.client.get('/api/back/location/regions/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -138,6 +156,13 @@ class CityTests(BaseTestCase): country=self.country ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + def test_city_CRUD(self): response = self.client.get('/api/back/location/cities/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 3a2739b2..cb8246a4 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -47,6 +47,7 @@ class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIVie # Country class CountryListCreateView(generics.ListCreateAPIView): """List/Create view for model Country.""" + queryset = models.Country.objects.all() serializer_class = serializers.CountryBackSerializer pagination_class = None permission_classes = [IsCountryAdmin] @@ -54,4 +55,5 @@ class CountryListCreateView(generics.ListCreateAPIView): class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer - permission_classes = [IsCountryAdmin] \ No newline at end of file + permission_classes = [IsCountryAdmin] + queryset = models.Country.objects.all() \ No newline at end of file From 3720ac2b8e07e07c49d6623534c556dd2e435784 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, 21 Oct 2019 12:17:12 +0300 Subject: [PATCH 305/319] Fix collection test --- apps/collection/tests.py | 18 +++++++++++------- sdtout.txt | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 sdtout.txt diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 72b40c37..b2b8231b 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -40,12 +40,13 @@ class CollectionDetailTests(BaseTestCase): def setUp(self): super().setUp() - country = Country.objects.first() - if not country: - country = Country.objects.create( - name=json.dumps({"en-GB": "Test country"}), - code="en" - ) + # country = Country.objects.first() + # if not country: + country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="en" + ) + country.save() self.collection = Collection.objects.create( name='Test collection', @@ -56,6 +57,8 @@ class CollectionDetailTests(BaseTestCase): slug='test-collection-slug', ) + self.collection.save() + def test_collection_detail_Read(self): response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en', format='json') @@ -66,7 +69,7 @@ class CollectionGuideTests(CollectionDetailTests): def test_guide_list_Read(self): response = self.client.get('/api/web/collections/guides/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class CollectionGuideDetailTests(CollectionDetailTests): @@ -78,6 +81,7 @@ class CollectionGuideDetailTests(CollectionDetailTests): start=datetime.now(pytz.utc), end=datetime.now(pytz.utc) ) + self.guide.save() def test_guide_detail_Read(self): response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') diff --git a/sdtout.txt b/sdtout.txt new file mode 100644 index 00000000..5f575e2c --- /dev/null +++ b/sdtout.txt @@ -0,0 +1,4 @@ +System check identified no issues (0 silenced). +{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 28, 'name': 'test establishment', 'name_translated': '', 'price_level': None, 'toque_number': None, 'public_mark': None, 'slug': None, 'preview_image': None, 'in_favorites': None, 'address': None, 'tags': []}]} +1 +{'id': 1, 'title_translated': 'test title', 'subtitle_translated': None, 'author': 'Test Author', 'published_at': None, 'in_favorites': False, 'description_translated': 'test description'} From 6dd66275ef3520d61a8f0c0ecc2fc200db325843 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, 21 Oct 2019 12:18:57 +0300 Subject: [PATCH 306/319] Fix trash --- sdtout.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 sdtout.txt diff --git a/sdtout.txt b/sdtout.txt deleted file mode 100644 index 5f575e2c..00000000 --- a/sdtout.txt +++ /dev/null @@ -1,4 +0,0 @@ -System check identified no issues (0 silenced). -{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 28, 'name': 'test establishment', 'name_translated': '', 'price_level': None, 'toque_number': None, 'public_mark': None, 'slug': None, 'preview_image': None, 'in_favorites': None, 'address': None, 'tags': []}]} -1 -{'id': 1, 'title_translated': 'test title', 'subtitle_translated': None, 'author': 'Test Author', 'published_at': None, 'in_favorites': False, 'description_translated': 'test description'} From 019c22a0c886461878ee8debfcf942ebbad68778 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, 21 Oct 2019 14:39:04 +0300 Subject: [PATCH 307/319] Fix test --- apps/news/serializers.py | 2 +- apps/news/views.py | 1 + apps/utils/tests/tests_translated.py | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c473be1d..f2e307e4 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -22,7 +22,7 @@ class NewsBaseSerializer(ProjectModelSerializer): """Base serializer for News model.""" # read only fields - title_translated = TranslatedField() + title_translated = TranslatedField(source='title') subtitle_translated = TranslatedField() # related fields diff --git a/apps/news/views.py b/apps/news/views.py index 105b9064..7e272657 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -35,6 +35,7 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() + class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 8f5caaaf..c6a990c0 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -37,6 +37,7 @@ class TranslateFieldTests(BaseTestCase): super().setUp() self.news_type = NewsType.objects.create(name="Test news type") + self.news_type.save() self.news_item = News.objects.create( created_by=self.user, @@ -53,9 +54,11 @@ class TranslateFieldTests(BaseTestCase): slug='test', state=News.PUBLISHED, ) + self.news_item.save() def test_model_field(self): - self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) + self.assertTrue(hasattr(self.news_item, "title_translated")) + def test_read_locale(self): response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') @@ -64,7 +67,7 @@ class TranslateFieldTests(BaseTestCase): self.assertIn("title_translated", news_data) - self.assertEqual(news_data['title_translated'], "Test news item") + self.assertIn("Test news item", news_data['title_translated']) class BaseAttributeTests(BaseTestCase): From ee93cf9ff6771c7f4976456f767dce2d113a7f26 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, 21 Oct 2019 15:15:19 +0300 Subject: [PATCH 308/319] Fix migrate --- apps/location/migrations/0012_data_migrate.py | 26 +- apps/location/migrations/migrate_lang.sql | 5 +- .../migrations/0005_auto_20191021_1201.py | 22 + .../migrations/006_data_migrate.py | 25 ++ apps/translation/migrations/migrate_lang.sql | 383 +++++++++++++++++ .../translation/migrations/remigrate_lang.sql | 391 ++++++++++++++++++ apps/translation/models.py | 3 +- project/urls/back.py | 2 - 8 files changed, 839 insertions(+), 18 deletions(-) create mode 100644 apps/translation/migrations/0005_auto_20191021_1201.py create mode 100644 apps/translation/migrations/006_data_migrate.py create mode 100644 apps/translation/migrations/migrate_lang.sql create mode 100644 apps/translation/migrations/remigrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index 511990db..de75c8ad 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -3,23 +3,23 @@ import os class Migration(migrations.Migration): - - def load_data_from_sql(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - def revert_data(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) + # Check migration + # def load_data_from_sql(apps, schema_editor): + # file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + # sql_statement = open(file_path).read() + # with connection.cursor() as c: + # c.execute(sql_statement) + # + # def revert_data(apps, schema_editor): + # file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + # sql_statement = open(file_path).read() + # with connection.cursor() as c: + # c.execute(sql_statement) dependencies = [ ('location', '0011_country_languages'), ] operations = [ - migrations.RunPython(load_data_from_sql, revert_data), + # migrations.RunPython(load_data_from_sql, revert_data), ] diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 11c93573..9f15e9c6 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -326,7 +326,7 @@ commit; INSERT INTO location_country (code, "name", low_price, high_price, created, modified) -select +select distinct lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, jsonb_build_object('en-GB', t.country), 0 as low_price, @@ -335,7 +335,7 @@ select now() as modified from ( - select + select distinct c.country from country_code c ) t @@ -348,6 +348,7 @@ commit; INSERT INTO translation_language (title, locale) select + distinct t.country as title, t.code as locale from diff --git a/apps/translation/migrations/0005_auto_20191021_1201.py b/apps/translation/migrations/0005_auto_20191021_1201.py new file mode 100644 index 00000000..61cc3294 --- /dev/null +++ b/apps/translation/migrations/0005_auto_20191021_1201.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-10-21 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0004_auto_20191018_0832'), + ] + + operations = [ + migrations.AlterField( + model_name='language', + name='locale', + field=models.CharField(max_length=10, verbose_name='Locale identifier'), + ), + migrations.AlterUniqueTogether( + name='language', + unique_together={('title', 'locale')}, + ), + ] diff --git a/apps/translation/migrations/006_data_migrate.py b/apps/translation/migrations/006_data_migrate.py new file mode 100644 index 00000000..d8142773 --- /dev/null +++ b/apps/translation/migrations/006_data_migrate.py @@ -0,0 +1,25 @@ +from django.db import migrations, connection +import os + + +class Migration(migrations.Migration): + + def load_data_from_sql(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + def revert_data(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + dependencies = [ + ('translation', '0005_auto_20191021_1201'), + ] + + operations = [ + migrations.RunPython(load_data_from_sql, revert_data), + ] diff --git a/apps/translation/migrations/migrate_lang.sql b/apps/translation/migrations/migrate_lang.sql new file mode 100644 index 00000000..9f15e9c6 --- /dev/null +++ b/apps/translation/migrations/migrate_lang.sql @@ -0,0 +1,383 @@ +SET search_path TO gm, public; + +CREATE TABLE codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + + +INSERT INTO codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + +--delete from location_country as lc + +INSERT INTO location_country +(code, "name", low_price, high_price, created, modified) +select distinct + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country), + 0 as low_price, + 100 as high_price, + now() as created, + now() as modified +from +( + select + distinct c.country + from country_code c +) t +; + +commit; + +--delete from translation_language as tl; + +INSERT INTO translation_language +(title, locale) +select + distinct + t.country as title, + t.code as locale +from +( + select + distinct c.country, c.code + from country_code c +) t +; + +commit; + + +--delete from location_country_languages + +INSERT INTO location_country_languages +(country_id, language_id) +select lc.id as country_id, + l.id as language_id +from location_country as lc +join ( + select tl.*, '"'||tl.title||'"' as country + from translation_language as tl +) l on l.country = (lc."name"::json->'en-GB')::text +; + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file diff --git a/apps/translation/migrations/remigrate_lang.sql b/apps/translation/migrations/remigrate_lang.sql new file mode 100644 index 00000000..160ac93e --- /dev/null +++ b/apps/translation/migrations/remigrate_lang.sql @@ -0,0 +1,391 @@ +SET search_path TO gm, public; + +CREATE TABLE codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + + +INSERT INTO codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + + +delete from location_country_languages as lcl +where lcl.country_id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; +commit; + + +delete from location_country as lcl +where lcl.id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; + +commit; + + +delete from translation_language tl +where tl.id in +( + SELECT tl.id + FROM + ( + select + distinct c.country, c.code + from country_code c + ) t + JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country +); + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file diff --git a/apps/translation/models.py b/apps/translation/models.py index bc9fbfbf..cb0729ea 100644 --- a/apps/translation/models.py +++ b/apps/translation/models.py @@ -22,7 +22,7 @@ class Language(models.Model): title = models.CharField(max_length=255, verbose_name=_('Language title')) - locale = models.CharField(max_length=10, unique=True, + locale = models.CharField(max_length=10, verbose_name=_('Locale identifier')) objects = LanguageQuerySet.as_manager() @@ -32,6 +32,7 @@ class Language(models.Model): verbose_name = _('Language') verbose_name_plural = _('Languages') + unique_together = ('title', 'locale') def __str__(self): """String method""" diff --git a/project/urls/back.py b/project/urls/back.py index 9194a44b..40b3415a 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -9,8 +9,6 @@ urlpatterns = [ path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('account/', include('account.urls.back')), - path('comment/', include('comment.urls.back')), path('review/', include('review.urls.back')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From 1ed48a43eb6ac25bc34b21313ad73050d6b01999 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, 21 Oct 2019 15:31:19 +0300 Subject: [PATCH 309/319] Fix url --- project/urls/back.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/project/urls/back.py b/project/urls/back.py index 9194a44b..40b3415a 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -9,8 +9,6 @@ urlpatterns = [ path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('account/', include('account.urls.back')), - path('comment/', include('comment.urls.back')), path('review/', include('review.urls.back')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From 4f02fef4568677eb3190f4eac4bc104b548c84b6 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, 21 Oct 2019 15:44:20 +0300 Subject: [PATCH 310/319] Fix migrate --- apps/location/migrations/migrate_lang.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 11c93573..021d251e 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -87,7 +87,6 @@ INSERT INTO codelang (code,country) VALUES ,('es-CR','Spanish (Costa Rica)') ,('es-DO','Spanish (Dominican Republic)') ,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') ,('es-ES','Spanish (Spain)') ; INSERT INTO codelang (code,country) VALUES From 196d335213a63100b5aeba74d49e18d7f784cff3 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, 21 Oct 2019 15:47:06 +0300 Subject: [PATCH 311/319] Fix --- apps/location/migrations/0012_data_migrate.py | 24 +- .../migrations/006_data_migrate.py | 25 -- apps/translation/migrations/migrate_lang.sql | 383 ----------------- .../translation/migrations/remigrate_lang.sql | 391 ------------------ 4 files changed, 12 insertions(+), 811 deletions(-) delete mode 100644 apps/translation/migrations/006_data_migrate.py delete mode 100644 apps/translation/migrations/migrate_lang.sql delete mode 100644 apps/translation/migrations/remigrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index de75c8ad..b61c43df 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -4,22 +4,22 @@ import os class Migration(migrations.Migration): # Check migration - # def load_data_from_sql(apps, schema_editor): - # file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - # sql_statement = open(file_path).read() - # with connection.cursor() as c: - # c.execute(sql_statement) - # - # def revert_data(apps, schema_editor): - # file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - # sql_statement = open(file_path).read() - # with connection.cursor() as c: - # c.execute(sql_statement) + def load_data_from_sql(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + def revert_data(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) dependencies = [ ('location', '0011_country_languages'), ] operations = [ - # migrations.RunPython(load_data_from_sql, revert_data), + migrations.RunPython(load_data_from_sql, revert_data), ] diff --git a/apps/translation/migrations/006_data_migrate.py b/apps/translation/migrations/006_data_migrate.py deleted file mode 100644 index d8142773..00000000 --- a/apps/translation/migrations/006_data_migrate.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.db import migrations, connection -import os - - -class Migration(migrations.Migration): - - def load_data_from_sql(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - def revert_data(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - dependencies = [ - ('translation', '0005_auto_20191021_1201'), - ] - - operations = [ - migrations.RunPython(load_data_from_sql, revert_data), - ] diff --git a/apps/translation/migrations/migrate_lang.sql b/apps/translation/migrations/migrate_lang.sql deleted file mode 100644 index 9f15e9c6..00000000 --- a/apps/translation/migrations/migrate_lang.sql +++ /dev/null @@ -1,383 +0,0 @@ -SET search_path TO gm, public; - -CREATE TABLE codelang ( - code varchar(100) NULL, - country varchar(10000) NULL -); - - -INSERT INTO codelang (code,country) VALUES -('af','Afrikaans') -,('af-ZA','Afrikaans (South Africa)') -,('ar','Arabic') -,('ar-AE','Arabic (U.A.E.)') -,('ar-BH','Arabic (Bahrain)') -,('ar-DZ','Arabic (Algeria)') -,('ar-EG','Arabic (Egypt)') -,('ar-IQ','Arabic (Iraq)') -,('ar-JO','Arabic (Jordan)') -,('ar-KW','Arabic (Kuwait)') -; -INSERT INTO codelang (code,country) VALUES -('ar-LB','Arabic (Lebanon)') -,('ar-LY','Arabic (Libya)') -,('ar-MA','Arabic (Morocco)') -,('ar-OM','Arabic (Oman)') -,('ar-QA','Arabic (Qatar)') -,('ar-SA','Arabic (Saudi Arabia)') -,('ar-SY','Arabic (Syria)') -,('ar-TN','Arabic (Tunisia)') -,('ar-YE','Arabic (Yemen)') -,('az','Azeri (Latin)') -; -INSERT INTO codelang (code,country) VALUES -('az-AZ','Azeri (Latin) (Azerbaijan)') -,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') -,('be','Belarusian') -,('be-BY','Belarusian (Belarus)') -,('bg','Bulgarian') -,('bg-BG','Bulgarian (Bulgaria)') -,('bs-BA','Bosnian (Bosnia and Herzegovina)') -,('ca','Catalan') -,('ca-ES','Catalan (Spain)') -,('cs','Czech') -; -INSERT INTO codelang (code,country) VALUES -('cs-CZ','Czech (Czech Republic)') -,('cy','Welsh') -,('cy-GB','Welsh (United Kingdom)') -,('da','Danish') -,('da-DK','Danish (Denmark)') -,('de','German') -,('de-AT','German (Austria)') -,('de-CH','German (Switzerland)') -,('de-DE','German (Germany)') -,('de-LI','German (Liechtenstein)') -; -INSERT INTO codelang (code,country) VALUES -('de-LU','German (Luxembourg)') -,('dv','Divehi') -,('dv-MV','Divehi (Maldives)') -,('el','Greek') -,('el-GR','Greek (Greece)') -,('en','English') -,('en-AU','English (Australia)') -,('en-BZ','English (Belize)') -,('en-CA','English (Canada)') -,('en-CB','English (Caribbean)') -; -INSERT INTO codelang (code,country) VALUES -('en-GB','English (United Kingdom)') -,('en-IE','English (Ireland)') -,('en-JM','English (Jamaica)') -,('en-NZ','English (New Zealand)') -,('en-PH','English (Republic of the Philippines)') -,('en-TT','English (Trinidad and Tobago)') -,('en-US','English (United States)') -,('en-ZA','English (South Africa)') -,('en-ZW','English (Zimbabwe)') -,('eo','Esperanto') -; -INSERT INTO codelang (code,country) VALUES -('es','Spanish') -,('es-AR','Spanish (Argentina)') -,('es-BO','Spanish (Bolivia)') -,('es-CL','Spanish (Chile)') -,('es-CO','Spanish (Colombia)') -,('es-CR','Spanish (Costa Rica)') -,('es-DO','Spanish (Dominican Republic)') -,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') -,('es-ES','Spanish (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('es-GT','Spanish (Guatemala)') -,('es-HN','Spanish (Honduras)') -,('es-MX','Spanish (Mexico)') -,('es-NI','Spanish (Nicaragua)') -,('es-PA','Spanish (Panama)') -,('es-PE','Spanish (Peru)') -,('es-PR','Spanish (Puerto Rico)') -,('es-PY','Spanish (Paraguay)') -,('es-SV','Spanish (El Salvador)') -,('es-UY','Spanish (Uruguay)') -; -INSERT INTO codelang (code,country) VALUES -('es-VE','Spanish (Venezuela)') -,('et','Estonian') -,('et-EE','Estonian (Estonia)') -,('eu','Basque') -,('eu-ES','Basque (Spain)') -,('fa','Farsi') -,('fa-IR','Farsi (Iran)') -,('fi','Finnish') -,('fi-FI','Finnish (Finland)') -,('fo','Faroese') -; -INSERT INTO codelang (code,country) VALUES -('fo-FO','Faroese (Faroe Islands)') -,('fr','French') -,('fr-BE','French (Belgium)') -,('fr-CA','French (Canada)') -,('fr-CH','French (Switzerland)') -,('fr-FR','French (France)') -,('fr-LU','French (Luxembourg)') -,('fr-MC','French (Principality of Monaco)') -,('gl','Galician') -,('gl-ES','Galician (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('gu','Gujarati') -,('gu-IN','Gujarati (India)') -,('he','Hebrew') -,('he-IL','Hebrew (Israel)') -,('hi','Hindi') -,('hi-IN','Hindi (India)') -,('hr','Croatian') -,('hr-BA','Croatian (Bosnia and Herzegovina)') -,('hr-HR','Croatian (Croatia)') -,('hu','Hungarian') -; -INSERT INTO codelang (code,country) VALUES -('hu-HU','Hungarian (Hungary)') -,('hy','Armenian') -,('hy-AM','Armenian (Armenia)') -,('id','Indonesian') -,('id-ID','Indonesian (Indonesia)') -,('is','Icelandic') -,('is-IS','Icelandic (Iceland)') -,('it','Italian') -,('it-CH','Italian (Switzerland)') -,('it-IT','Italian (Italy)') -; -INSERT INTO codelang (code,country) VALUES -('ja','Japanese') -,('ja-JP','Japanese (Japan)') -,('ka','Georgian') -,('ka-GE','Georgian (Georgia)') -,('kk','Kazakh') -,('kk-KZ','Kazakh (Kazakhstan)') -,('kn','Kannada') -,('kn-IN','Kannada (India)') -,('ko','Korean') -,('ko-KR','Korean (Korea)') -; -INSERT INTO codelang (code,country) VALUES -('kok','Konkani') -,('kok-IN','Konkani (India)') -,('ky','Kyrgyz') -,('ky-KG','Kyrgyz (Kyrgyzstan)') -,('lt','Lithuanian') -,('lt-LT','Lithuanian (Lithuania)') -,('lv','Latvian') -,('lv-LV','Latvian (Latvia)') -,('mi','Maori') -,('mi-NZ','Maori (New Zealand)') -; -INSERT INTO codelang (code,country) VALUES -('mk','FYRO Macedonian') -,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') -,('mn','Mongolian') -,('mn-MN','Mongolian (Mongolia)') -,('mr','Marathi') -,('mr-IN','Marathi (India)') -,('ms','Malay') -,('ms-BN','Malay (Brunei Darussalam)') -,('ms-MY','Malay (Malaysia)') -,('mt','Maltese') -; -INSERT INTO codelang (code,country) VALUES -('mt-MT','Maltese (Malta)') -,('nb','Norwegian (Bokm?l)') -,('nb-NO','Norwegian (Bokm?l) (Norway)') -,('nl','Dutch') -,('nl-BE','Dutch (Belgium)') -,('nl-NL','Dutch (Netherlands)') -,('nn-NO','Norwegian (Nynorsk) (Norway)') -,('ns','Northern Sotho') -,('ns-ZA','Northern Sotho (South Africa)') -,('pa','Punjabi') -; -INSERT INTO codelang (code,country) VALUES -('pa-IN','Punjabi (India)') -,('pl','Polish') -,('pl-PL','Polish (Poland)') -,('ps','Pashto') -,('ps-AR','Pashto (Afghanistan)') -,('pt','Portuguese') -,('pt-BR','Portuguese (Brazil)') -,('pt-PT','Portuguese (Portugal)') -,('qu','Quechua') -,('qu-BO','Quechua (Bolivia)') -; -INSERT INTO codelang (code,country) VALUES -('qu-EC','Quechua (Ecuador)') -,('qu-PE','Quechua (Peru)') -,('ro','Romanian') -,('ro-RO','Romanian (Romania)') -,('ru','Russian') -,('ru-RU','Russian (Russia)') -,('sa','Sanskrit') -,('sa-IN','Sanskrit (India)') -,('se','Sami (Northern)') -,('se-FI','Sami (Northern) (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('se-FI','Sami (Skolt) (Finland)') -,('se-FI','Sami (Inari) (Finland)') -,('se-NO','Sami (Northern) (Norway)') -,('se-NO','Sami (Lule) (Norway)') -,('se-NO','Sami (Southern) (Norway)') -,('se-SE','Sami (Northern) (Sweden)') -,('se-SE','Sami (Lule) (Sweden)') -,('se-SE','Sami (Southern) (Sweden)') -,('sk','Slovak') -,('sk-SK','Slovak (Slovakia)') -; -INSERT INTO codelang (code,country) VALUES -('sl','Slovenian') -,('sl-SI','Slovenian (Slovenia)') -,('sq','Albanian') -,('sq-AL','Albanian (Albania)') -,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') -,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') -,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') -,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') -,('sv','Swedish') -,('sv-FI','Swedish (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('sv-SE','Swedish (Sweden)') -,('sw','Swahili') -,('sw-KE','Swahili (Kenya)') -,('syr','Syriac') -,('syr-SY','Syriac (Syria)') -,('ta','Tamil') -,('ta-IN','Tamil (India)') -,('te','Telugu') -,('te-IN','Telugu (India)') -,('th','Thai') -; -INSERT INTO codelang (code,country) VALUES -('th-TH','Thai (Thailand)') -,('tl','Tagalog') -,('tl-PH','Tagalog (Philippines)') -,('tn','Tswana') -,('tn-ZA','Tswana (South Africa)') -,('tr','Turkish') -,('tr-TR','Turkish (Turkey)') -,('tt','Tatar') -,('tt-RU','Tatar (Russia)') -,('ts','Tsonga') -; -INSERT INTO codelang (code,country) VALUES -('uk','Ukrainian') -,('uk-UA','Ukrainian (Ukraine)') -,('ur','Urdu') -,('ur-PK','Urdu (Islamic Republic of Pakistan)') -,('uz','Uzbek (Latin)') -,('uz-UZ','Uzbek (Latin) (Uzbekistan)') -,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') -,('vi','Vietnamese') -,('vi-VN','Vietnamese (Viet Nam)') -,('xh','Xhosa') -; -INSERT INTO codelang (code,country) VALUES -('xh-ZA','Xhosa (South Africa)') -,('zh','Chinese') -,('zh-CN','Chinese (S)') -,('zh-HK','Chinese (Hong Kong)') -,('zh-MO','Chinese (Macau)') -,('zh-SG','Chinese (Singapore)') -,('zh-TW','Chinese (T)') -,('zu','Zulu') -,('zu-ZA','Zulu (South Africa)') -; -/***************************/ --- Manual migrate - -CREATE TABLE country_code ( - code varchar(100) NULL, - country varchar(10000) NULL -); - -insert into country_code(code, country) -select distinct - t.code, - coalesce( - case when length(t.country_name2) = 1 then null else t.country_name2 end, - case when length(t.contry_name1) = 1 then null else t.contry_name1 end, - t.country - ) as country -from -( - select trim(c.code) as code, - substring(trim(c.country) from '\((.+)\)') as contry_name1, - substring( - substring(trim(c.country) from '\((.+)\)') - from '\((.*)$') as country_name2, - trim(c.country) as country - from codelang as c -) t; - -commit; - ---delete from location_country as lc - -INSERT INTO location_country -(code, "name", low_price, high_price, created, modified) -select distinct - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country), - 0 as low_price, - 100 as high_price, - now() as created, - now() as modified -from -( - select - distinct c.country - from country_code c -) t -; - -commit; - ---delete from translation_language as tl; - -INSERT INTO translation_language -(title, locale) -select - distinct - t.country as title, - t.code as locale -from -( - select - distinct c.country, c.code - from country_code c -) t -; - -commit; - - ---delete from location_country_languages - -INSERT INTO location_country_languages -(country_id, language_id) -select lc.id as country_id, - l.id as language_id -from location_country as lc -join ( - select tl.*, '"'||tl.title||'"' as country - from translation_language as tl -) l on l.country = (lc."name"::json->'en-GB')::text -; - -commit; - -drop table country_code; -drop table codelang; - -commit; \ No newline at end of file diff --git a/apps/translation/migrations/remigrate_lang.sql b/apps/translation/migrations/remigrate_lang.sql deleted file mode 100644 index 160ac93e..00000000 --- a/apps/translation/migrations/remigrate_lang.sql +++ /dev/null @@ -1,391 +0,0 @@ -SET search_path TO gm, public; - -CREATE TABLE codelang ( - code varchar(100) NULL, - country varchar(10000) NULL -); - - -INSERT INTO codelang (code,country) VALUES -('af','Afrikaans') -,('af-ZA','Afrikaans (South Africa)') -,('ar','Arabic') -,('ar-AE','Arabic (U.A.E.)') -,('ar-BH','Arabic (Bahrain)') -,('ar-DZ','Arabic (Algeria)') -,('ar-EG','Arabic (Egypt)') -,('ar-IQ','Arabic (Iraq)') -,('ar-JO','Arabic (Jordan)') -,('ar-KW','Arabic (Kuwait)') -; -INSERT INTO codelang (code,country) VALUES -('ar-LB','Arabic (Lebanon)') -,('ar-LY','Arabic (Libya)') -,('ar-MA','Arabic (Morocco)') -,('ar-OM','Arabic (Oman)') -,('ar-QA','Arabic (Qatar)') -,('ar-SA','Arabic (Saudi Arabia)') -,('ar-SY','Arabic (Syria)') -,('ar-TN','Arabic (Tunisia)') -,('ar-YE','Arabic (Yemen)') -,('az','Azeri (Latin)') -; -INSERT INTO codelang (code,country) VALUES -('az-AZ','Azeri (Latin) (Azerbaijan)') -,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') -,('be','Belarusian') -,('be-BY','Belarusian (Belarus)') -,('bg','Bulgarian') -,('bg-BG','Bulgarian (Bulgaria)') -,('bs-BA','Bosnian (Bosnia and Herzegovina)') -,('ca','Catalan') -,('ca-ES','Catalan (Spain)') -,('cs','Czech') -; -INSERT INTO codelang (code,country) VALUES -('cs-CZ','Czech (Czech Republic)') -,('cy','Welsh') -,('cy-GB','Welsh (United Kingdom)') -,('da','Danish') -,('da-DK','Danish (Denmark)') -,('de','German') -,('de-AT','German (Austria)') -,('de-CH','German (Switzerland)') -,('de-DE','German (Germany)') -,('de-LI','German (Liechtenstein)') -; -INSERT INTO codelang (code,country) VALUES -('de-LU','German (Luxembourg)') -,('dv','Divehi') -,('dv-MV','Divehi (Maldives)') -,('el','Greek') -,('el-GR','Greek (Greece)') -,('en','English') -,('en-AU','English (Australia)') -,('en-BZ','English (Belize)') -,('en-CA','English (Canada)') -,('en-CB','English (Caribbean)') -; -INSERT INTO codelang (code,country) VALUES -('en-GB','English (United Kingdom)') -,('en-IE','English (Ireland)') -,('en-JM','English (Jamaica)') -,('en-NZ','English (New Zealand)') -,('en-PH','English (Republic of the Philippines)') -,('en-TT','English (Trinidad and Tobago)') -,('en-US','English (United States)') -,('en-ZA','English (South Africa)') -,('en-ZW','English (Zimbabwe)') -,('eo','Esperanto') -; -INSERT INTO codelang (code,country) VALUES -('es','Spanish') -,('es-AR','Spanish (Argentina)') -,('es-BO','Spanish (Bolivia)') -,('es-CL','Spanish (Chile)') -,('es-CO','Spanish (Colombia)') -,('es-CR','Spanish (Costa Rica)') -,('es-DO','Spanish (Dominican Republic)') -,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') -,('es-ES','Spanish (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('es-GT','Spanish (Guatemala)') -,('es-HN','Spanish (Honduras)') -,('es-MX','Spanish (Mexico)') -,('es-NI','Spanish (Nicaragua)') -,('es-PA','Spanish (Panama)') -,('es-PE','Spanish (Peru)') -,('es-PR','Spanish (Puerto Rico)') -,('es-PY','Spanish (Paraguay)') -,('es-SV','Spanish (El Salvador)') -,('es-UY','Spanish (Uruguay)') -; -INSERT INTO codelang (code,country) VALUES -('es-VE','Spanish (Venezuela)') -,('et','Estonian') -,('et-EE','Estonian (Estonia)') -,('eu','Basque') -,('eu-ES','Basque (Spain)') -,('fa','Farsi') -,('fa-IR','Farsi (Iran)') -,('fi','Finnish') -,('fi-FI','Finnish (Finland)') -,('fo','Faroese') -; -INSERT INTO codelang (code,country) VALUES -('fo-FO','Faroese (Faroe Islands)') -,('fr','French') -,('fr-BE','French (Belgium)') -,('fr-CA','French (Canada)') -,('fr-CH','French (Switzerland)') -,('fr-FR','French (France)') -,('fr-LU','French (Luxembourg)') -,('fr-MC','French (Principality of Monaco)') -,('gl','Galician') -,('gl-ES','Galician (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('gu','Gujarati') -,('gu-IN','Gujarati (India)') -,('he','Hebrew') -,('he-IL','Hebrew (Israel)') -,('hi','Hindi') -,('hi-IN','Hindi (India)') -,('hr','Croatian') -,('hr-BA','Croatian (Bosnia and Herzegovina)') -,('hr-HR','Croatian (Croatia)') -,('hu','Hungarian') -; -INSERT INTO codelang (code,country) VALUES -('hu-HU','Hungarian (Hungary)') -,('hy','Armenian') -,('hy-AM','Armenian (Armenia)') -,('id','Indonesian') -,('id-ID','Indonesian (Indonesia)') -,('is','Icelandic') -,('is-IS','Icelandic (Iceland)') -,('it','Italian') -,('it-CH','Italian (Switzerland)') -,('it-IT','Italian (Italy)') -; -INSERT INTO codelang (code,country) VALUES -('ja','Japanese') -,('ja-JP','Japanese (Japan)') -,('ka','Georgian') -,('ka-GE','Georgian (Georgia)') -,('kk','Kazakh') -,('kk-KZ','Kazakh (Kazakhstan)') -,('kn','Kannada') -,('kn-IN','Kannada (India)') -,('ko','Korean') -,('ko-KR','Korean (Korea)') -; -INSERT INTO codelang (code,country) VALUES -('kok','Konkani') -,('kok-IN','Konkani (India)') -,('ky','Kyrgyz') -,('ky-KG','Kyrgyz (Kyrgyzstan)') -,('lt','Lithuanian') -,('lt-LT','Lithuanian (Lithuania)') -,('lv','Latvian') -,('lv-LV','Latvian (Latvia)') -,('mi','Maori') -,('mi-NZ','Maori (New Zealand)') -; -INSERT INTO codelang (code,country) VALUES -('mk','FYRO Macedonian') -,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') -,('mn','Mongolian') -,('mn-MN','Mongolian (Mongolia)') -,('mr','Marathi') -,('mr-IN','Marathi (India)') -,('ms','Malay') -,('ms-BN','Malay (Brunei Darussalam)') -,('ms-MY','Malay (Malaysia)') -,('mt','Maltese') -; -INSERT INTO codelang (code,country) VALUES -('mt-MT','Maltese (Malta)') -,('nb','Norwegian (Bokm?l)') -,('nb-NO','Norwegian (Bokm?l) (Norway)') -,('nl','Dutch') -,('nl-BE','Dutch (Belgium)') -,('nl-NL','Dutch (Netherlands)') -,('nn-NO','Norwegian (Nynorsk) (Norway)') -,('ns','Northern Sotho') -,('ns-ZA','Northern Sotho (South Africa)') -,('pa','Punjabi') -; -INSERT INTO codelang (code,country) VALUES -('pa-IN','Punjabi (India)') -,('pl','Polish') -,('pl-PL','Polish (Poland)') -,('ps','Pashto') -,('ps-AR','Pashto (Afghanistan)') -,('pt','Portuguese') -,('pt-BR','Portuguese (Brazil)') -,('pt-PT','Portuguese (Portugal)') -,('qu','Quechua') -,('qu-BO','Quechua (Bolivia)') -; -INSERT INTO codelang (code,country) VALUES -('qu-EC','Quechua (Ecuador)') -,('qu-PE','Quechua (Peru)') -,('ro','Romanian') -,('ro-RO','Romanian (Romania)') -,('ru','Russian') -,('ru-RU','Russian (Russia)') -,('sa','Sanskrit') -,('sa-IN','Sanskrit (India)') -,('se','Sami (Northern)') -,('se-FI','Sami (Northern) (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('se-FI','Sami (Skolt) (Finland)') -,('se-FI','Sami (Inari) (Finland)') -,('se-NO','Sami (Northern) (Norway)') -,('se-NO','Sami (Lule) (Norway)') -,('se-NO','Sami (Southern) (Norway)') -,('se-SE','Sami (Northern) (Sweden)') -,('se-SE','Sami (Lule) (Sweden)') -,('se-SE','Sami (Southern) (Sweden)') -,('sk','Slovak') -,('sk-SK','Slovak (Slovakia)') -; -INSERT INTO codelang (code,country) VALUES -('sl','Slovenian') -,('sl-SI','Slovenian (Slovenia)') -,('sq','Albanian') -,('sq-AL','Albanian (Albania)') -,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') -,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') -,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') -,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') -,('sv','Swedish') -,('sv-FI','Swedish (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('sv-SE','Swedish (Sweden)') -,('sw','Swahili') -,('sw-KE','Swahili (Kenya)') -,('syr','Syriac') -,('syr-SY','Syriac (Syria)') -,('ta','Tamil') -,('ta-IN','Tamil (India)') -,('te','Telugu') -,('te-IN','Telugu (India)') -,('th','Thai') -; -INSERT INTO codelang (code,country) VALUES -('th-TH','Thai (Thailand)') -,('tl','Tagalog') -,('tl-PH','Tagalog (Philippines)') -,('tn','Tswana') -,('tn-ZA','Tswana (South Africa)') -,('tr','Turkish') -,('tr-TR','Turkish (Turkey)') -,('tt','Tatar') -,('tt-RU','Tatar (Russia)') -,('ts','Tsonga') -; -INSERT INTO codelang (code,country) VALUES -('uk','Ukrainian') -,('uk-UA','Ukrainian (Ukraine)') -,('ur','Urdu') -,('ur-PK','Urdu (Islamic Republic of Pakistan)') -,('uz','Uzbek (Latin)') -,('uz-UZ','Uzbek (Latin) (Uzbekistan)') -,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') -,('vi','Vietnamese') -,('vi-VN','Vietnamese (Viet Nam)') -,('xh','Xhosa') -; -INSERT INTO codelang (code,country) VALUES -('xh-ZA','Xhosa (South Africa)') -,('zh','Chinese') -,('zh-CN','Chinese (S)') -,('zh-HK','Chinese (Hong Kong)') -,('zh-MO','Chinese (Macau)') -,('zh-SG','Chinese (Singapore)') -,('zh-TW','Chinese (T)') -,('zu','Zulu') -,('zu-ZA','Zulu (South Africa)') -; -/***************************/ --- Manual migrate - -CREATE TABLE country_code ( - code varchar(100) NULL, - country varchar(10000) NULL -); - -insert into country_code(code, country) -select distinct - t.code, - coalesce( - case when length(t.country_name2) = 1 then null else t.country_name2 end, - case when length(t.contry_name1) = 1 then null else t.contry_name1 end, - t.country - ) as country -from -( - select trim(c.code) as code, - substring(trim(c.country) from '\((.+)\)') as contry_name1, - substring( - substring(trim(c.country) from '\((.+)\)') - from '\((.*)$') as country_name2, - trim(c.country) as country - from codelang as c -) t; - -commit; - - -delete from location_country_languages as lcl -where lcl.country_id in -( - select - lc.id - from - ( - select - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country) as "name" - from - ( - select - distinct c.country - from country_code c - ) t - ) d - join location_country lc on lc.code = d.code and d."name"=lc."name" -) -; -commit; - - -delete from location_country as lcl -where lcl.id in -( - select - lc.id - from - ( - select - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country) as "name" - from - ( - select - distinct c.country - from country_code c - ) t - ) d - join location_country lc on lc.code = d.code and d."name"=lc."name" -) -; - -commit; - - -delete from translation_language tl -where tl.id in -( - SELECT tl.id - FROM - ( - select - distinct c.country, c.code - from country_code c - ) t - JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country -); - -commit; - -drop table country_code; -drop table codelang; - -commit; \ No newline at end of file From adbc22f1f6ad6e635f2f96a5a2cfac27c9ce4676 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, 21 Oct 2019 16:03:19 +0300 Subject: [PATCH 312/319] Fix comment test --- apps/utils/tests/tests_permissions.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index ccb202ab..edc1a5d7 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -5,21 +5,14 @@ from translation.models import Language class BasePermissionTests(APITestCase): def setUp(self): - - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() From 87708df08446c5b0a0d76a8a971f984bfe58d8be 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, 21 Oct 2019 16:11:28 +0300 Subject: [PATCH 313/319] Fix test establishment --- apps/establishment/tests.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 9eb8c987..a1d8fcb5 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -28,20 +28,14 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") # Create lang object - self.lang = Language.objects.create( - title='English', - locale='en-GB' + self.lang = Language.objects.get( + title='Russia', + locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() self.region = Region.objects.create(name='Moscow area', code='01', country=self.country_ru) @@ -331,7 +325,7 @@ class EstablishmentWebTagTests(BaseTestCase): def test_tag_Read(self): response = self.client.get('/api/web/establishments/tags/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class EstablishmentWebSlugTests(ChildTestCase): From d1d092e49dfd751d3515a545128247ee7e65f73c 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, 21 Oct 2019 16:21:14 +0300 Subject: [PATCH 314/319] Fix news tests --- apps/news/tests.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/news/tests.py b/apps/news/tests.py index dd256bac..b4e2b296 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -25,20 +25,14 @@ class BaseTestCase(APITestCase): 'refresh_token': tokkens.get('refresh_token')}) self.test_news_type = NewsType.objects.create(name="Test news type") - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() role = Role.objects.create( role=Role.CONTENT_PAGE_MANAGER, From 23b46e69edd5a1bf08a0d29c7997d0a5b675db32 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, 21 Oct 2019 16:28:32 +0300 Subject: [PATCH 315/319] Fix test location --- apps/location/tests.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index edb719bd..cb574036 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -9,8 +9,8 @@ from location.models import City, Region, Country, Language from django.contrib.gis.geos import Point from account.models import Role, UserRole -class BaseTestCase(APITestCase): +class BaseTestCase(APITestCase): def setUp(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' @@ -29,20 +29,14 @@ class BaseTestCase(APITestCase): {'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() self.role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country_ru) @@ -52,18 +46,15 @@ class BaseTestCase(APITestCase): self.user_role.save() - # role = Role.objects.create(role=Role.COUNTRY_ADMIN) - class CountryTests(BaseTestCase): def setUp(self): super().setUp() - def test_country_CRUD(self): data = { - 'name': {"ru-RU":"Russia"}, - 'code': 'test' + 'name': {"ru-RU": "NewCountry"}, + 'code': 'test1' } response = self.client.post('/api/back/location/countries/', data=data, format='json') From 94502cfc736e1bc177d08fd12c57a83cb1c23088 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 16:47:01 +0300 Subject: [PATCH 316/319] added priority field to tag model (f7facf826df3f01cceaed754f9486a0e17d96581) --- apps/tag/migrations/0004_tag_priority.py | 18 ++++++++++++++++++ apps/tag/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 apps/tag/migrations/0004_tag_priority.py diff --git a/apps/tag/migrations/0004_tag_priority.py b/apps/tag/migrations/0004_tag_priority.py new file mode 100644 index 00000000..3e7a6d7f --- /dev/null +++ b/apps/tag/migrations/0004_tag_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-21 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0003_auto_20191018_0758'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='priority', + field=models.IntegerField(default=None, null=True, unique=True), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index 85d86e74..44eacddc 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -14,6 +14,7 @@ class Tag(TranslatedFieldsMixin, models.Model): category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) + priority = models.IntegerField(unique=True, null=True, default=None) class Meta: """Meta class.""" From afc212764687f3afd13d68c22f4a8e54f3b6c0b6 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Mon, 21 Oct 2019 14:45:34 +0000 Subject: [PATCH 317/319] Updated news model with agenda and banner --- .../migrations/0022_auto_20191021_1306.py | 57 +++++++++++++++++++ apps/news/models.py | 42 ++++++++++++-- apps/news/serializers.py | 51 ++++++++++++++--- 3 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 apps/news/migrations/0022_auto_20191021_1306.py diff --git a/apps/news/migrations/0022_auto_20191021_1306.py b/apps/news/migrations/0022_auto_20191021_1306.py new file mode 100644 index 00000000..de8747f5 --- /dev/null +++ b/apps/news/migrations/0022_auto_20191021_1306.py @@ -0,0 +1,57 @@ +# Generated by Django 2.2.4 on 2019-10-21 13:06 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('news', '0021_auto_20191009_1408'), + ] + + operations = [ + migrations.CreateModel( + name='NewsBanner', + 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')), + ('title', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='title')), + ('image_url', models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path')), + ('content_url', models.URLField(blank=True, default=None, null=True, verbose_name='Content URL path')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, utils.models.TranslatedFieldsMixin), + ), + migrations.CreateModel( + name='Agenda', + 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')), + ('event_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Event datetime')), + ('content', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='content')), + ('address', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, utils.models.TranslatedFieldsMixin), + ), + migrations.AddField( + model_name='news', + name='agenda', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.Agenda', verbose_name='agenda'), + ), + migrations.AddField( + model_name='news', + name='banner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.NewsBanner', verbose_name='banner'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 8a6a89f4..0460174a 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -4,7 +4,7 @@ from django.contrib.contenttypes import fields as generic from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse -from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin +from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin from rating.models import Rating @@ -38,7 +38,7 @@ class NewsQuerySet(models.QuerySet): def with_extended_related(self): """Return qs with related objects.""" - return self.select_related('created_by') + return self.select_related('created_by', 'agenda', 'banner') def by_type(self, news_type): """Filter News by type""" @@ -61,15 +61,39 @@ class NewsQuerySet(models.QuerySet): # todo: filter by best score # todo: filter by country? def should_read(self, news): - return self.model.objects.exclude(pk=news.pk).published().\ + return self.model.objects.exclude(pk=news.pk).published(). \ with_base_related().by_type(news.news_type).distinct().order_by('?') def same_theme(self, news): - return self.model.objects.exclude(pk=news.pk).published().\ - with_base_related().by_type(news.news_type).\ + return self.model.objects.exclude(pk=news.pk).published(). \ + with_base_related().by_type(news.news_type). \ by_tags(news.tags.all()).distinct().order_by('-start') +class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): + """News agenda model""" + + event_datetime = models.DateTimeField(default=timezone.now, editable=False, + verbose_name=_('Event datetime')) + address = models.ForeignKey('location.Address', blank=True, null=True, + default=None, verbose_name=_('address'), + on_delete=models.SET_NULL) + content = TJSONField(blank=True, null=True, default=None, + verbose_name=_('content'), + help_text='{"en-GB":"some text"}') + + +class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin): + """News banner model""" + title = TJSONField(blank=True, null=True, default=None, + verbose_name=_('title'), + help_text='{"en-GB":"some text"}') + image_url = models.URLField(verbose_name=_('Image URL path'), + blank=True, null=True, default=None) + content_url = models.URLField(verbose_name=_('Content URL path'), + blank=True, null=True, default=None) + + class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" @@ -139,6 +163,14 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('Tags')) ratings = generic.GenericRelation(Rating) + agenda = models.ForeignKey('news.Agenda', blank=True, null=True, + on_delete=models.SET_NULL, + verbose_name=_('agenda')) + + banner = models.ForeignKey('news.NewsBanner', blank=True, null=True, + on_delete=models.SET_NULL, + verbose_name=_('banner')) + objects = NewsQuerySet.as_manager() class Meta: diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 6f0b73b6..c730de0d 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -2,12 +2,46 @@ from rest_framework import serializers from account.serializers.common import UserBaseSerializer from location import models as location_models -from location.serializers import CountrySimpleSerializer +from location.serializers import CountrySimpleSerializer, AddressBaseSerializer from news import models from tag.serializers import TagBaseSerializer from utils.serializers import TranslatedField, ProjectModelSerializer +class AgendaSerializer(ProjectModelSerializer): + event_datetime = serializers.DateTimeField() + address = AddressBaseSerializer() + content_translated = TranslatedField() + + class Meta: + """Meta class.""" + + model = models.Agenda + fields = ( + 'id', + 'event_datetime', + 'address', + 'content_translated' + ) + + +class NewsBannerSerializer(ProjectModelSerializer): + title_translated = TranslatedField() + image_url = serializers.URLField() + content_url = serializers.URLField() + + class Meta: + """Meta class.""" + + model = models.NewsBanner + fields = ( + 'id', + 'title_translated', + 'image_url', + 'content_url' + ) + + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -76,6 +110,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer): same_theme = NewsBaseSerializer(many=True, read_only=True) should_read = NewsBaseSerializer(many=True, read_only=True) + agenda = AgendaSerializer() + banner = NewsBannerSerializer() class Meta(NewsDetailSerializer.Meta): """Meta class.""" @@ -83,6 +119,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer): fields = NewsDetailSerializer.Meta.fields + ( 'same_theme', 'should_read', + 'agenda', + 'banner' ) @@ -116,10 +154,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, fields = NewsBackOfficeBaseSerializer.Meta.fields + \ NewsDetailSerializer.Meta.fields + ( - 'description', - 'news_type_id', - 'country_id', - 'template', - 'template_display', + 'description', + 'news_type_id', + 'country_id', + 'template', + 'template_display', ) - From 4ceb0e4d86aeba8a88879a8773a3c08096dcf35d Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 20:22:47 +0300 Subject: [PATCH 318/319] fix establishment migration --- .../0039_establishmentsubtype_index_name.py | 2 +- apps/search_indexes/signals.py | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py index 5473600a..a29b1ae0 100644 --- a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py +++ b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py @@ -24,7 +24,7 @@ class Migration(migrations.Migration): name='index_name', field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), ), - migrations.RunPython(fill_establishment_subtype), + migrations.RunPython(fill_establishment_subtype, migrations.RunPython.noop), migrations.AlterField( model_name='establishmentsubtype', name='index_name', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 0f6a071f..77660a2c 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -29,16 +29,20 @@ def update_document(sender, **kwargs): registry.update(establishment) if app_label == 'establishment': + # todo: remove after migration + from establishment import models as establishment_models if model_name == 'establishmenttype': - establishments = Establishment.objects.filter( - establishment_type=instance) - for establishment in establishments: - registry.update(establishment) + if isinstance(instance, establishment_models.EstablishmentType): + establishments = Establishment.objects.filter( + establishment_type=instance) + for establishment in establishments: + registry.update(establishment) if model_name == 'establishmentsubtype': - establishments = Establishment.objects.filter( - establishment_subtypes=instance) - for establishment in establishments: - registry.update(establishment) + if instance(instance, establishment_models.EstablishmentSubType): + establishments = Establishment.objects.filter( + establishment_subtypes=instance) + for establishment in establishments: + registry.update(establishment) if app_label == 'tag': if model_name == 'tag': From 4c9ea2e0e7a7187c0b0b5f36cc319f0aab9b03c2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 15:45:27 +0300 Subject: [PATCH 319/319] change field properties for Collection model --- .../migrations/0014_auto_20191022_1242.py | 23 +++++++++++++++++++ apps/collection/models.py | 9 ++++---- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 apps/collection/migrations/0014_auto_20191022_1242.py diff --git a/apps/collection/migrations/0014_auto_20191022_1242.py b/apps/collection/migrations/0014_auto_20191022_1242.py new file mode 100644 index 00000000..d70c0cfa --- /dev/null +++ b/apps/collection/migrations/0014_auto_20191022_1242.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-10-22 12:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0013_collection_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='end', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'), + ), + migrations.AlterField( + model_name='guide', + name='end', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 25bf1ef9..e7c930c3 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -1,13 +1,11 @@ -from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import ContentType - -from utils.models import TJSONField +from django.contrib.postgres.fields import JSONField from django.db import models from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, URLImageMixin +from utils.models import TJSONField from utils.models import TranslatedFieldsMixin - from utils.querysets import RelatedObjectsCountMixin @@ -24,7 +22,8 @@ class CollectionNameMixin(models.Model): class CollectionDateMixin(models.Model): """CollectionDate mixin""" start = models.DateTimeField(_('start')) - end = models.DateTimeField(_('end')) + end = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('end')) class Meta: """Meta class"""