Merge branch 'develop' into feature/gm-148

# Conflicts:
#	apps/news/models.py
#	apps/news/serializers.py
This commit is contained in:
Anatoly 2019-10-02 15:46:35 +03:00
commit ae4d6c3ccd
8 changed files with 126 additions and 187 deletions

View File

@ -77,6 +77,23 @@ class UserSerializer(serializers.ModelSerializer):
return instance return instance
class UserBaseSerializer(serializers.ModelSerializer):
"""Serializer is used to display brief information about the user."""
fullname = serializers.CharField(source='get_full_name', read_only=True)
class Meta:
"""Meta class."""
model = models.User
fields = (
'fullname',
'cropped_image_url',
'image_url',
)
read_only_fields = fields
class ChangePasswordSerializer(serializers.ModelSerializer): class ChangePasswordSerializer(serializers.ModelSerializer):
"""Serializer for model User.""" """Serializer for model User."""

View File

@ -4,11 +4,24 @@ from collection import models
from location import models as location_models from location import models as location_models
class CollectionSerializer(serializers.ModelSerializer): class CollectionBaseSerializer(serializers.ModelSerializer):
"""Collection serializer""" """Collection base serializer"""
# RESPONSE # RESPONSE
description_translated = serializers.CharField(read_only=True, allow_null=True) description_translated = serializers.CharField(read_only=True, allow_null=True)
class Meta:
model = models.Collection
fields = [
'id',
'name',
'description_translated',
'image_url',
'slug',
]
class CollectionSerializer(CollectionBaseSerializer):
"""Collection serializer"""
# COMMON # COMMON
block_size = serializers.JSONField() block_size = serializers.JSONField()
is_publish = serializers.BooleanField() is_publish = serializers.BooleanField()
@ -24,18 +37,13 @@ class CollectionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.Collection model = models.Collection
fields = [ fields = CollectionBaseSerializer.Meta.fields + [
'id',
'name',
'description_translated',
'start', 'start',
'end', 'end',
'image_url',
'is_publish', 'is_publish',
'on_top', 'on_top',
'country', 'country',
'block_size', 'block_size',
'slug',
] ]

View File

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

View File

@ -12,7 +12,14 @@ from collection.serializers import common as serializers
class CollectionViewMixin(generics.GenericAPIView): class CollectionViewMixin(generics.GenericAPIView):
"""Mixin for Collection view""" """Mixin for Collection view"""
model = models.Collection model = models.Collection
queryset = models.Collection.objects.all() permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""Override get_queryset method."""
return models.Collection.objects.published() \
.by_country_code(code=self.request.country_code) \
.filter_all_related_gt(3) \
.order_by('-on_top', '-modified')
class GuideViewMixin(generics.GenericAPIView): class GuideViewMixin(generics.GenericAPIView):
@ -23,41 +30,22 @@ class GuideViewMixin(generics.GenericAPIView):
# Views # Views
# Collections # Collections
class CollectionListView(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)\
.order_by('-on_top', '-created')
return queryset
class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView): class CollectionHomePageView(CollectionViewMixin, generics.ListAPIView):
"""List Collection view""" """List Collection view"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.CollectionSerializer 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 CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView):
"""Retrieve detail of Collection instance."""
lookup_field = 'slug'
serializer_class = serializers.CollectionBaseSerializer
class CollectionEstablishmentListView(CollectionListView): class CollectionEstablishmentListView(CollectionHomePageView):
"""Retrieve list of establishment for collection.""" """Retrieve list of establishment for collection."""
permission_classes = (permissions.AllowAny,) lookup_field = 'slug'
pagination_class = ProjectPageNumberPagination pagination_class = ProjectPageNumberPagination
serializer_class = EstablishmentBaseSerializer serializer_class = EstablishmentBaseSerializer
lookup_field = 'slug'
def get_queryset(self): def get_queryset(self):
""" """

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.4 on 2019-10-02 09:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0019_news_author'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='author',
),
]

View File

