Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	apps/account/urls/back.py
#	apps/account/views/back.py
#	apps/transfer/models.py
This commit is contained in:
Dmitriy Kuzmenko 2019-12-13 11:17:12 +03:00
commit 4c872b936b
37 changed files with 700 additions and 157 deletions

View File

@ -10,5 +10,5 @@ urlpatterns = [
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user/', views.UserLstView.as_view(), name='user-create-list'),
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'), path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
path('owner/', views.OwnerListView.as_view(), name='owner-list'), path('user/<int:id>/csv', views.get_user_csv, name='user-csv'),
] ]

View File

@ -1,6 +1,9 @@
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions from rest_framework import generics, permissions
from rest_framework.filters import OrderingFilter from rest_framework.filters import OrderingFilter
import csv
from django.http import HttpResponse, HttpResponseNotFound
from rest_framework.authtoken.models import Token
from account import models from account import models
from account.models import User from account.models import User
@ -48,7 +51,67 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id' lookup_field = 'id'
class OwnerListView(generics.ListAPIView): def get_user_csv(request, id):
serializer_class = serializers.OwnerSerializer # fields = ["id", "uuid", "nickname", "locale", "country_code", "city", "role", "consent_purpose", "consent_at",
queryset = models.User.objects.active() # "last_seen_at", "created_at", "updated_at", "email", "is_admin", "ezuser_id", "ez_user_id",
permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) # "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at",
# "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip",
# "confirmation_token", "confirmed_at", "confirmation_sent_at", "unconfirmed_email", "webpush_subscription"]
# uuid == id
#
# Не найдены:
# consent_purpose
# consent_at
# ezuser_id
# ez_user_id
# remember_created_at
# sign_in_count
# current_sign_in_at
# current_sign_in_ip
# last_sign_in_ip
# confirmed_at
# confirmation_sent_at
# webpush_subscription
#
# country_code не получить - клиент не привязан к стране
try:
user = User.objects.get(id=id)
except User.DoesNotExist:
return HttpResponseNotFound("User not found")
try:
roles = " ".join([role for role in user.roles])
except:
roles = ""
token, _ = Token.objects.get_or_create(user=user)
fields = {
"id": user.id,
"uuid": user.id,
"username": getattr(user, "username", ""),
"locale": getattr(user, "locale", ""),
"city": getattr(user, "city", ""),
"role": roles,
"created_at": getattr(user, "date_joined", ""),
"updated_at": user.last_login,
"email": user.email,
"is_admin": user.is_superuser,
"encrypted_password": user.password,
"reset_password_token": token.key,
"reset_password_sent_at": token.created, # TODO: не уверен в назначении поля, лучше проверить
"last_sign_in_at": user.last_login, # Повтор?
"confirmation_token": user.confirm_email_token,
"unconfirmed_email": 1 if user.unconfirmed_email else 0
}
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{user.email}.csv"'
writer = csv.writer(response)
writer.writerow(fields.keys())
writer.writerow(fields.values())
return response

View File

