Merge branch 'develop' into feature/permission_liquor

This commit is contained in:
Виктор Гладких 2019-12-11 09:28:25 +03:00
commit b48cd4a81c
15 changed files with 91 additions and 18 deletions

View File

@ -1,3 +1,4 @@
FROM mdillon/postgis:10 FROM mdillon/postgis:10
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8 RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
ENV LANG ru_RU.utf8 ENV LANG ru_RU.utf8
COPY hstore.sql /docker-entrypoint-initdb.d

View File

@ -0,0 +1 @@
create extension hstore;

View File

@ -42,8 +42,9 @@ class BaseTestCase(APITestCase):
start=datetime.fromisoformat("2020-12-03 12:00:00"), start=datetime.fromisoformat("2020-12-03 12:00:00"),
end=datetime.fromisoformat("2020-12-03 12:00:00"), end=datetime.fromisoformat("2020-12-03 12:00:00"),
state=News.PUBLISHED, state=News.PUBLISHED,
slug='test-news' slugs={'en-GB': 'test-news'}
) )
self.slug = next(iter(self.test_news.slugs.values()))
self.test_content_type = ContentType.objects.get( self.test_content_type = ContentType.objects.get(
app_label="news", model="news") app_label="news", model="news")

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.7 on 2019-12-10 13:49
import django.contrib.postgres.fields.hstore
from django.db import migrations
from django.contrib.postgres.operations import HStoreExtension
def migrate_slugs(apps, schemaeditor):
News = apps.get_model('news', 'News')
for news in News.objects.all():
if news.slug:
news.slugs = {'en-GB': news.slug}
news.save()
class Migration(migrations.Migration):
dependencies = [
('news', '0038_news_backoffice_title'),
]
operations = [
HStoreExtension(),
migrations.AddField(
model_name='news',
name='slugs',
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=None, help_text='{"en-GB":"some slug"}', null=True, verbose_name='Slugs for current news obj'),
),
migrations.RunPython(migrate_slugs, migrations.RunPython.noop)
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-10 16:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0039_news_slugs'),
]
operations = [
migrations.RemoveField(
model_name='news',
name='slug',
),
]

View File

@ -12,6 +12,7 @@ from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, Has
FavoritesMixin) FavoritesMixin)
from utils.querysets import TranslationQuerysetMixin from utils.querysets import TranslationQuerysetMixin
from django.conf import settings from django.conf import settings
from django.contrib.postgres.fields import HStoreField
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
@ -180,8 +181,9 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
verbose_name=_('Start')) 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=255, slugs = HStoreField(null=True, blank=True, default=None,
verbose_name=_('News slug')) verbose_name=_('Slugs for current news obj'),
help_text='{"en-GB":"some slug"}')
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('State')) verbose_name=_('State'))
is_highlighted = models.BooleanField(default=False, is_highlighted = models.BooleanField(default=False,
@ -230,7 +232,7 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
@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': next(iter(self.slugs.values()))})
def should_read(self, user): def should_read(self, user):
return self.__class__.objects.should_read(self, user)[:3] return self.__class__.objects.should_read(self, user)[:3]

View File

@ -80,7 +80,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
'is_highlighted', 'is_highlighted',
'news_type', 'news_type',
'tags', 'tags',
'slug', 'slugs',
'view_counter', 'view_counter',
) )
@ -177,6 +177,14 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
'backoffice_title': {'allow_null': False}, 'backoffice_title': {'allow_null': False},
} }
def validate(self, attrs):
slugs = attrs.get('slugs', {})
if models.News.objects.filter(
slugs__values__contains=list(slugs.values())
).exclude(id=attrs.get('id', 0)).exists():
raise serializers.ValidationError({'slugs': _('News with this slug already exists.')})
return attrs
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
NewsDetailSerializer): NewsDetailSerializer):
@ -256,7 +264,7 @@ class NewsFavoritesCreateSerializer(FavoritesCreateSerializer):
def validate(self, attrs): def validate(self, attrs):
"""Overridden validate method""" """Overridden validate method"""
# Check establishment object # Check establishment object
news_qs = models.News.objects.filter(slug=self.slug) news_qs = models.News.objects.filter(slugs__values__contains=[self.slug])
# Check establishment obj by slug from lookup_kwarg # Check establishment obj by slug from lookup_kwarg
if not news_qs.exists(): if not news_qs.exists():

