Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop

This commit is contained in:
Dmitriy Kuzmenko 2019-09-27 17:25:51 +03:00
commit 54c392762c
27 changed files with 511 additions and 169 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

@ -1,20 +1,23 @@
"""Establishment models.""" """Establishment models."""
from functools import reduce from functools import reduce
from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.measure import Distance as DistanceMeasure from django.conf import settings
from django.contrib.gis.geos import Point
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance as DistanceMeasure
from django.core.exceptions import ValidationError from django.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.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 location.models import Address
from collection.models import Collection from collection.models import Collection
from main.models import MetaDataContent
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)
@ -70,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:
@ -91,15 +108,20 @@ class EstablishmentQuerySet(models.QuerySet):
""" """
return self.filter(is_publish=True) return self.filter(is_publish=True)
def annotate_distance(self, point: Point): def has_published_reviews(self):
"""
Return QuerySet establishments with published reviews.
"""
return self.filter(reviews__status=Review.READY,)
def annotate_distance(self, point: Point = None):
""" """
Return QuerySet with annotated field - distance Return QuerySet with annotated field - distance
Description: Description:
""" """
return self.annotate(distance=models.Value( return self.annotate(distance=Distance('address__coordinates', point,
DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m, srid=settings.GEO_DEFAULT_SRID))
output_field=models.FloatField()))
def annotate_intermediate_public_mark(self): def annotate_intermediate_public_mark(self):
""" """
@ -108,11 +130,11 @@ class EstablishmentQuerySet(models.QuerySet):
If establishments in collection POP and its mark is null, then If establishments in collection POP and its mark is null, then
intermediate_mark is set to 10; intermediate_mark is set to 10;
""" """
return self.annotate(intermediate_public_mark=models.Case( return self.annotate(intermediate_public_mark=Case(
models.When( When(
collections__collection_type=Collection.POP, collections__collection_type=Collection.POP,
public_mark__isnull=True, public_mark__isnull=True,
then=10 then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK
), ),
default='public_mark', default='public_mark',
output_field=models.FloatField())) output_field=models.FloatField()))
@ -123,38 +145,50 @@ class EstablishmentQuerySet(models.QuerySet):
Description: Description:
Similarity mark determined by comparison with compared establishment mark Similarity mark determined by comparison with compared establishment mark
""" """
return self.annotate(mark_similarity=models.ExpressionWrapper( return self.annotate(mark_similarity=ExpressionWrapper(
mark - models.F('intermediate_public_mark'), mark - F('intermediate_public_mark'),
output_field=models.FloatField() output_field=models.FloatField()
)) ))
def similar(self, establishment_slug: str, output_objects: int = 12): def similar(self, establishment_slug: str):
""" """
Return QuerySet with objects that similar to Establishment. Return QuerySet with objects that similar to Establishment.
:param establishment_slug: str Establishment slug :param establishment_slug: str Establishment slug
:param output_objects: int of output objects
""" """
establishment_qs = Establishment.objects.filter(slug=establishment_slug, establishment_qs = self.filter(slug=establishment_slug,
public_mark__isnull=False) public_mark__isnull=False)
if establishment_qs.exists(): if establishment_qs.exists():
establishment = establishment_qs.first() establishment = establishment_qs.first()
subquery_filter_by_distance = Subquery(
# TODO fix error: self.exclude(slug=establishment_slug)
# AttributeError: 'NoneType' object has no attribute 'coordinates' .filter(image_url__isnull=False, public_mark__gte=10)
return self.exclude(slug=establishment_slug) \ .has_published_reviews()
.filter(is_publish=True, .annotate_distance(point=establishment.location)
image_url__isnull=False, .order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
reviews__isnull=False, .values('id')
reviews__status=Review.READY, )
public_mark__gte=10) \ return self.filter(id__in=subquery_filter_by_distance) \
.annotate_distance(point=establishment.address.coordinates) \
.annotate_intermediate_public_mark() \ .annotate_intermediate_public_mark() \
.annotate_mark_similarity(mark=establishment.public_mark) \ .annotate_mark_similarity(mark=establishment.public_mark) \
.order_by('distance') \ .order_by('mark_similarity')
.order_by('mark_similarity')[:output_objects]
else: else:
return self.none() return self.none()
def last_reviewed(self, point: Point):
"""
Return QuerySet with last reviewed establishments.
:param point: location Point object, needs to ordering
"""
subquery_filter_by_distance = Subquery(
self.filter(image_url__isnull=False, public_mark__gte=10)
.has_published_reviews()
.annotate_distance(point=point)
.order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
.values('id')
)
return self.filter(id__in=subquery_filter_by_distance) \
.order_by('-reviews__published_at')
def prefetch_actual_employees(self): def prefetch_actual_employees(self):
"""Prefetch actual employees.""" """Prefetch actual employees."""
return self.prefetch_related( return self.prefetch_related(
@ -168,10 +202,10 @@ class EstablishmentQuerySet(models.QuerySet):
favorite_establishments = [] favorite_establishments = []
if user.is_authenticated: if user.is_authenticated:
favorite_establishments = user.favorites.by_content_type(app_label='establishment', favorite_establishments = user.favorites.by_content_type(app_label='establishment',
model='establishment')\ model='establishment') \
.values_list('object_id', flat=True) .values_list('object_id', flat=True)
return self.annotate(in_favorites=models.Case( return self.annotate(in_favorites=Case(
models.When( When(
id__in=favorite_establishments, id__in=favorite_establishments,
then=True), then=True),
default=False, default=False,
@ -195,7 +229,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
name = models.CharField(_('name'), max_length=255, default='') name = models.CharField(_('name'), max_length=255, default='')
name_translated = models.CharField(_('Transliterated name'), name_translated = models.CharField(_('Transliterated name'),
max_length=255, default='') max_length=255, default='')
description = TJSONField(blank=True, null=True, default=None, description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'), verbose_name=_('description'),
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
@ -279,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
@ -309,6 +343,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
return [{'id': tag.metadata.id, return [{'id': tag.metadata.id,
'label': tag.metadata.label} for tag in self.tags.all()] 'label': tag.metadata.label} for tag in self.tags.all()]
@property
def last_published_review(self):
"""Return last published review"""
return self.reviews.published()\
.order_by('-published_at').first()
@property
def location(self):
"""
Return Point object of establishment location
"""
return self.address.coordinates
class Position(BaseAttributes, TranslatedFieldsMixin): class Position(BaseAttributes, TranslatedFieldsMixin):
"""Position model.""" """Position model."""

View File

@ -1,16 +1,14 @@
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):
@ -24,6 +22,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')
class Meta: class Meta:
model = models.Establishment model = models.Establishment
@ -55,6 +54,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
@ -89,7 +89,7 @@ class SocialNetworkSerializers(serializers.ModelSerializer):
class PlatesSerializers(PlateSerializer): class PlatesSerializers(PlateSerializer):
"""Social network serializers.""" """Social network serializers."""
name = serializers.JSONField() 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

View File

@ -1,17 +1,18 @@
"""Establishment serializers.""" """Establishment serializers."""
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
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
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
from utils.serializers import TJSONSerializer
class ContactPhonesSerializer(serializers.ModelSerializer): class ContactPhonesSerializer(serializers.ModelSerializer):
@ -60,7 +61,7 @@ class PlateSerializer(serializers.ModelSerializer):
class MenuSerializers(serializers.ModelSerializer): class MenuSerializers(serializers.ModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set') plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category = serializers.JSONField() category = TJSONSerializer()
category_translated = serializers.CharField(read_only=True) category_translated = serializers.CharField(read_only=True)
class Meta: class Meta:
@ -74,9 +75,9 @@ class MenuSerializers(serializers.ModelSerializer):
] ]
class MenuRUDSerializers(serializers.ModelSerializer): class MenuRUDSerializers(serializers.ModelSerializer, ):
plates = PlateSerializer(read_only=True, many=True, source='plate_set') plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category = serializers.JSONField() category = TJSONSerializer()
class Meta: class Meta:
model = models.Menu model = models.Menu
@ -142,11 +143,11 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
class EstablishmentBaseSerializer(serializers.ModelSerializer): class EstablishmentBaseSerializer(serializers.ModelSerializer):
"""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')
address = AddressSerializer()
tags = MetaDataContentSerializer(many=True)
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
address = AddressSimpleSerializer()
tags = MetaDataContentSerializer(many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -159,60 +160,53 @@ 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 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 = serializers.SerializerMethodField() 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)
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) class Meta(EstablishmentListSerializer.Meta):
in_favorites = serializers.SerializerMethodField()
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,23 +222,17 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer):
'best_price_menu', 'best_price_menu',
'best_price_carte', 'best_price_carte',
'transportation', 'transportation',
'slug',
] ]
def get_review(self, obj): # def get_in_favorites(self, obj):
"""Serializer method for getting last published review""" # """Get in_favorites status flag"""
return ReviewSerializer(obj.reviews.by_status(status=review_models.Review.READY) # user = self.context.get('request').user
.order_by('-published_at').first()).data # if user.is_authenticated:
# return obj.id in user.favorites.by_content_type(app_label='establishment',
def get_in_favorites(self, obj): # model='establishment')\
"""Get in_favorites status flag""" # .values_list('object_id', flat=True)
user = self.context.get('request').user # else:
if user.is_authenticated: # return False
return obj.id in user.favorites.by_content_type(app_label='establishment',
model='establishment')\
.values_list('object_id', flat=True)
else:
return False
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):

View File

@ -5,7 +5,6 @@ from rest_framework import status
from http.cookies import SimpleCookie 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.

View File

@ -8,6 +8,8 @@ app_name = 'establishment'
urlpatterns = [ urlpatterns = [
path('', views.EstablishmentListView.as_view(), name='list'), path('', views.EstablishmentListView.as_view(), name='list'),
path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), path('tags/', views.EstablishmentTagListView.as_view(), name='tags'),
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
name='recent-reviews'),
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'), path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),

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):
"""Overrided method 'get_queryset'."""
return models.Establishment.objects.published() \
.prefetch_actual_employees()

View File

@ -1,5 +1,5 @@
"""Establishment app views.""" """Establishment app views."""
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
from rest_framework import generics, permissions from rest_framework import generics, permissions
@ -7,36 +7,76 @@ 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.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
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.EstablishmentListSerializer
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):
"""List view for last reviewed establishments."""
pagination_class = EstablishmentPortionPagination
def get_queryset(self):
"""Overridden method 'get_queryset'."""
qs = super().get_queryset()
user_ip = methods.get_user_ip(self.request)
query_params = self.request.query_params
if 'longitude' in query_params and 'latitude' in query_params:
longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
else:
longitude, latitude = methods.determine_coordinates(user_ip)
if not longitude or not latitude:
return qs.none()
point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
return qs.last_reviewed(point=point)
class EstablishmentSimilarListView(EstablishmentListView): class EstablishmentSimilarListView(EstablishmentListView):
"""Resource for getting a list of establishments.""" """Resource for getting a list of establishments."""
serializer_class = serializers.EstablishmentListSerializer serializer_class = serializers.EstablishmentListSerializer
pagination_class = EstablishmentPortionPagination
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Override get_queryset method"""
return super().get_queryset().similar(establishment_slug=self.kwargs.get('slug')) qs = super().get_queryset()
return qs.similar(establishment_slug=self.kwargs.get('slug'))
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentDetailSerializer
class EstablishmentTypeListView(generics.ListAPIView): class EstablishmentTypeListView(generics.ListAPIView):

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())
@ -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

@ -1,9 +1,10 @@
"""Main app methods.""" """Main app methods."""
import logging import logging
from django.conf import settings from django.conf import settings
from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception
from main import models
from main import models
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -38,6 +39,19 @@ def determine_country_code(ip_addr):
return country_code return country_code
def determine_coordinates(ip_addr):
longitude, latitude = None, None
if ip_addr:
try:
geoip = GeoIP2()
longitude, latitude = geoip.coords(ip_addr)
except GeoIP2Exception as ex:
logger.info(f'GEOIP Exception: {ex}. ip: {ip_addr}')
except Exception as ex:
logger.error(f'GEOIP Base exception: {ex}')
return longitude, latitude
def determine_user_site_url(country_code): def determine_user_site_url(country_code):
"""Determine user's site url.""" """Determine user's site url."""
try: try:

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,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

@ -39,7 +39,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 +49,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,11 +91,11 @@ 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, state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('Publish status')) 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,
@ -78,6 +106,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 +126,10 @@ 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})

View File

@ -28,8 +28,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 +50,8 @@ class NewsDetailSerializer(NewsBaseSerializer):
description_translated = TranslatedField() description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True) country = CountrySimpleSerializer(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,6 +62,8 @@ class NewsDetailSerializer(NewsBaseSerializer):
'end', 'end',
'playlist', 'playlist',
'is_publish', 'is_publish',
'state',
'state_display',
'author', 'author',
'country', 'country',
) )
@ -86,10 +88,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 +102,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
'description', 'description',
'news_type_id', 'news_type_id',
'country_id', 'country_id',
'template',
'template_display',
) )

