From b1b9ab5ab264b98275bf966c482d41acc5d68a35 Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 11:14:18 +0500 Subject: [PATCH 01/67] 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 02/67] 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 03/67] 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 755490c6cf92dc170934bd0126ccbbbe96fdee1a Mon Sep 17 00:00:00 2001 From: michail Date: Fri, 27 Sep 2019 17:12:24 +0500 Subject: [PATCH 04/67] 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 d71ed0c1de634dee9335b28efde89f41047e5ff9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 27 Sep 2019 19:44:00 +0300 Subject: [PATCH 05/67] 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 06/67] 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 4388fb73468d57473c7c227dc89d147c435bcba7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 25 Sep 2019 22:00:05 +0300 Subject: [PATCH 07/67] 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 08/67] 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 09/67] 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 10/67] 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 11/67] 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 90ae87a14ece3f86e2ae33ffdd8bfb8115ad24f0 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:09:53 +0500 Subject: [PATCH 12/67] 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 13/67] 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 14/67] 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 88df92a7a54edaaf5ca0f9a5913f727fea383a57 Mon Sep 17 00:00:00 2001 From: michail Date: Mon, 30 Sep 2019 12:42:49 +0500 Subject: [PATCH 15/67] 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 16/67] 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 17/67] 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 18/67] 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 19/67] 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 20/67] 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 21/67] 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 22/67] 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 23/67] 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 24/67] 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 25/67] 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 26/67] 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 27/67] 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 28/67] 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 29/67] 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 30/67] 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 31/67] 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 32/67] 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 df6b221029ed651568781615df9f28c92afe7a0c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 21:32:24 +0300 Subject: [PATCH 33/67] 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 34/67] 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 35/67] 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 36/67] 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 37/67] 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 38/67] 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 39/67] 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 40/67] 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 41/67] 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 42/67] 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 43/67] 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 44/67] 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 45/67] 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 46/67] 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 47/67] 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 48/67] 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 49/67] 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 50/67] 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 51/67] 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 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 52/67] 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 53/67] 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 729a77866db44ee03ed3079ed96d0d806268a8f3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 3 Oct 2019 17:19:30 +0300 Subject: [PATCH 54/67] 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 55/67] 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 56/67] 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 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 57/67] 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 58/67] 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 59/67] 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 60/67] 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 61/67] 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 62/67] 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 63/67] 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 64/67] 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 65/67] 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 3bf65472d91d6360ddb6494e8dd2338bc50d38f8 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 7 Oct 2019 16:31:50 +0300 Subject: [PATCH 66/67] 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 861daf8871dc057a079fa423a999f8192d347037 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 8 Oct 2019 14:39:42 +0300 Subject: [PATCH 67/67] 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 = (