Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop
This commit is contained in:
commit
54c392762c
|
|
@ -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'))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
|
|
||||||
|
|
@ -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):
|
|
||||||
"""Overrided method 'get_queryset'."""
|
|
||||||
return models.Establishment.objects.published() \
|
|
||||||
.prefetch_actual_employees()
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
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),
|
||||||
|
),
|
||||||
|
]
|
||||||
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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})
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user