View File

@ -23,6 +23,10 @@ class ReviewQuerySet(models.QuerySet):
"""Filter by status""" """Filter by status"""
return self.filter(status=status) return self.filter(status=status)
def published(self):
"""Return published reviews"""
return self.filter(status=Review.READY)
class Review(BaseAttributes, TranslatedFieldsMixin): class Review(BaseAttributes, TranslatedFieldsMixin):
"""Review model""" """Review model"""

View File

@ -46,6 +46,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
description_translated = serializers.SerializerMethodField(allow_null=True) description_translated = serializers.SerializerMethodField(allow_null=True)
preview_image = serializers.URLField(source='preview_image_url')
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -53,7 +54,6 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
fields = ( fields = (
'id', 'id',
'name', 'name',
'description',
'public_mark', 'public_mark',
'toque_number', 'toque_number',
'price_level', 'price_level',
@ -63,10 +63,34 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
'collections', 'collections',
'establishment_type', 'establishment_type',
'establishment_subtypes', 'establishment_subtypes',
'preview_image_url', 'preview_image',
'slug', 'slug',
) )
@staticmethod @staticmethod
def get_description_translated(obj): def get_description_translated(obj):
return get_translated_value(obj.description) return get_translated_value(obj.description)
def to_representation(self, instance):
ret = super().to_representation(instance)
dict_merge = lambda a, b: a.update(b) or a
ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}),
ret['tags'])
ret['establishment_subtypes'] = map(
lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}),
ret['establishment_subtypes'])
if ret.get('establishment_type'):
ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name'))
if ret.get('address'):
ret['address']['city']['country']['name_translated'] = get_translated_value(
ret['address']['city']['country'].pop('name'))
location = ret['address'].pop('location')
if location:
ret['address']['geo_lon'] = location['lon']
ret['address']['geo_lat'] = location['lat']
ret['type'] = ret.pop('establishment_type')
ret['subtypes'] = ret.pop('establishment_subtypes')
return ret

