Merge branch 'feature/refactor-news' into develop

This commit is contained in:
evgeniy-st 2019-09-23 16:11:20 +03:00
commit fdf2974ee5
21 changed files with 347 additions and 164 deletions

View File

@ -1,7 +1,9 @@
"""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
class CountrySerializer(serializers.ModelSerializer): class CountrySerializer(serializers.ModelSerializer):
@ -20,6 +22,18 @@ class CountrySerializer(serializers.ModelSerializer):
] ]
class CountrySimpleSerializer(serializers.ModelSerializer):
"""Simple country serializer."""
name_translated = TranslatedField()
class Meta:
"""Meta class."""
model = models.Country
fields = ('id', 'code', 'name_translated')
class RegionSerializer(serializers.ModelSerializer): class RegionSerializer(serializers.ModelSerializer):
"""Region serializer""" """Region serializer"""

View File

@ -1,9 +1,9 @@
"""Main app serializers.""" """Main app serializers."""
from rest_framework import serializers from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from utils.serializers import TranslatedField
class FeatureSerializer(serializers.ModelSerializer): class FeatureSerializer(serializers.ModelSerializer):
@ -109,16 +109,16 @@ class AwardSerializer(AwardBaseSerializer):
class MetaDataContentSerializer(serializers.ModelSerializer): class MetaDataContentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='metadata.id', read_only=True, ) """MetaData content serializer."""
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True) id = serializers.IntegerField(source='metadata.id', read_only=True)
label_translated = TranslatedField(source='metadata.label_translated')
class Meta: class Meta:
"""Meta class."""
model = models.MetaDataContent model = models.MetaDataContent
fields = [ fields = ('id', 'label_translated')
'id',
'label_translated',
]
class CurrencySerializer(serializers.ModelSerializer): class CurrencySerializer(serializers.ModelSerializer):

View File

@ -0,0 +1,54 @@
# Generated by Django 2.2.4 on 2019-09-23 11:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('news', '0009_auto_20190901_1032'),
]
operations = [
migrations.AddField(
model_name='news',
name='author',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author'),
),
migrations.AddField(
model_name='news',
name='image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
),
migrations.AddField(
model_name='news',
name='preview_image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Preview image URL path'),
),
migrations.AlterField(
model_name='news',
name='address',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address'),
),
migrations.AlterField(
model_name='news',
name='country',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='country'),
),
migrations.AlterField(
model_name='news',
name='end',
field=models.DateTimeField(verbose_name='End'),
),
migrations.AlterField(
model_name='news',
name='news_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='news.NewsType', verbose_name='news type'),
),
migrations.AlterField(
model_name='news',
name='start',
field=models.DateTimeField(verbose_name='Start'),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.4 on 2019-09-23 11:34
from django.db import migrations
from django.conf import settings
def copy_image_links(apps, schemaeditor):
News = apps.get_model('news', 'News')
for news in News.objects.all():
if news.image:
news.image_url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{news.image.image.url}'
class Migration(migrations.Migration):
dependencies = [
('news', '0010_auto_20190923_1131'),
]
operations = [
migrations.RunPython(copy_image_links, migrations.RunPython.noop),
migrations.RemoveField(
model_name='news',
name='image',
),
]

View File

@ -1,25 +1,31 @@
"""News app models.""" """News app models."""
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.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
class NewsType(models.Model): class NewsType(models.Model):
"""NewsType model.""" """NewsType model."""
name = models.CharField(_('name'), max_length=250) name = models.CharField(_('name'), max_length=250)
class Meta: class Meta:
"""Meta class."""
verbose_name_plural = _('news types') verbose_name_plural = _('news types')
verbose_name = _('news type') verbose_name = _('news type')
def __str__(self): def __str__(self):
"""Overrided __str__ method."""
return self.name return self.name
class NewsQuerySet(models.QuerySet): class NewsQuerySet(models.QuerySet):
"""QuerySet for model News""" """QuerySet for model News"""
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)
@ -30,48 +36,56 @@ class NewsQuerySet(models.QuerySet):
def published(self): def published(self):
"""Return only published news""" """Return only published news"""
return self.filter(is_publish=True) now = timezone.now()
return self.filter(is_publish=True, start__lte=now, end__gte=now)
def with_related(self):
"""Return qs with related objects."""
return self.select_related('news_type', 'country').prefetch_related('tags')
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
"""News model.""" """News model."""
image = models.ForeignKey( news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
'gallery.Image', null=True, blank=True, default=None, verbose_name=_('news type'))
verbose_name=_('News image'), on_delete=models.CASCADE) title = TJSONField(blank=True, null=True, default=None,
news_type = models.ForeignKey( verbose_name=_('title'),
NewsType, verbose_name=_('news type'), on_delete=models.CASCADE) help_text='{"en-GB":"some text"}')
subtitle = TJSONField(blank=True, null=True, default=None,
title = TJSONField( verbose_name=_('subtitle'),
_('title'), null=True, blank=True, help_text='{"en-GB":"some text"}')
default=None, help_text='{"en-GB":"some text"}') description = TJSONField(blank=True, null=True, default=None,
subtitle = TJSONField( verbose_name=_('description'),
_('subtitle'), null=True, blank=True, help_text='{"en-GB":"some text"}')
default=None, help_text='{"en-GB":"some text"}' start = models.DateTimeField(verbose_name=_('Start'))
) end = models.DateTimeField(verbose_name=_('End'))
description = TJSONField(
_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}'
)
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))
playlist = models.IntegerField(_('playlist')) playlist = models.IntegerField(_('playlist'))
address = models.ForeignKey( is_publish = models.BooleanField(default=False,
'location.Address', verbose_name=_('address'), blank=True, verbose_name=_('Publish status'))
null=True, default=None, on_delete=models.CASCADE) author = models.CharField(max_length=255, blank=True, null=True,
is_publish = models.BooleanField( default=None,verbose_name=_('Author'))
default=False, verbose_name=_('Publish status')) is_highlighted = models.BooleanField(default=False,
country = models.ForeignKey( verbose_name=_('Is highlighted'))
'location.Country', blank=True, null=True,
verbose_name=_('country'), on_delete=models.CASCADE)
is_highlighted = models.BooleanField(
default=False, 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'))
address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'),
on_delete=models.SET_NULL)
country = models.ForeignKey('location.Country', blank=True, null=True,
on_delete=models.SET_NULL,
verbose_name=_('country'))
tags = generic.GenericRelation(to='main.MetaDataContent')
objects = NewsQuerySet.as_manager() objects = NewsQuerySet.as_manager()
class Meta: class Meta:
"""Meta class."""
verbose_name = _('news') verbose_name = _('news')
verbose_name_plural = _('news') verbose_name_plural = _('news')

101
apps/news/serializers.py Normal file
View File

@ -0,0 +1,101 @@
"""News app common serializers."""
from rest_framework import serializers
from location import models as location_models
from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer
from news import models
from utils.serializers import TranslatedField
class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer."""
class Meta:
"""Meta class."""
model = models.NewsType
fields = ('id', 'name')
class NewsBaseSerializer(serializers.ModelSerializer):
"""Base serializer for News model."""
# read only fields
title_translated = TranslatedField()
subtitle_translated = TranslatedField()
# related fields
news_type = NewsTypeSerializer(read_only=True)
tags = MetaDataContentSerializer(read_only=True, many=True)
class Meta:
"""Meta class."""
model = models.News
fields = (
'id',
'title_translated',
'subtitle_translated',
'image_url',
'image_url',
'preview_image_url',
'news_type',
'tags',
)
class NewsDetailSerializer(NewsBaseSerializer):
"""News detail serializer."""
description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True)
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
fields = NewsBaseSerializer.Meta.fields + (
'description_translated',
'start',
'end',
'playlist',
'is_highlighted',
'is_publish',
'author',
'country',
)
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer."""
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
fields = NewsBaseSerializer.Meta.fields + (
'title',
'subtitle',
)
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
NewsDetailSerializer):
"""News detail serializer for back-office users."""
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())
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
"""Meta class."""
fields = NewsBackOfficeBaseSerializer.Meta.fields + \
NewsDetailSerializer.Meta.fields + (
'description',
'news_type_id',
'country_id',
)

View File

@ -1,76 +0,0 @@
"""News app common serializers."""
from rest_framework import serializers
from gallery import models as gallery_models
from location.models import Address
from location.serializers import AddressSerializer
from news import models
class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer."""
class Meta:
model = models.NewsType
fields = [
'id',
'name'
]
class NewsSerializer(serializers.ModelSerializer):
"""News serializer."""
address = AddressSerializer()
title_translated = serializers.CharField(read_only=True, allow_null=True)
subtitle_translated = serializers.CharField(read_only=True, allow_null=True)
description_translated = serializers.CharField(read_only=True, allow_null=True)
image_url = serializers.ImageField(source='image.image', allow_null=True)
class Meta:
model = models.News
fields = [
'id',
'news_type',
'start',
'end',
'playlist',
'address',
'is_highlighted',
'image_url',
# Localized fields
'title_translated',
'subtitle_translated',
'description_translated',
]
class NewsCreateUpdateSerializer(NewsSerializer):
"""News update serializer."""
title = serializers.JSONField()
subtitle = serializers.JSONField()
description = serializers.JSONField()
image = serializers.PrimaryKeyRelatedField(
queryset=gallery_models.Image.objects.all(), required=True,)
news_type = serializers.PrimaryKeyRelatedField(
queryset=models.NewsType.objects.all(), write_only=True)
address = serializers.PrimaryKeyRelatedField(
queryset=Address.objects.all(), write_only=True)
class Meta:
model = models.News
read_only_fields = [
'id'
]
fields = [
'id',
'news_type',
'title',
'subtitle',
'description',
'start',
'end',
'playlist',
'address',
'image',
'is_publish',
'country'
]