@ -9,11 +9,17 @@ from utils.views import BindObjectMixin
class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""ViewSet for Collection model.""" """ViewSet for Collection model."""
pagination_class = None # pagination_class = None
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
queryset = models.Collection.objects.all()
serializer_class = serializers.CollectionBackOfficeSerializer serializer_class = serializers.CollectionBackOfficeSerializer
def get_queryset(self):
"""Overridden method 'get_queryset'."""
qs = models.Collection.objects.all().order_by('-created')
if self.request.country_code:
qs = qs.by_country_code(self.request.country_code)
return qs
class CollectionBackOfficeViewSet(mixins.CreateModelMixin, class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,

View File

@ -213,7 +213,13 @@ class EstablishmentQuerySet(models.QuerySet):
)) ))
def similar_base(self, establishment): def similar_base(self, establishment):
"""
Return filtered QuerySet by base filters.
Filters including:
1 Filter by type (and subtype) establishment.
2 Filter by published Review.
3 With annotated distance.
"""
filters = { filters = {
'reviews__status': Review.READY, 'reviews__status': Review.READY,
'establishment_type': establishment.establishment_type, 'establishment_type': establishment.establishment_type,
@ -224,27 +230,64 @@ class EstablishmentQuerySet(models.QuerySet):
.filter(**filters) \ .filter(**filters) \
.annotate_distance(point=establishment.location) .annotate_distance(point=establishment.location)
def similar_base_subquery(self, establishment, filters: dict) -> Subquery:
"""
Return filtered Subquery object by filters.
Filters including:
1 Filter by transmitted filters.
2 With ordering by distance.
"""
return Subquery(
self.similar_base(establishment)
.filter(**filters)
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
.values('id')
)
def similar_restaurants(self, slug): def similar_restaurants(self, slug):
""" """
Return QuerySet with objects that similar to Restaurant. Return QuerySet with objects that similar to Restaurant.
:param restaurant_slug: str Establishment slug :param slug: str restaurant slug
""" """
restaurant_qs = self.filter(slug=slug, restaurant_qs = self.filter(slug=slug)
public_mark__isnull=False)
if restaurant_qs.exists(): if restaurant_qs.exists():
establishment = restaurant_qs.first() restaurant = restaurant_qs.first()
subquery_filter_by_distance = Subquery( ids_by_subquery = self.similar_base_subquery(
self.similar_base(establishment) establishment=restaurant,
.filter(public_mark__gte=10, filters={
establishment_gallery__is_main=True) 'public_mark__gte': 10,
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] 'establishment_gallery__is_main': True,
.values('id') }
) )
return self.filter(id__in=subquery_filter_by_distance) \ return self.filter(id__in=ids_by_subquery) \
.annotate_intermediate_public_mark() \ .annotate_intermediate_public_mark() \
.annotate_mark_similarity(mark=establishment.public_mark) \ .annotate_mark_similarity(mark=restaurant.public_mark) \
.order_by('mark_similarity') \ .order_by('mark_similarity') \
.distinct('mark_similarity', 'id') .distinct('mark_similarity', 'id')
else:
return self.none()
def similar_artisans(self, slug):
"""
Return QuerySet with objects that similar to Artisan.
:param slug: str artisan slug
"""
artisan_qs = self.filter(slug=slug)
if artisan_qs.exists():
artisan = artisan_qs.first()
ids_by_subquery = self.similar_base_subquery(
establishment=artisan,
filters={
'public_mark__gte': 10,
}
)
return self.filter(id__in=ids_by_subquery) \
.annotate_intermediate_public_mark() \
.annotate_mark_similarity(mark=artisan.public_mark) \
.order_by('mark_similarity') \
.distinct('mark_similarity', 'id')
else:
return self.none()
def by_wine_region(self, wine_region): def by_wine_region(self, wine_region):
""" """

View File

@ -21,6 +21,8 @@ urlpatterns = [
path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(), path('slug/<slug:slug>/similar/', views.RestaurantSimilarListView.as_view(),
name='similar-restaurants'), name='similar-restaurants'),
path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(), path('slug/<slug:slug>/similar/wineries/', views.WinerySimilarListView.as_view(),
name='similar-restaurants'), name='similar-wineries'),
path('slug/<slug:slug>/similar/artisans/', views.ArtisanSimilarListView.as_view(),
name='similar-artisans'),
] ]

View File

@ -1,6 +1,7 @@
"""Establishment app views.""" """Establishment app views."""
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions, status from rest_framework import generics, permissions, status
from establishment import filters, models, serializers from establishment import filters, models, serializers
@ -41,7 +42,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view""" """Establishment schedule RUD view"""
lookup_field = 'slug' lookup_field = 'slug'
serializer_class = ScheduleRUDSerializer serializer_class = ScheduleRUDSerializer
permission_classes = [IsWineryReviewer |IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
def get_object(self): def get_object(self):
""" """
@ -75,6 +76,11 @@ class MenuListCreateView(generics.ListCreateAPIView):
serializer_class = serializers.MenuSerializers serializer_class = serializers.MenuSerializers
queryset = models.Menu.objects.all() queryset = models.Menu.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
filter_backends = (DjangoFilterBackend,)
filterset_fields = (
'establishment',
'establishment__slug',
)
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -161,7 +167,7 @@ class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
class EmployeeListCreateView(generics.ListCreateAPIView): class EmployeeListCreateView(generics.ListCreateAPIView):
"""Emplyoee list create view.""" """Emplyoee list create view."""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
filter_class = filters.EmployeeBackFilter filter_class = filters.EmployeeBackFilter
serializer_class = serializers.EmployeeBackSerializers serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all() queryset = models.Employee.objects.all()
@ -170,7 +176,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
class EstablishmentEmployeeListView(generics.ListCreateAPIView): class EstablishmentEmployeeListView(generics.ListCreateAPIView):
"""Establishment emplyoees list view.""" """Establishment emplyoees list view."""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.EstablishmentEmployeeBackSerializer serializer_class = serializers.EstablishmentEmployeeBackSerializer
def get_queryset(self): def get_queryset(self):
@ -352,8 +358,8 @@ class EstablishmentEmployeeCreateView(generics.CreateAPIView):
class EstablishmentEmployeeDeleteView(generics.DestroyAPIView): class EstablishmentEmployeeDeleteView(generics.DestroyAPIView):
def _get_object_to_delete(self, establishment_id, employee_id): def _get_object_to_delete(self, establishment_id, employee_id):
result_qs = models.EstablishmentEmployee\ result_qs = models.EstablishmentEmployee \
.objects\ .objects \
.filter(establishment_id=establishment_id, employee_id=employee_id) .filter(establishment_id=establishment_id, employee_id=employee_id)
if not result_qs.exists(): if not result_qs.exists():
raise Http404 raise Http404
@ -371,6 +377,6 @@ class EstablishmentPositionListView(generics.ListAPIView):
"""Establishment positions list view.""" """Establishment positions list view."""
pagination_class = None pagination_class = None
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
queryset = models.Position.objects.all() queryset = models.Position.objects.all()
serializer_class = serializers.PositionBackSerializer serializer_class = serializers.PositionBackSerializer

View File

@ -87,7 +87,7 @@ class RestaurantSimilarListView(EstablishmentSimilarList):
"""Resource for getting a list of similar restaurants.""" """Resource for getting a list of similar restaurants."""
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Overridden get_queryset method"""
return EstablishmentMixinView.get_queryset(self) \ return EstablishmentMixinView.get_queryset(self) \
.similar_restaurants(slug=self.kwargs.get('slug')) .similar_restaurants(slug=self.kwargs.get('slug'))
@ -96,11 +96,20 @@ class WinerySimilarListView(EstablishmentSimilarList):
"""Resource for getting a list of similar wineries.""" """Resource for getting a list of similar wineries."""
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Overridden get_queryset method"""
return EstablishmentMixinView.get_queryset(self) \ return EstablishmentMixinView.get_queryset(self) \
.similar_wineries(slug=self.kwargs.get('slug')) .similar_wineries(slug=self.kwargs.get('slug'))
class ArtisanSimilarListView(EstablishmentSimilarList):
"""Resource for getting a list of similar artisans."""
def get_queryset(self):
"""Overridden get_queryset method"""
return EstablishmentMixinView.get_queryset(self) \
.similar_artisans(slug=self.kwargs.get('slug'))
class EstablishmentTypeListView(generics.ListAPIView): class EstablishmentTypeListView(generics.ListAPIView):
"""Resource for getting a list of establishment types.""" """Resource for getting a list of establishment types."""

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-11 15:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gallery', '0006_merge_20191027_1758'),
]
operations = [
migrations.AlterModelOptions(
name='image',
options={'ordering': ['-modified'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'},
),
]

View File

@ -34,6 +34,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
"""Meta class.""" """Meta class."""
verbose_name = _('Image') verbose_name = _('Image')
verbose_name_plural = _('Images') verbose_name_plural = _('Images')
ordering = ['-modified']
def __str__(self): def __str__(self):
"""String representation""" """String representation"""

View File

@ -1,8 +1,9 @@
from django.conf import settings from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.files.base import ContentFile
from rest_framework import serializers from rest_framework import serializers
from sorl.thumbnail.parsers import parse_crop from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.parsers import ThumbnailParseError from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from . import models from . import models
@ -88,15 +89,23 @@ class CropImageSerializer(ImageSerializer):
quality = validated_data.pop('quality') quality = validated_data.pop('quality')
crop = validated_data.pop('crop') crop = validated_data.pop('crop')
crop_params = {
'geometry': f'{width}x{height}',
'quality': quality,
'crop': crop,
}
cropped_image = self._image.get_cropped_image(**crop_params)
image = self._image image = self._image
image.pk = None
crop_params['geometry_string'] = crop_params.pop('geometry')
resized = get_thumbnail(self._image.image, **crop_params)
image.image.save(resized.name, ContentFile(resized.read()), True)
image.save()
if image and width and height: if image and width and height:
setattr(image, setattr(image,
'cropped_image', 'cropped_image',
image.get_cropped_image( cropped_image)
geometry=f'{width}x{height}',
quality=quality,
crop=crop))
return image return image
@property @property

View File

@ -10,6 +10,7 @@ urlpatterns = [
path('addresses/<int:pk>/', views.AddressRUDView.as_view(), name='address-RUD'), path('addresses/<int:pk>/', views.AddressRUDView.as_view(), name='address-RUD'),
path('cities/', views.CityListCreateView.as_view(), name='city-list-create'), path('cities/', views.CityListCreateView.as_view(), name='city-list-create'),
path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'),
path('cities/<int:pk>/', views.CityRUDView.as_view(), name='city-retrieve'), path('cities/<int:pk>/', views.CityRUDView.as_view(), name='city-retrieve'),
path('cities/<int:pk>/gallery/', views.CityGalleryListView.as_view(), path('cities/<int:pk>/gallery/', views.CityGalleryListView.as_view(),
name='gallery-list'), name='gallery-list'),

View File

@ -37,6 +37,15 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
filter_class = filters.CityBackFilter filter_class = filters.CityBackFilter
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City."""
serializer_class = serializers.CitySerializer
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
queryset = models.City.objects.all()
filter_class = filters.CityBackFilter
pagination_class = None
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model City.""" """RUD view for model City."""
serializer_class = serializers.CitySerializer serializer_class = serializers.CitySerializer

View File

@ -0,0 +1,38 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from account.models import User
from main.models import Panel, SiteSettings
from transfer.models import Panels
class Command(BaseCommand):
help = '''Add panels from legacy DB.'''
def handle(self, *args, **kwargs):
objects = []
deleted = 0
panels_list = Panels.objects.filter(name__isnull=False)
# remove existing panel
exist_panel = Panel.objects.filter(old_id__isnull=False)
if exist_panel.exists():
deleted = exist_panel.count()
exist_panel.delete()
for old_panel in tqdm(panels_list, desc='Add panels'):
site = SiteSettings.objects.filter(old_id=old_panel.site_id).first()
user = User.objects.filter(old_id=old_panel.site_id).first()
if site:
new_panel = Panel(
old_id=old_panel.id,
user=user,
site=site,
name=old_panel.name,
display=old_panel.display,
description=old_panel.description,
query=old_panel.query,
)
objects.append(new_panel)
Panel.objects.bulk_create(objects)
self.stdout.write(
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.'))

View File

@ -0,0 +1,36 @@
# Generated by Django 2.2.7 on 2019-12-12 12:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('main', '0041_auto_20191211_0631'),
]
operations = [
migrations.CreateModel(
name='Panel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=255, verbose_name='name')),
('display', models.CharField(blank=True, choices=[('table', 'table'), ('table', 'mailing')], default=None, max_length=255, null=True, verbose_name='display')),
('description', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='description')),
('query', models.TextField(blank=True, default=None, null=True, verbose_name='query')),
('old_id', models.IntegerField(blank=True, default=None, null=True, verbose_name='old id')),
('site', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'panel',
'verbose_name_plural': 'panels',
},
),
]

View File

@ -361,3 +361,46 @@ class Footer(ProjectBaseMixin):
) )
about_us = models.TextField(_('about_us')) about_us = models.TextField(_('about_us'))
copyright = models.TextField(_('copyright')) copyright = models.TextField(_('copyright'))
class PanelQuerySet(models.QuerySet):
"""Panels QuerySet."""
class Panel(ProjectBaseMixin):
"""Custom panel model with stored SQL query."""
TABLE = 'table'
MAILING = 'table'
DISPLAY_CHOICES = (
(TABLE, _('table')),
(MAILING, _('mailing'))
)
name = models.CharField(_('name'), max_length=255)
display = models.CharField(
_('display'), max_length=255, choices=DISPLAY_CHOICES,
blank=True, null=True, default=None
)
description = models.CharField(
_('description'), max_length=255, blank=True, null=True, default=None)
query = models.TextField(_('query'), blank=True, null=True, default=None)
user = models.ForeignKey(
'account.User', verbose_name=_('user'), null=True,
on_delete=models.SET_NULL)
site = models.ForeignKey(
'main.SiteSettings', verbose_name=_('site'), null=True,
on_delete=models.SET_NULL)
old_id = models.IntegerField(
_('old id'), null=True, blank=True, default=None)
objects = PanelQuerySet.as_manager()
class Meta:
verbose_name = _('panel')
verbose_name_plural = _('panels')
def __str__(self):
return self.name
def execute_query(self):
pass