View File

@ -6,5 +6,6 @@ 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')
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -17,4 +17,6 @@ def get_translated_value(value):
return None return None
elif not isinstance(value, dict): elif not isinstance(value, dict):
field_dict = value.to_dict() field_dict = value.to_dict()
elif isinstance(value, dict):
field_dict = value
return field_dict.get(get_current_language()) return field_dict.get(get_current_language())

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)
@ -31,14 +32,3 @@ def parse_cookies(get_response):
response = get_response(request) response = get_response(request)
return response return response
return middleware return middleware
class CORSMiddleware:
"""Added parameter {Access-Control-Allow-Origin: *} to response"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response["Access-Control-Allow-Origin"] = '*'
return response

View File

@ -10,6 +10,8 @@ from django.utils.translation import ugettext_lazy as _, get_language
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from utils.methods import image_path, svg_image_path from utils.methods import image_path, svg_image_path
from utils.validators import svg_image_validator from utils.validators import svg_image_validator
from django.db.models.fields import Field
from django.core import exceptions
class ProjectBaseMixin(models.Model): class ProjectBaseMixin(models.Model):
@ -26,6 +28,10 @@ class ProjectBaseMixin(models.Model):
abstract = True abstract = True
def valid(value):
print("Run")
class TJSONField(JSONField): class TJSONField(JSONField):
"""Overrided JsonField.""" """Overrided JsonField."""
@ -52,6 +58,7 @@ def translate_field(self, field_name):
if isinstance(field, dict): if isinstance(field, dict):
return field.get(to_locale(get_language())) return field.get(to_locale(get_language()))
return None return None
return translate return translate
@ -70,6 +77,7 @@ def index_field(self, field_name):
for key, value in field.items(): for key, value in field.items():
setattr(obj, key, value) setattr(obj, key, value)
return obj return obj
return index return index
@ -236,7 +244,8 @@ class LocaleManagerMixin(models.Manager):
queryset = self.filter(**filters) queryset = self.filter(**filters)
# Prepare field for annotator # Prepare field for annotator
localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in fields} localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in
fields}
# Annotate them # Annotate them
for _ in fields: for _ in fields:
@ -245,7 +254,6 @@ class LocaleManagerMixin(models.Manager):
class GMTokenGenerator(PasswordResetTokenGenerator): class GMTokenGenerator(PasswordResetTokenGenerator):
CHANGE_EMAIL = 0 CHANGE_EMAIL = 0
RESET_PASSWORD = 1 RESET_PASSWORD = 1
CHANGE_PASSWORD = 2 CHANGE_PASSWORD = 2
@ -268,10 +276,10 @@ class GMTokenGenerator(PasswordResetTokenGenerator):
""" """
fields = [str(timestamp), str(user.is_active), str(user.pk)] fields = [str(timestamp), str(user.is_active), str(user.pk)]
if self.purpose == self.CHANGE_EMAIL or \ if self.purpose == self.CHANGE_EMAIL or \
self.purpose == self.CONFIRM_EMAIL: self.purpose == self.CONFIRM_EMAIL:
fields.extend([str(user.email_confirmed), str(user.email)]) fields.extend([str(user.email_confirmed), str(user.email)])
elif self.purpose == self.RESET_PASSWORD or \ elif self.purpose == self.RESET_PASSWORD or \
self.purpose == self.CHANGE_PASSWORD: self.purpose == self.CHANGE_PASSWORD:
fields.append(str(user.password)) fields.append(str(user.password))
return fields return fields

View File

@ -1,6 +1,8 @@
"""Pagination settings.""" """Pagination settings."""
from base64 import b64encode from base64 import b64encode
from urllib import parse as urlparse from urllib import parse as urlparse
from django.conf import settings
from rest_framework.pagination import PageNumberPagination, CursorPagination from rest_framework.pagination import PageNumberPagination, CursorPagination
@ -44,3 +46,10 @@ class ProjectMobilePagination(ProjectPageNumberPagination):
if not self.page.has_previous(): if not self.page.has_previous():
return None return None
return self.page.previous_page_number() return self.page.previous_page_number()
class EstablishmentPortionPagination(ProjectMobilePagination):
"""
Pagination for app establishments with limit page size equal to 12
"""
page_size = settings.LIMITING_OUTPUT_OBJECTS

View File

@ -1,6 +1,8 @@
"""Utils app serializer.""" """Utils app serializer."""
from rest_framework import serializers from rest_framework import serializers
from utils.models import PlatformMixin from utils.models import PlatformMixin
from django.core import exceptions
from translation.models import Language
class EmptySerializer(serializers.Serializer): class EmptySerializer(serializers.Serializer):
@ -21,3 +23,26 @@ class TranslatedField(serializers.CharField):
**kwargs): **kwargs):
super().__init__(allow_null=allow_null, required=required, super().__init__(allow_null=allow_null, required=required,
read_only=read_only, **kwargs) read_only=read_only, **kwargs)
def validate_tjson(value):
if not isinstance(value, dict):
raise exceptions.ValidationError(
'invalid_json',
code='invalid_json',
params={'value': value},
)
lang_count = Language.objects.filter(locale__in=value.keys()).count()
if lang_count == 0:
raise exceptions.ValidationError(
'invalid_translated_keys',
code='invalid_translated_keys',
params={'value': value},
)
class TJSONSerializer(serializers.JSONField):
validators = [validate_tjson]

View File

@ -1,5 +1,5 @@
import pytz import pytz
from datetime import datetime from datetime import datetime, timedelta
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from rest_framework import status from rest_framework import status
@ -8,6 +8,11 @@ from http.cookies import SimpleCookie
from account.models import User from account.models import User
from news.models import News, NewsType from news.models import News, NewsType
from django.test import TestCase
from translation.models import Language
from django.core import exceptions
from .serializers import validate_tjson
from establishment.models import Establishment, EstablishmentType, Employee from establishment.models import Establishment, EstablishmentType, Employee
@ -47,17 +52,18 @@ class TranslateFieldTests(BaseTestCase):
}, },
description={"en-GB": "Test description"}, description={"en-GB": "Test description"},
playlist=1, playlist=1,
start=datetime.now(pytz.utc), start=datetime.now(pytz.utc) + timedelta(hours=-13),
end=datetime.now(pytz.utc), end=datetime.now(pytz.utc) + timedelta(hours=13),
is_publish=True, is_publish=True,
news_type=self.news_type news_type=self.news_type,
slug='test',
) )
def test_model_field(self): def test_model_field(self):
self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) self.assertIsNotNone(getattr(self.news_item, "title_translated", None))
def test_read_locale(self): def test_read_locale(self):
response = self.client.get(f"/api/web/news/{self.news_item.id}/", format='json') response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
news_data = response.json() news_data = response.json()
@ -118,3 +124,36 @@ class BaseAttributeTests(BaseTestCase):
employee.refresh_from_db() employee.refresh_from_db()
self.assertEqual(modify_user, employee.modified_by) self.assertEqual(modify_user, employee.modified_by)
self.assertEqual(self.user, employee.created_by) self.assertEqual(self.user, employee.created_by)
class ValidJSONTest(TestCase):
def test_valid_json(self):
lang = Language.objects.create(title='English', locale='en-GB')
lang.save()
data = 'str'
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_json')
data = {
"string": "value"
}
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_translated_keys')
data = {
"en-GB": "English"
}
try:
validate_tjson(data)
self.assertTrue(True)
except exceptions.ValidationError:
self.assert_(False, "Test json translated FAILED")

View File

@ -327,15 +327,30 @@ FCM_DJANGO_SETTINGS = {
# Thumbnail settings # Thumbnail settings
THUMBNAIL_ALIASES = { THUMBNAIL_ALIASES = {
'': { 'news_preview': {
'tiny': {'size': (100, 0), }, 'web': {'size': (300, 260), }
'small': {'size': (480, 0), }, },
'middle': {'size': (700, 0), }, 'news_promo_horizontal': {
'large': {'size': (1500, 0), }, 'web': {'size': (1900, 600), },
'default': {'size': (300, 200), 'crop': True}, 'mobile': {'size': (375, 260), },
'gallery': {'size': (240, 160), 'crop': True}, },
'establishment_preview': {'size': (300, 280), 'crop': True}, 'news_tile_horizontal': {
} 'web': {'size': (300, 275), },
'mobile': {'size': (343, 180), },
},
'news_tile_vertical': {
'web': {'size': (300, 380), },
},
'news_highlight_vertical': {
'web': {'size': (460, 630), },
},
'news_editor': {
'web': {'size': (940, 430), }, # при загрузке через контент эдитор
'mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe
},
'avatar_comments': {
'web': {'size': (116, 116), },
},
} }
# Password reset # Password reset
@ -412,3 +427,14 @@ SOLO_CACHE_TIMEOUT = 300
SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/'
SITE_NAME = 'Gault & Millau' SITE_NAME = 'Gault & Millau'
# Used in annotations for establishments.
DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10
# Limit output objects (see in pagination classes).
LIMITING_OUTPUT_OBJECTS = 12
# Need to restrict objects to sort (3 times more then expected).
LIMITING_QUERY_NUMBER = LIMITING_OUTPUT_OBJECTS * 3
# GEO
# A Spatial Reference System Identifier
GEO_DEFAULT_SRID = 4326