Merge remote-tracking branch 'origin/develop' into es_product
This commit is contained in:
commit
b5e1344b38
|
|
@ -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
|
||||||
1
_dockerfiles/db/hstore.sql
Normal file
1
_dockerfiles/db/hstore.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
create extension hstore;
|
||||||
|
|
@ -24,8 +24,8 @@ from collection.models import Collection
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from location.models import WineOriginAddressMixin
|
from location.models import WineOriginAddressMixin
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
from tag.models import Tag
|
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
|
from tag.models import Tag
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
||||||
IntermediateGalleryModelMixin, HasTagsMixin,
|
IntermediateGalleryModelMixin, HasTagsMixin,
|
||||||
|
|
@ -209,23 +209,34 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""
|
"""
|
||||||
return self.annotate(mark_similarity=ExpressionWrapper(
|
return self.annotate(mark_similarity=ExpressionWrapper(
|
||||||
mark - F('intermediate_public_mark'),
|
mark - F('intermediate_public_mark'),
|
||||||
output_field=models.FloatField()
|
output_field=models.FloatField(default=0)
|
||||||
))
|
))
|
||||||
|
|
||||||
def similar(self, establishment_slug: str):
|
def similar_base(self, establishment):
|
||||||
"""
|
|
||||||
Return QuerySet with objects that similar to Establishment.
|
filters = {
|
||||||
:param establishment_slug: str Establishment slug
|
'reviews__status': Review.READY,
|
||||||
"""
|
'establishment_type': establishment.establishment_type,
|
||||||
establishment_qs = self.filter(slug=establishment_slug,
|
}
|
||||||
public_mark__isnull=False)
|
if establishment.establishment_subtypes.exists():
|
||||||
if establishment_qs.exists():
|
filters.update({'establishment_subtypes__in': establishment.establishment_subtypes.all()})
|
||||||
establishment = establishment_qs.first()
|
return self.exclude(id=establishment.id) \
|
||||||
subquery_filter_by_distance = Subquery(
|
.filter(**filters) \
|
||||||
self.exclude(slug=establishment_slug)
|
|
||||||
.filter(image_url__isnull=False, public_mark__gte=10)
|
|
||||||
.has_published_reviews()
|
|
||||||
.annotate_distance(point=establishment.location)
|
.annotate_distance(point=establishment.location)
|
||||||
|
|
||||||
|
def similar_restaurants(self, slug):
|
||||||
|
"""
|
||||||
|
Return QuerySet with objects that similar to Restaurant.
|
||||||
|
:param restaurant_slug: str Establishment slug
|
||||||
|
"""
|
||||||
|
restaurant_qs = self.filter(slug=slug,
|
||||||
|
public_mark__isnull=False)
|
||||||
|
if restaurant_qs.exists():
|
||||||
|
establishment = restaurant_qs.first()
|
||||||
|
subquery_filter_by_distance = Subquery(
|
||||||
|
self.similar_base(establishment)
|
||||||
|
.filter(public_mark__gte=10,
|
||||||
|
establishment_gallery__is_main=True)
|
||||||
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
||||||
.values('id')
|
.values('id')
|
||||||
)
|
)
|
||||||
|
|
@ -234,6 +245,36 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
.annotate_mark_similarity(mark=establishment.public_mark) \
|
.annotate_mark_similarity(mark=establishment.public_mark) \
|
||||||
.order_by('mark_similarity') \
|
.order_by('mark_similarity') \
|
||||||
.distinct('mark_similarity', 'id')
|
.distinct('mark_similarity', 'id')
|
||||||
|
|
||||||
|
def by_wine_region(self, wine_region):
|
||||||
|
"""
|
||||||
|
Return filtered QuerySet by wine region in wine origin.
|
||||||
|
:param wine_region: wine region.
|
||||||
|
"""
|
||||||
|
return self.filter(wine_origin__wine_region=wine_region).distinct()
|
||||||
|
|
||||||
|
def by_wine_sub_region(self, wine_sub_region):
|
||||||
|
"""
|
||||||
|
Return filtered QuerySet by wine region in wine origin.
|
||||||
|
:param wine_sub_region: wine sub region.
|
||||||
|
"""
|
||||||
|
return self.filter(wine_origin__wine_sub_region=wine_sub_region).distinct()
|
||||||
|
|
||||||
|
def similar_wineries(self, slug: str):
|
||||||
|
"""
|
||||||
|
Return QuerySet with objects that similar to Winery.
|
||||||
|
:param establishment_slug: str Establishment slug
|
||||||
|
"""
|
||||||
|
winery_qs = self.filter(slug=slug)
|
||||||
|
if winery_qs.exists():
|
||||||
|
winery = winery_qs.first()
|
||||||
|
return self.similar_base(winery) \
|
||||||
|
.order_by(F('wine_origins__wine_region').asc(),
|
||||||
|
F('wine_origins__wine_sub_region').asc()) \
|
||||||
|
.annotate_distance(point=winery.location) \
|
||||||
|
.order_by('distance') \
|
||||||
|
.distinct('distance', 'wine_origins__wine_region',
|
||||||
|
'wine_origins__wine_sub_region', 'id')
|
||||||
else:
|
else:
|
||||||
return self.none()
|
return self.none()
|
||||||
|
|
||||||
|
|
@ -457,15 +498,9 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def visible_tags(self):
|
def visible_tags(self):
|
||||||
return super().visible_tags \
|
return super().visible_tags \
|
||||||
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||||
'business_tag', 'business_tags_de']) \
|
'business_tag', 'business_tags_de', 'tag'])
|
||||||
.exclude(value__in=['rss', 'rss_selection'])
|
|
||||||
# todo: recalculate toque_number
|
# todo: recalculate toque_number
|
||||||
|
|
||||||
@property
|
|
||||||
def visible_tags_detail(self):
|
|
||||||
"""Removes some tags from detail Establishment representation"""
|
|
||||||
return self.visible_tags.exclude(category__index_name__in=['tag'])
|
|
||||||
|
|
||||||
def recalculate_toque_number(self):
|
def recalculate_toque_number(self):
|
||||||
toque_number = 0
|
toque_number = 0
|
||||||
if self.address and self.public_mark:
|
if self.address and self.public_mark:
|
||||||
|
|
@ -830,6 +865,25 @@ class ContactEmail(models.Model):
|
||||||
return f'{self.email}'
|
return f'{self.email}'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# class Wine(TranslatedFieldsMixin, models.Model):
|
||||||
|
# """Wine model."""
|
||||||
|
# establishment = models.ForeignKey(
|
||||||
|
# 'establishment.Establishment', verbose_name=_('establishment'),
|
||||||
|
# on_delete=models.CASCADE)
|
||||||
|
# bottles = models.IntegerField(_('bottles'))
|
||||||
|
# price_min = models.DecimalField(
|
||||||
|
# _('price min'), max_digits=14, decimal_places=2)
|
||||||
|
# price_max = models.DecimalField(
|
||||||
|
# _('price max'), max_digits=14, decimal_places=2)
|
||||||
|
# by_glass = models.BooleanField(_('by glass'))
|
||||||
|
# price_glass_min = models.DecimalField(
|
||||||
|
# _('price min'), max_digits=14, decimal_places=2)
|
||||||
|
# price_glass_max = models.DecimalField(
|
||||||
|
# _('price max'), max_digits=14, decimal_places=2)
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
class Plate(TranslatedFieldsMixin, models.Model):
|
class Plate(TranslatedFieldsMixin, models.Model):
|
||||||
"""Plate model."""
|
"""Plate model."""
|
||||||
STR_FIELD_NAME = 'name'
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
name='recent-reviews'),
|
name='recent-reviews'),
|
||||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
|
||||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
path('slug/<slug:slug>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
|
||||||
name='create-comment'),
|
name='create-comment'),
|
||||||
|
|
@ -17,4 +16,11 @@ urlpatterns = [
|
||||||
name='rud-comment'),
|
name='rud-comment'),
|
||||||
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
|
||||||
name='create-destroy-favorites'),
|
name='create-destroy-favorites'),
|
||||||
|
|
||||||
|
# similar establishments
|
||||||
|
path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(),
|
||||||
|
name='similar-restaurants'),
|
||||||
|
path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(),
|
||||||
|
name='similar-restaurants'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -77,16 +77,28 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
return qs.last_reviewed(point=point)
|
return qs.last_reviewed(point=point)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarListView(EstablishmentListView):
|
class EstablishmentSimilarList(EstablishmentListView):
|
||||||
"""Resource for getting a list of establishments."""
|
"""Resource for getting a list of similar establishments."""
|
||||||
|
|
||||||
serializer_class = serializers.EstablishmentSimilarSerializer
|
serializer_class = serializers.EstablishmentSimilarSerializer
|
||||||
pagination_class = EstablishmentPortionPagination
|
pagination_class = EstablishmentPortionPagination
|
||||||
|
|
||||||
|
|
||||||
|
class RestaurantSimilarListView(EstablishmentSimilarList):
|
||||||
|
"""Resource for getting a list of similar restaurants."""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
qs = super().get_queryset()
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
return qs.similar(establishment_slug=self.kwargs.get('slug'))
|
.similar_restaurants(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
|
class WinerySimilarListView(EstablishmentSimilarList):
|
||||||
|
"""Resource for getting a list of similar wineries."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method"""
|
||||||
|
return EstablishmentMixinView.get_queryset(self) \
|
||||||
|
.similar_wineries(slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeListView(generics.ListAPIView):
|
class EstablishmentTypeListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
18
apps/news/migrations/0038_news_backoffice_title.py
Normal file
18
apps/news/migrations/0038_news_backoffice_title.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-10 12:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0037_auto_20191129_1320'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='backoffice_title',
|
||||||
|
field=models.TextField(default=None, null=True, verbose_name='Title for searching via BO'),
|
||||||
|
),
|
||||||
|
]
|
||||||
29
apps/news/migrations/0039_news_slugs.py
Normal file
29
apps/news/migrations/0039_news_slugs.py
Normal 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)
|
||||||
|
]
|
||||||
17
apps/news/migrations/0040_remove_news_slug.py
Normal file
17
apps/news/migrations/0040_remove_news_slug.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -168,6 +169,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
|
||||||
title = TJSONField(blank=True, null=True, default=None,
|
title = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('title'),
|
verbose_name=_('title'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
backoffice_title = models.TextField(null=True, default=None,
|
||||||
|
verbose_name=_('Title for searching via BO'))
|
||||||
subtitle = TJSONField(blank=True, null=True, default=None,
|
subtitle = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('subtitle'),
|
verbose_name=_('subtitle'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
|
@ -178,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,
|
||||||
|
|
@ -228,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]
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
'is_highlighted',
|
'is_highlighted',
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
'slug',
|
'slugs',
|
||||||
'view_counter',
|
'view_counter',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -169,9 +169,21 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
|
|
||||||
fields = NewsBaseSerializer.Meta.fields + (
|
fields = NewsBaseSerializer.Meta.fields + (
|
||||||
'title',
|
'title',
|
||||||
|
'backoffice_title',
|
||||||
'subtitle',
|
'subtitle',
|
||||||
'is_published',
|
'is_published',
|
||||||
)
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'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,
|
||||||
|
|
@ -252,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():
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ 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')
|
||||||
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
subtitle = fields.ObjectField(attr='subtitle_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES)
|
properties=OBJECT_FIELD_PROPERTIES)
|
||||||
description = fields.ObjectField(attr='description_indexing',
|
description = fields.ObjectField(attr='description_indexing',
|
||||||
|
|
@ -43,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',
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,8 @@ class TagCategoryDocument(Document):
|
||||||
to the updating of a lot of items.
|
to the updating of a lot of items.
|
||||||
"""
|
"""
|
||||||
if isinstance(related_instance, News):
|
if isinstance(related_instance, News):
|
||||||
return related_instance.tags
|
tag_categories = []
|
||||||
|
for tag in related_instance.tags.all():
|
||||||
|
if tag.category not in tag_categories:
|
||||||
|
tag_categories.append(tag.category)
|
||||||
|
return tag_categories
|
||||||
|
|
@ -225,7 +225,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
'news_type',
|
'news_type',
|
||||||
'tags',
|
'tags',
|
||||||
'start',
|
'start',
|
||||||
'slug',
|
'slugs',
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,8 @@ class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet):
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
filters.CustomGeoSpatialFilteringFilterBackend,
|
||||||
|
GeoSpatialOrderingFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user