View File

@ -5,6 +5,8 @@ from rest_framework import serializers
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
from account.serializers.back import BackUserSerializer
from account.models import User
class FeatureSerializer(serializers.ModelSerializer): class FeatureSerializer(serializers.ModelSerializer):
@ -265,8 +267,32 @@ class PageTypeBaseSerializer(serializers.ModelSerializer):
class ContentTypeBackSerializer(serializers.ModelSerializer): class ContentTypeBackSerializer(serializers.ModelSerializer):
"""Serializer fro model ContentType.""" """Serializer for model ContentType."""
class Meta: class Meta:
model = ContentType model = ContentType
fields = '__all__' fields = '__all__'
class PanelSerializer(serializers.ModelSerializer):
"""Serializer for Custom panel."""
user_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
source='user',
write_only=True
)
user = BackUserSerializer(read_only=True)
class Meta:
model = models.Panel
fields = [
'id',
'name',
'display',
'description',
'query',
'created',
'modified',
'user',
'user_id'
]

View File

@ -21,7 +21,11 @@ urlpatterns = [
path('footer/', views.FooterBackView.as_view(), name='footer-list-create'), path('footer/', views.FooterBackView.as_view(), name='footer-list-create'),
path('footer/<int:pk>/', views.FooterRUDBackView.as_view(), name='footer-rud'), path('footer/<int:pk>/', views.FooterRUDBackView.as_view(), name='footer-rud'),
path('page-types/', views.PageTypeListCreateView.as_view(), path('page-types/', views.PageTypeListCreateView.as_view(),
name='page-types-list-create') name='page-types-list-create'),
path('panels/', views.PanelsListCreateView.as_view(), name='panels'),
path('panels/<int:pk>/', views.PanelsListCreateView.as_view(), name='panels-rud'),
# path('panels/<int:pk>/execute/', views.PanelsView.as_view(), name='panels-execute')
] ]

