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 rest_framework import serializers
from location import models
from utils.serializers import TranslatedField
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):
"""Region serializer"""

View File

@ -1,9 +1,9 @@
"""Main app serializers."""
from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer
from main import models
from utils.serializers import TranslatedField
class FeatureSerializer(serializers.ModelSerializer):
@ -109,16 +109,16 @@ class AwardSerializer(AwardBaseSerializer):
class MetaDataContentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='metadata.id', read_only=True, )
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True)
"""MetaData content serializer."""
id = serializers.IntegerField(source='metadata.id', read_only=True)
label_translated = TranslatedField(source='metadata.label_translated')
class Meta:
"""Meta class."""
model = models.MetaDataContent
fields = [
'id',
'label_translated',
]
fields = ('id', 'label_translated')
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."""
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 rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
class NewsType(models.Model):
"""NewsType model."""
name = models.CharField(_('name'), max_length=250)
class Meta:
"""Meta class."""
verbose_name_plural = _('news types')
verbose_name = _('news type')
def __str__(self):
"""Overrided __str__ method."""
return self.name
class NewsQuerySet(models.QuerySet):
"""QuerySet for model News"""
def by_type(self, news_type):
"""Filter News by type"""
return self.filter(news_type__name=news_type)
@ -30,48 +36,56 @@ class NewsQuerySet(models.QuerySet):
def published(self):
"""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):
"""News model."""
image = models.ForeignKey(
'gallery.Image', null=True, blank=True, default=None,
verbose_name=_('News image'), on_delete=models.CASCADE)
news_type = models.ForeignKey(
NewsType, verbose_name=_('news type'), on_delete=models.CASCADE)
title = TJSONField(
_('title'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}')
subtitle = TJSONField(
_('subtitle'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}'
)
description = TJSONField(
_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}'
)
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))
news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
verbose_name=_('news type'))
title = TJSONField(blank=True, null=True, default=None,
verbose_name=_('title'),
help_text='{"en-GB":"some text"}')
subtitle = TJSONField(blank=True, null=True, default=None,
verbose_name=_('subtitle'),
help_text='{"en-GB":"some text"}')
description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'),
help_text='{"en-GB":"some text"}')
start = models.DateTimeField(verbose_name=_('Start'))
end = models.DateTimeField(verbose_name=_('End'))
playlist = models.IntegerField(_('playlist'))
address = models.ForeignKey(
'location.Address', verbose_name=_('address'), blank=True,
null=True, default=None, on_delete=models.CASCADE)
is_publish = models.BooleanField(
default=False, verbose_name=_('Publish status'))
country = models.ForeignKey(
'location.Country', blank=True, null=True,
verbose_name=_('country'), on_delete=models.CASCADE)
is_highlighted = models.BooleanField(
default=False, verbose_name=_('Is highlighted'))
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'))
is_highlighted = models.BooleanField(default=False,
verbose_name=_('Is highlighted'))
# TODO: metadata_keys - описание ключей для динамического построения полей метаданных
# 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()
class Meta:
"""Meta class."""
verbose_name = _('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 news.views import common
from news import views
app_name = 'news'
urlpatterns = [
path('', common.NewsListView.as_view(), name='list'),
path('<int:pk>/', common.NewsDetailView.as_view(), name='rud'),
path('type/', common.NewsTypeListView.as_view(), name='type'),
path('', views.NewsListView.as_view(), name='list'),
path('<int:pk>/', views.NewsDetailView.as_view(), name='rud'),
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.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')
urlpatterns = router.urls

View File

@ -1,6 +1,5 @@
"""Utils app serializer."""
from rest_framework import serializers
from utils.models import PlatformMixin
@ -13,3 +12,12 @@ class SourceSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=PlatformMixin.SOURCES,
default=PlatformMixin.WEB,
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 = {
'search_indexes.documents.news': 'development_news',
# 'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment',
}

View File

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

View File

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

View File

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