Merge branch 'develop' into feature/sending-news-email
This commit is contained in:
commit
266ace6912
|
|
@ -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'))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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__
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
22
apps/news/migrations/0014_auto_20190927_0845.py
Normal file
22
apps/news/migrations/0014_auto_20190927_0845.py
Normal 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)
|
||||||
|
]
|
||||||
18
apps/news/migrations/0015_auto_20190927_0853.py
Normal file
18
apps/news/migrations/0015_auto_20190927_0853.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/news/migrations/0016_news_template.py
Normal file
18
apps/news/migrations/0016_news_template.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
apps/news/migrations/0016_remove_news_author.py
Normal file
17
apps/news/migrations/0016_remove_news_author.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
22
apps/news/migrations/0017_auto_20190927_1403.py
Normal file
22
apps/news/migrations/0017_auto_20190927_1403.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/news/migrations/0018_merge_20190927_1432.py
Normal file
14
apps/news/migrations/0018_merge_20190927_1432.py
Normal 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 = [
|
||||||
|
]
|
||||||
18
apps/news/migrations/0019_news_author.py
Normal file
18
apps/news/migrations/0019_news_author.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',)
|
||||||
|
|
|
||||||
|
|
@ -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']
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user