View File

@ -4,7 +4,7 @@ from rest_framework import generics, permissions
from main import serializers from main import serializers
from main.filters import AwardFilter from main.filters import AwardFilter
from main.models import Award, Footer, PageType from main.models import Award, Footer, PageType, Panel
from main.views import SiteSettingsView, SiteListView from main.views import SiteSettingsView, SiteListView
@ -89,3 +89,21 @@ class PageTypeListCreateView(generics.ListCreateAPIView):
pagination_class = None pagination_class = None
serializer_class = serializers.PageTypeBaseSerializer serializer_class = serializers.PageTypeBaseSerializer
queryset = PageType.objects.all() queryset = PageType.objects.all()
class PanelsListCreateView(generics.ListCreateAPIView):
"""Custom panels view."""
permission_classes = (
permissions.IsAdminUser,
)
serializer_class = serializers.PanelSerializer
queryset = Panel.objects.all()
class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Custom panels view."""
permission_classes = (
permissions.IsAdminUser,
)
serializer_class = serializers.PanelSerializer
queryset = Panel.objects.all()

View File

@ -70,9 +70,6 @@ class CarouselListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
country_code = self.request.country_code country_code = self.request.country_code
if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES:
qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS)
return qs
qs = models.Carousel.objects.is_parsed().active() qs = models.Carousel.objects.is_parsed().active()
if country_code: if country_code:
qs = qs.by_country_code(country_code) qs = qs.by_country_code(country_code)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-12-11 15:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gallery', '0007_auto_20191211_1528'),
('news', '0040_remove_news_slug'),
]
operations = [
migrations.AlterUniqueTogether(
name='newsgallery',
unique_together={('news', 'image')},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-12-12 13:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('news', '0041_auto_20191211_1528'),
]
operations = [
migrations.AddField(
model_name='news',
name='duplication_date',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Duplication datetime'),
),
]

View File

@ -211,6 +211,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
verbose_name=_('banner')) verbose_name=_('banner'))
site = models.ForeignKey('main.SiteSettings', blank=True, null=True, site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
on_delete=models.SET_NULL, verbose_name=_('site settings')) on_delete=models.SET_NULL, verbose_name=_('site settings'))
duplication_date = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('Duplication datetime'))
objects = NewsQuerySet.as_manager() objects = NewsQuerySet.as_manager()
class Meta: class Meta:
@ -220,7 +222,16 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi
verbose_name_plural = _('news') verbose_name_plural = _('news')
def __str__(self): def __str__(self):
return f'news: {self.slug}' return f'news: {next(iter(self.slugs.values()))}'
def create_duplicate(self, new_country, view_count_model):
self.pk = None
self.state = self.WAITING
self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()}
self.country = new_country
self.views_count = view_count_model
self.duplication_date = timezone.now()
self.save()
@property @property
def is_publish(self): def is_publish(self):
@ -320,4 +331,4 @@ class NewsGallery(IntermediateGalleryModelMixin):
"""NewsGallery meta class.""" """NewsGallery meta class."""
verbose_name = _('news gallery') verbose_name = _('news gallery')
verbose_name_plural = _('news galleries') verbose_name_plural = _('news galleries')
unique_together = (('news', 'is_main'), ('news', 'image')) unique_together = [['news', 'image'],]

View File

@ -13,6 +13,9 @@ from tag.serializers import TagBaseSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import (TranslatedField, ProjectModelSerializer, from utils.serializers import (TranslatedField, ProjectModelSerializer,
FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer) FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer)
from rating import models as rating_models
from django.shortcuts import get_object_or_404
from utils.models import get_current_locale, get_default_locale
class AgendaSerializer(ProjectModelSerializer): class AgendaSerializer(ProjectModelSerializer):
@ -68,6 +71,13 @@ class NewsBaseSerializer(ProjectModelSerializer):
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags') tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags')
in_favorites = serializers.BooleanField(allow_null=True, read_only=True) in_favorites = serializers.BooleanField(allow_null=True, read_only=True)
view_counter = serializers.IntegerField(read_only=True) view_counter = serializers.IntegerField(read_only=True)
slug = serializers.SerializerMethodField(read_only=True, allow_null=True)
def get_slug(self, obj):
if obj.slugs:
return obj.slugs.get(get_current_locale()) \
or obj.slugs.get(get_default_locale()) \
or next(iter(obj.slugs.values()))
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -75,12 +85,12 @@ class NewsBaseSerializer(ProjectModelSerializer):
model = models.News model = models.News
fields = ( fields = (
'id', 'id',
'slug',
'title_translated', 'title_translated',
'subtitle_translated', 'subtitle_translated',
'is_highlighted', 'is_highlighted',
'news_type', 'news_type',
'tags', 'tags',
'slugs',
'view_counter', 'view_counter',
) )
@ -171,10 +181,13 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
'title', 'title',
'backoffice_title', 'backoffice_title',
'subtitle', 'subtitle',
'slugs',
'is_published', 'is_published',
'duplication_date',
) )
extra_kwargs = { extra_kwargs = {
'backoffice_title': {'allow_null': False}, 'backoffice_title': {'allow_null': False},
'duplication_date': {'read_only': True},
} }
def create(self, validated_data): def create(self, validated_data):
@ -243,6 +256,16 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
"""Get url kwargs from request.""" """Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs') return self.context.get('request').parser_context.get('kwargs')
def create(self, validated_data):
news_pk = self.get_request_kwargs().get('pk')
image_id = self.get_request_kwargs().get('image_id')
qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk)
instance = qs.first()
if instance:
qs.update(**validated_data)
return instance
return super().create(validated_data)
def validate(self, attrs): def validate(self, attrs):
"""Override validate method.""" """Override validate method."""
news_pk = self.get_request_kwargs().get('pk') news_pk = self.get_request_kwargs().get('pk')
@ -259,8 +282,8 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
news = news_qs.first() news = news_qs.first()
image = image_qs.first() image = image_qs.first()
if image in news.gallery.all(): # if image in news.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')}) # raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['news'] = news attrs['news'] = news
attrs['image'] = image attrs['image'] = image
@ -317,3 +340,24 @@ class NewsCarouselCreateSerializer(CarouselCreateSerializer):
'content_object': validated_data.pop('news') 'content_object': validated_data.pop('news')
}) })
return super().create(validated_data) return super().create(validated_data)
class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer,
NewsDetailSerializer):
"""Serializer for creating news clone."""
template_display = serializers.CharField(source='get_template_display',
read_only=True)
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
fields = NewsBackOfficeBaseSerializer.Meta.fields + NewsDetailSerializer.Meta.fields + (
'template_display',
)
read_only_fields = fields
def create(self, validated_data):
kwargs = self.context.get('request').parser_context.get('kwargs')
instance = get_object_or_404(models.News, pk=kwargs['pk'])
new_country = get_object_or_404(location_models.Country, code=kwargs['country_code'])
view_count_model = rating_models.ViewCount.objects.create(count=0)
instance.create_duplicate(new_country, view_count_model)
return instance

