Merge branch 'develop' into feature/guides

This commit is contained in:
Anatoly 2019-12-19 18:18:39 +03:00
commit 021d608bdc
18 changed files with 239 additions and 69 deletions

View File

@ -1,4 +1,5 @@
from django.shortcuts import get_object_or_404
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, permissions, status, serializers
from rest_framework.response import Response
@ -96,6 +97,13 @@ class CreatePendingBooking(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = PendingBookingSerializer
@swagger_auto_schema(operation_description="Request body params\n\n"
"IN GUESTONLINE (type:G): {"
"'restaurant_id', 'booking_time', "
"'booking_date', 'booked_persons_number'}\n"
"IN LASTABLE (type:L): {'booking_time', "
"'booked_persons_number', 'offer_id' (Req), "
"'email', 'phone', 'first_name', 'last_name'}")
def post(self, request, *args, **kwargs):
data = request.data.copy()
if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None:
@ -135,6 +143,10 @@ class UpdatePendingBooking(generics.UpdateAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = UpdateBookingSerializer
@swagger_auto_schema(operation_description="Request body params\n\n"
"Required: 'email', 'phone', 'last_name', "
"'first_name', 'country_code', 'pending_booking_id',"
"Not req: 'note'")
def patch(self, request, *args, **kwargs):
instance = self.get_object()
data = request.data.copy()

View File

@ -1,5 +1,5 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from transfer.models import Reviews, ReviewTexts
@ -22,7 +22,7 @@ class Command(BaseCommand):
'updated_at',
)
for r_id, establishment_id, new_date in queryset:
for r_id, establishment_id, new_date in tqdm(queryset):
try:
review_id, date = valid_reviews[establishment_id]
except KeyError:
@ -41,7 +41,7 @@ class Command(BaseCommand):
'text',
)
for es_id, locale, text in text_qs:
for es_id, locale, text in tqdm(text_qs):
establishment = Establishment.objects.filter(old_id=es_id).first()
if establishment:
description = establishment.description
@ -53,7 +53,7 @@ class Command(BaseCommand):
count += 1
# Если нет en-GB в поле
for establishment in Establishment.objects.filter(old_id__isnull=False):
for establishment in tqdm(Establishment.objects.filter(old_id__isnull=False)):
description = establishment.description
if len(description) and 'en-GB' not in description:
description.update({

View File

@ -0,0 +1,45 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from transfer.models import Descriptions
class Command(BaseCommand):
help = """Add description to establishment from old db."""
def handle(self, *args, **kwarg):
establishments = Establishment.objects.exclude(old_id__isnull=True)
self.stdout.write(self.style.WARNING(f'Clear old descriptions'))
for item in tqdm(establishments):
item.description = None
item.save()
queryset = Descriptions.objects.filter(
establishment_id__in=list(establishments.values_list('old_id', flat=True)),
).values_list('establishment_id', 'locale', 'text')
self.stdout.write(self.style.WARNING(f'Update new description'))
for establishment_id, locale, text in tqdm(queryset):
establishment = Establishment.objects.filter(old_id=establishment_id).first()
if establishment:
if establishment.description:
establishment.description.update({
locale: text
})
else:
establishment.description = {locale: text}
establishment.save()
self.stdout.write(self.style.WARNING(f'Update en-GB description'))
for establishment in tqdm(establishments.filter(description__isnull=False)):
description = establishment.description
if len(description) and 'en-GB' not in description:
description.update({
'en-GB': next(iter(description.values()))
})
establishment.description = description
establishment.save()
self.stdout.write(self.style.WARNING(f'Done'))

View File

@ -90,7 +90,6 @@ class CitySerializer(serializers.ModelSerializer):
fields = [
'id',
'name',
'code',
'region',
'region_id',
'country_id',

View File

@ -1,4 +1,5 @@
"""Location app views."""
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics
from location import models, serializers
@ -11,6 +12,7 @@ from utils.serializers import ImageBaseSerializer
from location import filters
# Address
@ -18,29 +20,36 @@ class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView)
"""Create view for model Address."""
serializer_class = serializers.AddressDetailSerializer
queryset = models.Address.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model Address."""
serializer_class = serializers.AddressDetailSerializer
queryset = models.Address.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
# City
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City."""
serializer_class = serializers.CitySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
queryset = models.City.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
# queryset = models.City.objects.all()
filter_class = filters.CityBackFilter
def get_queryset(self):
"""Overridden method 'get_queryset'."""
qs = models.City.objects.all()
if self.request.country_code:
qs = qs.by_country_code(self.request.country_code)
return qs
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City."""
serializer_class = serializers.CitySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
queryset = models.City.objects.all()
filter_class = filters.CityBackFilter
pagination_class = None
@ -49,14 +58,14 @@ class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model City."""
serializer_class = serializers.CitySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
class CityGalleryCreateDestroyView(common.CityViewMixin,
CreateDestroyGalleryViewMixin):
"""Resource for a create gallery for product for back-office users."""
serializer_class = serializers.CityGallerySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
def get_object(self):
"""
@ -77,7 +86,7 @@ class CityGalleryListView(common.CityViewMixin,
generics.ListAPIView):
"""Resource for returning gallery for product for back-office users."""
serializer_class = ImageBaseSerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
def get_object(self):
"""Override get_object method."""
@ -99,13 +108,18 @@ class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
"""Create view for model Region"""
pagination_class = None
serializer_class = serializers.RegionSerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
filter_backends = (DjangoFilterBackend,)
ordering_fields = '__all__'
filterset_fields = (
'country',
)
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""Retrieve view for model Region"""
serializer_class = serializers.RegionSerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
# Country
@ -114,11 +128,11 @@ class CountryListCreateView(generics.ListCreateAPIView):
queryset = models.Country.objects.all()
serializer_class = serializers.CountryBackSerializer
pagination_class = None
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model Country."""
serializer_class = serializers.CountryBackSerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
queryset = models.Country.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
queryset = models.Country.objects.all()

View File

@ -210,25 +210,6 @@ class CarouselQuerySet(models.QuerySet):
"""Filter collection by country code."""
return self.filter(country__code=code)
def create_or_destroy(self, instance_to_bind, country):
"""Creates or destroys Carousel instance depending on instance fields"""
toggle = True
kwargs = {
'content_type': ContentType.objects.get_for_model(instance_to_bind),
'object_id': instance_to_bind.pk,
'country': country,
}
if toggle is None:
return
elif toggle:
kwargs.update({
'is_parse': True,
'active': True,
})
self.create(**kwargs)
else:
self.filter(**kwargs).delete()
class Carousel(models.Model):
"""Carousel model."""

View File

@ -6,7 +6,6 @@ from rest_framework.response import Response
from main import methods, models, serializers
#
# class FeatureViewMixin:
# """Feature view mixin."""
@ -86,8 +85,13 @@ class DetermineLocation(generics.GenericAPIView):
longitude, latitude = methods.determine_coordinates(request)
city = methods.determine_user_city(request)
country_name = methods.determine_country_name(request)
country_code = methods.determine_country_code(request)
if longitude and latitude and city and country_name:
return Response(data={'latitude': latitude, 'longitude': longitude,
'city': city, 'country_name': country_name})
return Response(data={
'latitude': latitude,
'longitude': longitude,
'city': city,
'country_name': country_name,
'country_code': country_code,
})
raise Http404

View File

@ -72,4 +72,6 @@ class NewsListFilterSet(filters.FilterSet):
return queryset
def sort_by_field(self, queryset, name, value):
if value == self.SORT_BY_START_CHOICE:
return queryset.order_by('-publication_date', '-publication_time')
return queryset.order_by(f'-{value}')

View File

@ -0,0 +1,38 @@
# Generated by Django 2.2.7 on 2019-12-18 14:37
from django.db import migrations, models
def fill_publication_date_and_time(apps, schema_editor):
News = apps.get_model('news', 'News')
for news in News.objects.all():
if news.start is not None:
news.publication_date = news.start.date()
news.publication_time = news.start.time()
news.save()
class Migration(migrations.Migration):
dependencies = [
('news', '0045_news_must_of_the_week'),
]
operations = [
migrations.AddField(
model_name='news',
name='publication_date',
field=models.DateField(blank=True, help_text='date since when news item is published', null=True, verbose_name='News publication date'),
),
migrations.AddField(
model_name='news',
name='publication_time',
field=models.TimeField(blank=True, help_text='time since when news item is published', null=True, verbose_name='News publication time'),
),
migrations.AlterField(
model_name='news',
name='must_of_the_week',
field=models.BooleanField(default=False, verbose_name='Show in the carousel'),
),
migrations.RunPython(fill_publication_date_and_time, migrations.RunPython.noop),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-18 16:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0046_auto_20191218_1437'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='start',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-19 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0047_remove_news_start'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='must_of_the_week',
),
]

View File

@ -3,6 +3,7 @@ import uuid
from django.conf import settings
from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import HStoreField
from django.db import models
from django.db.models import Case, When
@ -10,11 +11,13 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse
from main.models import Carousel
from rating.models import Rating, ViewCount
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin,
FavoritesMixin)
from utils.querysets import TranslationQuerysetMixin
from datetime import datetime
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
@ -64,7 +67,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
def sort_by_start(self):
"""Return qs sorted by start DESC"""
return self.order_by('-start')
return self.order_by('-publication_date', '-publication_time')
def rating_value(self):
return self.annotate(rating=models.Count('ratings__ip', distinct=True))
@ -99,9 +102,13 @@ class NewsQuerySet(TranslationQuerysetMixin):
def published(self):
"""Return only published news"""
now = timezone.now()
return self.filter(models.Q(models.Q(end__gte=now) |
date_now = now.date()
time_now = now.time()
return self.exclude(models.Q(publication_date__isnull=True) | models.Q(publication_time__isnull=True)). \
filter(models.Q(models.Q(end__gte=now) |
models.Q(end__isnull=True)),
state__in=self.model.PUBLISHED_STATES, start__lte=now)
state__in=self.model.PUBLISHED_STATES, publication_date__lte=date_now,
publication_time__lte=time_now)
# todo: filter by best score
# todo: filter by country?
@ -114,7 +121,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
return self.model.objects.exclude(pk=news.pk).published(). \
annotate_in_favorites(user). \
with_base_related().by_type(news.news_type). \
by_tags(news.tags.all()).distinct().order_by('-start')
by_tags(news.tags.all()).distinct().sort_by_start()
def annotate_in_favorites(self, user):
"""Annotate flag in_favorites"""
@ -185,8 +192,10 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
locale_to_description_is_active = HStoreField(null=True, default=dict, blank=True,
verbose_name=_('Is description for certain locale active'),
help_text='{"en-GB": true, "fr-FR": false}')
start = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('Start'))
publication_date = models.DateField(blank=True, null=True, verbose_name=_('News publication date'),
help_text=_('date since when news item is published'))
publication_time = models.TimeField(blank=True, null=True, verbose_name=_('News publication time'),
help_text=_('time since when news item is published'))
end = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('End'))
slugs = HStoreField(null=True, blank=True, default=dict,
@ -223,7 +232,6 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
verbose_name=_('Duplication datetime'))
duplication_uuid = models.UUIDField(default=uuid.uuid4, editable=True, unique=False,
verbose_name=_('Field to detect doubles'))
must_of_the_week = models.BooleanField(default=False, verbose_name=_('Show in the carousel'))
objects = NewsQuerySet.as_manager()
class Meta:
@ -244,6 +252,24 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
self.duplication_date = timezone.now()
self.save()
@property
def must_of_the_week(self) -> bool:
"""Detects whether current item in carousel"""
kwargs = {
'content_type': ContentType.objects.get_for_model(self),
'object_id': self.pk,
'country': self.country,
}
return Carousel.objects.filter(**kwargs).exists()
@property
def publication_datetime(self):
"""Represents datetime object combined from `publication_date` & `publication_time` fields"""
try:
return datetime.combine(date=self.publication_date, time=self.publication_time)
except TypeError:
return None
@property
def duplicates(self):
"""Duplicates for this news item excluding same country code labeled"""

View File

@ -128,6 +128,7 @@ class NewsDetailSerializer(NewsBaseSerializer):
state_display = serializers.CharField(source='get_state_display',
read_only=True)
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
start = serializers.DateTimeField(source='publication_datetime', read_only=True)
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
@ -186,11 +187,13 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
'is_published',
'duplication_date',
'must_of_the_week',
'publication_date',
'publication_time',
)
extra_kwargs = {
'backoffice_title': {'allow_null': False},
'duplication_date': {'read_only': True},
'locale_to_description_is_active': {'allow_null': False}
'locale_to_description_is_active': {'allow_null': False},
'must_of_the_week': {'read_only': True},
}
def create(self, validated_data):
@ -200,9 +203,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
slugs__values__contains=list(slugs.values())
).exists():
raise serializers.ValidationError({'slugs': _('News with this slug already exists.')})
instance = super().create(validated_data)
Carousel.objects.create_or_destroy(instance, instance.address.city.country)
return instance
return super().create(validated_data)
def update(self, instance, validated_data):
slugs = validated_data.get('slugs')
@ -211,10 +212,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
slugs__values__contains=list(slugs.values())
).exclude(pk=instance.pk).exists():
raise serializers.ValidationError({'slugs': _('News with this slug already exists.')})
ret = super().update(instance, validated_data)
if ret.must_of_the_week != instance.must_of_the_week:
Carousel.objects.create_or_destroy(instance, instance.address.city.country)
return ret
return super().update(instance, validated_data)
class NewsBackOfficeDuplicationInfoSerializer(serializers.ModelSerializer):
@ -282,8 +280,8 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
return self.context.get('request').parser_context.get('kwargs')
def create(self, validated_data):
news_pk = self.get_request_kwargs().get('pk')
image_id = self.get_request_kwargs().get('image_id')
news_pk = self.request_kwargs.get('pk')
image_id = self.request_kwargs.get('image_id')
qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk)
instance = qs.first()
if instance:
@ -362,7 +360,12 @@ class NewsCarouselCreateSerializer(CarouselCreateSerializer):
def create(self, validated_data, *args, **kwargs):
validated_data.update({
'content_object': validated_data.pop('news')
'country': validated_data['news'].country
})
validated_data.update({
'content_object': validated_data.pop('news'),
'is_parse': True,
'active': True,
})
return super().create(validated_data)

View File

@ -14,5 +14,5 @@ urlpatterns = [
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'),
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='create-destroy-carousels'),
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='clone-news-item'),
]

View File

@ -22,7 +22,7 @@ class NewsMixinView:
qs = models.News.objects.published() \
.with_base_related() \
.annotate_in_favorites(self.request.user) \
.order_by('-is_highlighted', '-start')
.order_by('-is_highlighted', '-publication_date', '-publication_time')
country_code = self.request.country_code
if country_code:
@ -31,9 +31,9 @@ class NewsMixinView:
else:
qs = qs.by_country_code(country_code)
locale = kwargs.get('locale')
if locale:
qs = qs.by_locale(locale)
# locale = kwargs.get('locale')
# if locale:
# qs = qs.by_locale(locale)
return qs

View File

@ -45,7 +45,7 @@ class NewsDocument(Document):
},
multi=True)
favorites_for_users = fields.ListField(field=fields.IntegerField())
start = fields.DateField(attr='start')
start = fields.DateField(attr='publication_datetime')
has_any_desc_active = fields.BooleanField()
def prepare_slugs(self, instance):

View File

@ -1,13 +1,15 @@
"""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,
GeoSpatialOrderingFilterBackend,
OrderingFilterBackend,
)
from elasticsearch_dsl import TermsFacet
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
from elasticsearch_dsl import TermsFacet
from rest_framework import permissions
from product.models import Product
from search_indexes import serializers, filters, utils
from search_indexes.documents import EstablishmentDocument, NewsDocument
from search_indexes.documents.product import ProductDocument
@ -346,6 +348,12 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
# GeoSpatialOrderingFilterBackend,
]
def get_queryset(self):
qs = super(ProductDocumentViewSet, self).get_queryset()
qs = qs.filter('match', state=Product.PUBLISHED)
return qs
ordering_fields = {
'created': {
'field': 'created',

View File

@ -77,7 +77,11 @@ class EstablishmentSerializer(serializers.ModelSerializer):
schedules = validated_data.pop('schedules')
subtypes = [validated_data.pop('subtype', None)]
establishment = Establishment.objects.create(**validated_data)
# establishment = Establishment.objects.create(**validated_data)
establishment, _ = Establishment.objects.update_or_create(
old_id=validated_data['old_id'],
defaults=validated_data,
)
if email:
ContactEmail.objects.get_or_create(
email=email,