@ -1,11 +1,9 @@
"""News app models.""" """News app models."""
from django.contrib.contenttypes import fields as generic
from django.db import models from django.db import models
from django.contrib.contenttypes import fields as generic
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 rest_framework.reverse import reverse from rest_framework.reverse import reverse
from sorl.thumbnail import delete
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
from random import sample as random_sample from random import sample as random_sample
@ -29,10 +27,21 @@ class NewsType(models.Model):
class NewsQuerySet(models.QuerySet): class NewsQuerySet(models.QuerySet):
"""QuerySet for model News""" """QuerySet for model News"""
def with_base_related(self):
"""Return qs with related objects."""
return self.select_related('news_type', 'country').prefetch_related('tags')
def with_extended_related(self):
"""Return qs with related objects."""
return self.select_related('created_by')
def by_type(self, news_type): def by_type(self, news_type):
"""Filter News by type""" """Filter News by type"""
return self.filter(news_type__name=news_type) return self.filter(news_type__name=news_type)
def by_tags(self, tags):
return self.filter(tags__in=tags)
def by_country_code(self, code): def by_country_code(self, code):
"""Filter collection by country code.""" """Filter collection by country code."""
return self.filter(country__code=code) return self.filter(country__code=code)
@ -44,9 +53,16 @@ class NewsQuerySet(models.QuerySet):
models.Q(end__isnull=True)), models.Q(end__isnull=True)),
state__in=self.model.PUBLISHED_STATES, start__lte=now) state__in=self.model.PUBLISHED_STATES, start__lte=now)
def with_related(self): # todo: filter by best score
"""Return qs with related objects.""" # todo: filter by country?
return self.select_related('news_type', 'country').prefetch_related('tags') def should_read(self, news):
return self.model.objects.exclude(pk=news.pk).published().\
with_base_related().by_type(news.news_type).distinct().order_by('?')
def same_theme(self, news):
return self.model.objects.exclude(pk=news.pk).published().\
with_base_related().by_type(news.news_type).\
by_tags(news.tags.all()).distinct().order_by('-start')
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
@ -97,19 +113,16 @@ class News(BaseAttributes, TranslatedFieldsMixin):
slug = models.SlugField(unique=True, max_length=50, slug = models.SlugField(unique=True, max_length=50,
verbose_name=_('News slug')) verbose_name=_('News slug'))
playlist = models.IntegerField(_('playlist')) playlist = models.IntegerField(_('playlist'))
# author = models.CharField(max_length=255, blank=True, null=True,
# default=None,verbose_name=_('Author'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('State')) verbose_name=_('State'))
author = models.CharField(max_length=255, blank=True, null=True,
default=None,verbose_name=_('Author'))
is_highlighted = models.BooleanField(default=False, is_highlighted = models.BooleanField(default=False,
verbose_name=_('Is highlighted')) verbose_name=_('Is highlighted'))
# TODO: metadata_keys - описание ключей для динамического построения полей метаданных # TODO: metadata_keys - описание ключей для динамического построения полей метаданных
# TODO: metadata_values - Описание значений для динамических полей из MetadataKeys # TODO: metadata_values - Описание значений для динамических полей из MetadataKeys
image_url = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Image URL path'))
preview_image_url = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Preview image URL path'))
template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) 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'),
@ -119,8 +132,6 @@ class News(BaseAttributes, TranslatedFieldsMixin):
verbose_name=_('country')) verbose_name=_('country'))
tags = generic.GenericRelation(to='main.MetaDataContent') tags = generic.GenericRelation(to='main.MetaDataContent')
gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
objects = NewsQuerySet.as_manager() objects = NewsQuerySet.as_manager()
class Meta: class Meta:
@ -136,51 +147,15 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def is_publish(self): def is_publish(self):
return self.state in self.PUBLISHED_STATES 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 @property
def web_url(self): def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug}) return reverse('web:news:rud', kwargs={'slug': self.slug})
@property @property
def original_images(self): def should_read(self):
return self.gallery.originals() return self.__class__.objects.should_read(self)[:3]
@property
def same_theme(self):
return self.__class__.objects.same_theme(self)[:3]
class NewsGalleryQuerySet(models.QuerySet):
"""QuerySet for model News"""
class NewsGallery(models.Model):
news = models.ForeignKey(News, null=True,
related_name='news_gallery',
on_delete=models.SET_NULL,
verbose_name=_('news'))
image = models.ForeignKey('gallery.Image', null=True,
related_name='news_gallery',
on_delete=models.SET_NULL,
verbose_name=_('gallery'))
objects = NewsGalleryQuerySet.as_manager()
class Meta:
"""NewsGallery meta class."""
verbose_name = _('news gallery')
verbose_name_plural = _('news galleries')

View File

