Merge branch 'develop' into feature/gm-148

# Conflicts:
#	apps/news/models.py
This commit is contained in:
Anatoly 2019-09-30 15:43:45 +03:00
commit aeebdd02c6
31 changed files with 467 additions and 110 deletions

View File

@ -1,5 +1,6 @@
from rest_framework.test import APITestCase
from account.models import User
from django.urls import reverse
# Create your tests here.
@ -22,7 +23,6 @@ def get_tokens_for_user(
class AuthorizationTests(APITestCase):
def setUp(self):
print("Auth!")
data = get_tokens_for_user()
self.username = data["username"]
self.password = data["password"]
@ -33,7 +33,7 @@ class AuthorizationTests(APITestCase):
'password': self.password,
'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['refresh_token'], self.tokens.get('refresh_token'))

View File

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

View File

@ -8,6 +8,7 @@ from http.cookies import SimpleCookie
from collection.models import Collection, Guide
from location.models import Country
from establishment.models import Establishment, EstablishmentType
# Create your tests here.
@ -81,3 +82,27 @@ class CollectionGuideDetailTests(CollectionDetailTests):
def test_guide_detail_Read(self):
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionWebHomeTests(CollectionDetailTests):
def setUp(self):
super().setUp()
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
for i in range(1, 5):
setattr(self, f"establishment{i}",
Establishment.objects.create(
name=f"Test establishment {i}",
establishment_type_id=self.establishment_type.id,
is_publish=True,
slug=f"test-establishment-{i}"
)
)
getattr(self, f"establishment{i}").collections.add(self.collection)
def test_collection_list_filter(self):
response = self.client.get('/api/web/collections/?country_code=en', format='json')
data = response.json()
self.assertIn('count', data)
self.assertGreater(data['count'], 0)

View File

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

View File

@ -30,10 +30,27 @@ class CollectionListView(CollectionViewMixin, generics.ListAPIView):
def get_queryset(self):
"""Override get_queryset method"""
return models.Collection.objects.published()\
queryset = models.Collection.objects.published()\
.by_country_code(code=self.request.country_code)\
.order_by('-on_top', '-created')
return queryset
class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView):
"""List Collection view"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.CollectionSerializer
def get_queryset(self):
"""Override get_queryset method"""
queryset = models.Collection.objects.published()\
.by_country_code(code=self.request.country_code)\
.filter_all_related_gt(3)\
.order_by('-on_top', '-modified')
return queryset
class CollectionEstablishmentListView(CollectionListView):
"""Retrieve list of establishment for collection."""

View File

@ -14,9 +14,10 @@ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from collection.models import Collection
from main.models import MetaDataContent
from location.models import Address
from review.models import Review
from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, URLImageMixin,
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
TranslatedFieldsMixin, BaseAttributes)
@ -72,6 +73,20 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
class EstablishmentQuerySet(models.QuerySet):
"""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):
"""Search text in JSON fields."""
if locale is not None:
@ -298,12 +313,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
return country.low_price, country.high_price
# todo: make via prefetch
@property
def subtypes(self):
return EstablishmentSubType.objects.filter(
subtype_establishment=self,
establishment_type=self.establishment_type,
establishment_type__use_subtypes=True)
# @property
# def subtypes(self):
# return EstablishmentSubType.objects.filter(
# subtype_establishment=self,
# establishment_type=self.establishment_type,
# establishment_type__use_subtypes=True)
def set_establishment_type(self, establishment_type):
self.establishment_type = establishment_type

View File

@ -1,12 +1,9 @@
import json
from rest_framework import serializers
from establishment import models
from timetable.models import Timetable
from establishment.serializers import (
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer
)
ContactPhonesSerializer, SocialNetworkRelatedSerializers,
EstablishmentTypeSerializer)
from utils.decorators import with_base_attributes
@ -25,6 +22,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
emails = ContactEmailsSerializer(read_only=True, many=True, )
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50)
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
class Meta:
model = models.Establishment
@ -56,6 +54,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
phones = ContactPhonesSerializer(read_only=False, many=True, )
emails = ContactEmailsSerializer(read_only=False, many=True, )
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
type = EstablishmentTypeSerializer(source='establishment_type')
class Meta:
model = models.Establishment

View File