View File

@ -14,4 +14,5 @@ urlpatterns = [
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'), path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='create-destroy-carousels'),
] ]

View File

@ -7,7 +7,7 @@ from news import filters, models, serializers
from rating.tasks import add_rating from rating.tasks import add_rating
from utils.permissions import IsCountryAdmin, IsContentPageManager from utils.permissions import IsCountryAdmin, IsContentPageManager
from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView
from utils.serializers import ImageBaseSerializer from utils.serializers import ImageBaseSerializer, EmptySerializer
class NewsMixinView: class NewsMixinView:
@ -167,3 +167,10 @@ class NewsCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
_model = models.News _model = models.News
serializer_class = serializers.NewsCarouselCreateSerializer serializer_class = serializers.NewsCarouselCreateSerializer
class NewsCloneView(generics.CreateAPIView):
"""View for creating clone News"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.NewsCloneCreateSerializer
queryset = models.News.objects.all()

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2019-12-12 09:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0020_merge_20191209_0911'),
]
operations = [
migrations.AlterField(
model_name='product',
name='product_type',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='product.ProductType', verbose_name='Type'),
),
]

View File

@ -171,7 +171,7 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
default=None, help_text='{"en-GB":"some text"}') default=None, help_text='{"en-GB":"some text"}')
available = models.BooleanField(_('available'), default=True) available = models.BooleanField(_('available'), default=True)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
null=True, null=True, blank=True, default=None,
related_name='products', verbose_name=_('Type')) related_name='products', verbose_name=_('Type'))
subtypes = models.ManyToManyField(ProductSubType, blank=True, subtypes = models.ManyToManyField(ProductSubType, blank=True,
related_name='products', related_name='products',

View File

@ -5,7 +5,7 @@ from search_indexes.utils import OBJECT_FIELD_PROPERTIES
from product import models from product import models
ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product')) ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product'))
ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields':{'limit': 3000}}) ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields': {'limit': 3000}})
@ProductIndex.doc_type @ProductIndex.doc_type
@ -14,12 +14,13 @@ class ProductDocument(Document):
description = fields.ObjectField(attr='description_indexing', description = fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES) properties=OBJECT_FIELD_PROPERTIES)
product_type = fields.ObjectField(properties={ product_type = fields.ObjectField(
'id': fields.IntegerField(), properties={
'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), 'id': fields.IntegerField(),
'index_name': fields.KeywordField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES),
'use_subtypes': fields.BooleanField(), 'index_name': fields.KeywordField(),
}) },
)
subtypes = fields.ObjectField( subtypes = fields.ObjectField(
properties={ properties={
'id': fields.IntegerField(), 'id': fields.IntegerField(),
@ -54,15 +55,12 @@ class ProductDocument(Document):
), ),
'address': fields.ObjectField( 'address': fields.ObjectField(
properties={ properties={
'city': fields.ObjectField( 'id': fields.IntegerField(),
properties={ 'street_name_1': fields.TextField(
'country': fields.ObjectField( fields={'raw': fields.KeywordField()}
properties={ ),
'code': fields.KeywordField() 'postal_code': fields.KeywordField(),
} 'coordinates': fields.GeoPointField(attr='location_field_indexing'),
)
}
)
} }
) )
} }
@ -155,7 +153,7 @@ class ProductDocument(Document):
name_ru = fields.TextField(attr='display_name', analyzer='russian') name_ru = fields.TextField(attr='display_name', analyzer='russian')
name_fr = fields.TextField(attr='display_name', analyzer='french') name_fr = fields.TextField(attr='display_name', analyzer='french')
favorites_for_users = fields.ListField(field=fields.IntegerField()) favorites_for_users = fields.ListField(field=fields.IntegerField())
created = fields.DateField(attr='created') # publishing date (?) created = fields.DateField(attr='created') # publishing date (?)
class Django: class Django:
model = models.Product model = models.Product

View File

@ -40,6 +40,8 @@ class ProductSubtypeDocumentSerializer(serializers.Serializer):
name_translated = serializers.SerializerMethodField() name_translated = serializers.SerializerMethodField()
def get_name_translated(self, obj): def get_name_translated(self, obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name) return get_translated_value(obj.name)
@ -93,7 +95,7 @@ class TagDocumentSerializer(serializers.Serializer):
return get_translated_value(obj.label) return get_translated_value(obj.label)
class ProductTypeDocumentSerializer(serializers.Serializer): class ProductTypeSerializer(serializers.Serializer):
"""Product type ES document serializer.""" """Product type ES document serializer."""
id = serializers.IntegerField() id = serializers.IntegerField()
@ -102,8 +104,13 @@ class ProductTypeDocumentSerializer(serializers.Serializer):
@staticmethod @staticmethod
def get_name_translated(obj): def get_name_translated(obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name) return get_translated_value(obj.name)
def get_attribute(self, instance):
return instance.product_type if instance and instance.product_type else None
class CityDocumentShortSerializer(serializers.Serializer): class CityDocumentShortSerializer(serializers.Serializer):
"""City serializer for ES Document,""" """City serializer for ES Document,"""
@ -114,7 +121,6 @@ class CityDocumentShortSerializer(serializers.Serializer):
class CountryDocumentSerializer(serializers.Serializer): class CountryDocumentSerializer(serializers.Serializer):
id = serializers.IntegerField() id = serializers.IntegerField()
code = serializers.CharField(allow_null=True) code = serializers.CharField(allow_null=True)
svg_image = serializers.CharField() svg_image = serializers.CharField()
@ -122,11 +128,12 @@ class CountryDocumentSerializer(serializers.Serializer):
@staticmethod @staticmethod
def get_name_translated(obj): def get_name_translated(obj):
if isinstance(obj, dict):
return get_translated_value(obj.get('name'))
return get_translated_value(obj.name) return get_translated_value(obj.name)
class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer):
country = CountryDocumentSerializer() country = CountryDocumentSerializer()
def to_representation(self, instance): def to_representation(self, instance):
@ -136,19 +143,6 @@ class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer):
return None return None
class ProductEstablishmentDocumentSerializer(serializers.Serializer):
"""Related to Product Establishment ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
index_name = serializers.CharField()
city = AnotherCityDocumentShortSerializer()
def get_attribute(self, instance):
return instance.establishment if instance and instance.establishment else None
class AddressDocumentSerializer(serializers.Serializer): class AddressDocumentSerializer(serializers.Serializer):
"""Address serializer for ES Document.""" """Address serializer for ES Document."""
@ -171,6 +165,28 @@ class AddressDocumentSerializer(serializers.Serializer):
return None return None
class PSAddressDocumentSerializer(serializers.Serializer):
"""Address serializer for ES Document."""
id = serializers.IntegerField()
street_name_1 = serializers.CharField()
postal_code = serializers.CharField()
class ProductEstablishmentSerializer(serializers.Serializer):
"""Related to Product Establishment ES document serializer."""
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
index_name = serializers.CharField()
city = AnotherCityDocumentShortSerializer()
address = PSAddressDocumentSerializer(allow_null=True)
def get_attribute(self, instance):
return instance.establishment if instance and instance.establishment else None
class ScheduleDocumentSerializer(serializers.Serializer): class ScheduleDocumentSerializer(serializers.Serializer):
"""Schedule serializer for ES Document""" """Schedule serializer for ES Document"""
@ -206,6 +222,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
subtitle_translated = serializers.SerializerMethodField(allow_null=True) subtitle_translated = serializers.SerializerMethodField(allow_null=True)
news_type = NewsTypeSerializer() news_type = NewsTypeSerializer()
tags = TagsDocumentSerializer(many=True, source='visible_tags') tags = TagsDocumentSerializer(many=True, source='visible_tags')
slug = serializers.SerializerMethodField(allow_null=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -221,9 +238,13 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'news_type', 'news_type',
'tags', 'tags',
'start', 'start',
'slugs', 'slug',
) )
@staticmethod
def get_slug(obj):
return get_translated_value(obj.slugs)
@staticmethod @staticmethod
def get_title_translated(obj): def get_title_translated(obj):
return get_translated_value(obj.title) return get_translated_value(obj.title)
@ -285,15 +306,15 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
) )
class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): class ProductDocumentSerializer(InFavoritesMixin):
"""Product document serializer""" """Product document serializer"""
tags = TagsDocumentSerializer(many=True, source='related_tags') tags = TagsDocumentSerializer(many=True, source='related_tags')
subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True)
wine_colors = TagDocumentSerializer(many=True) wine_colors = TagDocumentSerializer(many=True)
grape_variety = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True)
product_type = ProductTypeDocumentSerializer(allow_null=True) product_type = ProductTypeSerializer(allow_null=True)
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True) establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True)
wine_origins = WineOriginSerializer(many=True) wine_origins = WineOriginSerializer(many=True)
class Meta: class Meta:
@ -317,7 +338,6 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer):
'subtypes', 'subtypes',
'wine_colors', 'wine_colors',
'grape_variety', 'grape_variety',
'establishment_detail',
'average_price', 'average_price',
'created', 'created',
'wine_origins', 'wine_origins',