@ -1,10 +1,6 @@
"""News app common serializers.""" """News app common serializers."""
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from account.serializers.common import UserBaseSerializer
from account.serializers.common import UserSerializer
from gallery.models import Image
from gallery.serializers import ImageSerializer
from location import models as location_models from location import models as location_models
from location.serializers import CountrySimpleSerializer from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer from main.serializers import MetaDataContentSerializer
@ -12,53 +8,6 @@ from news import models
from utils.serializers import TranslatedField, ProjectModelSerializer from utils.serializers import TranslatedField, ProjectModelSerializer
class NewsCropImageSerializer(ImageSerializer):
"""Serializer for returning crop images of news image."""
orientation_display = serializers.CharField(source='get_orientation_display',
read_only=True)
web_url = serializers.SerializerMethodField()
mobile_url = serializers.SerializerMethodField()
class Meta:
model = Image
fields = [
'id',
'title',
'orientation_display',
'web_url',
'mobile_url',
]
extra_kwargs = {
'orientation': {'write_only': True}
}
def get_web_url(self, obj):
"""Return URL of cropped image by thumbnail."""
return obj.get_image_url('news_promo_horizontal_web')
def get_mobile_url(self, obj):
"""Return URL of cropped image by thumbnail."""
return obj.get_image_url('news_promo_horizontal_mobile')
class NewsImageSerializer(ImageSerializer):
"""News images"""
url = serializers.URLField(source='image.url', read_only=True)
crops = NewsCropImageSerializer(source='childs', allow_null=True, many=True)
class Meta:
model = Image
fields = [
'id',
'title',
'url',
'crops',
]
extra_kwargs = {
'orientation': {'write_only': True}
}
class NewsTypeSerializer(serializers.ModelSerializer): class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer.""" """News type serializer."""
@ -79,7 +28,6 @@ class NewsBaseSerializer(ProjectModelSerializer):
# related fields # related fields
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)
gallery = NewsImageSerializer(source='original_images', read_only=True, many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -90,10 +38,11 @@ class NewsBaseSerializer(ProjectModelSerializer):
'title_translated', 'title_translated',
'subtitle_translated', 'subtitle_translated',
'is_highlighted', 'is_highlighted',
'image_url',
'preview_image_url',
'news_type', 'news_type',
'tags', 'tags',
'slug', 'slug',
'gallery',
) )
@ -102,8 +51,7 @@ class NewsDetailSerializer(NewsBaseSerializer):
description_translated = TranslatedField() description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True) country = CountrySimpleSerializer(read_only=True)
# todo: check the data redundancy author = UserBaseSerializer(source='created_by', read_only=True)
author = UserSerializer(source='created_by', read_only=True)
state_display = serializers.CharField(source='get_state_display', state_display = serializers.CharField(source='get_state_display',
read_only=True) read_only=True)
@ -120,7 +68,21 @@ class NewsDetailSerializer(NewsBaseSerializer):
'state_display', 'state_display',
'author', 'author',
'country', 'country',
'list_also_like_news', )
class NewsDetailWebSerializer(NewsDetailSerializer):
"""News detail serializer for web users.."""
same_theme = NewsBaseSerializer(many=True, read_only=True)
should_read = NewsBaseSerializer(many=True, read_only=True)
class Meta(NewsDetailSerializer.Meta):
"""Meta class."""
fields = NewsDetailSerializer.Meta.fields + (
'same_theme',
'should_read',
) )
@ -161,40 +123,3 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
'template_display', 'template_display',
) )
class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
"""Serializer class for model NewsGallery."""
class Meta:
"""Meta class"""
model = models.NewsGallery
fields = [
'id',
]
def get_request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def validate(self, attrs):
"""Override validate method."""
news_pk = self.get_request_kwargs().get('pk')
image_id = self.get_request_kwargs().get('image_id')
news_qs = models.News.objects.filter(pk=news_pk)
image_qs = Image.objects.filter(id=image_id)
if not news_qs.exists():
raise serializers.ValidationError({'detail': _('News not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
news = news_qs.first()
image = image_qs.first()
if news.news_gallery.filter(image=image).exists():
raise serializers.ValidationError({'detail': _('Image is already added')})
attrs['news'] = news
attrs['image'] = image
return attrs

View File

@ -15,7 +15,7 @@ class NewsMixinView:
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
"""Override get_queryset method.""" """Override get_queryset method."""
qs = models.News.objects.with_related().published()\ qs = models.News.objects.published().with_base_related()\
.order_by('-is_highlighted', '-created') .order_by('-is_highlighted', '-created')
if self.request.country_code: if self.request.country_code:
qs = qs.by_country_code(self.request.country_code) qs = qs.by_country_code(self.request.country_code)
@ -32,7 +32,11 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""News detail view.""" """News detail view."""
lookup_field = 'slug' lookup_field = 'slug'
serializer_class = serializers.NewsDetailSerializer serializer_class = serializers.NewsDetailWebSerializer
def get_queryset(self):
"""Override get_queryset method."""
return super().get_queryset().with_extended_related()
class NewsTypeListView(generics.ListAPIView): class NewsTypeListView(generics.ListAPIView):
@ -48,7 +52,7 @@ class NewsBackOfficeMixinView:
"""News back office mixin view.""" """News back office mixin view."""
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
queryset = models.News.objects.with_related() \ queryset = models.News.objects.with_base_related() \
.order_by('-is_highlighted', '-created') .order_by('-is_highlighted', '-created')
@ -65,6 +69,10 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
return self.create_serializers_class return self.create_serializers_class
return super().get_serializer_class() return super().get_serializer_class()
def get_queryset(self):
"""Override get_queryset method."""
return super().get_queryset().with_extended_related()
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
generics.CreateAPIView, generics.CreateAPIView,