Merge branch 'develop' into feature/sending-news-email

This commit is contained in:
Виктор Гладких 2019-10-01 17:52:28 +03:00
commit 266ace6912
36 changed files with 503 additions and 150 deletions

View File

@ -1,5 +1,6 @@
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from account.models import User from account.models import User
from django.urls import reverse
# Create your tests here. # Create your tests here.
@ -22,7 +23,6 @@ def get_tokens_for_user(
class AuthorizationTests(APITestCase): class AuthorizationTests(APITestCase):
def setUp(self): def setUp(self):
print("Auth!")
data = get_tokens_for_user() data = get_tokens_for_user()
self.username = data["username"] self.username = data["username"]
self.password = data["password"] self.password = data["password"]
@ -33,7 +33,7 @@ class AuthorizationTests(APITestCase):
'password': self.password, 'password': self.password,
'remember': True 'remember': True
} }
response = self.client.post('/api/auth/login/', data=data) response = self.client.post(reverse('auth:authorization:login'), data=data)
self.assertEqual(response.data['access_token'], self.tokens.get('access_token')) self.assertEqual(response.data['access_token'], self.tokens.get('access_token'))
self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token')) self.assertEqual(response.data['refresh_token'], self.tokens.get('refresh_token'))

View File

@ -8,6 +8,8 @@ from django.utils.translation import gettext_lazy as _
from utils.models import ProjectBaseMixin, URLImageMixin from utils.models import ProjectBaseMixin, URLImageMixin
from utils.models import TranslatedFieldsMixin from utils.models import TranslatedFieldsMixin
from utils.querysets import RelatedObjectsCountMixin
# Mixins # Mixins
class CollectionNameMixin(models.Model): class CollectionNameMixin(models.Model):
@ -30,7 +32,7 @@ class CollectionDateMixin(models.Model):
# Models # Models
class CollectionQuerySet(models.QuerySet): class CollectionQuerySet(RelatedObjectsCountMixin):
"""QuerySet for model Collection""" """QuerySet for model Collection"""
def by_country_code(self, code): def by_country_code(self, code):

View File