View File

@ -66,10 +66,11 @@ class BaseTestCase(APITestCase):
start=datetime.now() + timedelta(hours=-2), start=datetime.now() + timedelta(hours=-2),
end=datetime.now() + timedelta(hours=2), end=datetime.now() + timedelta(hours=2),
state=News.PUBLISHED, state=News.PUBLISHED,
slug='test-news-slug', slugs={'en-GB': 'test-news-slug'},
country=self.country_ru, country=self.country_ru,
site=self.site_ru site=self.site_ru
) )
self.slug = next(iter(self.test_news.slugs.values()))
class NewsTestCase(BaseTestCase): class NewsTestCase(BaseTestCase):
@ -84,7 +85,7 @@ class NewsTestCase(BaseTestCase):
"start": datetime.now() + timedelta(hours=-2), "start": datetime.now() + timedelta(hours=-2),
"end": datetime.now() + timedelta(hours=2), "end": datetime.now() + timedelta(hours=2),
"state": News.PUBLISHED, "state": News.PUBLISHED,
"slug": 'test-news-slug_post', "slugs": {'en-GB': 'test-news-slug_post'},
"country_id": self.country_ru.id, "country_id": self.country_ru.id,
"site_id": self.site_ru.id "site_id": self.site_ru.id
} }
@ -97,7 +98,7 @@ class NewsTestCase(BaseTestCase):
response = self.client.get(reverse('web:news:list')) response = self.client.get(reverse('web:news:list'))
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") response = self.client.get(f"/api/web/news/slug/{self.slug}/")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.get("/api/web/news/types/") response = self.client.get("/api/web/news/types/")
@ -117,7 +118,7 @@ class NewsTestCase(BaseTestCase):
data = { data = {
'id': self.test_news.id, 'id': self.test_news.id,
'description': {"ru-RU": "Description test news!"}, 'description': {"ru-RU": "Description test news!"},
'slug': self.test_news.slug, 'slugs': self.test_news.slugs,
'start': self.test_news.start, 'start': self.test_news.start,
'news_type_id': self.test_news.news_type_id, 'news_type_id': self.test_news.news_type_id,
'country_id': self.country_ru.id, 'country_id': self.country_ru.id,
@ -133,10 +134,10 @@ class NewsTestCase(BaseTestCase):
"object_id": self.test_news.id "object_id": self.test_news.id
} }
response = self.client.post(f'/api/web/news/slug/{self.test_news.slug}/favorites/', data=data) response = self.client.post(f'/api/web/news/slug/{self.slug}/favorites/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.delete(f'/api/web/news/slug/{self.test_news.slug}/favorites/', format='json') response = self.client.delete(f'/api/web/news/slug/{self.slug}/favorites/', format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

View File

@ -31,6 +31,10 @@ class NewsMixinView:
qs = qs.by_country_code(country_code) qs = qs.by_country_code(country_code)
return qs return qs
def get_object(self):
return self.get_queryset() \
.filter(slugs__values__contains=[self.kwargs['slug']]).first()
class NewsListView(NewsMixinView, generics.ListAPIView): class NewsListView(NewsMixinView, generics.ListAPIView):
"""News list view.""" """News list view."""
@ -46,7 +50,7 @@ class NewsListView(NewsMixinView, generics.ListAPIView):
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""News detail view.""" """News detail view."""
lookup_field = 'slug' lookup_field = None
serializer_class = serializers.NewsDetailWebSerializer serializer_class = serializers.NewsDetailWebSerializer
def get_queryset(self): def get_queryset(self):

View File

@ -7,7 +7,7 @@ from establishment import models
EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__,
'establishment')) 'establishment'))
EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1) EstablishmentIndex.settings(number_of_shards=5, number_of_replicas=2)
@EstablishmentIndex.doc_type @EstablishmentIndex.doc_type

View File

@ -17,6 +17,7 @@ class NewsDocument(Document):
'name': fields.KeywordField()}) 'name': fields.KeywordField()})
title = fields.ObjectField(attr='title_indexing', title = fields.ObjectField(attr='title_indexing',
properties=OBJECT_FIELD_PROPERTIES) properties=OBJECT_FIELD_PROPERTIES)
slugs = fields.ObjectField(properties=OBJECT_FIELD_PROPERTIES)
backoffice_title = fields.TextField(analyzer='english') backoffice_title = fields.TextField(analyzer='english')
subtitle = fields.ObjectField(attr='subtitle_indexing', subtitle = fields.ObjectField(attr='subtitle_indexing',
properties=OBJECT_FIELD_PROPERTIES) properties=OBJECT_FIELD_PROPERTIES)
@ -44,13 +45,16 @@ class NewsDocument(Document):
multi=True) multi=True)
favorites_for_users = fields.ListField(field=fields.IntegerField()) favorites_for_users = fields.ListField(field=fields.IntegerField())
start = fields.DateField(attr='start') start = fields.DateField(attr='start')
def prepare_slugs(self, instance):
return {locale: instance.slugs.get(locale) for locale in OBJECT_FIELD_PROPERTIES}
class Django: class Django:
model = models.News model = models.News
fields = ( fields = (
'id', 'id',
'end', 'end',
'slug',
'state', 'state',
'is_highlighted', 'is_highlighted',
'template', 'template',

View File

@ -221,7 +221,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'news_type', 'news_type',
'tags', 'tags',
'start', 'start',
'slug', 'slugs',
) )
@staticmethod @staticmethod

View File

@ -56,17 +56,18 @@ class TranslateFieldTests(BaseTestCase):
start=datetime.now(pytz.utc) + timedelta(hours=-13), start=datetime.now(pytz.utc) + timedelta(hours=-13),
end=datetime.now(pytz.utc) + timedelta(hours=13), end=datetime.now(pytz.utc) + timedelta(hours=13),
news_type=self.news_type, news_type=self.news_type,
slug='test', slugs={'en-GB': 'test'},
state=News.PUBLISHED, state=News.PUBLISHED,
country=self.country_ru, country=self.country_ru,
) )
self.slug = next(iter(self.news_item.slugs.values()))
self.news_item.save() self.news_item.save()
def test_model_field(self): def test_model_field(self):
self.assertTrue(hasattr(self.news_item, "title_translated")) self.assertTrue(hasattr(self.news_item, "title_translated"))
def test_read_locale(self): def test_read_locale(self):
response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') response = self.client.get(f"/api/web/news/slug/{self.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()
self.assertIn("title_translated", news_data) self.assertIn("title_translated", news_data)

View File

@ -9,6 +9,7 @@ from rest_framework.response import Response
from gallery.tasks import delete_image from gallery.tasks import delete_image
from search_indexes.documents import es_update from search_indexes.documents import es_update
from news.models import News
# JWT # JWT
@ -124,6 +125,8 @@ class BaseCreateDestroyMixinView(generics.CreateAPIView, generics.DestroyAPIView
lookup_field = 'slug' lookup_field = 'slug'
def get_base_object(self): def get_base_object(self):
if 'slugs' in [f.name for f in self._model._meta.get_fields()]: # slugs instead of `slug`
return get_object_or_404(self._model, slugs__values__contains=[self.kwargs['slug']])
return get_object_or_404(self._model, slug=self.kwargs['slug']) return get_object_or_404(self._model, slug=self.kwargs['slug'])
def es_update_base_object(self): def es_update_base_object(self):

View File

@ -48,6 +48,7 @@ CONTRIB_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.gis', 'django.contrib.gis',
'django.contrib.postgres',
] ]