View File

@ -12,8 +12,8 @@ def update_document(sender, **kwargs):
instance = kwargs['instance'] instance = kwargs['instance']
app_label_model_name_to_filter = { app_label_model_name_to_filter = {
('location','country'): 'address__city__country', ('location', 'country'): 'address__city__country',
('location','city'): 'address__city', ('location', 'city'): 'address__city',
('location', 'address'): 'address', ('location', 'address'): 'address',
# todo: remove after migration # todo: remove after migration
('establishment', 'establishmenttype'): 'establishment_type', ('establishment', 'establishmenttype'): 'establishment_type',
@ -34,8 +34,8 @@ def update_news(sender, **kwargs):
model_name = sender._meta.model_name model_name = sender._meta.model_name
instance = kwargs['instance'] instance = kwargs['instance']
app_label_model_name_to_filter = { app_label_model_name_to_filter = {
('location','country'): 'country', ('location', 'country'): 'country',
('news','newstype'): 'news_type', ('news', 'newstype'): 'news_type',
('tag', 'tag'): 'tags', ('tag', 'tag'): 'tags',
} }
filter_name = app_label_model_name_to_filter.get((app_label, model_name)) filter_name = app_label_model_name_to_filter.get((app_label, model_name))
@ -52,9 +52,9 @@ def update_product(sender, **kwargs):
model_name = sender._meta.model_name model_name = sender._meta.model_name
instance = kwargs['instance'] instance = kwargs['instance']
app_label_model_name_to_filter = { app_label_model_name_to_filter = {
('product','productstandard'): 'standards', ('product', 'productstandard'): 'standards',
('product', 'producttype'): 'product_type', ('product', 'producttype'): 'product_type',
('tag','tag'): 'tags', ('tag', 'tag'): 'tags',
('location', 'wineregion'): 'wine_region', ('location', 'wineregion'): 'wine_region',
('location', 'winesubregion'): 'wine_sub_region', ('location', 'winesubregion'): 'wine_sub_region',
('location', 'winevillage'): 'wine_village', ('location', 'winevillage'): 'wine_village',

View File

@ -3,7 +3,6 @@ from rest_framework import permissions
from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf import constants
from django_elasticsearch_dsl_drf.filter_backends import ( from django_elasticsearch_dsl_drf.filter_backends import (
FilteringFilterBackend, FilteringFilterBackend,
GeoSpatialFilteringFilterBackend,
GeoSpatialOrderingFilterBackend, GeoSpatialOrderingFilterBackend,
OrderingFilterBackend, OrderingFilterBackend,
) )
@ -13,9 +12,24 @@ from search_indexes import serializers, filters, utils
from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.documents import EstablishmentDocument, NewsDocument
from search_indexes.documents.product import ProductDocument from search_indexes.documents.product import ProductDocument
from utils.pagination import ESDocumentPagination from utils.pagination import ESDocumentPagination
from tag.models import TagCategory
class NewsDocumentViewSet(BaseDocumentViewSet): class CustomBaseDocumentViewSet(BaseDocumentViewSet):
def __init__(self, *args, **kwargs):
if self.filter_fields:
for name in TagCategory.objects.all().values('index_name'):
self.filter_fields.update({
f'{name["index_name"]}_id': {
'field': 'tags.id',
'lookups': [constants.LOOKUP_QUERY_IN]
}
})
super().__init__(*args, **kwargs)
class NewsDocumentViewSet(CustomBaseDocumentViewSet):
"""News document ViewSet.""" """News document ViewSet."""
document = NewsDocument document = NewsDocument
@ -94,7 +108,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet):
] ]
class EstablishmentDocumentViewSet(BaseDocumentViewSet): class EstablishmentDocumentViewSet(CustomBaseDocumentViewSet):
"""Establishment document ViewSet.""" """Establishment document ViewSet."""
document = EstablishmentDocument document = EstablishmentDocument
@ -319,7 +333,7 @@ class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet):
] ]
class ProductDocumentViewSet(BaseDocumentViewSet): class ProductDocumentViewSet(CustomBaseDocumentViewSet):
"""Product document ViewSet.""" """Product document ViewSet."""
document = ProductDocument document = ProductDocument