@ -1,19 +1,20 @@
"""Establishment serializers."""
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from comment import models as comment_models
from comment.serializers import common as comment_serializers
from establishment import models
from favorites.models import Favorites
from location.serializers import AddressSerializer
from location.serializers import AddressSimpleSerializer, AddressSerializer
from main.models import MetaDataContent
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
from review import models as review_models
from timetable.serialziers import ScheduleRUDSerializer
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):
"""Contact phone serializer"""
class Meta:
@ -142,11 +143,11 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
class EstablishmentBaseSerializer(serializers.ModelSerializer):
"""Base serializer for Establishment model."""
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
subtypes = EstablishmentSubTypeSerializer(many=True)
preview_image = serializers.URLField(source='preview_image_url')
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
address = AddressSerializer()
tags = MetaDataContentSerializer(many=True)
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
class Meta:
"""Meta class."""
@ -159,60 +160,60 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
'price_level',
'toque_number',
'public_mark',
'type',
'subtypes',
'slug',
'preview_image',
'in_favorites',
'address',
'tags',
'slug',
]
class EstablishmentListSerializer(EstablishmentBaseSerializer):
"""Serializer for Establishment model."""
# Annotated fields
in_favorites = serializers.BooleanField(allow_null=True)
preview_image = serializers.URLField(source='preview_image_url')
class Meta:
class Meta(EstablishmentBaseSerializer.Meta):
"""Meta class."""
model = models.Establishment
fields = EstablishmentBaseSerializer.Meta.fields + [
'in_favorites',
'preview_image',
]
class EstablishmentAllListSerializer(EstablishmentListSerializer):
""" Serailizer for api/*/establishments """
address = AddressSimpleSerializer()
class Meta(EstablishmentListSerializer.Meta):
pass
class EstablishmentDetailSerializer(EstablishmentListSerializer):
"""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)
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True, )
emails = ContactEmailsSerializer(read_only=True, many=True, )
phones = ContactPhonesSerializer(read_only=True, many=True)
emails = ContactEmailsSerializer(read_only=True, many=True)
review = ReviewSerializer(source='last_published_review', allow_null=True)
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=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_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50)
in_favorites = serializers.BooleanField()
image = serializers.URLField(source='image_url')
class Meta:
class Meta(EstablishmentListSerializer.Meta):
"""Meta class."""
model = models.Establishment
fields = EstablishmentListSerializer.Meta.fields + [
'description_translated',
'price_level',
'image',
'subtypes',
'type',
'awards',
'schedule',
'website',
@ -228,9 +229,18 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer):
'best_price_menu',
'best_price_carte',
'transportation',
'slug',
]
# def get_in_favorites(self, obj):
# """Get in_favorites status flag"""
# user = self.context.get('request').user
# if user.is_authenticated:
# 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):
"""Create comment serializer"""

View File

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

View File

@ -4,10 +4,17 @@ from rest_framework import generics
from establishment import models
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."""
queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentListCreateSerializer

View File

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

View File

@ -1,5 +1,4 @@
"""Establishment app views."""
from django.conf import settings
from django.contrib.gis.geos import Point
from django.shortcuts import get_object_or_404
@ -8,27 +7,50 @@ from rest_framework import generics, permissions
from comment import models as comment_models
from establishment import filters
from establishment import models, serializers
from establishment.views import EstablishmentMixin
from main import methods
from main.models import MetaDataContent
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."""
serializer_class = serializers.EstablishmentListSerializer
filter_class = filters.EstablishmentFilter
serializer_class = serializers.EstablishmentAllListSerializer
def get_queryset(self):
"""Overridden method 'get_queryset'."""
qs = super(EstablishmentListView, self).get_queryset()
return qs.by_country_code(code=self.request.country_code) \
.annotate_in_favorites(user=self.request.user)
if self.request.country_code:
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):
@ -57,17 +79,6 @@ class EstablishmentSimilarListView(EstablishmentListView):
return qs.similar(establishment_slug=self.kwargs.get('slug'))
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentDetailSerializer
def get_queryset(self):
"""Override 'get_queryset' method."""
return super(EstablishmentRetrieveView, self).get_queryset() \
.annotate_in_favorites(self.request.user)
class EstablishmentTypeListView(generics.ListAPIView):
"""Resource for getting a list of establishment types."""

View File

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

View File

@ -1,7 +1,6 @@
"""Location app common serializers."""
from django.contrib.gis.geos import Point
from rest_framework import serializers
from location import models
from utils.serializers import TranslatedField
@ -86,6 +85,7 @@ class CitySerializer(serializers.ModelSerializer):
class AddressSerializer(serializers.ModelSerializer):
"""Address serializer."""
city_id = serializers.PrimaryKeyRelatedField(
source='city',
queryset=models.City.objects.all())
@ -104,7 +104,7 @@ class AddressSerializer(serializers.ModelSerializer):
'number',
'postal_code',
'geo_lon',
'geo_lat'
'geo_lat',
]
def validate(self, attrs):
@ -128,3 +128,18 @@ class AddressSerializer(serializers.ModelSerializer):
setattr(instance, 'geo_lon', float(0))
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
from random import sample as random_sample
class NewsType(models.Model):
@ -40,7 +41,7 @@ class NewsQuerySet(models.QuerySet):
now = timezone.now()
return self.filter(models.Q(models.Q(end__gte=now) |
models.Q(end__isnull=True)),
is_publish=True, start__lte=now)
state__in=self.model.PUBLISHED_STATES, start__lte=now)
def with_related(self):
"""Return qs with related objects."""
@ -50,6 +51,34 @@ class NewsQuerySet(models.QuerySet):
class News(BaseAttributes, TranslatedFieldsMixin):
"""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,
verbose_name=_('news type'))
title = TJSONField(blank=True, null=True, default=None,
@ -64,17 +93,23 @@ class News(BaseAttributes, TranslatedFieldsMixin):
start = models.DateTimeField(verbose_name=_('Start'))
end = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('End'))
slug = models.SlugField(unique=True, max_length=50, null=True,
verbose_name=_('News slug'), editable=True,)
slug = models.SlugField(unique=True, max_length=50,
verbose_name=_('News slug'))
playlist = models.IntegerField(_('playlist'))
is_publish = models.BooleanField(default=False,
verbose_name=_('Publish status'))
# author = models.CharField(max_length=255, blank=True, null=True,
# default=None,verbose_name=_('Author'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('State'))
author = models.CharField(max_length=255, blank=True, null=True,
default=None,verbose_name=_('Author'))
is_highlighted = models.BooleanField(default=False,
verbose_name=_('Is highlighted'))
# TODO: metadata_keys - описание ключей для динамического построения полей метаданных
# TODO: metadata_values - Описание значений для динамических полей из MetadataKeys
template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER)
address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'),
on_delete=models.SET_NULL)
@ -96,6 +131,28 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def __str__(self):
return f'news: {self.slug}'
@property
def is_publish(self):
return self.state in self.PUBLISHED_STATES
@property
def list_also_like_news(self):
# without "distinct" method the doubles are arising
like_news = News.objects.published().filter(news_type=self.news_type, tags__in=models.F("tags"))\
.exclude(id=self.id).distinct()
news_count = like_news.count()
if news_count >= 6:
random_ids = random_sample(range(news_count), 6)
else:
random_ids = random_sample(range(news_count), news_count)
news_list = [{"id": like_news[r].id, "slug": like_news[r].slug} for r in random_ids]
return news_list
@property
def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug})