@ -1,14 +1,15 @@
import json, pytz import json
import pytz
from datetime import datetime 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 http.cookies import SimpleCookie
from collection.models import Collection, Guide from rest_framework import status
from location.models import Country from rest_framework.test import APITestCase
# Create your tests here. from account.models import User
from collection.models import Collection, Guide
from establishment.models import Establishment, EstablishmentType
from location.models import Country
class BaseTestCase(APITestCase): class BaseTestCase(APITestCase):
@ -20,7 +21,7 @@ class BaseTestCase(APITestCase):
self.newsletter = True self.newsletter = True
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, email=self.email, password=self.password) username=self.username, email=self.email, password=self.password)
#get tokens # get tokens
tokens = User.create_jwt_tokens(self.user) tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie( self.client.cookies = SimpleCookie(
{'access_token': tokens.get('access_token'), {'access_token': tokens.get('access_token'),
@ -81,3 +82,27 @@ class CollectionGuideDetailTests(CollectionDetailTests):
def test_guide_detail_Read(self): def test_guide_detail_Read(self):
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionWebHomeTests(CollectionDetailTests):
def setUp(self):
super().setUp()
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
for i in range(1, 5):
setattr(self, f"establishment{i}",
Establishment.objects.create(
name=f"Test establishment {i}",
establishment_type_id=self.establishment_type.id,
is_publish=True,
slug=f"test-establishment-{i}"
)
)
getattr(self, f"establishment{i}").collections.add(self.collection)
def test_collection_list_filter(self):
response = self.client.get('/api/web/collections/?country_code=en', format='json')
data = response.json()
self.assertIn('count', data)
self.assertGreater(data['count'], 0)

View File

@ -6,7 +6,7 @@ from collection.views import common as views
app_name = 'collection' app_name = 'collection'
urlpatterns = [ urlpatterns = [
path('', views.CollectionListView.as_view(), name='list'), path('', views.CollectionHomePageView.as_view(), name='list'),
path('<slug:slug>/establishments/', views.CollectionEstablishmentListView.as_view(), path('<slug:slug>/establishments/', views.CollectionEstablishmentListView.as_view(),
name='detail'), name='detail'),

View File

@ -30,10 +30,27 @@ class CollectionListView(CollectionViewMixin, generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Override get_queryset method"""
return models.Collection.objects.published()\ queryset = models.Collection.objects.published()\
.by_country_code(code=self.request.country_code)\ .by_country_code(code=self.request.country_code)\
.order_by('-on_top', '-created') .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 CollectionEstablishmentListView(CollectionListView): class CollectionEstablishmentListView(CollectionListView):
"""Retrieve list of establishment for collection.""" """Retrieve list of establishment for collection."""

View File

@ -8,15 +8,16 @@ from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance as DistanceMeasure from django.contrib.gis.measure import Distance as DistanceMeasure
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models 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 import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from collection.models import Collection from collection.models import Collection
from main.models import Award, MetaDataContent
from location.models import Address from location.models import Address
from review.models import Review from review.models import Review
from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, URLImageMixin, from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
TranslatedFieldsMixin, BaseAttributes) TranslatedFieldsMixin, BaseAttributes)
@ -72,6 +73,20 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
class EstablishmentQuerySet(models.QuerySet): class EstablishmentQuerySet(models.QuerySet):
"""Extended queryset for Establishment model.""" """Extended queryset for Establishment model."""
def with_base_related(self):
"""Return qs with related objects."""
return self.select_related('address').prefetch_related(
models.Prefetch('tags',
MetaDataContent.objects.select_related(
'metadata__category'))
)
def with_extended_related(self):
return self.select_related('establishment_type').\
prefetch_related('establishment_subtypes', 'awards', 'schedule',
'phones').\
prefetch_actual_employees()
def search(self, value, locale=None): def search(self, value, locale=None):
"""Search text in JSON fields.""" """Search text in JSON fields."""
if locale is not None: if locale is not None:
@ -266,7 +281,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
slug = models.SlugField(unique=True, max_length=50, null=True, slug = models.SlugField(unique=True, max_length=50, null=True,
verbose_name=_('Establishment slug'), editable=True) verbose_name=_('Establishment slug'), editable=True)
awards = generic.GenericRelation(to='main.Award') awards = generic.GenericRelation(to='main.Award', related_query_name='establishment')
tags = generic.GenericRelation(to='main.MetaDataContent') tags = generic.GenericRelation(to='main.MetaDataContent')
reviews = generic.GenericRelation(to='review.Review') reviews = generic.GenericRelation(to='review.Review')
comments = generic.GenericRelation(to='comment.Comment') comments = generic.GenericRelation(to='comment.Comment')
@ -298,12 +313,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
return country.low_price, country.high_price return country.low_price, country.high_price
# todo: make via prefetch # todo: make via prefetch
@property # @property
def subtypes(self): # def subtypes(self):
return EstablishmentSubType.objects.filter( # return EstablishmentSubType.objects.filter(
subtype_establishment=self, # subtype_establishment=self,
establishment_type=self.establishment_type, # establishment_type=self.establishment_type,
establishment_type__use_subtypes=True) # establishment_type__use_subtypes=True)
def set_establishment_type(self, establishment_type): def set_establishment_type(self, establishment_type):
self.establishment_type = establishment_type self.establishment_type = establishment_type
@ -315,6 +330,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
raise ValidationError('Establishment type of subtype does not match') raise ValidationError('Establishment type of subtype does not match')
self.establishment_subtypes.add(establishment_subtype) self.establishment_subtypes.add(establishment_subtype)
@property
def vintage_year(self):
last_review = self.reviews.by_status(Review.READY).last()
if last_review:
return last_review.vintage
@property @property
def best_price_menu(self): def best_price_menu(self):
return 150 return 150
@ -341,6 +363,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
""" """
return self.address.coordinates return self.address.coordinates
@property
def the_most_recent_award(self):
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest(
field_name='vintage_year')
class Position(BaseAttributes, TranslatedFieldsMixin): class Position(BaseAttributes, TranslatedFieldsMixin):
"""Position model.""" """Position model."""
@ -394,8 +421,8 @@ class Employee(BaseAttributes):
verbose_name=_('User')) verbose_name=_('User'))
name = models.CharField(max_length=255, verbose_name=_('Last name')) name = models.CharField(max_length=255, verbose_name=_('Last name'))
establishments = models.ManyToManyField(Establishment, related_name='employees', establishments = models.ManyToManyField(Establishment, related_name='employees',
through=EstablishmentEmployee) through=EstablishmentEmployee,)
awards = generic.GenericRelation(to='main.Award') awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
tags = generic.GenericRelation(to='main.MetaDataContent') tags = generic.GenericRelation(to='main.MetaDataContent')
class Meta: class Meta:

View File

@ -1,17 +1,13 @@
import json
from rest_framework import serializers from rest_framework import serializers
from establishment import models from establishment import models
from timetable.models import Timetable
from establishment.serializers import ( from establishment.serializers import (
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer ContactPhonesSerializer, SocialNetworkRelatedSerializers,
) EstablishmentTypeSerializer)
from utils.decorators import with_base_attributes from utils.decorators import with_base_attributes
from main.models import Currency from main.models import Currency
from utils.serializers import TJSONSerializer
class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
@ -25,6 +21,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
emails = ContactEmailsSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, )
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) slug = serializers.SlugField(required=True, allow_blank=False, max_length=50)
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
class Meta: class Meta:
model = models.Establishment model = models.Establishment
@ -56,6 +53,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
phones = ContactPhonesSerializer(read_only=False, many=True, ) phones = ContactPhonesSerializer(read_only=False, many=True, )
emails = ContactEmailsSerializer(read_only=False, many=True, ) emails = ContactEmailsSerializer(read_only=False, many=True, )
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
type = EstablishmentTypeSerializer(source='establishment_type')
class Meta: class Meta:
model = models.Establishment model = models.Establishment
@ -90,13 +88,15 @@ class SocialNetworkSerializers(serializers.ModelSerializer):
class PlatesSerializers(PlateSerializer): class PlatesSerializers(PlateSerializer):
"""Social network serializers.""" """Social network serializers."""
name = TJSONSerializer
currency_id = serializers.PrimaryKeyRelatedField( currency_id = serializers.PrimaryKeyRelatedField(
source='currency', source='currency',
queryset=Currency.objects.all(), write_only=True queryset=Currency.objects.all(), write_only=True
) )
class Meta: class Meta:
"""Meta class."""
model = models.Plate model = models.Plate
fields = PlateSerializer.Meta.fields + [ fields = PlateSerializer.Meta.fields + [
'name', 'name',

View File

@ -1,18 +1,18 @@
"""Establishment serializers.""" """Establishment serializers."""
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from comment import models as comment_models from comment import models as comment_models
from comment.serializers import common as comment_serializers from comment.serializers import common as comment_serializers
from establishment import models from establishment import models
from favorites.models import Favorites from favorites.models import Favorites
from location.serializers import AddressSerializer from location.serializers import AddressSimpleSerializer, AddressSerializer
from main.models import MetaDataContent from main.models import MetaDataContent
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
from review import models as review_models from review import models as review_models
from timetable.serialziers import ScheduleRUDSerializer from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from django.utils.translation import gettext_lazy as _ from utils.serializers import TranslatedField, ProjectModelSerializer
from utils.serializers import TJSONSerializer
class ContactPhonesSerializer(serializers.ModelSerializer): class ContactPhonesSerializer(serializers.ModelSerializer):
"""Contact phone serializer""" """Contact phone serializer"""
@ -43,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) currency = CurrencySerializer(read_only=True)
class Meta: class Meta:
@ -58,9 +58,8 @@ class PlateSerializer(serializers.ModelSerializer):
] ]
class MenuSerializers(serializers.ModelSerializer): class MenuSerializers(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set') plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category = TJSONSerializer()
category_translated = serializers.CharField(read_only=True) category_translated = serializers.CharField(read_only=True)
class Meta: class Meta:
@ -74,9 +73,8 @@ class MenuSerializers(serializers.ModelSerializer):
] ]
class MenuRUDSerializers(serializers.ModelSerializer, ): class MenuRUDSerializers(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set') plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category = TJSONSerializer()
class Meta: class Meta:
model = models.Menu model = models.Menu
@ -140,13 +138,13 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'position_translated', 'awards', 'priority') fields = ('id', 'name', 'position_translated', 'awards', 'priority')
class EstablishmentBaseSerializer(serializers.ModelSerializer): class EstablishmentBaseSerializer(ProjectModelSerializer):
"""Base serializer for Establishment model.""" """Base serializer for Establishment model."""
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeSerializer(many=True) preview_image = serializers.URLField(source='preview_image_url')
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
address = AddressSerializer() address = AddressSerializer()
tags = MetaDataContentSerializer(many=True) tags = MetaDataContentSerializer(many=True)
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -159,60 +157,61 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
'price_level', 'price_level',
'toque_number', 'toque_number',
'public_mark', 'public_mark',
'type', 'slug',
'subtypes', 'preview_image',
'in_favorites',
'address', 'address',
'tags', 'tags',
'slug',
] ]
class EstablishmentListSerializer(EstablishmentBaseSerializer): class EstablishmentListSerializer(EstablishmentBaseSerializer):
"""Serializer for Establishment model.""" """Serializer for Establishment model."""
# Annotated fields
in_favorites = serializers.BooleanField(allow_null=True) in_favorites = serializers.BooleanField(allow_null=True)
preview_image = serializers.URLField(source='preview_image_url') class Meta(EstablishmentBaseSerializer.Meta):
class Meta:
"""Meta class.""" """Meta class."""
model = models.Establishment
fields = EstablishmentBaseSerializer.Meta.fields + [ fields = EstablishmentBaseSerializer.Meta.fields + [
'in_favorites', 'in_favorites',
'preview_image',
] ]
class EstablishmentAllListSerializer(EstablishmentListSerializer):
""" Serailizer for api/*/establishments """
address = AddressSimpleSerializer()
class Meta(EstablishmentListSerializer.Meta):
pass
class EstablishmentDetailSerializer(EstablishmentListSerializer): class EstablishmentDetailSerializer(EstablishmentListSerializer):
"""Serializer for Establishment model.""" """Serializer for Establishment model."""
description_translated = serializers.CharField(allow_null=True)
description_translated = TranslatedField()
image = serializers.URLField(source='image_url')
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeSerializer(many=True, source='establishment_subtypes')
awards = AwardSerializer(many=True) awards = AwardSerializer(many=True)
schedule = ScheduleRUDSerializer(many=True, allow_null=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True, ) phones = ContactPhonesSerializer(read_only=True, many=True)
emails = ContactEmailsSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True)
review = ReviewSerializer(source='last_published_review', allow_null=True) review = ReviewSerializer(source='last_published_review', allow_null=True)
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=True) many=True)
menu = MenuSerializers(source='menu_set', many=True, read_only=True) menu = MenuSerializers(source='menu_set', many=True, read_only=True)
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_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) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
vintage_year = serializers.ReadOnlyField()
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) class Meta(EstablishmentListSerializer.Meta):
in_favorites = serializers.BooleanField()
image = serializers.URLField(source='image_url')
class Meta:
"""Meta class.""" """Meta class."""
model = models.Establishment
fields = EstablishmentListSerializer.Meta.fields + [ fields = EstablishmentListSerializer.Meta.fields + [
'description_translated', 'description_translated',
'price_level',
'image', 'image',
'subtypes',
'type',
'awards', 'awards',
'schedule', 'schedule',
'website', 'website',
@ -228,7 +227,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer):
'best_price_menu', 'best_price_menu',
'best_price_carte', 'best_price_carte',
'transportation', 'transportation',
'slug', 'vintage_year',
] ]

View File

@ -6,6 +6,7 @@ from http.cookies import SimpleCookie
from main.models import Currency from main.models import Currency
from establishment.models import Establishment, EstablishmentType, Menu from establishment.models import Establishment, EstablishmentType, Menu
# Create your tests here. # Create your tests here.
from translation.models import Language
class BaseTestCase(APITestCase): class BaseTestCase(APITestCase):
@ -25,6 +26,12 @@ class BaseTestCase(APITestCase):
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
# Create lang object
Language.objects.create(
title='English',
locale='en-GB'
)
class EstablishmentBTests(BaseTestCase): class EstablishmentBTests(BaseTestCase):
def test_establishment_CRUD(self): def test_establishment_CRUD(self):

View File

@ -4,10 +4,17 @@ from rest_framework import generics
from establishment import models from establishment import models
from establishment import serializers from establishment import serializers
from establishment.views.common import EstablishmentMixin
class EstablishmentListCreateView(EstablishmentMixin, generics.ListCreateAPIView): class EstablishmentMixinViews:
"""Establishment mixin."""
def get_queryset(self):
"""Overrided method 'get_queryset'."""
return models.Establishment.objects.published().with_base_related()
class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView):
"""Establishment list/create view.""" """Establishment list/create view."""
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentListCreateSerializer serializer_class = serializers.EstablishmentListCreateSerializer

View File

@ -1,16 +0,0 @@
"""Establishment app views."""
from rest_framework import permissions
from establishment import models
class EstablishmentMixin:
"""Establishment mixin."""
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""Overridden method 'get_queryset'."""
return models.Establishment.objects.published() \
.prefetch_actual_employees()

View File

@ -1,5 +1,4 @@
"""Establishment app views.""" """Establishment app views."""
from django.conf import settings from django.conf import settings
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@ -8,27 +7,50 @@ from rest_framework import generics, permissions
from comment import models as comment_models from comment import models as comment_models
from establishment import filters from establishment import filters
from establishment import models, serializers from establishment import models, serializers
from establishment.views import EstablishmentMixin
from main import methods from main import methods
from main.models import MetaDataContent from main.models import MetaDataContent
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
from utils.pagination import EstablishmentPortionPagination from utils.pagination import EstablishmentPortionPagination
class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): class EstablishmentMixinView:
"""Establishment mixin."""
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""Overrided method 'get_queryset'."""
return models.Establishment.objects.published().with_base_related().\
annotate_in_favorites(self.request.user)
class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
"""Resource for getting a list of establishments.""" """Resource for getting a list of establishments."""
serializer_class = serializers.EstablishmentListSerializer
filter_class = filters.EstablishmentFilter filter_class = filters.EstablishmentFilter
serializer_class = serializers.EstablishmentAllListSerializer
def get_queryset(self): def get_queryset(self):
"""Overridden method 'get_queryset'.""" """Overridden method 'get_queryset'."""
qs = super(EstablishmentListView, self).get_queryset() qs = super(EstablishmentListView, self).get_queryset()
return qs.by_country_code(code=self.request.country_code) \ if self.request.country_code:
.annotate_in_favorites(user=self.request.user) qs = qs.by_country_code(self.request.country_code)
return qs
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentDetailSerializer
def get_queryset(self):
return super().get_queryset().with_extended_related()
class EstablishmentRecentReviewListView(EstablishmentListView): class EstablishmentRecentReviewListView(EstablishmentListView):
"""List view for last reviewed establishments.""" """List view for last reviewed establishments."""
pagination_class = EstablishmentPortionPagination pagination_class = EstablishmentPortionPagination
def get_queryset(self): def get_queryset(self):
@ -57,17 +79,6 @@ class EstablishmentSimilarListView(EstablishmentListView):
return qs.similar(establishment_slug=self.kwargs.get('slug')) return qs.similar(establishment_slug=self.kwargs.get('slug'))
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentDetailSerializer
def get_queryset(self):
"""Override 'get_queryset' method."""
return super(EstablishmentRetrieveView, self).get_queryset() \
.annotate_in_favorites(self.request.user)
class EstablishmentTypeListView(generics.ListAPIView): class EstablishmentTypeListView(generics.ListAPIView):
"""Resource for getting a list of establishment types.""" """Resource for getting a list of establishment types."""

View File

@ -27,7 +27,7 @@ class BaseTestCase(APITestCase):
news_type=self.test_news_type, news_type=self.test_news_type,
description={"en-GB": "Description test news"}, description={"en-GB": "Description test news"},
playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00",
is_publish=True) state=News.PUBLISHED, slug='test-news')
self.test_content_type = ContentType.objects.get(app_label="news", model="news") self.test_content_type = ContentType.objects.get(app_label="news", model="news")

View File

@ -1,7 +1,6 @@
"""Location app common serializers.""" """Location app common serializers."""
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from rest_framework import serializers from rest_framework import serializers
from location import models from location import models
from utils.serializers import TranslatedField from utils.serializers import TranslatedField
@ -86,6 +85,7 @@ class CitySerializer(serializers.ModelSerializer):
class AddressSerializer(serializers.ModelSerializer): class AddressSerializer(serializers.ModelSerializer):
"""Address serializer.""" """Address serializer."""
city_id = serializers.PrimaryKeyRelatedField( city_id = serializers.PrimaryKeyRelatedField(
source='city', source='city',
queryset=models.City.objects.all()) queryset=models.City.objects.all())
@ -104,7 +104,7 @@ class AddressSerializer(serializers.ModelSerializer):
'number', 'number',
'postal_code', 'postal_code',
'geo_lon', 'geo_lon',
'geo_lat' 'geo_lat',
] ]
def validate(self, attrs): def validate(self, attrs):
@ -128,3 +128,18 @@ class AddressSerializer(serializers.ModelSerializer):
setattr(instance, 'geo_lon', float(0)) setattr(instance, 'geo_lon', float(0))
return super().to_representation(instance) return super().to_representation(instance)
class AddressSimpleSerializer(serializers.ModelSerializer):
"""Serializer for address obj in related objects."""
class Meta:
"""Meta class."""
model = models.Address
fields = (
'id',
'street_name_1',
'street_name_2',
'number',
'postal_code',
)

View File

@ -318,9 +318,9 @@ class Carousel(models.Model):
@property @property
def vintage_year(self): def vintage_year(self):
if hasattr(self.content_object, 'reviews'): if hasattr(self.content_object, 'reviews'):
review_qs = self.content_object.reviews.by_status(Review.READY) last_review = self.content_object.reviews.by_status(Review.READY).last()
if review_qs.exists(): if last_review:
return review_qs.last().vintage return last_review.vintage
@property @property
def toque_number(self): def toque_number(self):
@ -337,6 +337,16 @@ class Carousel(models.Model):
if hasattr(self.content_object, 'image_url'): if hasattr(self.content_object, 'image_url'):
return 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 the_most_recent_award(self):
if hasattr(self.content_object, 'the_most_recent_award'):
return self.content_object.the_most_recent_award
@property @property
def model_name(self): def model_name(self):
return self.content_object.__class__.__name__ return self.content_object.__class__.__name__

View File

@ -3,6 +3,7 @@ from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from establishment.models import Establishment
from utils.serializers import TranslatedField from utils.serializers import TranslatedField
@ -141,6 +142,7 @@ class CarouselListSerializer(serializers.ModelSerializer):
image = serializers.URLField(source='image_url') image = serializers.URLField(source='image_url')
awards = AwardBaseSerializer(many=True) awards = AwardBaseSerializer(many=True)
vintage_year = serializers.IntegerField() vintage_year = serializers.IntegerField()
last_award = AwardBaseSerializer(source='the_most_recent_award', allow_null=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -154,6 +156,8 @@ class CarouselListSerializer(serializers.ModelSerializer):
'public_mark', 'public_mark',
'image', 'image',
'vintage_year', 'vintage_year',
'last_award',
'slug',
] ]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.2.4 on 2019-09-27 08:45
from django.db import migrations
from django.core.validators import EMPTY_VALUES
def fill_slug(apps,schemaeditor):
News = apps.get_model('news', 'News')
for news in News.objects.all():
if news.slug in EMPTY_VALUES:
news.slug = f'Slug_{news.id}'
news.save()
class Migration(migrations.Migration):
dependencies = [
('news', '0013_auto_20190924_0806'),
]
operations = [
migrations.RunPython(fill_slug, migrations.RunPython.noop)
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-27 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('news', '0014_auto_20190927_0845'),
]
operations = [
migrations.AlterField(
model_name='news',
name='slug',
field=models.SlugField(unique=True, verbose_name='News slug'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-27 13:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('news', '0015_auto_20190927_0853'),
]
operations = [
migrations.AddField(
model_name='news',
name='template',
field=models.PositiveIntegerField(choices=[(0, 'newspaper'), (1, 'main.pdf.erb'), (2, 'main')], default=0),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.4 on 2019-09-27 13:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0015_auto_20190927_0853'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='author',
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.2.4 on 2019-09-27 14:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('news', '0016_news_template'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='is_publish',
),
migrations.AddField(
model_name='news',
name='state',
field=models.PositiveSmallIntegerField(choices=[(0, 'Waiting'), (1, 'Hidden'), (2, 'Published'), (3, 'Published exclusive')], default=0, verbose_name='State'),
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-27 14:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0017_auto_20190927_1403'),
('news', '0016_remove_news_author'),
]
operations = [
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-27 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('news', '0018_merge_20190927_1432'),
]
operations = [
migrations.AddField(
model_name='news',
name='author',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author'),
),
]

View File

@ -5,6 +5,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
from random import sample as random_sample
class NewsType(models.Model): class NewsType(models.Model):
@ -39,7 +40,7 @@ class NewsQuerySet(models.QuerySet):
now = timezone.now() now = timezone.now()
return self.filter(models.Q(models.Q(end__gte=now) | return self.filter(models.Q(models.Q(end__gte=now) |
models.Q(end__isnull=True)), models.Q(end__isnull=True)),
is_publish=True, start__lte=now) state__in=self.model.PUBLISHED_STATES, start__lte=now)
def with_related(self): def with_related(self):
"""Return qs with related objects.""" """Return qs with related objects."""
@ -49,6 +50,34 @@ class NewsQuerySet(models.QuerySet):
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
"""News model.""" """News model."""
STR_FIELD_NAME = 'title'
# TEMPLATE CHOICES
NEWSPAPER = 0
MAIN_PDF_ERB = 1
MAIN = 2
TEMPLATE_CHOICES = (
(NEWSPAPER, 'newspaper'),
(MAIN_PDF_ERB, 'main.pdf.erb'),
(MAIN, 'main'),
)
# STATE CHOICES
WAITING = 0
HIDDEN = 1
PUBLISHED = 2
PUBLISHED_EXCLUSIVE = 3
PUBLISHED_STATES = [PUBLISHED, PUBLISHED_EXCLUSIVE]
STATE_CHOICES = (
(WAITING, _('Waiting')),
(HIDDEN, _('Hidden')),
(PUBLISHED, _('Published')),
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
)
news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT, news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
verbose_name=_('news type')) verbose_name=_('news type'))
title = TJSONField(blank=True, null=True, default=None, title = TJSONField(blank=True, null=True, default=None,
@ -63,13 +92,18 @@ class News(BaseAttributes, TranslatedFieldsMixin):
start = models.DateTimeField(verbose_name=_('Start')) start = models.DateTimeField(verbose_name=_('Start'))
end = models.DateTimeField(blank=True, null=True, default=None, end = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('End')) verbose_name=_('End'))
slug = models.SlugField(unique=True, max_length=50, null=True, slug = models.SlugField(unique=True, max_length=50,
verbose_name=_('News slug'), editable=True,) verbose_name=_('News slug'))
playlist = models.IntegerField(_('playlist')) playlist = models.IntegerField(_('playlist'))
is_publish = models.BooleanField(default=False,
verbose_name=_('Publish status')) # author = models.CharField(max_length=255, blank=True, null=True,
# default=None,verbose_name=_('Author'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('State'))
author = models.CharField(max_length=255, blank=True, null=True, author = models.CharField(max_length=255, blank=True, null=True,
default=None,verbose_name=_('Author')) default=None,verbose_name=_('Author'))
is_highlighted = models.BooleanField(default=False, is_highlighted = models.BooleanField(default=False,
verbose_name=_('Is highlighted')) verbose_name=_('Is highlighted'))
# TODO: metadata_keys - описание ключей для динамического построения полей метаданных # TODO: metadata_keys - описание ключей для динамического построения полей метаданных
@ -78,6 +112,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
verbose_name=_('Image URL path')) verbose_name=_('Image URL path'))
preview_image_url = models.URLField(blank=True, null=True, default=None, preview_image_url = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Preview image URL path')) verbose_name=_('Preview image URL path'))
template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER)
address = models.ForeignKey('location.Address', blank=True, null=True, address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'), default=None, verbose_name=_('address'),
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
@ -97,6 +132,28 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def __str__(self): def __str__(self):
return f'news: {self.slug}' return f'news: {self.slug}'
@property
def is_publish(self):
return self.state in self.PUBLISHED_STATES
@property @property
def web_url(self): def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug}) 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

View File

@ -1,10 +1,11 @@
"""News app common serializers.""" """News app common serializers."""
from rest_framework import serializers from rest_framework import serializers
from account.serializers.common import UserSerializer
from location import models as location_models from location import models as location_models
from location.serializers import CountrySimpleSerializer from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer from main.serializers import MetaDataContentSerializer
from news import models from news import models
from utils.serializers import TranslatedField from utils.serializers import TranslatedField, ProjectModelSerializer
class NewsTypeSerializer(serializers.ModelSerializer): class NewsTypeSerializer(serializers.ModelSerializer):
@ -17,7 +18,7 @@ class NewsTypeSerializer(serializers.ModelSerializer):
fields = ('id', 'name') fields = ('id', 'name')
class NewsBaseSerializer(serializers.ModelSerializer): class NewsBaseSerializer(ProjectModelSerializer):
"""Base serializer for News model.""" """Base serializer for News model."""
# read only fields # read only fields
@ -28,8 +29,6 @@ class NewsBaseSerializer(serializers.ModelSerializer):
news_type = NewsTypeSerializer(read_only=True) news_type = NewsTypeSerializer(read_only=True)
tags = MetaDataContentSerializer(read_only=True, many=True) tags = MetaDataContentSerializer(read_only=True, many=True)
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -52,6 +51,10 @@ class NewsDetailSerializer(NewsBaseSerializer):
description_translated = TranslatedField() description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True) country = CountrySimpleSerializer(read_only=True)
# todo: check the data redundancy
author = UserSerializer(source='created_by', read_only=True)
state_display = serializers.CharField(source='get_state_display',
read_only=True)
class Meta(NewsBaseSerializer.Meta): class Meta(NewsBaseSerializer.Meta):
"""Meta class.""" """Meta class."""
@ -62,8 +65,11 @@ class NewsDetailSerializer(NewsBaseSerializer):
'end', 'end',
'playlist', 'playlist',
'is_publish', 'is_publish',
'state',
'state_display',
'author', 'author',
'country', 'country',
'list_also_like_news',
) )
@ -86,10 +92,11 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
news_type_id = serializers.PrimaryKeyRelatedField( news_type_id = serializers.PrimaryKeyRelatedField(
source='news_type', write_only=True, source='news_type', write_only=True,
queryset=models.NewsType.objects.all()) queryset=models.NewsType.objects.all())
country_id = serializers.PrimaryKeyRelatedField( country_id = serializers.PrimaryKeyRelatedField(
source='country', write_only=True, source='country', write_only=True,
queryset=location_models.Country.objects.all()) queryset=location_models.Country.objects.all())
template_display = serializers.CharField(source='get_template_display',
read_only=True)
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
"""Meta class.""" """Meta class."""
@ -99,5 +106,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
'description', 'description',
'news_type_id', 'news_type_id',
'country_id', 'country_id',
'template',
'template_display',
) )

View File

@ -26,7 +26,7 @@ class BaseTestCase(APITestCase):
news_type=self.test_news_type, description={"en-GB": "Description test news"}, news_type=self.test_news_type, description={"en-GB": "Description test news"},
playlist=1, start=datetime.now() + timedelta(hours=-2), playlist=1, start=datetime.now() + timedelta(hours=-2),
end=datetime.now() + timedelta(hours=2), end=datetime.now() + timedelta(hours=2),
is_publish=True, slug='test-news-slug',) state=News.PUBLISHED, slug='test-news-slug',)
class NewsTestCase(BaseTestCase): class NewsTestCase(BaseTestCase):

View File

@ -20,11 +20,13 @@ class NewsMixinView:
class NewsListView(NewsMixinView, generics.ListAPIView): class NewsListView(NewsMixinView, generics.ListAPIView):
"""News list view.""" """News list view."""
filter_class = filters.NewsListFilterSet filter_class = filters.NewsListFilterSet
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""News detail view.""" """News detail view."""
lookup_field = 'slug' lookup_field = 'slug'
serializer_class = serializers.NewsDetailSerializer serializer_class = serializers.NewsDetailSerializer
@ -54,6 +56,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
create_serializers_class = serializers.NewsBackOfficeDetailSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer
def get_serializer_class(self): def get_serializer_class(self):
"""Override serializer class."""
if self.request.method == 'POST': if self.request.method == 'POST':
return self.create_serializers_class return self.create_serializers_class
return super().get_serializer_class() return super().get_serializer_class()

View File

@ -6,5 +6,8 @@ from search_indexes import views
router = routers.SimpleRouter() router = routers.SimpleRouter()
# router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled # router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile')
router.register(r'news', views.NewsDocumentViewSet, basename='news')
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -4,7 +4,8 @@ from django_elasticsearch_dsl_drf import constants
from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend, from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend,
GeoSpatialFilteringFilterBackend) GeoSpatialFilteringFilterBackend)
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination
from utils.pagination import ProjectPageNumberPagination
from search_indexes import serializers, filters from search_indexes import serializers, filters
from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.documents import EstablishmentDocument, NewsDocument
@ -14,7 +15,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
document = NewsDocument document = NewsDocument
lookup_field = 'slug' lookup_field = 'slug'
pagination_class = PageNumberPagination pagination_class = ProjectPageNumberPagination
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.NewsDocumentSerializer serializer_class = serializers.NewsDocumentSerializer
ordering = ('id',) ordering = ('id',)
@ -40,7 +41,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
document = EstablishmentDocument document = EstablishmentDocument
lookup_field = 'slug' lookup_field = 'slug'
pagination_class = PageNumberPagination pagination_class = ProjectPageNumberPagination
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.EstablishmentDocumentSerializer serializer_class = serializers.EstablishmentDocumentSerializer
ordering = ('id',) ordering = ('id',)

View File

@ -36,3 +36,4 @@ class Timetable(ProjectBaseMixin):
"""Meta class.""" """Meta class."""
verbose_name = _('Timetable') verbose_name = _('Timetable')
verbose_name_plural = _('Timetables') verbose_name_plural = _('Timetables')
ordering = ['weekday']

View File

@ -19,6 +19,7 @@ def parse_cookies(get_response):
cookie_dict = request.COOKIES cookie_dict = request.COOKIES
# processing locale cookie # processing locale cookie
locale = get_locale(cookie_dict) locale = get_locale(cookie_dict)
# todo: don't use DB!!! Use cache
if not Language.objects.filter(locale=locale).exists(): if not Language.objects.filter(locale=locale).exists():
locale = TranslationSettings.get_solo().default_language locale = TranslationSettings.get_solo().default_language
translation.activate(locale) translation.activate(locale)

View File

@ -1,6 +1,8 @@
"""Utils QuerySet Mixins""" """Utils QuerySet Mixins"""
from django.db import models from django.db import models
from django.db.models import Q, Sum, F
from functools import reduce
from operator import add
from utils.methods import get_contenttype from utils.methods import get_contenttype
@ -15,3 +17,40 @@ class ContentTypeQuerySetMixin(models.QuerySet):
"""Filter QuerySet by ContentType.""" """Filter QuerySet by ContentType."""
return self.filter(content_type=get_contenttype(app_label=app_label, return self.filter(content_type=get_contenttype(app_label=app_label,
model=model)) model=model))
class RelatedObjectsCountMixin(models.QuerySet):
"""QuerySet for ManyToMany related count filter"""
def _get_related_objects_names(self):
"""Get all related objects (with reversed)"""
related_objects_names = []
for related_object in self.model._meta.related_objects:
if isinstance(related_object, models.ManyToManyRel):
related_objects_names.append(related_object.name)
return related_objects_names
def _annotate_related_objects_count(self):
"""Annotate all related objects to queryset"""
annotations = {}
for related_object in self._get_related_objects_names():
annotations[f"{related_object}_count"] = models.Count(f"{related_object}")
return self.annotate(**annotations)
def filter_related_gt(self, count):
"""QuerySet filter by related objects count"""
q_objects = Q()
for related_object in self._get_related_objects_names():
q_objects.add(Q(**{f"{related_object}_count__gt": count}), Q.OR)
return self._annotate_related_objects_count().filter(q_objects)
def filter_all_related_gt(self, count):
"""Queryset filter by all related objects count"""
exp =reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()])
return self._annotate_related_objects_count()\
.annotate(all_related_count=exp)\
.filter(all_related_count__gt=count)

View File

@ -1,7 +1,7 @@
"""Utils app serializer.""" """Utils app serializer."""
from rest_framework import serializers
from utils.models import PlatformMixin
from django.core import exceptions from django.core import exceptions
from rest_framework import serializers
from utils import models
from translation.models import Language from translation.models import Language
@ -11,8 +11,8 @@ class EmptySerializer(serializers.Serializer):
class SourceSerializerMixin(serializers.Serializer): class SourceSerializerMixin(serializers.Serializer):
"""Base authorization serializer mixin""" """Base authorization serializer mixin"""
source = serializers.ChoiceField(choices=PlatformMixin.SOURCES, source = serializers.ChoiceField(choices=models.PlatformMixin.SOURCES,
default=PlatformMixin.WEB, default=models.PlatformMixin.WEB,
write_only=True) write_only=True)
@ -25,18 +25,16 @@ class TranslatedField(serializers.CharField):
read_only=read_only, **kwargs) read_only=read_only, **kwargs)
# todo: view validation in more detail
def validate_tjson(value): def validate_tjson(value):
if not isinstance(value, dict): if not isinstance(value, dict):
raise exceptions.ValidationError( raise exceptions.ValidationError(
'invalid_json', 'invalid_json',
code='invalid_json', code='invalid_json',
params={'value': value}, params={'value': value},
) )
lang_count = Language.objects.filter(locale__in=value.keys()).count() lang_count = Language.objects.filter(locale__in=value.keys()).count()
if lang_count != len(value.keys()):
if lang_count == 0:
raise exceptions.ValidationError( raise exceptions.ValidationError(
'invalid_translated_keys', 'invalid_translated_keys',
code='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] validators = [validate_tjson]
class ProjectModelSerializer(serializers.ModelSerializer):
"""Overrided ModelSerializer."""
serializers.ModelSerializer.serializer_field_mapping[models.TJSONField] = TJSONField

View File

@ -54,9 +54,9 @@ class TranslateFieldTests(BaseTestCase):
playlist=1, playlist=1,
start=datetime.now(pytz.utc) + timedelta(hours=-13), start=datetime.now(pytz.utc) + timedelta(hours=-13),
end=datetime.now(pytz.utc) + timedelta(hours=13), end=datetime.now(pytz.utc) + timedelta(hours=13),
is_publish=True,
news_type=self.news_type, news_type=self.news_type,
slug='test', slug='test',
state=News.PUBLISHED,
) )
def test_model_field(self): def test_model_field(self):
@ -95,6 +95,7 @@ class BaseAttributeTests(BaseTestCase):
response_data = response.json() response_data = response.json()
self.assertIn("id", response_data) self.assertIn("id", response_data)
self.assertIsInstance(response_data['id'], int)
employee = Employee.objects.get(id=response_data['id']) employee = Employee.objects.get(id=response_data['id'])
@ -118,7 +119,7 @@ class BaseAttributeTests(BaseTestCase):
'name': 'Test new name' 'name': 'Test new name'
} }
response = self.client.patch('/api/back/establishments/employees/1/', data=update_data) response = self.client.patch(f'/api/back/establishments/employees/{employee.pk}/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
employee.refresh_from_db() employee.refresh_from_db()

View File

@ -12,8 +12,6 @@ services:
- POSTGRES_DB=postgres - POSTGRES_DB=postgres
ports: ports:
- "5436:5432" - "5436:5432"
networks:
- db-net
volumes: volumes:
- gm-db:/var/lib/postgresql/data/ - gm-db:/var/lib/postgresql/data/
elasticsearch: elasticsearch:
@ -28,8 +26,7 @@ services:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node - discovery.type=single-node
- xpack.security.enabled=false - xpack.security.enabled=false
networks:
- app-net
# RabbitMQ # RabbitMQ
rabbitmq: rabbitmq:
image: rabbitmq:latest image: rabbitmq:latest
@ -83,19 +80,12 @@ services:
- worker - worker
- worker_beat - worker_beat
- elasticsearch - elasticsearch
networks:
- app-net
- db-net
volumes: volumes:
- .:/code - .:/code
- gm-media:/media-data - gm-media:/media-data
ports: ports:
- "8000:8000" - "8000:8000"
networks:
app-net:
db-net:
volumes: volumes:
gm-db: gm-db:
name: gm-db name: gm-db

View File

@ -1,5 +1,6 @@
"""Local settings.""" """Local settings."""
from .base import * from .base import *
import sys
ALLOWED_HOSTS = ['*', ] ALLOWED_HOSTS = ['*', ]
@ -67,3 +68,7 @@ ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'local_news', # 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
} }
TESTING = sys.argv[1:2] == ['test']
if TESTING:
ELASTICSEARCH_INDEX_NAMES = {}