View File

@ -123,19 +123,7 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
return obj in ['open_now', ] return obj in ['open_now', ]
def get_param_name(self, obj): def get_param_name(self, obj):
if obj == 'service': return f'{obj.index_name}_id__in'
return 'tags_id__in'
elif obj == 'pop':
return 'tags_id__in'
elif obj == 'open_now':
return 'open_now'
elif obj == 'wine_region':
return 'wine_region_id__in'
return '%s__in' % obj.index_name
def get_fields(self, *args, **kwargs): def get_fields(self, *args, **kwargs):
fields = super(FiltersTagCategoryBaseSerializer, self).get_fields() fields = super(FiltersTagCategoryBaseSerializer, self).get_fields()

View File

@ -1,17 +1,15 @@
"""Tag views.""" """Tag views."""
from django.conf import settings from django.conf import settings
from rest_framework import generics from rest_framework import generics, mixins, permissions, status, viewsets
from rest_framework import mixins
from rest_framework import permissions
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from django.utils.translation import gettext_lazy as _
from search_indexes import views as search_views
from location.models import WineRegion from location.models import WineRegion
from tag import filters from product.models import ProductType
from tag import models from tag import filters, models, serializers
from tag import serializers
class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet):
@ -61,14 +59,9 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
# User`s views & viewsets # User`s views & viewsets
class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class FiltersTagCategoryViewSet(TagCategoryViewSet):
"""ViewSet for TagCategory model.""" """ViewSet for TagCategory model."""
filterset_class = filters.TagCategoryFilterSet
pagination_class = None
permission_classes = (permissions.AllowAny,)
queryset = models.TagCategory.objects.with_tags().with_base_related(). \
distinct()
serializer_class = serializers.FiltersTagCategoryBaseSerializer serializer_class = serializers.FiltersTagCategoryBaseSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@ -78,9 +71,30 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
result_list = serializer.data result_list = serializer.data
query_params = request.query_params query_params = request.query_params
params_type = query_params['type'] params_type = query_params.get('type')
if query_params.get('establishment_type'):
params_type = query_params.get('establishment_type')
elif query_params.get('product_type'):
params_type = query_params.get('product_type')
if params_type == 'restaurant' and 'toque_number__in' in query_params: week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
filter_flags = {flag_name: False for flag_name in flags}
additional_flags = []
if params_type == 'restaurant':
additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now']
elif params_type in ['winery', 'wine']:
additional_flags += ['wine_region']
elif params_type == 'artisan':
additional_flags += ['works_now', 'works_at_weekday']
for flag_name in additional_flags:
filter_flags[flag_name] = True
if filter_flags['toque_number']:
toques = { toques = {
"index_name": "toque_number", "index_name": "toque_number",
"label_translated": "Toques", "label_translated": "Toques",
@ -93,28 +107,29 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
} }
result_list.append(toques) result_list.append(toques)
if params_type == 'winery' and 'wine_region_id__in' in query_params: if request.query_params.get('product_type') == ProductType.WINE:
try: wine_region_id = query_params.get('wine_region_id__in')
wine_region_id = int(query_params['wine_region_id__in'])
wine_regions = { if str(wine_region_id).isdigit():
"index_name": "wine_region", queryset = WineRegion.objects.filter(id=int(wine_region_id))
"label_translated": "Wine region",
"param_name": "wine_region_id__in",
"filters": [{
"id": obj.id,
"index_name": obj.name.lower().replace(' ', '_'),
"label_translated": obj.name
} for obj in WineRegion.objects.filter(id=wine_region_id)]
}
result_list.append(wine_regions) else:
queryset = WineRegion.objects.all()
except ValueError: wine_regions = {
pass "index_name": "wine_region",
"label_translated": "Wine region",
"param_name": "wine_region_id__in",
"filters": [{
"id": obj.id,
"index_name": obj.name.lower().replace(' ', '_'),
"label_translated": obj.name
} for obj in queryset]
}
if params_type == 'restaurant' and 'works_noon__in' in query_params: result_list.append(wine_regions)
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
if filter_flags['works_noon']:
works_noon = { works_noon = {
"index_name": "works_noon", "index_name": "works_noon",
"label_translated": "Open noon", "label_translated": "Open noon",
@ -127,8 +142,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
} }
result_list.append(works_noon) result_list.append(works_noon)
if params_type == 'restaurant' and 'works_evening__in' in query_params: if filter_flags['works_evening']:
week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
works_evening = { works_evening = {
"index_name": "works_evening", "index_name": "works_evening",
"label_translated": "Open evening", "label_translated": "Open evening",
@ -141,7 +156,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
} }
result_list.append(works_evening) result_list.append(works_evening)
if params_type in ('restaurant', 'artisan') and 'works_now' in query_params: if filter_flags['works_now']:
works_now = { works_now = {
"index_name": "open_now", "index_name": "open_now",
"label_translated": "Open now", "label_translated": "Open now",
@ -150,12 +165,55 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
} }
result_list.append(works_now) result_list.append(works_now)
if 'tags_id__in' in query_params: if filter_flags['works_at_weekday']:
# filtering by params_type and tags id works_at_weekday = {
# todo: result_list.append( filtering_data ) "index_name": "works_at_weekday",
pass "label_translated": "Works at weekday",
"param_name": "works_at_weekday__in",
"filters": [{
"id": weekday,
"index_name": week_days[weekday].lower(),
"label_translated": week_days[weekday]
} for weekday in range(7)]
}
result_list.append(works_at_weekday)
return Response(result_list) search_view_class = self.define_search_view_by_request(request)
facets = search_view_class.as_view({'get': 'list'})(self.mutate_request(self.request)).data['facets']
return Response(self.remove_empty_filters(result_list, facets))
@staticmethod
def mutate_request(request):
"""Remove all filtering get params and remove s_ from the rest of them"""
request.GET._mutable = True
for name in request.query_params.copy().keys():
value = request.query_params.pop(name)
if name.startswith('s_'):
request.query_params[name[2:]] = value[0]
request.GET._mutable = False
return request._request
@staticmethod
def define_search_view_by_request(request):
request.GET._mutable = True
if request.query_params.get('items'):
items = request.query_params.pop('items')[0]
else:
raise ValidationError({'detail': _('Missing required "items" parameter')})
item_to_class = {
'news': search_views.NewsDocumentViewSet,
'establishments': search_views.EstablishmentDocumentViewSet,
'products': search_views.ProductDocumentViewSet,
}
klass = item_to_class.get(items)
if klass is None:
raise ValidationError({'detail': _('news/establishments/products')})
request.GET._mutable = False
return klass
@staticmethod
def remove_empty_filters(filters, facets):
return filters
# BackOffice user`s views & viewsets # BackOffice user`s views & viewsets