View File

@ -9,7 +9,7 @@ from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer
from news import models
from utils.serializers import TranslatedField
from account.serializers.common import UserSerializer
class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer."""
@ -33,8 +33,6 @@ class NewsBaseSerializer(serializers.ModelSerializer):
tags = MetaDataContentSerializer(read_only=True, many=True)
gallery = ImageSerializer(read_only=True, many=True)
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
class Meta:
"""Meta class."""
@ -56,6 +54,9 @@ class NewsDetailSerializer(NewsBaseSerializer):
description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True)
author = UserSerializer(source='created_by')
state_display = serializers.CharField(source='get_state_display',
read_only=True)
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
@ -66,8 +67,11 @@ class NewsDetailSerializer(NewsBaseSerializer):
'end',
'playlist',
'is_publish',
'state',
'state_display',
'author',
'country',
'list_also_like_news',
)
@ -90,10 +94,11 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
news_type_id = serializers.PrimaryKeyRelatedField(
source='news_type', write_only=True,
queryset=models.NewsType.objects.all())
country_id = serializers.PrimaryKeyRelatedField(
source='country', write_only=True,
queryset=location_models.Country.objects.all())
template_display = serializers.CharField(source='get_template_display',
read_only=True)
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
"""Meta class."""
@ -103,6 +108,8 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
'description',
'news_type_id',
'country_id',
'template',
'template_display',
)

View File

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

View File

@ -46,6 +46,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
description_translated = serializers.SerializerMethodField(allow_null=True)
preview_image = serializers.URLField(source='preview_image_url')
class Meta:
"""Meta class."""
@ -53,7 +54,6 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
fields = (
'id',
'name',
'description',
'public_mark',
'toque_number',
'price_level',
@ -63,10 +63,34 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
'collections',
'establishment_type',
'establishment_subtypes',
'preview_image_url',
'preview_image',
'slug',
)
@staticmethod
def get_description_translated(obj):
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,8 @@ from search_indexes import views
router = routers.SimpleRouter()
# router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile')
router.register(r'news', views.NewsDocumentViewSet, basename='news')
urlpatterns = router.urls

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import pytz
from datetime import datetime
from datetime import datetime, timedelta
from rest_framework.test import APITestCase
from rest_framework import status
@ -52,17 +52,18 @@ class TranslateFieldTests(BaseTestCase):
},
description={"en-GB": "Test description"},
playlist=1,
start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc),
is_publish=True,
news_type=self.news_type
start=datetime.now(pytz.utc) + timedelta(hours=-13),
end=datetime.now(pytz.utc) + timedelta(hours=13),
news_type=self.news_type,
slug='test',
state=News.PUBLISHED,
)
def test_model_field(self):
self.assertIsNotNone(getattr(self.news_item, "title_translated", None))
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)
news_data = response.json()
@ -94,6 +95,7 @@ class BaseAttributeTests(BaseTestCase):
response_data = response.json()
self.assertIn("id", response_data)
self.assertIsInstance(response_data['id'], int)
employee = Employee.objects.get(id=response_data['id'])
@ -117,7 +119,7 @@ class BaseAttributeTests(BaseTestCase):
'name': 'Test new name'
}
response = self.client.patch('/api/back/establishments/employees/1/', data=update_data)
response = self.client.patch(f'/api/back/establishments/employees/{employee.pk}/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
employee.refresh_from_db()