11
apps/news/urls/back.py Normal file
View File

@ -0,0 +1,11 @@
"""News app urlpatterns for backoffice"""
from django.urls import path
from news import views
app_name = 'news'
urlpatterns = [
path('', views.NewsBackOfficeLCView.as_view(), name='list-create'),
path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(),
name='retrieve-update-destroy'),
]

View File

@ -1,12 +1,11 @@
"""Location app urlconf.""" """News app urlconf."""
from django.urls import path from django.urls import path
from news import views
from news.views import common
app_name = 'news' app_name = 'news'
urlpatterns = [ urlpatterns = [
path('', common.NewsListView.as_view(), name='list'), path('', views.NewsListView.as_view(), name='list'),
path('<int:pk>/', common.NewsDetailView.as_view(), name='rud'), path('<int:pk>/', views.NewsDetailView.as_view(), name='rud'),
path('type/', common.NewsTypeListView.as_view(), name='type'), path('types/', views.NewsTypeListView.as_view(), name='type'),
] ]

68
apps/news/views.py Normal file
View File

@ -0,0 +1,68 @@
"""News app views."""
from rest_framework import generics, permissions
from news import filters, models, serializers
class NewsMixinView:
"""News mixin."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.NewsBaseSerializer
def get_queryset(self, *args, **kwargs):
"""Override get_queryset method."""
qs = models.News.objects.with_related().published()\
.order_by('-is_highlighted', '-created')
if self.request.country_code:
qs = qs.by_country_code(self.request.country_code)
return qs
class NewsListView(NewsMixinView, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""News detail view."""
serializer_class = serializers.NewsDetailSerializer
class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
pagination_class = None
permission_classes = (permissions.AllowAny, )
queryset = models.NewsType.objects.all()
serializer_class = serializers.NewsTypeSerializer
class NewsBackOfficeMixinView:
"""News back office mixin view."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.News.objects.with_related() \
.order_by('-is_highlighted', '-created')
class NewsBackOfficeLCView(NewsBackOfficeMixinView,
generics.ListCreateAPIView):
"""Resource for a list of news for back-office users."""
serializer_class = serializers.NewsBackOfficeBaseSerializer
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
def get_serializer_class(self):
if self.request.method == 'POST':
return self.create_serializers_class
return super().get_serializer_class()
class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
generics.RetrieveUpdateDestroyAPIView):
"""Resource for detailed information about news for back-office users."""
serializer_class = serializers.NewsBackOfficeDetailSerializer

View File

@ -1,38 +0,0 @@
"""News app common app."""
from rest_framework import generics, permissions
from news import filters, models
from news.serializers import common as serializers
from utils.views import JWTGenericViewMixin
class NewsMixin:
"""News mixin."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.NewsSerializer
def get_queryset(self, *args, **kwargs):
"""Override get_queryset method"""
return models.News.objects.published() \
.by_country_code(code=self.request.country_code) \
.order_by('-is_highlighted', '-created')
class NewsListView(NewsMixin, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet
class NewsDetailView(NewsMixin, JWTGenericViewMixin, generics.RetrieveAPIView):
"""News detail view."""
class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
serializer_class = serializers.NewsTypeSerializer
permission_classes = (permissions.AllowAny, )
pagination_class = None
queryset = models.NewsType.objects.all()

View File

View File

@ -4,7 +4,7 @@ from search_indexes import views
router = routers.SimpleRouter() router = routers.SimpleRouter()
router.register(r'news', views.NewsDocumentViewSet, basename='news') # 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')
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -1,6 +1,5 @@
"""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
@ -13,3 +12,12 @@ class SourceSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=PlatformMixin.SOURCES, source = serializers.ChoiceField(choices=PlatformMixin.SOURCES,
default=PlatformMixin.WEB, default=PlatformMixin.WEB,
write_only=True) write_only=True)
class TranslatedField(serializers.CharField):
"""Translated field."""
def __init__(self, allow_null=True, required=False, read_only=True,
**kwargs):
super().__init__(allow_null=allow_null, required=required,
read_only=read_only, **kwargs)

View File

@ -24,7 +24,7 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', # 'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.establishment': 'development_establishment',
} }

View File

@ -64,6 +64,6 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'local_news', # 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
} }

View File

@ -22,6 +22,6 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'stage_news', # 'search_indexes.documents.news': 'stage_news', #temporarily disabled
'search_indexes.documents.establishment': 'stage_establishment', 'search_indexes.documents.establishment': 'stage_establishment',
} }

View File

@ -7,4 +7,5 @@ urlpatterns = [
namespace='gallery')), namespace='gallery')),
path('establishments/', include('establishment.urls.back')), path('establishments/', include('establishment.urls.back')),
path('location/', include('location.urls.back')), path('location/', include('location.urls.back')),
path('news/', include('news.urls.back'))
] ]