View File

@ -1238,3 +1238,19 @@ class OwnershipAffs(MigrateMixin):
class Meta: class Meta:
managed = False managed = False
db_table = 'ownership_affs' db_table = 'ownership_affs'
class Panels(MigrateMixin):
using = 'legacy'
name = models.CharField(max_length=255, blank=True, null=True)
display = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
query = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(blank=True, null=True)
updated_at = models.DateTimeField(blank=True, null=True)
account_id = models.IntegerField(blank=True, null=True)
site_id = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'panels'

View File

@ -3,6 +3,7 @@ import logging
import random import random
import re import re
import string import string
from collections import namedtuple
import requests import requests
from django.conf import settings from django.conf import settings
@ -124,3 +125,10 @@ def absolute_url_decorator(func):
def get_point_from_coordinates(latitude: str, longitude: str): def get_point_from_coordinates(latitude: str, longitude: str):
if latitude and longitude: if latitude and longitude:
return Point(x=longitude, y=latitude, srid=4326) return Point(x=longitude, y=latitude, srid=4326)
def namedtuplefetchall(cursor):
"""Return all rows from a cursor as a namedtuple."""
desc = cursor.description
nt_result = namedtuple('Result', [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]

View File

@ -16,8 +16,6 @@ services:
- .:/code - .:/code
# PostgreSQL database # PostgreSQL database
db: db:
build: build:

View File

@ -516,9 +516,6 @@ PHONENUMBER_DEFAULT_REGION = "FR"
FALLBACK_LOCALE = 'en-GB' FALLBACK_LOCALE = 'en-GB'
# TMP TODO remove it later
# Временный хардкод для демонстрации > 15 ноября, потом удалить!
CAROUSEL_ITEMS = [465]
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop'] ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership'] NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next'] INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']