Merge branch 'develop' into feature/fix-country-region-city-transfer
# Conflicts: # apps/location/models.py # apps/location/transfer_data.py # apps/location/views/common.py # apps/transfer/management/commands/transfer.py # project/settings/local.py
This commit is contained in:
commit
470bd096b0
|
|
@ -1,6 +1,7 @@
|
||||||
"""Back account serializers"""
|
"""Back account serializers"""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from account import models
|
from account import models
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(serializers.ModelSerializer):
|
class RoleSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -18,4 +19,33 @@ class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'user',
|
'user',
|
||||||
'role'
|
'role'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BackUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = '__all__'
|
||||||
|
extra_kwargs = {
|
||||||
|
'password': {'write_only': True}
|
||||||
|
}
|
||||||
|
read_only_fields = ('old_password', 'last_login', 'date_joined')
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = super().create(validated_data)
|
||||||
|
user.set_password(validated_data['password'])
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class BackDetailUserSerializer(BackUserSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
exclude = ('password',)
|
||||||
|
read_only_fields = ('old_password', 'last_login', 'date_joined')
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = super().create(validated_data)
|
||||||
|
user.set_password(validated_data['password'])
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,18 @@ class UserBaseSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
|
class UserShortSerializer(UserSerializer):
|
||||||
|
"""Compact serializer for model User."""
|
||||||
|
|
||||||
|
class Meta(UserSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'fullname',
|
||||||
|
'email',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordSerializer(serializers.ModelSerializer):
|
class ChangePasswordSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model User."""
|
"""Serializer for model User."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,52 @@ class UserRoleTests(APITestCase):
|
||||||
|
|
||||||
response = self.client.post(url, data=data, format='json')
|
response = self.client.post(url, data=data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
class UserTestCase(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user_1 = User.objects.create_user(
|
||||||
|
username='alex',
|
||||||
|
email='alex@mail.com',
|
||||||
|
password='alex_password',
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user_2 = User.objects.create_user(
|
||||||
|
username='boris',
|
||||||
|
email='boris@mail.com',
|
||||||
|
password='boris_password',
|
||||||
|
)
|
||||||
|
|
||||||
|
# get tokens
|
||||||
|
tokens = User.create_jwt_tokens(self.user_1)
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
|
def test_user_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/account/user/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': 'roman',
|
||||||
|
'email': 'roman@mail.com',
|
||||||
|
'password': 'roman_password',
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/account/user/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/back/account/user/{self.user_2.id}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'first_name': 'Boris'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/back/account/user/{self.user_2.id}/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/account/user/{self.user_2.id}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@ app_name = 'account'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
||||||
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-list-create'),
|
||||||
|
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
from rest_framework import generics
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from account.serializers import back as serializers
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
|
from account.models import User
|
||||||
|
from account.serializers import back as serializers
|
||||||
|
|
||||||
|
|
||||||
class RoleLstView(generics.ListCreateAPIView):
|
class RoleLstView(generics.ListCreateAPIView):
|
||||||
|
|
@ -10,4 +13,27 @@ class RoleLstView(generics.ListCreateAPIView):
|
||||||
|
|
||||||
class UserRoleLstView(generics.ListCreateAPIView):
|
class UserRoleLstView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.UserRoleSerializer
|
serializer_class = serializers.UserRoleSerializer
|
||||||
queryset = models.Role.objects.all()
|
queryset = models.Role.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class UserLstView(generics.ListCreateAPIView):
|
||||||
|
"""User list create view."""
|
||||||
|
queryset = User.objects.all()
|
||||||
|
serializer_class = serializers.BackUserSerializer
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
filter_backends = (DjangoFilterBackend,)
|
||||||
|
filterset_fields = (
|
||||||
|
'email_confirmed',
|
||||||
|
'is_staff',
|
||||||
|
'is_active',
|
||||||
|
'is_superuser',
|
||||||
|
'roles',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""User RUD view."""
|
||||||
|
queryset = User.objects.all()
|
||||||
|
serializer_class = serializers.BackDetailUserSerializer
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
lookup_field = 'id'
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class SendConfirmationEmailView(generics.GenericAPIView):
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmEmailView(JWTGenericViewMixin):
|
class ConfirmEmailView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""View for confirm changing email"""
|
"""View for confirm changing email"""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class PasswordResetView(generics.GenericAPIView):
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmView(JWTGenericViewMixin):
|
class PasswordResetConfirmView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""View for confirmation new password"""
|
"""View for confirmation new password"""
|
||||||
serializer_class = serializers.PasswordResetConfirmSerializer
|
serializer_class = serializers.PasswordResetConfirmSerializer
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
|
||||||
17
apps/advertisement/migrations/0008_auto_20191116_1135.py
Normal file
17
apps/advertisement/migrations/0008_auto_20191116_1135.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-16 11:35
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('advertisement', '0007_auto_20191115_0750'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='advertisement',
|
||||||
|
options={'verbose_name': 'Advertisement', 'verbose_name_plural': 'Advertisements'},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from main.models import Page
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin, URLImageMixin
|
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin, URLImageMixin
|
||||||
from main.models import Page
|
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementQuerySet(models.QuerySet):
|
class AdvertisementQuerySet(models.QuerySet):
|
||||||
|
|
@ -25,6 +26,10 @@ class AdvertisementQuerySet(models.QuerySet):
|
||||||
"""Filter by locale."""
|
"""Filter by locale."""
|
||||||
return self.filter(target_languages__locale=locale)
|
return self.filter(target_languages__locale=locale)
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
"""Return only valid advertisements."""
|
||||||
|
return self.filter(end__lte=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
class Advertisement(ProjectBaseMixin):
|
class Advertisement(ProjectBaseMixin):
|
||||||
"""Advertisement model."""
|
"""Advertisement model."""
|
||||||
|
|
@ -50,11 +55,17 @@ class Advertisement(ProjectBaseMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Advertisement')
|
verbose_name = _('Advertisement')
|
||||||
verbose_name_plural = _('Advertisement')
|
verbose_name_plural = _('Advertisements')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.url)
|
return str(self.url)
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
"""Overridden delete method."""
|
||||||
|
# Delete all related pages.
|
||||||
|
self.pages.all().delete()
|
||||||
|
return super().delete(using, keep_parents)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mobile_page(self):
|
def mobile_page(self):
|
||||||
"""Return mobile page"""
|
"""Return mobile page"""
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
from .common import *
|
from .common import *
|
||||||
from .mobile import *
|
from .mobile import *
|
||||||
from .web import *
|
from .web import *
|
||||||
|
from .back import *
|
||||||
|
|
|
||||||
26
apps/advertisement/serializers/back.py
Normal file
26
apps/advertisement/serializers/back.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
"""Serializers for back office app advertisements"""
|
||||||
|
from main.serializers import PageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementPageBaseSerializer(PageBaseSerializer):
|
||||||
|
"""Base serializer for linking page w/ advertisement."""
|
||||||
|
|
||||||
|
class Meta(PageBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
PageBaseSerializer.Meta.extra_kwargs.update({
|
||||||
|
'advertisement': {'write_only': True},
|
||||||
|
'image_url': {'required': True},
|
||||||
|
'width': {'required': True},
|
||||||
|
'height': {'required': True},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementPageListCreateSerializer(AdvertisementPageBaseSerializer):
|
||||||
|
"""Serializer for linking page w/ advertisement."""
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
|
||||||
|
validated_data['advertisement'] = self.context.get('view').get_object()
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
@ -3,15 +3,28 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from advertisement import models
|
from advertisement import models
|
||||||
from translation.serializers import LanguageSerializer
|
from translation.serializers import LanguageSerializer
|
||||||
from main.serializers import SiteShortSerializer
|
from main.serializers import SiteShortSerializer, PageBaseSerializer
|
||||||
from main.serializers import PageBaseSerializer
|
from translation.models import Language
|
||||||
|
from main.models import SiteSettings
|
||||||
|
|
||||||
|
|
||||||
class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Base serializer for model Advertisement."""
|
"""Base serializer for model Advertisement."""
|
||||||
|
|
||||||
languages = LanguageSerializer(many=True, read_only=True)
|
languages = LanguageSerializer(many=True, read_only=True,
|
||||||
|
source='target_languages')
|
||||||
|
target_languages = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=Language.objects.all(),
|
||||||
|
many=True,
|
||||||
|
write_only=True
|
||||||
|
)
|
||||||
sites = SiteShortSerializer(many=True, read_only=True)
|
sites = SiteShortSerializer(many=True, read_only=True)
|
||||||
|
target_sites = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=SiteSettings.objects.all(),
|
||||||
|
many=True,
|
||||||
|
write_only=True,
|
||||||
|
source='sites'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Advertisement
|
model = models.Advertisement
|
||||||
|
|
@ -21,7 +34,9 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
||||||
'url',
|
'url',
|
||||||
'block_level',
|
'block_level',
|
||||||
'languages',
|
'languages',
|
||||||
|
'target_languages',
|
||||||
'sites',
|
'sites',
|
||||||
|
'target_sites',
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,42 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from transfer.models import Ads
|
from transfer.models import Ads
|
||||||
from transfer.serializers.advertisement import AdvertisementSerializer
|
from transfer.serializers.advertisement import AdvertisementSerializer, AdvertisementImageSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_advertisement():
|
def transfer_advertisement():
|
||||||
queryset = Ads.objects.filter(href__isnull=False).values_list('id', 'href', 'attachment_suffix_url')
|
errors = []
|
||||||
|
queryset = Ads.objects.exclude(href__isnull=True) \
|
||||||
|
.exclude(attachment_suffix_url__isnull=True) \
|
||||||
|
.exclude(site_id__isnull=True)
|
||||||
|
|
||||||
serialized_data = AdvertisementSerializer(data=list(queryset.values()), many=True)
|
serialized_data = AdvertisementSerializer(data=list(queryset.values()), many=True)
|
||||||
|
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"News serializer errors: {serialized_data.errors}")
|
for d in serialized_data.errors: errors.append(d) if d else None
|
||||||
|
pprint(f"transfer_product errors: {errors}")
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_page():
|
||||||
|
errors = []
|
||||||
|
queryset = Ads.objects.exclude(href__isnull=True) \
|
||||||
|
.exclude(attachment_suffix_url__isnull=True) \
|
||||||
|
.exclude(site_id__isnull=True)
|
||||||
|
|
||||||
|
serialized_data = AdvertisementImageSerializer(data=list(queryset.values()), many=True)
|
||||||
|
|
||||||
|
if serialized_data.is_valid():
|
||||||
|
serialized_data.save()
|
||||||
|
else:
|
||||||
|
for d in serialized_data.errors: errors.append(d) if d else None
|
||||||
|
pprint(f"transfer_page errors: {errors}")
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"commercial": [transfer_advertisement]
|
"commercial": [
|
||||||
|
transfer_advertisement,
|
||||||
|
transfer_page
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
apps/advertisement/urls/back.py
Normal file
18
apps/advertisement/urls/back.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
"""Advertisement back office urlpaths."""
|
||||||
|
from django.urls import path
|
||||||
|
from advertisement import views
|
||||||
|
from advertisement.urls.common import common_urlpatterns
|
||||||
|
|
||||||
|
|
||||||
|
app_name = 'advertisements'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.AdvertisementListCreateView.as_view(), name='list-create'),
|
||||||
|
path('<int:pk>/', views.AdvertisementRUDView.as_view(), name='rud'),
|
||||||
|
path('<int:pk>/pages/', views.AdvertisementPageListCreateView.as_view(),
|
||||||
|
name='page-list-create'),
|
||||||
|
path('<int:ad_pk>/pages/<int:page_pk>/', views.AdvertisementPageRUDView.as_view(),
|
||||||
|
name='page-rud')
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns += common_urlpatterns
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Advertisement common urlpaths."""
|
"""Advertisement common urlpaths."""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from advertisement import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'advertisements'
|
app_name = 'advertisements'
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
from .common import *
|
from .common import *
|
||||||
from .mobile import *
|
from .mobile import *
|
||||||
from .web import *
|
from .web import *
|
||||||
|
from .back import *
|
||||||
|
|
|
||||||
77
apps/advertisement/views/back.py
Normal file
77
apps/advertisement/views/back.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
"""Back office views for app advertisement"""
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework import permissions
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from advertisement.models import Advertisement
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from advertisement.serializers import (AdvertisementBaseSerializer,
|
||||||
|
AdvertisementPageBaseSerializer,
|
||||||
|
AdvertisementPageListCreateSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementBackOfficeViewMixin(generics.GenericAPIView):
|
||||||
|
"""Base back office advertisement view."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get queryset method."""
|
||||||
|
return Advertisement.objects.with_base_related()
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementListCreateView(AdvertisementBackOfficeViewMixin, generics.ListCreateAPIView):
|
||||||
|
"""List|Create advertisement view."""
|
||||||
|
|
||||||
|
serializer_class = AdvertisementBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementRUDView(AdvertisementBackOfficeViewMixin,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Retrieve|Update|Destroy advertisement page view."""
|
||||||
|
|
||||||
|
serializer_class = AdvertisementBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin,
|
||||||
|
generics.ListCreateAPIView):
|
||||||
|
"""Retrieve|Update|Destroy advertisement page view."""
|
||||||
|
|
||||||
|
serializer_class = AdvertisementPageListCreateSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
ad_qs = Advertisement.objects.all()
|
||||||
|
filtered_ad_qs = self.filter_queryset(ad_qs)
|
||||||
|
|
||||||
|
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, ad)
|
||||||
|
|
||||||
|
return ad
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
return self.get_object().pages.all()
|
||||||
|
|
||||||
|
|
||||||
|
class AdvertisementPageRUDView(AdvertisementBackOfficeViewMixin,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Create|Retrieve|Update|Destroy advertisement page view."""
|
||||||
|
|
||||||
|
serializer_class = AdvertisementPageBaseSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
ad_qs = Advertisement.objects.all()
|
||||||
|
filtered_ad_qs = self.filter_queryset(ad_qs)
|
||||||
|
|
||||||
|
ad = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('ad_pk'))
|
||||||
|
page = get_object_or_404(ad.pages.all(), pk=self.kwargs.get('page_pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, page)
|
||||||
|
|
||||||
|
return page
|
||||||
|
|
@ -71,7 +71,7 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
|
||||||
|
|
||||||
|
|
||||||
# Sign in via Facebook
|
# Sign in via Facebook
|
||||||
class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
|
class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""
|
"""
|
||||||
Implements an endpoint to convert a provider token to an access token
|
Implements an endpoint to convert a provider token to an access token
|
||||||
|
|
||||||
|
|
@ -142,7 +142,7 @@ class SignUpView(generics.GenericAPIView):
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationEmailView(JWTGenericViewMixin):
|
class ConfirmationEmailView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""View for confirmation email"""
|
"""View for confirmation email"""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
|
@ -174,7 +174,7 @@ class ConfirmationEmailView(JWTGenericViewMixin):
|
||||||
|
|
||||||
|
|
||||||
# Login by username|email + password
|
# Login by username|email + password
|
||||||
class LoginByUsernameOrEmailView(JWTGenericViewMixin):
|
class LoginByUsernameOrEmailView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""Login by email and password"""
|
"""Login by email and password"""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.LoginByUsernameOrEmailSerializer
|
serializer_class = serializers.LoginByUsernameOrEmailSerializer
|
||||||
|
|
@ -197,7 +197,7 @@ class LoginByUsernameOrEmailView(JWTGenericViewMixin):
|
||||||
|
|
||||||
|
|
||||||
# Logout
|
# Logout
|
||||||
class LogoutView(JWTGenericViewMixin):
|
class LogoutView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""Logout user"""
|
"""Logout user"""
|
||||||
permission_classes = (IsAuthenticatedAndTokenIsValid, )
|
permission_classes = (IsAuthenticatedAndTokenIsValid, )
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ class LogoutView(JWTGenericViewMixin):
|
||||||
|
|
||||||
|
|
||||||
# Refresh token
|
# Refresh token
|
||||||
class RefreshTokenView(JWTGenericViewMixin):
|
class RefreshTokenView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
"""Refresh access_token"""
|
"""Refresh access_token"""
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.RefreshTokenSerializer
|
serializer_class = serializers.RefreshTokenSerializer
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,50 @@ from utils.methods import get_user_ip
|
||||||
class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
""" Checks which service to use if establishmend is managed by any """
|
""" Checks which service to use if establishmend is managed by any """
|
||||||
|
|
||||||
|
_VALID_GUESTONLINE_PERIODS = {'lunch', 'dinner', 'afternoon', 'breakfast'}
|
||||||
|
_GUESTONLINE_PERIODS_TO_PRIOR = {
|
||||||
|
'breakfast': 1,
|
||||||
|
'lunch': 2,
|
||||||
|
'afternoon': 3,
|
||||||
|
'dinner': 4,
|
||||||
|
}
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = CheckBookingSerializer
|
serializer_class = CheckBookingSerializer
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
|
def _fill_period_template(self, period_template, period_name):
|
||||||
|
period_template_copy = period_template.copy()
|
||||||
|
period_template_copy['period'] = period_name
|
||||||
|
return period_template_copy
|
||||||
|
|
||||||
|
def _preprocess_guestonline_response(self, response):
|
||||||
|
periods = response['periods']
|
||||||
|
periods_by_name = {period['period']: period for period in periods if 'period' in period}
|
||||||
|
if not periods_by_name:
|
||||||
|
raise ValueError('Empty guestonline response')
|
||||||
|
|
||||||
|
period_template = iter(periods_by_name.values()).__next__().copy()
|
||||||
|
period_template.pop('total_left_seats')
|
||||||
|
period_template['hours'] = []
|
||||||
|
period_template.pop('period')
|
||||||
|
|
||||||
|
processed_periods = [
|
||||||
|
periods_by_name[period_name]
|
||||||
|
if period_name in periods_by_name
|
||||||
|
else self._fill_period_template(period_template, period_name)
|
||||||
|
for period_name in CheckWhetherBookingAvailable._VALID_GUESTONLINE_PERIODS
|
||||||
|
]
|
||||||
|
|
||||||
|
unnamed_periods = filter(lambda period: 'period' not in period, periods)
|
||||||
|
for unnamed_period in unnamed_periods:
|
||||||
|
processed_periods.append(unnamed_period)
|
||||||
|
|
||||||
|
response['periods'] = sorted(processed_periods,
|
||||||
|
key=lambda x: self._GUESTONLINE_PERIODS_TO_PRIOR[x.get('period', 'lunch')])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
is_booking_available = False
|
is_booking_available = False
|
||||||
establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
|
establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
|
||||||
|
|
@ -24,12 +64,12 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
date = request.query_params.get('date')
|
date = request.query_params.get('date')
|
||||||
g_service = GuestonlineService()
|
g_service = GuestonlineService()
|
||||||
l_service = LastableService()
|
l_service = LastableService()
|
||||||
if (not establishment.lastable_id is None) and l_service \
|
if establishment.lastable_id is not None and l_service \
|
||||||
.check_whether_booking_available(establishment.lastable_id, date):
|
.check_whether_booking_available(establishment.lastable_id, date):
|
||||||
is_booking_available = True
|
is_booking_available = True
|
||||||
service = l_service
|
service = l_service
|
||||||
service.service_id = establishment.lastable_id
|
service.service_id = establishment.lastable_id
|
||||||
elif (not establishment.guestonline_id is None) and g_service \
|
elif establishment.guestonline_id is not None and g_service \
|
||||||
.check_whether_booking_available(establishment.guestonline_id,
|
.check_whether_booking_available(establishment.guestonline_id,
|
||||||
**g_service.get_certain_keys(request.query_params,
|
**g_service.get_certain_keys(request.query_params,
|
||||||
{'date', 'persons'})):
|
{'date', 'persons'})):
|
||||||
|
|
@ -41,7 +81,11 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
|
||||||
'available': is_booking_available,
|
'available': is_booking_available,
|
||||||
'type': service.service if service else None,
|
'type': service.service if service else None,
|
||||||
}
|
}
|
||||||
response.update({'details': service.response} if service and service.response else {})
|
|
||||||
|
service_response = self._preprocess_guestonline_response(service.response) \
|
||||||
|
if establishment.guestonline_id is not None \
|
||||||
|
else service.response
|
||||||
|
response.update({'details': service_response} if service and service.response else {})
|
||||||
return Response(data=response, status=200)
|
return Response(data=response, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -97,8 +141,9 @@ class UpdatePendingBooking(generics.UpdateAPIView):
|
||||||
r = service.update_booking(service.get_certain_keys(data, {
|
r = service.update_booking(service.get_certain_keys(data, {
|
||||||
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', 'note',
|
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', 'note',
|
||||||
}, {
|
}, {
|
||||||
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id',
|
'email', 'phone', 'last_name', 'first_name',
|
||||||
}))
|
'country_code', 'pending_booking_id',
|
||||||
|
}))
|
||||||
if isinstance(r, Response):
|
if isinstance(r, Response):
|
||||||
return r
|
return r
|
||||||
if data.get('newsletter'):
|
if data.get('newsletter'):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from collection import models
|
||||||
|
from collection.serializers.common import CollectionBaseSerializer
|
||||||
|
from establishment.models import Establishment
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from location.serializers import CountrySimpleSerializer
|
from location.serializers import CountrySimpleSerializer
|
||||||
from collection.serializers.common import CollectionBaseSerializer
|
from product.models import Product
|
||||||
from collection import models
|
from utils.exceptions import (
|
||||||
|
BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
||||||
|
|
@ -31,3 +37,54 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionBindObjectSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for binding collection and objects"""
|
||||||
|
|
||||||
|
ESTABLISHMENT = 'establishment'
|
||||||
|
PRODUCT = 'product'
|
||||||
|
|
||||||
|
TYPE_CHOICES = (
|
||||||
|
(ESTABLISHMENT, 'Establishment'),
|
||||||
|
(PRODUCT, 'Product'),
|
||||||
|
)
|
||||||
|
|
||||||
|
type = serializers.ChoiceField(TYPE_CHOICES)
|
||||||
|
object_id = serializers.IntegerField()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
view = self.context.get('view')
|
||||||
|
request = self.context.get('request')
|
||||||
|
|
||||||
|
obj_type = attrs.get('type')
|
||||||
|
obj_id = attrs.get('object_id')
|
||||||
|
|
||||||
|
collection = view.get_object()
|
||||||
|
attrs['collection'] = collection
|
||||||
|
|
||||||
|
if obj_type == self.ESTABLISHMENT:
|
||||||
|
establishment = Establishment.objects.filter(pk=obj_id).\
|
||||||
|
first()
|
||||||
|
if not establishment:
|
||||||
|
raise BindingObjectNotFound()
|
||||||
|
if request.method == 'POST' and collection.establishments.\
|
||||||
|
filter(pk=establishment.pk).exists():
|
||||||
|
raise ObjectAlreadyAdded()
|
||||||
|
if request.method == 'DELETE' and not collection.\
|
||||||
|
establishments.filter(pk=establishment.pk).\
|
||||||
|
exists():
|
||||||
|
raise RemovedBindingObjectNotFound()
|
||||||
|
attrs['related_object'] = establishment
|
||||||
|
elif obj_type == self.PRODUCT:
|
||||||
|
product = Product.objects.filter(pk=obj_id).first()
|
||||||
|
if not product:
|
||||||
|
raise BindingObjectNotFound()
|
||||||
|
if request.method == 'POST' and collection.products.\
|
||||||
|
filter(pk=product.pk).exists():
|
||||||
|
raise ObjectAlreadyAdded()
|
||||||
|
if request.method == 'DELETE' and not collection.products.\
|
||||||
|
filter(pk=product.pk).exists():
|
||||||
|
raise RemovedBindingObjectNotFound()
|
||||||
|
attrs['related_object'] = product
|
||||||
|
return attrs
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
"""Collection common urlpaths."""
|
"""Collection common urlpaths."""
|
||||||
from django.urls import path
|
from rest_framework.routers import SimpleRouter
|
||||||
|
|
||||||
from collection.views import back as views
|
from collection.views import back as views
|
||||||
|
|
||||||
app_name = 'collection'
|
app_name = 'collection'
|
||||||
|
router = SimpleRouter()
|
||||||
|
router.register(r'', views.CollectionBackOfficeViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = router.urls
|
||||||
path('', views.CollectionListCreateView.as_view(), name='list-create'),
|
|
||||||
path('<int:pk>/', views.CollectionRUDView.as_view(), name='rud-collection'),
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,49 @@
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import permissions
|
||||||
|
from rest_framework import viewsets, mixins
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
from collection.serializers import back
|
from collection.serializers import back as serializers
|
||||||
|
from utils.views import BindObjectMixin
|
||||||
|
|
||||||
|
|
||||||
class CollectionListCreateView(generics.ListCreateAPIView):
|
class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
"""Collection list-create view."""
|
"""ViewSet for Collection model."""
|
||||||
|
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
queryset = models.Collection.objects.all()
|
queryset = models.Collection.objects.all()
|
||||||
serializer_class = back.CollectionBackOfficeSerializer
|
serializer_class = serializers.CollectionBackOfficeSerializer
|
||||||
# todo: conf. permissions by TT
|
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
|
||||||
|
|
||||||
|
|
||||||
class CollectionRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
"""Collection list-create view."""
|
mixins.UpdateModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
|
BindObjectMixin,
|
||||||
|
CollectionViewSet):
|
||||||
|
"""ViewSet for Collection model for BackOffice users."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
queryset = models.Collection.objects.all()
|
queryset = models.Collection.objects.all()
|
||||||
serializer_class = back.CollectionBackOfficeSerializer
|
serializer_class = serializers.CollectionBackOfficeSerializer
|
||||||
# todo: conf. permissions by TT
|
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
|
||||||
|
def perform_binding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
collection = data.pop('collection')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
|
||||||
|
collection.establishments.add(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.PRODUCT:
|
||||||
|
collection.products.add(related_object)
|
||||||
|
|
||||||
|
def perform_unbinding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
collection = data.pop('collection')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
|
||||||
|
collection.establishments.remove(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.PRODUCT:
|
||||||
|
collection.products.remove(related_object)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
from rest_framework import generics
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import permissions
|
from rest_framework import generics, permissions, pagination
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
from utils.pagination import ProjectPageNumberPagination
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from establishment.serializers import EstablishmentBaseSerializer
|
|
||||||
from collection.serializers import common as serializers
|
from collection.serializers import common as serializers
|
||||||
|
from establishment.serializers import EstablishmentSimilarSerializer
|
||||||
|
from utils.pagination import ProjectPageNumberPagination, ProjectMobilePagination
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
# Mixins
|
||||||
|
|
@ -18,8 +17,8 @@ class CollectionViewMixin(generics.GenericAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return models.Collection.objects.published() \
|
return models.Collection.objects.published() \
|
||||||
.by_country_code(code=self.request.country_code) \
|
.by_country_code(code=self.request.country_code) \
|
||||||
.order_by('-on_top', '-modified')
|
.order_by('-on_top', '-modified')
|
||||||
|
|
||||||
|
|
||||||
class GuideViewMixin(generics.GenericAPIView):
|
class GuideViewMixin(generics.GenericAPIView):
|
||||||
|
|
@ -40,7 +39,7 @@ class CollectionHomePageView(CollectionListView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset."""
|
"""Override get_queryset."""
|
||||||
return super(CollectionHomePageView, self).get_queryset() \
|
return super(CollectionHomePageView, self).get_queryset() \
|
||||||
.filter_all_related_gt(3)
|
.filter_all_related_gt(3)
|
||||||
|
|
||||||
|
|
||||||
class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView):
|
class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView):
|
||||||
|
|
@ -52,8 +51,8 @@ class CollectionDetailView(CollectionViewMixin, generics.RetrieveAPIView):
|
||||||
class CollectionEstablishmentListView(CollectionListView):
|
class CollectionEstablishmentListView(CollectionListView):
|
||||||
"""Retrieve list of establishment for collection."""
|
"""Retrieve list of establishment for collection."""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
pagination_class = ProjectPageNumberPagination
|
pagination_class = ProjectMobilePagination
|
||||||
serializer_class = EstablishmentBaseSerializer
|
serializer_class = EstablishmentSimilarSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -66,7 +65,7 @@ class CollectionEstablishmentListView(CollectionListView):
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, collection)
|
self.check_object_permissions(self.request, collection)
|
||||||
|
|
||||||
return collection.establishments.all()
|
return collection.establishments.all().annotate_in_favorites(self.request.user)
|
||||||
|
|
||||||
|
|
||||||
# Guide
|
# Guide
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from comment.models import Comment
|
||||||
from utils.admin import BaseModelAdminMixin
|
from utils.admin import BaseModelAdminMixin
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from main.models import Award
|
from main.models import Award
|
||||||
from product.models import Product
|
from product.models import Product, PurchasedProduct
|
||||||
from review import models as review_models
|
from review import models as review_models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -32,6 +32,12 @@ class ContactPhoneInline(admin.TabularInline):
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class GalleryImageInline(admin.TabularInline):
|
||||||
|
"""Gallery image inline admin."""
|
||||||
|
model = models.EstablishmentGallery
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class ContactEmailInline(admin.TabularInline):
|
class ContactEmailInline(admin.TabularInline):
|
||||||
"""Contact email inline admin."""
|
"""Contact email inline admin."""
|
||||||
model = models.ContactEmail
|
model = models.ContactEmail
|
||||||
|
|
@ -53,12 +59,29 @@ class ProductInline(admin.TabularInline):
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyInline(admin.TabularInline):
|
||||||
|
model = models.Company
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentNote(admin.TabularInline):
|
||||||
|
model = models.EstablishmentNote
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class PurchasedProduct(admin.TabularInline):
|
||||||
|
model = PurchasedProduct
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Establishment)
|
@admin.register(models.Establishment)
|
||||||
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
list_display = ['id', '__str__', 'image_tag', ]
|
list_display = ['id', '__str__', 'image_tag', ]
|
||||||
search_fields = ['id', 'name', 'index_name', 'slug']
|
search_fields = ['id', 'name', 'index_name', 'slug']
|
||||||
list_filter = ['public_mark', 'toque_number']
|
list_filter = ['public_mark', 'toque_number']
|
||||||
|
inlines = [GalleryImageInline, CompanyInline, EstablishmentNote,
|
||||||
|
PurchasedProduct]
|
||||||
|
|
||||||
# inlines = [
|
# inlines = [
|
||||||
# AwardInline, ContactPhoneInline, ContactEmailInline,
|
# AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||||
|
|
@ -107,3 +130,9 @@ class SocialChoiceAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class SocialNetworkAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""Admin conf for SocialNetwork model."""
|
"""Admin conf for SocialNetwork model."""
|
||||||
raw_id_fields = ('establishment',)
|
raw_id_fields = ('establishment',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Company)
|
||||||
|
class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
|
"""Admin conf for Company model."""
|
||||||
|
raw_id_fields = ['establishment', 'address', ]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import Establishment, EstablishmentGallery
|
||||||
|
from gallery.models import Image
|
||||||
|
import requests
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Fill establishment gallery from existing images'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
count = 0
|
||||||
|
not_valid_link_counter = 0
|
||||||
|
not_valid_urls = []
|
||||||
|
|
||||||
|
cdn_prefix = 'https://1dc3f33f6d-3.optimicdn.com/gaultmillau.com/'
|
||||||
|
establishments = Establishment.objects.exclude(image_url__isnull=True) \
|
||||||
|
.exclude(preview_image_url__isnull=True)
|
||||||
|
for establishment in establishments:
|
||||||
|
image_url = establishment.image_url.rstrip()
|
||||||
|
relative_image_path = image_url[len(cdn_prefix):]
|
||||||
|
|
||||||
|
#response = requests.head(image_url, allow_redirects=True)
|
||||||
|
#if response.status_code != status.HTTP_200_OK:
|
||||||
|
# not_valid_link_counter += 1
|
||||||
|
# not_valid_urls.append(image_url)
|
||||||
|
|
||||||
|
image, image_created = Image.objects.get_or_create(
|
||||||
|
orientation=Image.HORIZONTAL,
|
||||||
|
title=f'{establishment.name} - {relative_image_path}',
|
||||||
|
image=relative_image_path)
|
||||||
|
gallery, _ = EstablishmentGallery.objects.get_or_create(establishment=establishment,
|
||||||
|
image=image,
|
||||||
|
is_main=True)
|
||||||
|
if image_created:
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Created/updated {count} objects.\n'
|
||||||
|
f'Not valid link counter: {not_valid_link_counter}\n'
|
||||||
|
f'List of non valid image url: {not_valid_urls}'))
|
||||||
38
apps/establishment/migrations/0062_auto_20191117_1117.py
Normal file
38
apps/establishment/migrations/0062_auto_20191117_1117.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-17 11:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0006_merge_20191027_1758'),
|
||||||
|
('establishment', '0061_auto_20191114_0550'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='establishmentnote',
|
||||||
|
options={'verbose_name': 'establishment note', 'verbose_name_plural': 'establishment notes'},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentGallery',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_main', models.BooleanField(default=False, verbose_name='Is the main image')),
|
||||||
|
('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='establishment_gallery', to='establishment.Establishment', verbose_name='establishment')),
|
||||||
|
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='establishment_gallery', to='gallery.Image', verbose_name='image')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'establishment gallery',
|
||||||
|
'verbose_name_plural': 'establishment galleries',
|
||||||
|
'unique_together': {('establishment', 'is_main'), ('establishment', 'image')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='gallery',
|
||||||
|
field=models.ManyToManyField(through='establishment.EstablishmentGallery', to='gallery.Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
40
apps/establishment/migrations/0063_company.py
Normal file
40
apps/establishment/migrations/0063_company.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-18 14:19
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0027_auto_20191118_1313'),
|
||||||
|
('establishment', '0062_auto_20191117_1117'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Company',
|
||||||
|
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')),
|
||||||
|
('phones', django.contrib.postgres.fields.ArrayField(base_field=phonenumber_field.modelfields.PhoneNumberField(max_length=128), blank=True, default=None, null=True, size=None, verbose_name='contact phones')),
|
||||||
|
('faxes', django.contrib.postgres.fields.ArrayField(base_field=phonenumber_field.modelfields.PhoneNumberField(max_length=128), blank=True, default=None, null=True, size=None, verbose_name='fax numbers')),
|
||||||
|
('legal_entity', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='legal entity')),
|
||||||
|
('registry_number', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='registry number')),
|
||||||
|
('vat_number', models.CharField(blank=True, default=None, max_length=30, null=True, verbose_name='VAT identification number')),
|
||||||
|
('sic_code', models.IntegerField(blank=True, default=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)], verbose_name='sic code')),
|
||||||
|
('address', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='companies', to='location.Address', verbose_name='address')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='companies', to='establishment.Establishment', verbose_name='establishment')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'company',
|
||||||
|
'verbose_name_plural': 'companies',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/establishment/migrations/0064_auto_20191119_1546.py
Normal file
19
apps/establishment/migrations/0064_auto_20191119_1546.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-19 15:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0063_company'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishmentnote',
|
||||||
|
name='establishment',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='notes', to='establishment.Establishment', verbose_name='establishment'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-20 12:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0018_purchasedproduct'),
|
||||||
|
('establishment', '0064_auto_20191119_1546'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='purchased_products',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='Attribute from legacy db.\nMust be deleted after the implementation of the market.', related_name='establishments', through='product.PurchasedProduct', to='product.Product', verbose_name='purchased plaques'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -9,7 +9,9 @@ from django.contrib.contenttypes import fields as generic
|
||||||
from django.contrib.gis.db.models.functions import Distance
|
from django.contrib.gis.db.models.functions import Distance
|
||||||
from django.contrib.gis.geos import Point
|
from django.contrib.gis.geos import Point
|
||||||
from django.contrib.gis.measure import Distance as DistanceMeasure
|
from django.contrib.gis.measure import Distance as DistanceMeasure
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
@ -22,7 +24,8 @@ from location.models import Address
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes)
|
TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin,
|
||||||
|
IntermediateGalleryModelMixin, HasTagsMixin)
|
||||||
|
|
||||||
|
|
||||||
# todo: establishment type&subtypes check
|
# todo: establishment type&subtypes check
|
||||||
|
|
@ -316,9 +319,11 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
return self.exclude(address__city__country__in=countries)
|
return self.exclude(address__city__country__in=countries)
|
||||||
|
|
||||||
|
|
||||||
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin, HasTagsMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
||||||
|
# todo: delete image URL fields after moving on gallery
|
||||||
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
name = models.CharField(_('name'), max_length=255, default='')
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
transliterated_name = models.CharField(default='', max_length=255,
|
transliterated_name = models.CharField(default='', max_length=255,
|
||||||
|
|
@ -376,6 +381,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
related_name='establishments',
|
related_name='establishments',
|
||||||
blank=True, default=None,
|
blank=True, default=None,
|
||||||
verbose_name=_('Collections'))
|
verbose_name=_('Collections'))
|
||||||
|
gallery = models.ManyToManyField('gallery.Image', through='EstablishmentGallery')
|
||||||
preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), max_length=255,
|
preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), max_length=255,
|
||||||
blank=True, null=True, default=None)
|
blank=True, null=True, default=None)
|
||||||
slug = models.SlugField(unique=True, max_length=255, null=True,
|
slug = models.SlugField(unique=True, max_length=255, null=True,
|
||||||
|
|
@ -391,6 +397,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
verbose_name=_('currency'))
|
verbose_name=_('currency'))
|
||||||
|
purchased_products = models.ManyToManyField('product.Product', blank=True,
|
||||||
|
through='product.PurchasedProduct',
|
||||||
|
related_name='establishments',
|
||||||
|
verbose_name=_('purchased plaques'),
|
||||||
|
help_text=_('Attribute from legacy db.\n'
|
||||||
|
'Must be deleted after the '
|
||||||
|
'implementation of the market.'))
|
||||||
|
|
||||||
objects = EstablishmentQuerySet.as_manager()
|
objects = EstablishmentQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -403,6 +416,26 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'id:{self.id}-{self.name}'
|
return f'id:{self.id}-{self.name}'
|
||||||
|
|
||||||
|
def clean_fields(self, exclude=None):
|
||||||
|
super().clean_fields(exclude)
|
||||||
|
if self.purchased_products.filter(product_type__index_name='souvenir').exists():
|
||||||
|
raise ValidationError(
|
||||||
|
_('Only souvenirs.'))
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
"""Overridden delete method"""
|
||||||
|
# Delete all related companies
|
||||||
|
self.companies.all().delete()
|
||||||
|
# Delete all related notes
|
||||||
|
self.notes.all().delete()
|
||||||
|
return super().delete(using, keep_parents)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visible_tags(self):
|
||||||
|
return super().visible_tags\
|
||||||
|
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
|
||||||
|
'business_tag', 'business_tags_de'])\
|
||||||
|
|
||||||
# todo: recalculate toque_number
|
# todo: recalculate toque_number
|
||||||
def recalculate_toque_number(self):
|
def recalculate_toque_number(self):
|
||||||
toque_number = 0
|
toque_number = 0
|
||||||
|
|
@ -544,6 +577,12 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
"""Return list products with type wine"""
|
"""Return list products with type wine"""
|
||||||
return self.products.wines()
|
return self.products.wines()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def main_image(self):
|
||||||
|
qs = self.establishment_gallery.main_image()
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first().image
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNoteQuerySet(models.QuerySet):
|
class EstablishmentNoteQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model EstablishmentNote."""
|
"""QuerySet for model EstablishmentNote."""
|
||||||
|
|
@ -554,7 +593,7 @@ class EstablishmentNote(ProjectBaseMixin):
|
||||||
old_id = models.PositiveIntegerField(null=True, blank=True)
|
old_id = models.PositiveIntegerField(null=True, blank=True)
|
||||||
text = models.TextField(verbose_name=_('text'))
|
text = models.TextField(verbose_name=_('text'))
|
||||||
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
||||||
related_name='establishment_notes',
|
related_name='notes',
|
||||||
verbose_name=_('establishment'))
|
verbose_name=_('establishment'))
|
||||||
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
|
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -565,8 +604,26 @@ class EstablishmentNote(ProjectBaseMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
verbose_name_plural = _('product note')
|
verbose_name_plural = _('establishment notes')
|
||||||
verbose_name = _('product notes')
|
verbose_name = _('establishment note')
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentGallery(IntermediateGalleryModelMixin):
|
||||||
|
|
||||||
|
establishment = models.ForeignKey(Establishment, null=True,
|
||||||
|
related_name='establishment_gallery',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('establishment'))
|
||||||
|
image = models.ForeignKey('gallery.Image', null=True,
|
||||||
|
related_name='establishment_gallery',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('image'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('establishment gallery')
|
||||||
|
verbose_name_plural = _('establishment galleries')
|
||||||
|
unique_together = (('establishment', 'is_main'), ('establishment', 'image'))
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseAttributes, TranslatedFieldsMixin):
|
class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||||
|
|
@ -837,3 +894,46 @@ class RatingStrategy(ProjectBaseMixin):
|
||||||
return f'{self.country.code if self.country else "Other country"}. ' \
|
return f'{self.country.code if self.country else "Other country"}. ' \
|
||||||
f'"{self.toque_number}": {self.public_mark_min_value}-' \
|
f'"{self.toque_number}": {self.public_mark_min_value}-' \
|
||||||
f'{self.public_mark_max_value}'
|
f'{self.public_mark_max_value}'
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyQuerySet(models.QuerySet):
|
||||||
|
"""QuerySet for model Company."""
|
||||||
|
|
||||||
|
|
||||||
|
class Company(ProjectBaseMixin):
|
||||||
|
"""Establishment company model."""
|
||||||
|
|
||||||
|
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
||||||
|
related_name='companies',
|
||||||
|
verbose_name=_('establishment'))
|
||||||
|
name = models.CharField(max_length=255, verbose_name=_('name'))
|
||||||
|
phones = ArrayField(PhoneNumberField(max_length=128),
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('contact phones'))
|
||||||
|
faxes = ArrayField(PhoneNumberField(max_length=128),
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('fax numbers'))
|
||||||
|
legal_entity = models.CharField(max_length=255,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('legal entity'))
|
||||||
|
registry_number = models.CharField(max_length=255,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('registry number'))
|
||||||
|
vat_number = models.CharField(max_length=30,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('VAT identification number'))
|
||||||
|
sic_code = models.IntegerField(validators=[MinValueValidator(1),
|
||||||
|
MaxValueValidator(9999)],
|
||||||
|
blank=True, null=True, default=True,
|
||||||
|
verbose_name=_('sic code'))
|
||||||
|
address = models.ForeignKey(Address, on_delete=models.PROTECT,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
related_name='companies',
|
||||||
|
verbose_name=_('address'))
|
||||||
|
|
||||||
|
objects = CompanyQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('company')
|
||||||
|
verbose_name_plural = _('companies')
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,32 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from establishment.serializers import (
|
from establishment import serializers as model_serializers
|
||||||
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
|
|
||||||
ContactPhonesSerializer, SocialNetworkRelatedSerializers,
|
|
||||||
EstablishmentTypeBaseSerializer)
|
|
||||||
from location.serializers import AddressDetailSerializer
|
from location.serializers import AddressDetailSerializer
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
from utils.decorators import with_base_attributes
|
from utils.decorators import with_base_attributes
|
||||||
from utils.serializers import TimeZoneChoiceField
|
from utils.serializers import TimeZoneChoiceField
|
||||||
|
from gallery.models import Image
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from account.serializers.common import UserShortSerializer
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
"""Establishment create serializer"""
|
"""Establishment create serializer"""
|
||||||
|
|
||||||
type_id = serializers.PrimaryKeyRelatedField(
|
type_id = serializers.PrimaryKeyRelatedField(
|
||||||
source='establishment_type',
|
source='establishment_type',
|
||||||
queryset=models.EstablishmentType.objects.all(), write_only=True
|
queryset=models.EstablishmentType.objects.all(),
|
||||||
|
write_only=True
|
||||||
)
|
)
|
||||||
phones = ContactPhonesSerializer(read_only=True, many=True, )
|
phones = model_serializers.ContactPhonesSerializer(read_only=True,
|
||||||
emails = ContactEmailsSerializer(read_only=True, many=True, )
|
many=True, )
|
||||||
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
|
emails = model_serializers.ContactEmailsSerializer(read_only=True,
|
||||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
many=True, )
|
||||||
|
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
|
||||||
|
many=True, )
|
||||||
|
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type',
|
||||||
|
read_only=True)
|
||||||
tz = TimeZoneChoiceField()
|
tz = TimeZoneChoiceField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -50,7 +55,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
|
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
"""Establishment create serializer"""
|
"""Establishment create serializer"""
|
||||||
|
|
||||||
type_id = serializers.PrimaryKeyRelatedField(
|
type_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
|
@ -58,10 +63,13 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
|
||||||
queryset=models.EstablishmentType.objects.all(), write_only=True
|
queryset=models.EstablishmentType.objects.all(), write_only=True
|
||||||
)
|
)
|
||||||
address = AddressDetailSerializer()
|
address = AddressDetailSerializer()
|
||||||
phones = ContactPhonesSerializer(read_only=False, many=True, )
|
phones = model_serializers.ContactPhonesSerializer(read_only=False,
|
||||||
emails = ContactEmailsSerializer(read_only=False, many=True, )
|
many=True, )
|
||||||
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
|
emails = model_serializers.ContactEmailsSerializer(read_only=False,
|
||||||
type = EstablishmentTypeBaseSerializer(source='establishment_type')
|
many=True, )
|
||||||
|
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
|
||||||
|
many=True, )
|
||||||
|
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
|
|
@ -105,7 +113,7 @@ class SocialNetworkSerializers(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PlatesSerializers(PlateSerializer):
|
class PlatesSerializers(model_serializers.PlateSerializer):
|
||||||
"""Plates serializers."""
|
"""Plates serializers."""
|
||||||
|
|
||||||
currency_id = serializers.PrimaryKeyRelatedField(
|
currency_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
|
@ -117,14 +125,14 @@ class PlatesSerializers(PlateSerializer):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Plate
|
model = models.Plate
|
||||||
fields = PlateSerializer.Meta.fields + [
|
fields = model_serializers.PlateSerializer.Meta.fields + [
|
||||||
'name',
|
'name',
|
||||||
'currency_id',
|
'currency_id',
|
||||||
'menu'
|
'menu'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ContactPhoneBackSerializers(PlateSerializer):
|
class ContactPhoneBackSerializers(model_serializers.PlateSerializer):
|
||||||
"""ContactPhone serializers."""
|
"""ContactPhone serializers."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -136,7 +144,7 @@ class ContactPhoneBackSerializers(PlateSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ContactEmailBackSerializers(PlateSerializer):
|
class ContactEmailBackSerializers(model_serializers.PlateSerializer):
|
||||||
"""ContactEmail serializers."""
|
"""ContactEmail serializers."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -160,3 +168,112 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
'user',
|
'user',
|
||||||
'name'
|
'name'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer class for model EstablishmentGallery."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
|
||||||
|
model = models.EstablishmentGallery
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'is_main',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_request_kwargs(self):
|
||||||
|
"""Get url kwargs from request."""
|
||||||
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method."""
|
||||||
|
establishment_pk = self.get_request_kwargs().get('pk')
|
||||||
|
image_id = self.get_request_kwargs().get('image_id')
|
||||||
|
|
||||||
|
establishment_qs = models.Establishment.objects.filter(pk=establishment_pk)
|
||||||
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
||||||
|
if not establishment_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Establishment not found')})
|
||||||
|
|
||||||
|
if not image_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Image not found')})
|
||||||
|
|
||||||
|
establishment = establishment_qs.first()
|
||||||
|
image = image_qs.first()
|
||||||
|
|
||||||
|
if image in establishment.gallery.all():
|
||||||
|
raise serializers.ValidationError({'detail': _('Image is already added.')})
|
||||||
|
|
||||||
|
attrs['establishment'] = establishment
|
||||||
|
attrs['image'] = image
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCompanyListCreateSerializer(model_serializers.CompanyBaseSerializer):
|
||||||
|
"""Serializer for linking page w/ advertisement."""
|
||||||
|
|
||||||
|
class Meta(model_serializers.CompanyBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
model_serializers.CompanyBaseSerializer.Meta.extra_kwargs.update({
|
||||||
|
'establishment': {'required': False}
|
||||||
|
})
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
validated_data['establishment'] = self.context.get('view').get_object()
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentNoteBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model EstablishmentNote."""
|
||||||
|
|
||||||
|
user_detail = UserShortSerializer(read_only=True, source='user')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.EstablishmentNote
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
'modified',
|
||||||
|
'text',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
|
'establishment',
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'created': {'read_only': True},
|
||||||
|
'modified': {'read_only': True},
|
||||||
|
'establishment': {'required': False, 'write_only': True},
|
||||||
|
'user': {'required': False, 'write_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serializer_view(self):
|
||||||
|
"""Return view instance."""
|
||||||
|
return self.context.get('view')
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentNoteListCreateSerializer(EstablishmentNoteBaseSerializer):
|
||||||
|
"""Serializer for List|Create action for model EstablishmentNote."""
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
validated_data['user'] = self.user
|
||||||
|
validated_data['establishment'] = self.establishment
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
"""Return user instance from view."""
|
||||||
|
if self.serializer_view:
|
||||||
|
return self.serializer_view.request.user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def establishment(self):
|
||||||
|
"""Return establishment instance from view."""
|
||||||
|
if self.serializer_view:
|
||||||
|
return self.serializer_view.get_object()
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,26 @@
|
||||||
"""Establishment serializers."""
|
"""Establishment serializers."""
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from comment.serializers import common as comment_serializers
|
from comment.serializers import common as comment_serializers
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer
|
from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer, \
|
||||||
|
CityShortSerializer
|
||||||
from main.serializers import AwardSerializer, CurrencySerializer
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
|
from review.serializers import ReviewShortSerializer
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from timetable.serialziers import ScheduleRUDSerializer
|
from timetable.serialziers import ScheduleRUDSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
|
from utils.serializers import ImageBaseSerializer
|
||||||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||||
FavoritesCreateSerializer)
|
FavoritesCreateSerializer)
|
||||||
from review.serializers import ReviewShortSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
"""Contact phone serializer"""
|
"""Contact phone serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ContactPhone
|
model = models.ContactPhone
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -26,6 +30,7 @@ class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class ContactEmailsSerializer(serializers.ModelSerializer):
|
class ContactEmailsSerializer(serializers.ModelSerializer):
|
||||||
"""Contact email serializer"""
|
"""Contact email serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ContactEmail
|
model = models.ContactEmail
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -35,6 +40,7 @@ class ContactEmailsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class SocialNetworkRelatedSerializers(serializers.ModelSerializer):
|
class SocialNetworkRelatedSerializers(serializers.ModelSerializer):
|
||||||
"""Social network serializers."""
|
"""Social network serializers."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.SocialNetwork
|
model = models.SocialNetwork
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -45,7 +51,6 @@ class SocialNetworkRelatedSerializers(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class PlateSerializer(ProjectModelSerializer):
|
class PlateSerializer(ProjectModelSerializer):
|
||||||
|
|
||||||
name_translated = TranslatedField()
|
name_translated = TranslatedField()
|
||||||
currency = CurrencySerializer(read_only=True)
|
currency = CurrencySerializer(read_only=True)
|
||||||
|
|
||||||
|
|
@ -176,6 +181,7 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
city = CitySerializer(source='address.city', allow_null=True)
|
city = CitySerializer(source='address.city', allow_null=True)
|
||||||
establishment_type = EstablishmentTypeGeoSerializer()
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
||||||
|
currency = CurrencySerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -188,6 +194,31 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
'city',
|
'city',
|
||||||
'establishment_type',
|
'establishment_type',
|
||||||
'establishment_subtypes',
|
'establishment_subtypes',
|
||||||
|
'currency',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentProductShortSerializer(serializers.ModelSerializer):
|
||||||
|
"""SHORT Serializer for displaying info about an establishment on product page."""
|
||||||
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
|
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
||||||
|
address = AddressBaseSerializer()
|
||||||
|
city = CityShortSerializer(source='address.city', allow_null=True)
|
||||||
|
currency_detail = CurrencySerializer(source='currency', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Establishment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'index_name',
|
||||||
|
'slug',
|
||||||
|
'city',
|
||||||
|
'establishment_type',
|
||||||
|
'establishment_subtypes',
|
||||||
|
'address',
|
||||||
|
'currency_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -206,13 +237,18 @@ class EstablishmentProductSerializer(EstablishmentShortSerializer):
|
||||||
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
"""Base serializer for Establishment model."""
|
"""Base serializer for Establishment model."""
|
||||||
|
|
||||||
preview_image = serializers.URLField(source='preview_image_url')
|
|
||||||
address = AddressBaseSerializer()
|
address = AddressBaseSerializer()
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
in_favorites = serializers.BooleanField(allow_null=True)
|
||||||
tags = TagBaseSerializer(read_only=True, many=True)
|
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags')
|
||||||
currency = CurrencySerializer()
|
currency = CurrencySerializer()
|
||||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||||
|
image = serializers.URLField(source='image_url', read_only=True)
|
||||||
|
preview_image = serializers.URLField(source='preview_image_url',
|
||||||
|
allow_null=True,
|
||||||
|
read_only=True)
|
||||||
|
|
||||||
|
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -227,13 +263,15 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
'toque_number',
|
'toque_number',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
'slug',
|
'slug',
|
||||||
'preview_image',
|
|
||||||
'in_favorites',
|
'in_favorites',
|
||||||
'address',
|
'address',
|
||||||
'tags',
|
'tags',
|
||||||
'currency',
|
'currency',
|
||||||
'type',
|
'type',
|
||||||
'subtypes',
|
'subtypes',
|
||||||
|
'image',
|
||||||
|
'preview_image',
|
||||||
|
'new_image',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -272,7 +310,6 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
"""Serializer for Establishment model."""
|
"""Serializer for Establishment model."""
|
||||||
|
|
||||||
description_translated = TranslatedField()
|
description_translated = TranslatedField()
|
||||||
image = serializers.URLField(source='image_url')
|
|
||||||
awards = AwardSerializer(many=True)
|
awards = AwardSerializer(many=True)
|
||||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||||
phones = ContactPhonesSerializer(read_only=True, many=True)
|
phones = ContactPhonesSerializer(read_only=True, many=True)
|
||||||
|
|
@ -288,6 +325,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
range_price_menu = RangePriceSerializer(read_only=True)
|
range_price_menu = RangePriceSerializer(read_only=True)
|
||||||
range_price_carte = RangePriceSerializer(read_only=True)
|
range_price_carte = RangePriceSerializer(read_only=True)
|
||||||
vintage_year = serializers.ReadOnlyField()
|
vintage_year = serializers.ReadOnlyField()
|
||||||
|
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -313,9 +351,16 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
'range_price_carte',
|
'range_price_carte',
|
||||||
'transportation',
|
'transportation',
|
||||||
'vintage_year',
|
'vintage_year',
|
||||||
|
'gallery',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
|
"""Serializer for Establishment model."""
|
||||||
|
|
||||||
|
address = AddressDetailSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
||||||
"""Create comment serializer"""
|
"""Create comment serializer"""
|
||||||
mark = serializers.IntegerField()
|
mark = serializers.IntegerField()
|
||||||
|
|
@ -380,3 +425,56 @@ class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
})
|
})
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Company base serializer"""
|
||||||
|
phone_list = serializers.SerializerMethodField(source='phones', read_only=True)
|
||||||
|
fax_list = serializers.SerializerMethodField(source='faxes', read_only=True)
|
||||||
|
address_detail = AddressDetailSerializer(source='address', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Company
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'establishment',
|
||||||
|
'name',
|
||||||
|
'phones',
|
||||||
|
'faxes',
|
||||||
|
'legal_entity',
|
||||||
|
'registry_number',
|
||||||
|
'vat_number',
|
||||||
|
'sic_code',
|
||||||
|
'address',
|
||||||
|
'phone_list',
|
||||||
|
'fax_list',
|
||||||
|
'address_detail',
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'establishment': {'write_only': True},
|
||||||
|
'phones': {'write_only': True},
|
||||||
|
'faxes': {'write_only': True},
|
||||||
|
'address': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_phone_list(self, instance):
|
||||||
|
"""Return list of phone numbers."""
|
||||||
|
return instance.phones
|
||||||
|
|
||||||
|
def get_fax_list(self, instance):
|
||||||
|
"""Return list of fax numbers."""
|
||||||
|
return instance.faxes
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Overridden validate method"""
|
||||||
|
phones = [str_to_phonenumber(phone).as_national for phone in attrs.get('phones')]
|
||||||
|
faxes = [str_to_phonenumber(fax).as_national for fax in attrs.get('faxes')]
|
||||||
|
|
||||||
|
if faxes:
|
||||||
|
if models.Company.objects.filter(faxes__overlap=faxes).exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Fax is already reserved.')})
|
||||||
|
|
||||||
|
if phones:
|
||||||
|
if models.Company.objects.filter(phones__overlap=phones).exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Phones is already reserved.')})
|
||||||
|
return attrs
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ from django.db.models import Q, F
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from transfer.models import Establishments, Dishes, EstablishmentNotes
|
from product.models import PurchasedProduct, Product
|
||||||
|
from transfer.models import Establishments, Dishes, EstablishmentNotes, \
|
||||||
|
EstablishmentMerchandises
|
||||||
from transfer.serializers.establishment import EstablishmentSerializer, \
|
from transfer.serializers.establishment import EstablishmentSerializer, \
|
||||||
EstablishmentNoteSerializer
|
EstablishmentNoteSerializer
|
||||||
from transfer.serializers.plate import PlateSerializer
|
from transfer.serializers.plate import PlateSerializer
|
||||||
|
|
@ -140,6 +142,43 @@ def transfer_establishment_note():
|
||||||
pprint(f"transfer_establishment_note errors: {errors}")
|
pprint(f"transfer_establishment_note errors: {errors}")
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_purchased_plaques():
|
||||||
|
update_products_counter = 0
|
||||||
|
already_updated_counter = 0
|
||||||
|
not_existed_establishment_counter = 0
|
||||||
|
|
||||||
|
purchased = EstablishmentMerchandises.objects.values_list(
|
||||||
|
'establishment_id',
|
||||||
|
'merchandise__vintage',
|
||||||
|
'gifted',
|
||||||
|
'quantity'
|
||||||
|
)
|
||||||
|
for old_est_id, vintage, gifted, quantity in purchased:
|
||||||
|
establishment_qs = Establishment.objects.filter(old_id=old_est_id)
|
||||||
|
product_qs = Product.objects.filter(name='Plaque restaurants',
|
||||||
|
vintage=vintage)
|
||||||
|
if establishment_qs.exists() and product_qs.exists():
|
||||||
|
product = product_qs.first()
|
||||||
|
establishment = establishment_qs.first()
|
||||||
|
|
||||||
|
purchases, created = PurchasedProduct.objects.get_or_create(
|
||||||
|
establishment=establishment,
|
||||||
|
product=product,
|
||||||
|
is_gifted=gifted,
|
||||||
|
quantity=quantity
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
update_products_counter += 1
|
||||||
|
else:
|
||||||
|
already_updated_counter += 1
|
||||||
|
else:
|
||||||
|
not_existed_establishment_counter += 1
|
||||||
|
|
||||||
|
print(f'Updated products: {update_products_counter}\n'
|
||||||
|
f'Already updated: {already_updated_counter}\n'
|
||||||
|
f'Not existed establishment: {not_existed_establishment_counter}')
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"establishment": [
|
"establishment": [
|
||||||
transfer_establishment,
|
transfer_establishment,
|
||||||
|
|
@ -149,4 +188,7 @@ data_types = {
|
||||||
transfer_establishment_addresses
|
transfer_establishment_addresses
|
||||||
],
|
],
|
||||||
"menu": [transfer_menu],
|
"menu": [transfer_menu],
|
||||||
|
"purchased_plaques": [
|
||||||
|
transfer_purchased_plaques
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,19 @@ urlpatterns = [
|
||||||
name='schedule-rud'),
|
name='schedule-rud'),
|
||||||
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||||
name='schedule-create'),
|
name='schedule-create'),
|
||||||
|
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||||
|
name='gallery-list'),
|
||||||
|
path('<int:pk>/gallery/<int:image_id>/',
|
||||||
|
views.EstablishmentGalleryCreateDestroyView.as_view(),
|
||||||
|
name='gallery-create-destroy'),
|
||||||
|
path('<int:pk>/companies/', views.EstablishmentCompanyListCreateView.as_view(),
|
||||||
|
name='company-list-create'),
|
||||||
|
path('<int:pk>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(),
|
||||||
|
name='company-rud'),
|
||||||
|
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||||
|
name='note-list-create'),
|
||||||
|
path('<int:pk>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(),
|
||||||
|
name='note-rud'),
|
||||||
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
||||||
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||||
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||||
|
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||||
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
|
from timetable.models import Timetable
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentMixinViews:
|
class EstablishmentMixinViews:
|
||||||
|
|
@ -33,13 +37,14 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment schedule RUD view"""
|
"""Establishment schedule RUD view"""
|
||||||
serializer_class = ScheduleRUDSerializer
|
serializer_class = ScheduleRUDSerializer
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
establishment_pk = self.kwargs['pk']
|
establishment_pk = self.kwargs.get('pk')
|
||||||
schedule_id = self.kwargs['schedule_id']
|
schedule_id = self.kwargs.get('schedule_id')
|
||||||
|
|
||||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||||
pk=establishment_pk)
|
pk=establishment_pk)
|
||||||
|
|
@ -56,6 +61,8 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||||
"""Establishment schedule Create view"""
|
"""Establishment schedule Create view"""
|
||||||
serializer_class = ScheduleCreateSerializer
|
serializer_class = ScheduleCreateSerializer
|
||||||
|
queryset = Timetable.objects.all()
|
||||||
|
permission_classes = [IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
||||||
class MenuListCreateView(generics.ListCreateAPIView):
|
class MenuListCreateView(generics.ListCreateAPIView):
|
||||||
|
|
@ -184,3 +191,130 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment subtype retrieve/update/destroy view."""
|
"""Establishment subtype retrieve/update/destroy view."""
|
||||||
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||||
queryset = models.EstablishmentSubType.objects.all()
|
queryset = models.EstablishmentSubType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
|
CreateDestroyGalleryViewMixin):
|
||||||
|
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
||||||
|
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""
|
||||||
|
Returns the object the view is displaying.
|
||||||
|
"""
|
||||||
|
establishment_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk'))
|
||||||
|
gallery = get_object_or_404(establishment.establishment_gallery,
|
||||||
|
image_id=self.kwargs.get('image_id'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, gallery)
|
||||||
|
|
||||||
|
return gallery
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||||
|
generics.ListAPIView):
|
||||||
|
"""Resource for returning gallery for establishment for back-office users."""
|
||||||
|
serializer_class = serializers.ImageBaseSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Override get_object method."""
|
||||||
|
qs = super(EstablishmentGalleryListView, self).get_queryset()
|
||||||
|
establishment = get_object_or_404(qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
||||||
|
return establishment
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method."""
|
||||||
|
return self.get_object().crop_gallery
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
||||||
|
generics.ListCreateAPIView):
|
||||||
|
"""List|Create establishment company view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.EstablishmentCompanyListCreateSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
establishment_qs = models.Establishment.objects.all()
|
||||||
|
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
|
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
||||||
|
return establishment
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
return self.get_object().companies.all()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Create|Retrieve|Update|Destroy establishment company view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.CompanyBaseSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
establishment_qs = models.Establishment.objects.all()
|
||||||
|
filtered_ad_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
|
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk'))
|
||||||
|
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, company)
|
||||||
|
|
||||||
|
return company
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
||||||
|
generics.ListCreateAPIView):
|
||||||
|
"""Retrieve|Update|Destroy establishment note view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.EstablishmentNoteListCreateSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
establishment_qs = models.Establishment.objects.all()
|
||||||
|
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
|
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
||||||
|
return establishment
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
return self.get_object().notes.all()
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Create|Retrieve|Update|Destroy establishment note view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.EstablishmentNoteBaseSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
establishment_qs = models.Establishment.objects.all()
|
||||||
|
filtered_establishment_qs = self.filter_queryset(establishment_qs)
|
||||||
|
|
||||||
|
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk'))
|
||||||
|
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, note)
|
||||||
|
|
||||||
|
return note
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class EstablishmentRecentReviewListView(EstablishmentListView):
|
||||||
class EstablishmentSimilarListView(EstablishmentListView):
|
class EstablishmentSimilarListView(EstablishmentListView):
|
||||||
"""Resource for getting a list of establishments."""
|
"""Resource for getting a list of establishments."""
|
||||||
|
|
||||||
serializer_class = serializers.EstablishmentBaseSerializer
|
serializer_class = serializers.EstablishmentSimilarSerializer
|
||||||
pagination_class = EstablishmentPortionPagination
|
pagination_class = EstablishmentPortionPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from establishment.filters import EstablishmentFilter
|
from establishment.filters import EstablishmentFilter
|
||||||
from establishment.serializers import EstablishmentBaseSerializer
|
from establishment.serializers import EstablishmentBaseSerializer, EstablishmentSimilarSerializer
|
||||||
from news.filters import NewsListFilterSet
|
from news.filters import NewsListFilterSet
|
||||||
from news.models import News
|
from news.models import News
|
||||||
from news.serializers import NewsBaseSerializer, NewsListSerializer
|
from news.serializers import NewsBaseSerializer, NewsListSerializer
|
||||||
|
|
@ -23,7 +23,7 @@ class FavoritesBaseView(generics.GenericAPIView):
|
||||||
class FavoritesEstablishmentListView(generics.ListAPIView):
|
class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
"""List views for establishments in favorites."""
|
"""List views for establishments in favorites."""
|
||||||
|
|
||||||
serializer_class = EstablishmentBaseSerializer
|
serializer_class = EstablishmentSimilarSerializer
|
||||||
filter_class = EstablishmentFilter
|
filter_class = EstablishmentFilter
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
||||||
19
apps/location/migrations/0027_auto_20191118_1011.py
Normal file
19
apps/location/migrations/0027_auto_20191118_1011.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-11-18 10:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0026_country_is_active'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='winesubregion',
|
||||||
|
name='wine_region',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='wine_sub_region', to='location.WineRegion', verbose_name='wine sub region'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/location/migrations/0027_auto_20191118_1313.py
Normal file
19
apps/location/migrations/0027_auto_20191118_1313.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-18 13:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0026_country_is_active'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='winesubregion',
|
||||||
|
name='wine_region',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='wine_sub_region', to='location.WineRegion', verbose_name='wine sub region'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/location/migrations/0028_merge_20191118_1507.py
Normal file
14
apps/location/migrations/0028_merge_20191118_1507.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-18 15:07
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0027_auto_20191118_1313'),
|
||||||
|
('location', '0027_auto_20191118_1011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/location/migrations/0028_merge_20191119_0647.py
Normal file
14
apps/location/migrations/0028_merge_20191119_0647.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-19 06:47
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0027_auto_20191118_1313'),
|
||||||
|
('location', '0027_auto_20191118_1011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
14
apps/location/migrations/0029_merge_20191119_1438.py
Normal file
14
apps/location/migrations/0029_merge_20191119_1438.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-19 14:38
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0028_merge_20191119_0647'),
|
||||||
|
('location', '0028_merge_20191118_1507'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
34
apps/location/migrations/0030_auto_20191120_1010.py
Normal file
34
apps/location/migrations/0030_auto_20191120_1010.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-20 10:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0006_merge_20191027_1758'),
|
||||||
|
('location', '0029_merge_20191119_1438'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CityGallery',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_main', models.BooleanField(default=False, verbose_name='Is the main image')),
|
||||||
|
('city', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='city_gallery', to='location.City', verbose_name='city')),
|
||||||
|
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='city_gallery', to='gallery.Image', verbose_name='image')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'city gallery',
|
||||||
|
'verbose_name_plural': 'city galleries',
|
||||||
|
'unique_together': {('city', 'is_main'), ('city', 'image')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='city',
|
||||||
|
name='gallery',
|
||||||
|
field=models.ManyToManyField(through='location.CityGallery', to='gallery.Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -10,7 +10,8 @@ from django.contrib.postgres.fields import ArrayField
|
||||||
|
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
||||||
TranslatedFieldsMixin, get_current_locale)
|
TranslatedFieldsMixin, get_current_locale,
|
||||||
|
IntermediateGalleryModelMixin, GalleryModelMixin)
|
||||||
|
|
||||||
|
|
||||||
class CountryQuerySet(models.QuerySet):
|
class CountryQuerySet(models.QuerySet):
|
||||||
|
|
@ -101,9 +102,8 @@ class CityQuerySet(models.QuerySet):
|
||||||
return self.filter(country__code=code)
|
return self.filter(country__code=code)
|
||||||
|
|
||||||
|
|
||||||
class City(models.Model):
|
class City(GalleryModelMixin):
|
||||||
"""Region model."""
|
"""Region model."""
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=250)
|
name = models.CharField(_('name'), max_length=250)
|
||||||
name_translated = TJSONField(blank=True, null=True, default=None,
|
name_translated = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Translated name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Translated name'), help_text='{"en-GB":"some text"}')
|
||||||
|
|
@ -138,16 +138,8 @@ class City(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class CityGalleryQuerySet(models.QuerySet):
|
class CityGallery(IntermediateGalleryModelMixin):
|
||||||
"""QuerySet for model News"""
|
"""Gallery for model City."""
|
||||||
|
|
||||||
def main_image(self):
|
|
||||||
"""Return objects with flag is_main is True"""
|
|
||||||
return self.filter(is_main=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CityGallery(models.Model):
|
|
||||||
old_id = models.IntegerField(blank=True, null=True)
|
|
||||||
city = models.ForeignKey(City, null=True,
|
city = models.ForeignKey(City, null=True,
|
||||||
related_name='city_gallery',
|
related_name='city_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
@ -155,14 +147,10 @@ class CityGallery(models.Model):
|
||||||
image = models.ForeignKey('gallery.Image', null=True,
|
image = models.ForeignKey('gallery.Image', null=True,
|
||||||
related_name='city_gallery',
|
related_name='city_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('gallery'))
|
verbose_name=_('image'))
|
||||||
is_main = models.BooleanField(default=False,
|
|
||||||
verbose_name=_('Is the main image'))
|
|
||||||
|
|
||||||
objects = CityGalleryQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""NewsGallery meta class."""
|
"""CityGallery meta class."""
|
||||||
verbose_name = _('city gallery')
|
verbose_name = _('city gallery')
|
||||||
verbose_name_plural = _('city galleries')
|
verbose_name_plural = _('city galleries')
|
||||||
unique_together = (('city', 'is_main'), ('city', 'image'))
|
unique_together = (('city', 'is_main'), ('city', 'image'))
|
||||||
|
|
@ -216,6 +204,13 @@ class Address(models.Model):
|
||||||
class WineRegionQuerySet(models.QuerySet):
|
class WineRegionQuerySet(models.QuerySet):
|
||||||
"""Wine region queryset."""
|
"""Wine region queryset."""
|
||||||
|
|
||||||
|
def with_sub_region_related(self):
|
||||||
|
return self.prefetch_related('wine_sub_region')
|
||||||
|
|
||||||
|
def having_wines(self, value = True):
|
||||||
|
"""Return qs with regions, which have any wine related to them"""
|
||||||
|
return self.exclude(wines__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class WineRegion(models.Model, TranslatedFieldsMixin):
|
class WineRegion(models.Model, TranslatedFieldsMixin):
|
||||||
"""Wine region model."""
|
"""Wine region model."""
|
||||||
|
|
@ -254,6 +249,7 @@ class WineSubRegion(models.Model):
|
||||||
"""Wine sub region model."""
|
"""Wine sub region model."""
|
||||||
name = models.CharField(_('name'), max_length=255)
|
name = models.CharField(_('name'), max_length=255)
|
||||||
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
|
wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
|
||||||
|
related_name='wine_sub_region',
|
||||||
verbose_name=_('wine sub region'))
|
verbose_name=_('wine sub region'))
|
||||||
old_id = models.PositiveIntegerField(_('old id'), default=None,
|
old_id = models.PositiveIntegerField(_('old id'), default=None,
|
||||||
blank=True, null=True)
|
blank=True, null=True)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
from location import models
|
from location import models
|
||||||
from location.serializers import common
|
from location.serializers import common
|
||||||
|
from rest_framework import serializers
|
||||||
|
from gallery.models import Image
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class AddressCreateSerializer(common.AddressDetailSerializer):
|
class AddressCreateSerializer(common.AddressDetailSerializer):
|
||||||
|
|
@ -18,3 +21,45 @@ class CountryBackSerializer(common.CountrySerializer):
|
||||||
'name',
|
'name',
|
||||||
'country_id'
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CityGallerySerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer class for model CityGallery."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
|
||||||
|
model = models.CityGallery
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'is_main',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_request_kwargs(self):
|
||||||
|
"""Get url kwargs from request."""
|
||||||
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method."""
|
||||||
|
city_pk = self.get_request_kwargs().get('pk')
|
||||||
|
image_id = self.get_request_kwargs().get('image_id')
|
||||||
|
|
||||||
|
city_qs = models.City.objects.filter(pk=city_pk)
|
||||||
|
image_qs = Image.objects.filter(id=image_id)
|
||||||
|
|
||||||
|
if not city_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('City not found')})
|
||||||
|
|
||||||
|
if not image_qs.exists():
|
||||||
|
raise serializers.ValidationError({'detail': _('Image not found')})
|
||||||
|
|
||||||
|
city = city_qs.first()
|
||||||
|
image = image_qs.first()
|
||||||
|
|
||||||
|
if image in city.gallery.all():
|
||||||
|
raise serializers.ValidationError({'detail': _('Image is already added.')})
|
||||||
|
|
||||||
|
attrs['city'] = city
|
||||||
|
attrs['image'] = image
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,20 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||||
'country_id'
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class CityShortSerializer(serializers.ModelSerializer):
|
||||||
|
"""Short city serializer"""
|
||||||
|
country = CountrySerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
model = models.City
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'code',
|
||||||
|
'country',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CropImageSerializer(serializers.Serializer):
|
class CropImageSerializer(serializers.Serializer):
|
||||||
"""Serializer for crop images for City object."""
|
"""Serializer for crop images for City object."""
|
||||||
|
|
@ -273,3 +287,14 @@ class WineSubRegionBaseSerializer(serializers.ModelSerializer):
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegionSerializer(WineRegionBaseSerializer):
|
||||||
|
"""Wine region w/ subregion serializer"""
|
||||||
|
|
||||||
|
wine_sub_region = WineSubRegionBaseSerializer(allow_null=True, many=True)
|
||||||
|
|
||||||
|
class Meta(WineRegionBaseSerializer.Meta):
|
||||||
|
fields = WineRegionBaseSerializer.Meta.fields + [
|
||||||
|
'wine_sub_region'
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
from transfer.serializers import location as location_serializers
|
from transfer.serializers import location as location_serializers
|
||||||
from transfer import models as transfer_models
|
from transfer import models as transfer_models
|
||||||
from location.models import Country, Region, City, Address, WineRegion
|
from location.models import Country, Region, City, Address, WineRegion, CityGallery
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from gallery.models import Image
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import MultipleObjectsReturned
|
from django.core.exceptions import MultipleObjectsReturned
|
||||||
from collection.models import Collection
|
from collection.models import Collection
|
||||||
|
|
@ -494,6 +497,42 @@ def fix_location_models():
|
||||||
fix_chosen_tag()
|
fix_chosen_tag()
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_city_gallery():
|
||||||
|
created_counter = 0
|
||||||
|
cities_not_exists = {}
|
||||||
|
gallery_obj_exists_counter = 0
|
||||||
|
|
||||||
|
city_gallery = transfer_models.CityPhotos.objects.exclude(city__isnull=True) \
|
||||||
|
.exclude(city__country_code_2__isnull=True) \
|
||||||
|
.exclude(city__country_code_2__iexact='') \
|
||||||
|
.exclude(city__region_code__isnull=True) \
|
||||||
|
.exclude(city__region_code__iexact='') \
|
||||||
|
.values_list('city_id', 'attachment_suffix_url')
|
||||||
|
for old_city_id, image_suffix_url in city_gallery:
|
||||||
|
city = City.objects.filter(old_id=old_city_id)
|
||||||
|
if city.exists():
|
||||||
|
city = city.first()
|
||||||
|
image, _ = Image.objects.get_or_create(image=image_suffix_url,
|
||||||
|
defaults={
|
||||||
|
'image': image_suffix_url,
|
||||||
|
'orientation': Image.HORIZONTAL,
|
||||||
|
'title': f'{city.name} - {image_suffix_url}',
|
||||||
|
})
|
||||||
|
city_gallery, created = CityGallery.objects.get_or_create(image=image,
|
||||||
|
city=city,
|
||||||
|
is_main=True)
|
||||||
|
if created:
|
||||||
|
created_counter += 1
|
||||||
|
else:
|
||||||
|
gallery_obj_exists_counter += 1
|
||||||
|
else:
|
||||||
|
cities_not_exists.update({'city_old_id': old_city_id})
|
||||||
|
|
||||||
|
print(f'Created: {created_counter}\n'
|
||||||
|
f'City not exists: {cities_not_exists}\n'
|
||||||
|
f'Already added: {gallery_obj_exists_counter}')
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
transfer_countries,
|
transfer_countries,
|
||||||
|
|
@ -515,6 +554,8 @@ data_types = {
|
||||||
],
|
],
|
||||||
"fix_location": [
|
"fix_location": [
|
||||||
fix_location_models
|
fix_location_models
|
||||||
]
|
],
|
||||||
|
|
||||||
|
"fill_city_gallery": [transfer_city_gallery]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ urlpatterns = [
|
||||||
|
|
||||||
path('cities/', views.CityListCreateView.as_view(), name='city-list-create'),
|
path('cities/', views.CityListCreateView.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(),
|
||||||
|
name='gallery-list'),
|
||||||
|
path('cities/<int:pk>/gallery/<int:image_id>/',
|
||||||
|
views.CityGalleryCreateDestroyView.as_view(),
|
||||||
|
name='gallery-create-destroy'),
|
||||||
|
|
||||||
path('countries/', views.CountryListCreateView.as_view(), name='country-list-create'),
|
path('countries/', views.CountryListCreateView.as_view(), name='country-list-create'),
|
||||||
path('countries/<int:pk>/', views.CountryRUDView.as_view(), name='country-retrieve'),
|
path('countries/<int:pk>/', views.CountryRUDView.as_view(), name='country-retrieve'),
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,6 @@ urlpatterns = [
|
||||||
|
|
||||||
path('regions/', views.RegionListView.as_view(), name='region-list'),
|
path('regions/', views.RegionListView.as_view(), name='region-list'),
|
||||||
path('regions/<int:pk>/', views.RegionRetrieveView.as_view(), name='region-retrieve'),
|
path('regions/<int:pk>/', views.RegionRetrieveView.as_view(), name='region-retrieve'),
|
||||||
|
|
||||||
|
path('wine-regions/', views.WineRegionListView.as_view(), name='wine-region-list'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ from rest_framework import generics
|
||||||
from location import models, serializers
|
from location import models, serializers
|
||||||
from location.views import common
|
from location.views import common
|
||||||
from utils.permissions import IsCountryAdmin
|
from utils.permissions import IsCountryAdmin
|
||||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
|
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from utils.serializers import ImageBaseSerializer
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,6 +39,48 @@ class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
|
class CityGalleryCreateDestroyView(common.CityViewMixin,
|
||||||
|
CreateDestroyGalleryViewMixin):
|
||||||
|
"""Resource for a create gallery for product for back-office users."""
|
||||||
|
serializer_class = serializers.CityGallerySerializer
|
||||||
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""
|
||||||
|
Returns the object the view is displaying.
|
||||||
|
"""
|
||||||
|
city_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
city = get_object_or_404(city_qs, pk=self.kwargs.get('pk'))
|
||||||
|
gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs.get('image_id'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, gallery)
|
||||||
|
|
||||||
|
return gallery
|
||||||
|
|
||||||
|
|
||||||
|
class CityGalleryListView(common.CityViewMixin,
|
||||||
|
generics.ListAPIView):
|
||||||
|
"""Resource for returning gallery for product for back-office users."""
|
||||||
|
serializer_class = ImageBaseSerializer
|
||||||
|
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Override get_object method."""
|
||||||
|
qs = super(CityGalleryListView, self).get_queryset()
|
||||||
|
city = get_object_or_404(qs, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, city)
|
||||||
|
|
||||||
|
return city
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method."""
|
||||||
|
return self.get_object().crop_gallery
|
||||||
|
|
||||||
|
|
||||||
# Region
|
# Region
|
||||||
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model Region"""
|
"""Create view for model Region"""
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions, status
|
from rest_framework import generics, permissions, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from gallery.tasks import delete_image
|
from gallery.tasks import delete_image
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework import permissions
|
||||||
|
from django.db.models.expressions import RawSQL
|
||||||
from location import models, serializers
|
from location import models, serializers
|
||||||
|
from utils.models import get_current_locale
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
# Mixins
|
||||||
|
|
@ -41,7 +44,9 @@ class CountryListView(CountryViewMixin, generics.ListAPIView):
|
||||||
"""List view for model Country."""
|
"""List view for model Country."""
|
||||||
|
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset().order_by(RawSQL("name->>%s", (get_current_locale(),)))
|
||||||
|
return qs
|
||||||
|
|
||||||
class CountryRetrieveView(CountryViewMixin, generics.RetrieveAPIView):
|
class CountryRetrieveView(CountryViewMixin, generics.RetrieveAPIView):
|
||||||
"""Retrieve view for model Country."""
|
"""Retrieve view for model Country."""
|
||||||
|
|
@ -64,6 +69,15 @@ class RegionListView(RegionViewMixin, generics.ListAPIView):
|
||||||
serializer_class = serializers.CountrySerializer
|
serializer_class = serializers.CountrySerializer
|
||||||
|
|
||||||
|
|
||||||
|
class WineRegionListView(generics.ListAPIView):
|
||||||
|
"""List view for model WineRegion"""
|
||||||
|
pagination_class = None
|
||||||
|
model = models.WineRegion
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
queryset = models.WineRegion.objects.with_sub_region_related().having_wines()
|
||||||
|
serializer_class = serializers.WineRegionSerializer
|
||||||
|
|
||||||
|
|
||||||
class RegionDestroyView(RegionViewMixin, generics.DestroyAPIView):
|
class RegionDestroyView(RegionViewMixin, generics.DestroyAPIView):
|
||||||
"""Destroy view for model Country"""
|
"""Destroy view for model Country"""
|
||||||
serializer_class = serializers.CountrySerializer
|
serializer_class = serializers.CountrySerializer
|
||||||
|
|
|
||||||
|
|
@ -40,3 +40,8 @@ class CarouselAdmin(admin.ModelAdmin):
|
||||||
@admin.register(models.PageType)
|
@admin.register(models.PageType)
|
||||||
class PageTypeAdmin(admin.ModelAdmin):
|
class PageTypeAdmin(admin.ModelAdmin):
|
||||||
"""PageType admin."""
|
"""PageType admin."""
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Page)
|
||||||
|
class PageAdmin(admin.ModelAdmin):
|
||||||
|
"""Page admin."""
|
||||||
|
|
|
||||||
40
apps/main/filters.py
Normal file
40
apps/main/filters.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from main import models
|
||||||
|
|
||||||
|
|
||||||
|
class AwardFilter(filters.FilterSet):
|
||||||
|
"""Award filter set."""
|
||||||
|
|
||||||
|
establishment_id = filters.NumberFilter(field_name='object_id', )
|
||||||
|
product_id = filters.NumberFilter(field_name='object_id', )
|
||||||
|
employee_id = filters.NumberFilter(field_name='object_id', )
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Award
|
||||||
|
fields = (
|
||||||
|
'establishment_id',
|
||||||
|
'product_id',
|
||||||
|
'employee_id',
|
||||||
|
'state',
|
||||||
|
'award_type',
|
||||||
|
'vintage_year',
|
||||||
|
)
|
||||||
|
|
||||||
|
def by_establishment_id(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_establishment_id(value, content_type='establishment')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def by_product_id(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_product_id(value, content_type='product')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def by_employee_id(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_employee_id(value, content_type='establishmentemployee')
|
||||||
|
return queryset
|
||||||
|
|
@ -153,7 +153,7 @@ class Award(TranslatedFieldsMixin, URLImageMixin, models.Model):
|
||||||
PUBLISHED = 1
|
PUBLISHED = 1
|
||||||
|
|
||||||
STATE_CHOICES = (
|
STATE_CHOICES = (
|
||||||
(WAITING,'waiting'),
|
(WAITING, 'waiting'),
|
||||||
(PUBLISHED, 'published')
|
(PUBLISHED, 'published')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||||
'route',
|
'route',
|
||||||
'source',
|
'source',
|
||||||
'nested',
|
'nested',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CurrencySerializer(ProjectModelSerializer):
|
class CurrencySerializer(ProjectModelSerializer):
|
||||||
|
|
@ -145,6 +145,19 @@ class AwardSerializer(AwardBaseSerializer):
|
||||||
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
|
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
|
||||||
|
|
||||||
|
|
||||||
|
class BackAwardSerializer(AwardBaseSerializer):
|
||||||
|
"""Award serializer."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Award
|
||||||
|
fields = AwardBaseSerializer.Meta.fields + [
|
||||||
|
'award_type',
|
||||||
|
'state',
|
||||||
|
'content_type',
|
||||||
|
'object_id',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CarouselListSerializer(serializers.ModelSerializer):
|
class CarouselListSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for retrieving list of carousel items."""
|
"""Serializer for retrieving list of carousel items."""
|
||||||
|
|
||||||
|
|
@ -186,7 +199,11 @@ class PageBaseSerializer(serializers.ModelSerializer):
|
||||||
'image_url',
|
'image_url',
|
||||||
'width',
|
'width',
|
||||||
'height',
|
'height',
|
||||||
|
'advertisement',
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'establishment': {'write_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PageTypeBaseSerializer(serializers.ModelSerializer):
|
class PageTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -198,4 +215,4 @@ class PageTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
0
apps/main/tests/__init__.py
Normal file
0
apps/main/tests/__init__.py
Normal file
73
apps/main/tests/tests_back.py
Normal file
73
apps/main/tests/tests_back.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from location.models import Country
|
||||||
|
from main.models import Award, AwardType
|
||||||
|
|
||||||
|
|
||||||
|
class AwardTestCase(APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='alex',
|
||||||
|
email='alex@mail.com',
|
||||||
|
password='alex_password',
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# get tokens
|
||||||
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
|
self.client.cookies = SimpleCookie(
|
||||||
|
{'access_token': tokens.get('access_token'),
|
||||||
|
'refresh_token': tokens.get('refresh_token')})
|
||||||
|
|
||||||
|
self.country_ru = Country.objects.create(
|
||||||
|
name={'en-GB': 'Russian'},
|
||||||
|
code='RU',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.content_type = ContentType.objects.get(app_label="establishment", model="establishment")
|
||||||
|
|
||||||
|
self.award_type = AwardType.objects.create(
|
||||||
|
country=self.country_ru,
|
||||||
|
name="Test award type",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.award = Award.objects.create(
|
||||||
|
award_type=self.award_type,
|
||||||
|
vintage_year='2017',
|
||||||
|
state=Award.PUBLISHED,
|
||||||
|
object_id=1,
|
||||||
|
content_type_id=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_award_CRUD(self):
|
||||||
|
response = self.client.get('/api/back/main/awards/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'award_type': self.award_type.pk,
|
||||||
|
'state': 1,
|
||||||
|
'object_id': 1,
|
||||||
|
'content_type': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post('/api/back/main/awards/', data=data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
response = self.client.get(f'/api/back/main/awards/{self.award.id}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'vintage_year': '2019'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.patch(f'/api/back/main/awards/{self.award.id}/', data=update_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
response = self.client.delete(f'/api/back/main/awards/{self.award.id}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
11
apps/main/urls/back.py
Normal file
11
apps/main/urls/back.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""Back main URLs"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from main.views import back as views
|
||||||
|
|
||||||
|
app_name = 'main'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
|
||||||
|
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
|
||||||
|
]
|
||||||
21
apps/main/views/back.py
Normal file
21
apps/main/views/back.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
|
from main import serializers
|
||||||
|
from main.filters import AwardFilter
|
||||||
|
from main.models import Award
|
||||||
|
|
||||||
|
|
||||||
|
class AwardLstView(generics.ListCreateAPIView):
|
||||||
|
"""Award list create view."""
|
||||||
|
queryset = Award.objects.all()
|
||||||
|
serializer_class = serializers.BackAwardSerializer
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
filterset_class = AwardFilter
|
||||||
|
|
||||||
|
|
||||||
|
class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Award RUD view."""
|
||||||
|
queryset = Award.objects.all()
|
||||||
|
serializer_class = serializers.BackAwardSerializer
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
lookup_field = 'id'
|
||||||
|
|
@ -24,12 +24,19 @@ def send_email_action(modeladmin, request, queryset):
|
||||||
send_email_action.short_description = "Send the selected news by email"
|
send_email_action.short_description = "Send the selected news by email"
|
||||||
|
|
||||||
|
|
||||||
|
class NewsGalleryInline(admin.TabularInline):
|
||||||
|
"""News gallery inline."""
|
||||||
|
model = models.NewsGallery
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.News)
|
@admin.register(models.News)
|
||||||
class NewsAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class NewsAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""News admin."""
|
"""News admin."""
|
||||||
raw_id_fields = ('address',)
|
raw_id_fields = ('address',)
|
||||||
actions = [send_email_action]
|
actions = [send_email_action]
|
||||||
raw_id_fields = ('news_type', 'address', 'country')
|
raw_id_fields = ('news_type', 'address', 'country')
|
||||||
|
inlines = [NewsGalleryInline, ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.NewsGallery)
|
@admin.register(models.NewsGallery)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
),
|
),
|
||||||
method='by_tag_group'
|
method='by_tag_group'
|
||||||
)
|
)
|
||||||
|
tag_value__exclude = filters.CharFilter(method='exclude_tags')
|
||||||
|
tag_value__in = filters.CharFilter(method='in_tags')
|
||||||
|
type = filters.CharFilter(method='by_type')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
|
|
@ -24,8 +27,18 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
'title',
|
'title',
|
||||||
'is_highlighted',
|
'is_highlighted',
|
||||||
'tag_group',
|
'tag_group',
|
||||||
|
'tag_value__exclude',
|
||||||
|
'tag_value__in',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def in_tags(self, queryset, name, value):
|
||||||
|
tags = value.split('__')
|
||||||
|
return queryset.filter(tags__value__in=tags)
|
||||||
|
|
||||||
|
def exclude_tags(self, queryset, name, value):
|
||||||
|
tags = value.split('__')
|
||||||
|
return queryset.exclude(tags__value__in=tags)
|
||||||
|
|
||||||
def by_tag_group(self, queryset, name, value):
|
def by_tag_group(self, queryset, name, value):
|
||||||
if value == models.News.RECIPES_TAG_VALUE:
|
if value == models.News.RECIPES_TAG_VALUE:
|
||||||
queryset = queryset.recipe_news()
|
queryset = queryset.recipe_news()
|
||||||
|
|
@ -39,3 +52,9 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
return queryset.filter(**filters)
|
return queryset.filter(**filters)
|
||||||
else:
|
else:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def by_type(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.filter(news_type__name=value)
|
||||||
|
else:
|
||||||
|
return queryset
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
from rating.models import Rating, ViewCount
|
from rating.models import Rating, ViewCount
|
||||||
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin
|
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
|
ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin)
|
||||||
from utils.querysets import TranslationQuerysetMixin
|
from utils.querysets import TranslationQuerysetMixin
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
|
|
@ -86,6 +88,10 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
"""Returns news with tag 'cook' qs."""
|
"""Returns news with tag 'cook' qs."""
|
||||||
return self.filter(tags__value=News.RECIPES_TAG_VALUE)
|
return self.filter(tags__value=News.RECIPES_TAG_VALUE)
|
||||||
|
|
||||||
|
def international_news(self):
|
||||||
|
"""Returns only international news qs."""
|
||||||
|
return self.filter(tags__value=News.INTERNATIONAL_TAG_VALUE)
|
||||||
|
|
||||||
def published(self):
|
def published(self):
|
||||||
"""Return only published news"""
|
"""Return only published news"""
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
@ -120,7 +126,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class News(BaseAttributes, TranslatedFieldsMixin):
|
class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin):
|
||||||
"""News model."""
|
"""News model."""
|
||||||
|
|
||||||
STR_FIELD_NAME = 'title'
|
STR_FIELD_NAME = 'title'
|
||||||
|
|
@ -151,6 +157,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
|
||||||
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
|
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
INTERNATIONAL_TAG_VALUE = 'international'
|
||||||
RECIPES_TAG_VALUE = 'cook'
|
RECIPES_TAG_VALUE = 'cook'
|
||||||
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
|
@ -242,16 +249,51 @@ class News(BaseAttributes, TranslatedFieldsMixin):
|
||||||
count_value = self.views_count.count
|
count_value = self.views_count.count
|
||||||
return count_value
|
return count_value
|
||||||
|
|
||||||
|
# todo: remove in future
|
||||||
|
@property
|
||||||
|
def crop_gallery(self):
|
||||||
|
if hasattr(self, 'gallery'):
|
||||||
|
gallery = []
|
||||||
|
images = self.gallery.all()
|
||||||
|
model_name = self._meta.model_name.lower()
|
||||||
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
||||||
|
if p.startswith(model_name)]
|
||||||
|
for image in images:
|
||||||
|
d = {
|
||||||
|
'id': image.id,
|
||||||
|
'title': image.title,
|
||||||
|
'original_url': image.image.url,
|
||||||
|
'orientation_display': image.get_orientation_display(),
|
||||||
|
'auto_crop_images': {},
|
||||||
|
}
|
||||||
|
for crop in crop_parameters:
|
||||||
|
d['auto_crop_images'].update(
|
||||||
|
{f'{crop[len(f"{model_name}_"):]}_url': image.get_image_url(crop)})
|
||||||
|
gallery.append(d)
|
||||||
|
return gallery
|
||||||
|
|
||||||
class NewsGalleryQuerySet(models.QuerySet):
|
@property
|
||||||
"""QuerySet for model News"""
|
def crop_main_image(self):
|
||||||
|
if hasattr(self, 'main_image') and self.main_image:
|
||||||
def main_image(self):
|
image = self.main_image
|
||||||
"""Return objects with flag is_main is True"""
|
model_name = self._meta.model_name.lower()
|
||||||
return self.filter(is_main=True)
|
image_property = {
|
||||||
|
'id': image.id,
|
||||||
|
'title': image.title,
|
||||||
|
'original_url': image.image.url,
|
||||||
|
'orientation_display': image.get_orientation_display(),
|
||||||
|
'auto_crop_images': {},
|
||||||
|
}
|
||||||
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
||||||
|
if p.startswith(self._meta.model_name.lower())]
|
||||||
|
for crop in crop_parameters:
|
||||||
|
image_property['auto_crop_images'].update(
|
||||||
|
{f'{crop[len(f"{model_name}_"):]}_url': image.get_image_url(crop)})
|
||||||
|
return image_property
|
||||||
|
|
||||||
|
|
||||||
class NewsGallery(models.Model):
|
class NewsGallery(IntermediateGalleryModelMixin):
|
||||||
|
|
||||||
news = models.ForeignKey(News, null=True,
|
news = models.ForeignKey(News, null=True,
|
||||||
related_name='news_gallery',
|
related_name='news_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
@ -260,10 +302,6 @@ class NewsGallery(models.Model):
|
||||||
related_name='news_gallery',
|
related_name='news_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('gallery'))
|
verbose_name=_('gallery'))
|
||||||
is_main = models.BooleanField(default=False,
|
|
||||||
verbose_name=_('Is the main image'))
|
|
||||||
|
|
||||||
objects = NewsGalleryQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""NewsGallery meta class."""
|
"""NewsGallery meta class."""
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
|
||||||
from news import models
|
from news import models
|
||||||
from tag.serializers import TagBaseSerializer
|
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, FavoritesCreateSerializer
|
from utils.serializers import (TranslatedField, ProjectModelSerializer,
|
||||||
|
FavoritesCreateSerializer, ImageBaseSerializer)
|
||||||
|
|
||||||
|
|
||||||
class AgendaSerializer(ProjectModelSerializer):
|
class AgendaSerializer(ProjectModelSerializer):
|
||||||
|
|
@ -47,78 +48,6 @@ class NewsBannerSerializer(ProjectModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CropImageSerializer(serializers.Serializer):
|
|
||||||
"""Serializer for crop images for News object."""
|
|
||||||
|
|
||||||
preview_url = serializers.SerializerMethodField()
|
|
||||||
promo_horizontal_web_url = serializers.SerializerMethodField()
|
|
||||||
promo_horizontal_mobile_url = serializers.SerializerMethodField()
|
|
||||||
tile_horizontal_web_url = serializers.SerializerMethodField()
|
|
||||||
tile_horizontal_mobile_url = serializers.SerializerMethodField()
|
|
||||||
tile_vertical_web_url = serializers.SerializerMethodField()
|
|
||||||
highlight_vertical_web_url = serializers.SerializerMethodField()
|
|
||||||
editor_web_url = serializers.SerializerMethodField()
|
|
||||||
editor_mobile_url = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
def get_preview_url(self, obj):
|
|
||||||
"""Get crop preview."""
|
|
||||||
return obj.instance.get_image_url('news_preview')
|
|
||||||
|
|
||||||
def get_promo_horizontal_web_url(self, obj):
|
|
||||||
"""Get crop promo_horizontal_web."""
|
|
||||||
return obj.instance.get_image_url('news_promo_horizontal_web')
|
|
||||||
|
|
||||||
def get_promo_horizontal_mobile_url(self, obj):
|
|
||||||
"""Get crop promo_horizontal_mobile."""
|
|
||||||
return obj.instance.get_image_url('news_promo_horizontal_mobile')
|
|
||||||
|
|
||||||
def get_tile_horizontal_web_url(self, obj):
|
|
||||||
"""Get crop tile_horizontal_web."""
|
|
||||||
return obj.instance.get_image_url('news_tile_horizontal_web')
|
|
||||||
|
|
||||||
def get_tile_horizontal_mobile_url(self, obj):
|
|
||||||
"""Get crop tile_horizontal_mobile."""
|
|
||||||
return obj.instance.get_image_url('news_tile_horizontal_mobile')
|
|
||||||
|
|
||||||
def get_tile_vertical_web_url(self, obj):
|
|
||||||
"""Get crop tile_vertical_web."""
|
|
||||||
return obj.instance.get_image_url('news_tile_vertical_web')
|
|
||||||
|
|
||||||
def get_highlight_vertical_web_url(self, obj):
|
|
||||||
"""Get crop highlight_vertical_web."""
|
|
||||||
return obj.instance.get_image_url('news_highlight_vertical_web')
|
|
||||||
|
|
||||||
def get_editor_web_url(self, obj):
|
|
||||||
"""Get crop editor_web."""
|
|
||||||
return obj.instance.get_image_url('news_editor_web')
|
|
||||||
|
|
||||||
def get_editor_mobile_url(self, obj):
|
|
||||||
"""Get crop editor_mobile."""
|
|
||||||
return obj.instance.get_image_url('news_editor_mobile')
|
|
||||||
|
|
||||||
|
|
||||||
class NewsImageSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for returning crop images of news image."""
|
|
||||||
|
|
||||||
orientation_display = serializers.CharField(source='get_orientation_display',
|
|
||||||
read_only=True)
|
|
||||||
original_url = serializers.URLField(source='image.url')
|
|
||||||
auto_crop_images = CropImageSerializer(source='image', allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Image
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'title',
|
|
||||||
'orientation_display',
|
|
||||||
'original_url',
|
|
||||||
'auto_crop_images',
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
'orientation': {'write_only': True}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NewsTypeSerializer(serializers.ModelSerializer):
|
class NewsTypeSerializer(serializers.ModelSerializer):
|
||||||
"""News type serializer."""
|
"""News type serializer."""
|
||||||
|
|
||||||
|
|
@ -135,7 +64,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
|
||||||
title_translated = TranslatedField()
|
title_translated = TranslatedField()
|
||||||
subtitle_translated = TranslatedField()
|
subtitle_translated = TranslatedField()
|
||||||
news_type = NewsTypeSerializer(read_only=True)
|
news_type = NewsTypeSerializer(read_only=True)
|
||||||
tags = TagBaseSerializer(read_only=True, many=True)
|
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags')
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
in_favorites = serializers.BooleanField(allow_null=True)
|
||||||
view_counter = serializers.IntegerField(read_only=True)
|
view_counter = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
|
@ -170,7 +99,7 @@ class NewsSimilarListSerializer(NewsBaseSerializer):
|
||||||
class NewsListSerializer(NewsBaseSerializer):
|
class NewsListSerializer(NewsBaseSerializer):
|
||||||
"""List serializer for News model."""
|
"""List serializer for News model."""
|
||||||
|
|
||||||
image = NewsImageSerializer(source='main_image', allow_null=True)
|
image = ImageBaseSerializer(source='crop_main_image', allow_null=True)
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -188,7 +117,7 @@ class NewsDetailSerializer(NewsBaseSerializer):
|
||||||
author = UserBaseSerializer(source='created_by', read_only=True)
|
author = UserBaseSerializer(source='created_by', read_only=True)
|
||||||
state_display = serializers.CharField(source='get_state_display',
|
state_display = serializers.CharField(source='get_state_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
gallery = NewsImageSerializer(read_only=True, many=True)
|
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
"""News app views."""
|
"""News app views."""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.transaction import on_commit
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions, status
|
from rest_framework import generics, permissions
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from gallery.tasks import delete_image
|
|
||||||
from news import filters, models, serializers
|
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
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
|
from utils.serializers import ImageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class NewsMixinView:
|
class NewsMixinView:
|
||||||
|
|
@ -18,7 +16,7 @@ class NewsMixinView:
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.NewsBaseSerializer
|
serializer_class = serializers.NewsBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self, *args, **kwargs):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
qs = models.News.objects.published() \
|
qs = models.News.objects.published() \
|
||||||
.with_base_related() \
|
.with_base_related() \
|
||||||
|
|
@ -27,7 +25,10 @@ class NewsMixinView:
|
||||||
|
|
||||||
country_code = self.request.country_code
|
country_code = self.request.country_code
|
||||||
if country_code:
|
if country_code:
|
||||||
qs = qs.by_country_code(country_code)
|
if kwargs.get('international_preferred') and country_code in settings.INTERNATIONAL_COUNTRY_CODES:
|
||||||
|
qs = qs.international_news()
|
||||||
|
else:
|
||||||
|
qs = qs.by_country_code(country_code)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,6 +38,10 @@ class NewsListView(NewsMixinView, generics.ListAPIView):
|
||||||
serializer_class = serializers.NewsListSerializer
|
serializer_class = serializers.NewsListSerializer
|
||||||
filter_class = filters.NewsListFilterSet
|
filter_class = filters.NewsListFilterSet
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
kwargs.update({'international_preferred': True})
|
||||||
|
return super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
||||||
"""News detail view."""
|
"""News detail view."""
|
||||||
|
|
@ -77,6 +82,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
|
||||||
"""Resource for a list of news for back-office users."""
|
"""Resource for a list of news for back-office users."""
|
||||||
|
|
||||||
serializer_class = serializers.NewsBackOfficeBaseSerializer
|
serializer_class = serializers.NewsBackOfficeBaseSerializer
|
||||||
|
filter_class = filters.NewsListFilterSet
|
||||||
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
|
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
|
||||||
permission_classes = [IsCountryAdmin | IsContentPageManager]
|
permission_classes = [IsCountryAdmin | IsContentPageManager]
|
||||||
|
|
||||||
|
|
@ -102,8 +108,8 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
"""
|
"""
|
||||||
news_qs = self.filter_queryset(self.get_queryset())
|
news_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
news = get_object_or_404(news_qs, pk=self.kwargs['pk'])
|
news = get_object_or_404(news_qs, pk=self.kwargs.get('pk'))
|
||||||
gallery = get_object_or_404(news.news_gallery, image_id=self.kwargs['image_id'])
|
gallery = get_object_or_404(news.news_gallery, image_id=self.kwargs.get('image_id'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, gallery)
|
self.check_object_permissions(self.request, gallery)
|
||||||
|
|
@ -111,14 +117,15 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
return gallery
|
return gallery
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView):
|
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView,
|
||||||
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for news for back-office users."""
|
"""Resource for returning gallery for news for back-office users."""
|
||||||
serializer_class = serializers.NewsImageSerializer
|
serializer_class = ImageBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method."""
|
"""Override get_object method."""
|
||||||
qs = super(NewsBackOfficeGalleryListView, self).get_queryset()
|
qs = super(NewsBackOfficeGalleryListView, self).get_queryset()
|
||||||
news = get_object_or_404(qs, pk=self.kwargs['pk'])
|
news = get_object_or_404(qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, news)
|
self.check_object_permissions(self.request, news)
|
||||||
|
|
@ -127,7 +134,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIVie
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return self.get_object().gallery.all()
|
return self.get_object().crop_gallery
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
|
class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
|
||||||
|
|
@ -153,7 +160,7 @@ class NewsFavoritesCreateDestroyView(generics.CreateAPIView, generics.DestroyAPI
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
"""
|
"""
|
||||||
news = get_object_or_404(models.News, slug=self.kwargs['slug'])
|
news = get_object_or_404(models.News, slug=self.kwargs.get('slug'))
|
||||||
favorites = get_object_or_404(news.favorites.filter(user=self.request.user))
|
favorites = get_object_or_404(news.favorites.filter(user=self.request.user))
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, favorites)
|
self.check_object_permissions(self.request, favorites)
|
||||||
|
|
|
||||||
20
apps/notification/migrations/0003_auto_20191116_1248.py
Normal file
20
apps/notification/migrations/0003_auto_20191116_1248.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-16 12:48
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('notification', '0002_subscriber_old_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscriber',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscriber', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -74,7 +74,7 @@ class Subscriber(ProjectBaseMixin):
|
||||||
(USABLE, _('Usable')),
|
(USABLE, _('Usable')),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from django.db.models import Count
|
|
||||||
|
|
||||||
from transfer.models import EmailAddresses, NewsletterSubscriber
|
from transfer.models import EmailAddresses, NewsletterSubscriber
|
||||||
from transfer.serializers.notification import SubscriberSerializer, NewsletterSubscriberSerializer
|
from transfer.serializers.notification import SubscriberSerializer, NewsletterSubscriberSerializer
|
||||||
|
|
||||||
|
|
@ -25,14 +23,14 @@ def transfer_newsletter_subscriber():
|
||||||
'email_address__ip',
|
'email_address__ip',
|
||||||
'email_address__country_code',
|
'email_address__country_code',
|
||||||
'email_address__locale',
|
'email_address__locale',
|
||||||
'created_at',
|
'updated_at',
|
||||||
)
|
)
|
||||||
|
|
||||||
# serialized_data = NewsletterSubscriberSerializer(data=list(queryset.values()), many=True)
|
serialized_data = NewsletterSubscriberSerializer(data=list(queryset), many=True)
|
||||||
# if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
# serialized_data.save()
|
serialized_data.save()
|
||||||
# else:
|
else:
|
||||||
# pprint(f'NewsletterSubscriber serializer errors: {serialized_data.errors}')
|
pprint(f'NewsletterSubscriber serializer errors: {serialized_data.errors}')
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ from partner import models
|
||||||
@admin.register(models.Partner)
|
@admin.register(models.Partner)
|
||||||
class PartnerModelAdmin(admin.ModelAdmin):
|
class PartnerModelAdmin(admin.ModelAdmin):
|
||||||
"""Model admin for Partner model."""
|
"""Model admin for Partner model."""
|
||||||
|
raw_id_fields = ('establishment',)
|
||||||
|
|
|
||||||
50
apps/partner/migrations/0003_auto_20191121_1059.py
Normal file
50
apps/partner/migrations/0003_auto_20191121_1059.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-21 10:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0065_establishment_purchased_products'),
|
||||||
|
('partner', '0002_auto_20191101_0939'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='establishment',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='partners', to='establishment.Establishment', verbose_name='Establishment'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='expiry_date',
|
||||||
|
field=models.DateField(blank=True, null=True, verbose_name='expiry date'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='old_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='price_per_month',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='price per month'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='starting_date',
|
||||||
|
field=models.DateField(blank=True, null=True, verbose_name='starting date'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='type',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'Partner'), (1, 'Sponsor')], default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,13 +1,36 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
from utils.models import ImageMixin, ProjectBaseMixin
|
from utils.models import ImageMixin, ProjectBaseMixin
|
||||||
|
|
||||||
|
|
||||||
class Partner(ProjectBaseMixin):
|
class Partner(ProjectBaseMixin):
|
||||||
"""Partner model."""
|
"""Partner model."""
|
||||||
|
|
||||||
|
PARTNER = 0
|
||||||
|
SPONSOR = 1
|
||||||
|
MODEL_TYPES = (
|
||||||
|
(PARTNER, _('Partner')),
|
||||||
|
(SPONSOR, _('Sponsor')),
|
||||||
|
)
|
||||||
|
|
||||||
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
|
||||||
url = models.URLField(verbose_name=_('Partner URL'))
|
url = models.URLField(verbose_name=_('Partner URL'))
|
||||||
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
|
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
|
||||||
|
establishment = models.ForeignKey(
|
||||||
|
Establishment,
|
||||||
|
verbose_name=_('Establishment'),
|
||||||
|
related_name='partners',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
type = models.PositiveSmallIntegerField(choices=MODEL_TYPES, default=PARTNER)
|
||||||
|
starting_date = models.DateField(_('starting date'), blank=True, null=True)
|
||||||
|
expiry_date = models.DateField(_('expiry date'), blank=True, null=True)
|
||||||
|
price_per_month = models.DecimalField(_('price per month'), max_digits=10, decimal_places=2, blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('partner')
|
verbose_name = _('partner')
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,32 @@
|
||||||
from django.db.models import Value, IntegerField, F
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
from transfer.models import EstablishmentBacklinks
|
from transfer.models import EstablishmentBacklinks
|
||||||
from transfer.serializers.partner import PartnerSerializer
|
from transfer.serializers.partner import PartnerSerializer
|
||||||
|
|
||||||
|
|
||||||
def transfer_partner():
|
def transfer_partner():
|
||||||
queryset = EstablishmentBacklinks.objects.filter(type="Partner")
|
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
||||||
|
queryset = EstablishmentBacklinks.objects.filter(
|
||||||
|
establishment_id__in=list(establishments),
|
||||||
|
).values(
|
||||||
|
'id',
|
||||||
|
'establishment_id',
|
||||||
|
'partnership_name',
|
||||||
|
'partnership_icon',
|
||||||
|
'backlink_url',
|
||||||
|
'created_at',
|
||||||
|
'type',
|
||||||
|
'starting_date',
|
||||||
|
'expiry_date',
|
||||||
|
'price_per_month',
|
||||||
|
)
|
||||||
|
|
||||||
# queryset = EstablishmentBacklinks.objects.all() # Partner and Sponsor
|
serialized_data = PartnerSerializer(data=list(queryset), many=True)
|
||||||
|
|
||||||
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
|
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"News serializer errors: {serialized_data.errors}")
|
pprint(f"Partner serializer errors: {serialized_data.errors}")
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,23 @@ from utils.admin import BaseModelAdminMixin
|
||||||
from .models import Product, ProductType, ProductSubType, ProductGallery, Unit
|
from .models import Product, ProductType, ProductSubType, ProductGallery, Unit
|
||||||
|
|
||||||
|
|
||||||
|
class ProductGalleryInline(admin.TabularInline):
|
||||||
|
"""Product gallery inline."""
|
||||||
|
model = ProductGallery
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
@admin.register(Product)
|
||||||
class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
class ProductAdmin(BaseModelAdminMixin, admin.ModelAdmin):
|
||||||
"""Admin page for model Product."""
|
"""Admin page for model Product."""
|
||||||
search_fields = ('name', )
|
search_fields = ('name', )
|
||||||
list_filter = ('available', 'product_type')
|
list_filter = ('available', 'product_type')
|
||||||
list_display = ('id', '__str__', 'get_category_display', 'product_type')
|
list_display = ('id', '__str__', 'get_category_display', 'product_type')
|
||||||
|
inlines = [ProductGalleryInline, ]
|
||||||
raw_id_fields = ('subtypes', 'classifications', 'standards',
|
raw_id_fields = ('subtypes', 'classifications', 'standards',
|
||||||
'tags', 'gallery', 'establishment',)
|
'tags', 'gallery', 'establishment',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProductGallery)
|
|
||||||
class ProductGalleryAdmin(admin.ModelAdmin):
|
|
||||||
"""Admin page for model ProductGallery."""
|
|
||||||
raw_id_fields = ('product', 'image', )
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProductType)
|
@admin.register(ProductType)
|
||||||
class ProductTypeAdmin(admin.ModelAdmin):
|
class ProductTypeAdmin(admin.ModelAdmin):
|
||||||
"""Admin page for model ProductType."""
|
"""Admin page for model ProductType."""
|
||||||
|
|
|
||||||
19
apps/product/management/commands/add_average_price.py
Normal file
19
apps/product/management/commands/add_average_price.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from product.models import Product
|
||||||
|
from transfer.models import Products
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Add average price to product from legacy table products."""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwarg):
|
||||||
|
update_products = []
|
||||||
|
old_products = Products.objects.values_list('id', 'price')
|
||||||
|
for old_id, price in old_products:
|
||||||
|
product = Product.objects.get(old_id=old_id)
|
||||||
|
product.average_price = price
|
||||||
|
update_products.append(product)
|
||||||
|
|
||||||
|
Product.objects.bulk_update(update_products, ['average_price', ])
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated products: {len(update_products)}'))
|
||||||
|
|
@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from establishment.management.commands.add_position import namedtuplefetchall
|
from establishment.management.commands.add_position import namedtuplefetchall
|
||||||
from tag.models import Tag, TagCategory
|
from tag.models import Tag, TagCategory
|
||||||
from product.models import Product
|
from product.models import Product, ProductType
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,24 +26,47 @@ class Command(BaseCommand):
|
||||||
def add_category_tag(self):
|
def add_category_tag(self):
|
||||||
objects = []
|
objects = []
|
||||||
for c in tqdm(self.category_sql(), desc='Add category tags'):
|
for c in tqdm(self.category_sql(), desc='Add category tags'):
|
||||||
categories = TagCategory.objects.filter(index_name=c.category
|
categories = TagCategory.objects.filter(index_name=c.category)
|
||||||
)
|
|
||||||
if not categories.exists():
|
if not categories.exists():
|
||||||
objects.append(
|
objects.append(
|
||||||
TagCategory(label={"en-GB": c.category},
|
TagCategory(label={"en-GB": c.category},
|
||||||
value_type=c.value_type,
|
value_type=c.value_type,
|
||||||
index_name=c.category
|
index_name=c.category,
|
||||||
|
public=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
categories.update(public=True)
|
||||||
TagCategory.objects.bulk_create(objects)
|
TagCategory.objects.bulk_create(objects)
|
||||||
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
|
self.stdout.write(self.style.WARNING(f'Add or get tag category objects.'))
|
||||||
|
|
||||||
|
def product_type_category_sql(self):
|
||||||
|
with connections['legacy'].cursor() as cursor:
|
||||||
|
cursor.execute('''
|
||||||
|
select
|
||||||
|
DISTINCT
|
||||||
|
trim(CONVERT(v.key_name USING utf8)) as tag_category
|
||||||
|
FROM product_metadata m
|
||||||
|
join product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
|
||||||
|
join products p on p.id = m.product_id
|
||||||
|
where UPPER(trim(p.type)) = 'WINE'
|
||||||
|
''')
|
||||||
|
return namedtuplefetchall(cursor)
|
||||||
|
|
||||||
|
def add_type_product_category(self):
|
||||||
|
for c in tqdm(self.product_type_category_sql(), desc='Add type product category'):
|
||||||
|
type = ProductType.objects.get(index_name=ProductType.WINE)
|
||||||
|
category = TagCategory.objects.get(index_name=c.tag_category)
|
||||||
|
if category not in type.tag_categories.all():
|
||||||
|
type.tag_categories.add(category)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Add type product category objects.'))
|
||||||
|
|
||||||
def tag_sql(self):
|
def tag_sql(self):
|
||||||
with connections['legacy'].cursor() as cursor:
|
with connections['legacy'].cursor() as cursor:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
select
|
select
|
||||||
DISTINCT
|
DISTINCT
|
||||||
m.id as old_id,
|
|
||||||
trim(CONVERT(m.value USING utf8)) as tag_value,
|
trim(CONVERT(m.value USING utf8)) as tag_value,
|
||||||
trim(CONVERT(v.key_name USING utf8)) as tag_category
|
trim(CONVERT(v.key_name USING utf8)) as tag_category
|
||||||
FROM product_metadata m
|
FROM product_metadata m
|
||||||
|
|
@ -64,24 +87,35 @@ class Command(BaseCommand):
|
||||||
if not tags.exists():
|
if not tags.exists():
|
||||||
objects.append(Tag(label={"en-GB": t.tag_value},
|
objects.append(Tag(label={"en-GB": t.tag_value},
|
||||||
category=category,
|
category=category,
|
||||||
value=t.tag_value,
|
value=t.tag_value)
|
||||||
old_id_meta_product=t.old_id
|
)
|
||||||
))
|
|
||||||
else:
|
|
||||||
qs = tags.filter(old_id_meta_product__isnull=True)\
|
|
||||||
.update(old_id_meta_product=t.old_id)
|
|
||||||
|
|
||||||
Tag.objects.bulk_create(objects)
|
Tag.objects.bulk_create(objects)
|
||||||
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
|
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
|
||||||
|
|
||||||
|
def remove_tags_product(self):
|
||||||
|
print('Begin clear tags product')
|
||||||
|
products = Product.objects.all()
|
||||||
|
for p in tqdm(products, desc='Clear tags product'):
|
||||||
|
p.tags.clear()
|
||||||
|
print('End clear tags product')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_tags(self):
|
||||||
|
print('Begin delete many tags')
|
||||||
|
Tag.objects.\
|
||||||
|
filter(news__isnull=True, establishments__isnull=True).delete()
|
||||||
|
print('End delete many tags')
|
||||||
|
|
||||||
|
|
||||||
def product_sql(self):
|
def product_sql(self):
|
||||||
with connections['legacy'].cursor() as cursor:
|
with connections['legacy'].cursor() as cursor:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
select
|
select
|
||||||
DISTINCT
|
DISTINCT
|
||||||
m.id as old_id_tag,
|
|
||||||
m.product_id,
|
m.product_id,
|
||||||
lower(trim(CONVERT(m.value USING utf8))) as tag_value,
|
trim(CONVERT(m.value USING utf8)) as tag_value,
|
||||||
trim(CONVERT(v.key_name USING utf8)) as tag_category
|
trim(CONVERT(v.key_name USING utf8)) as tag_category
|
||||||
FROM product_metadata m
|
FROM product_metadata m
|
||||||
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
|
JOIN product_key_value_metadata v on v.id = m.product_key_value_metadatum_id
|
||||||
|
|
@ -90,10 +124,15 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def add_product_tag(self):
|
def add_product_tag(self):
|
||||||
for t in tqdm(self.product_sql(), desc='Add product tag'):
|
for t in tqdm(self.product_sql(), desc='Add product tag'):
|
||||||
tags = Tag.objects.filter(old_id_meta_product=t.old_id_tag)
|
category = TagCategory.objects.get(index_name=t.tag_category)
|
||||||
|
|
||||||
|
tags = Tag.objects.filter(
|
||||||
|
category=category,
|
||||||
|
value=t.tag_value
|
||||||
|
)
|
||||||
product = Product.objects.get(old_id=t.product_id)
|
product = Product.objects.get(old_id=t.product_id)
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
if product not in tag.products.all():
|
if tag not in product.tags.all():
|
||||||
product.tags.add(tag)
|
product.tags.add(tag)
|
||||||
|
|
||||||
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
|
self.stdout.write(self.style.WARNING(f'Add or get tag objects.'))
|
||||||
|
|
@ -111,7 +150,10 @@ class Command(BaseCommand):
|
||||||
tag.save()
|
tag.save()
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
|
self.remove_tags_product()
|
||||||
|
self.remove_tags()
|
||||||
self.add_category_tag()
|
self.add_category_tag()
|
||||||
|
self.add_type_product_category()
|
||||||
self.add_tag()
|
self.add_tag()
|
||||||
self.check_tag()
|
self.check_tag()
|
||||||
self.add_product_tag()
|
self.add_product_tag()
|
||||||
|
|
|
||||||
19
apps/product/migrations/0014_auto_20191117_1117.py
Normal file
19
apps/product/migrations/0014_auto_20191117_1117.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-17 11:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0013_auto_20191113_1512'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='productgallery',
|
||||||
|
name='image',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_gallery', to='gallery.Image', verbose_name='image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/product/migrations/0015_auto_20191117_1954.py
Normal file
18
apps/product/migrations/0015_auto_20191117_1954.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-17 19:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0014_auto_20191117_1117'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='producttype',
|
||||||
|
name='tag_categories',
|
||||||
|
field=models.ManyToManyField(related_name='product_types', to='tag.TagCategory', verbose_name='Tag categories'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/product/migrations/0016_product_average_price.py
Normal file
18
apps/product/migrations/0016_product_average_price.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-19 13:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0015_auto_20191117_1954'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='average_price',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, default=None, max_digits=14, null=True, verbose_name='average price'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/product/migrations/0017_auto_20191119_1546.py
Normal file
19
apps/product/migrations/0017_auto_20191119_1546.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-19 15:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0016_product_average_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='productnote',
|
||||||
|
name='product',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='notes', to='product.Product', verbose_name='product'),
|
||||||
|
),
|
||||||
|
]
|
||||||
30
apps/product/migrations/0018_purchasedproduct.py
Normal file
30
apps/product/migrations/0018_purchasedproduct.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-20 12:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0064_auto_20191119_1546'),
|
||||||
|
('product', '0017_auto_20191119_1546'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PurchasedProduct',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_gifted', models.NullBooleanField(default=None, verbose_name='is gifted')),
|
||||||
|
('quantity', models.PositiveSmallIntegerField(verbose_name='quantity')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchased_plaques', to='establishment.Establishment', verbose_name='establishment')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchased_by_establishments', to='product.Product', verbose_name='plaque')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'purchased plaque',
|
||||||
|
'verbose_name_plural': 'purchased plaques',
|
||||||
|
'unique_together': {('establishment', 'product')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -7,8 +7,9 @@ from django.db.models import Case, When
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
|
||||||
from utils.models import (BaseAttributes, ProjectBaseMixin,
|
from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin,
|
||||||
TranslatedFieldsMixin, TJSONField)
|
TranslatedFieldsMixin, TJSONField,
|
||||||
|
GalleryModelMixin, IntermediateGalleryModelMixin)
|
||||||
|
|
||||||
|
|
||||||
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
|
|
@ -30,7 +31,7 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='product_types',
|
related_name='product_types',
|
||||||
verbose_name=_('Tag'))
|
verbose_name=_('Tag categories'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -82,7 +83,12 @@ class ProductQuerySet(models.QuerySet):
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
"""Returns qs with almost all related objects."""
|
"""Returns qs with almost all related objects."""
|
||||||
return self.with_base_related() \
|
return self.with_base_related() \
|
||||||
.prefetch_related('tags', 'standards', 'classifications', 'classifications__standard',
|
.prefetch_related('tags', 'tags__category', 'tags__category__country',
|
||||||
|
'standards', 'classifications', 'classifications__standard',
|
||||||
|
'establishment__address', 'establishment__establishment_type',
|
||||||
|
'establishment__address__city', 'establishment__address__city__country',
|
||||||
|
'establishment__establishment_subtypes', 'product_gallery',
|
||||||
|
'gallery', 'product_type', 'subtypes',
|
||||||
'classifications__classification_type', 'classifications__tags') \
|
'classifications__classification_type', 'classifications__tags') \
|
||||||
.select_related('wine_region', 'wine_sub_region')
|
.select_related('wine_region', 'wine_sub_region')
|
||||||
|
|
||||||
|
|
@ -125,7 +131,7 @@ class ProductQuerySet(models.QuerySet):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Product(TranslatedFieldsMixin, BaseAttributes):
|
class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, HasTagsMixin):
|
||||||
"""Product models."""
|
"""Product models."""
|
||||||
|
|
||||||
EARLIEST_VINTAGE_YEAR = 1700
|
EARLIEST_VINTAGE_YEAR = 1700
|
||||||
|
|
@ -205,6 +211,9 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
null=True, blank=True, default=None,
|
null=True, blank=True, default=None,
|
||||||
validators=[MinValueValidator(EARLIEST_VINTAGE_YEAR),
|
validators=[MinValueValidator(EARLIEST_VINTAGE_YEAR),
|
||||||
MaxValueValidator(LATEST_VINTAGE_YEAR)])
|
MaxValueValidator(LATEST_VINTAGE_YEAR)])
|
||||||
|
average_price = models.DecimalField(max_digits=14, decimal_places=2,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('average price'))
|
||||||
gallery = models.ManyToManyField('gallery.Image', through='ProductGallery')
|
gallery = models.ManyToManyField('gallery.Image', through='ProductGallery')
|
||||||
reviews = generic.GenericRelation(to='review.Review')
|
reviews = generic.GenericRelation(to='review.Review')
|
||||||
comments = generic.GenericRelation(to='comment.Comment')
|
comments = generic.GenericRelation(to='comment.Comment')
|
||||||
|
|
@ -222,15 +231,11 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
"""Override str dunder method."""
|
"""Override str dunder method."""
|
||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
def clean_fields(self, exclude=None):
|
def delete(self, using=None, keep_parents=False):
|
||||||
super().clean_fields(exclude=exclude)
|
"""Overridden delete method"""
|
||||||
if self.product_type.index_name == ProductType.WINE and not self.wine_region:
|
# Delete all related notes
|
||||||
raise ValidationError(_('wine_region field must be specified.'))
|
self.notes.all().delete()
|
||||||
if not self.product_type.index_name == ProductType.WINE and self.wine_region:
|
return super().delete(using, keep_parents)
|
||||||
raise ValidationError(_('wine_region field must not be specified.'))
|
|
||||||
# if (self.wine_region and self.wine_appellation) and \
|
|
||||||
# self.wine_appellation not in self.wine_region.appellations.all():
|
|
||||||
# raise ValidationError(_('Wine appellation not exists in wine region.'))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def product_type_translated_name(self):
|
def product_type_translated_name(self):
|
||||||
|
|
@ -255,33 +260,50 @@ class Product(TranslatedFieldsMixin, BaseAttributes):
|
||||||
return self.tags.filter(category__index_name='bottles-produced')
|
return self.tags.filter(category__index_name='bottles-produced')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def main_image(self):
|
def grape_variety(self):
|
||||||
qs = ProductGallery.objects.filter(product=self, is_main=True)
|
return self.tags.filter(category__index_name='grape-variety')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bottle_sizes(self):
|
||||||
|
return self.tags.filter(category__index_name='bottle_size')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alcohol_percentage(self):
|
||||||
|
qs = self.tags.filter(category__index_name='alcohol_percentage')
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
return qs.first().image
|
return qs.first()
|
||||||
|
|
||||||
@property
|
|
||||||
def main_image_url(self):
|
|
||||||
return self.main_image.image if self.main_image else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def preview_main_image_url(self):
|
|
||||||
return self.main_image.get_image_url('product_preview') if self.main_image else None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def related_tags(self):
|
def related_tags(self):
|
||||||
return self.tags.exclude(
|
return super().visible_tags.exclude(category__index_name__in=[
|
||||||
category__index_name__in=['sugar-content', 'wine-color', 'bottles-produced',
|
'sugar-content', 'wine-color', 'bottles-produced',
|
||||||
'serial-number', 'grape-variety'])
|
'serial-number', 'grape-variety', 'serial_number',
|
||||||
|
'alcohol_percentage', 'bottle_size',
|
||||||
|
])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
name = f'{self.name} ' \
|
name = f'{self.name} ' \
|
||||||
f'({self.vintage if self.vintage else "BSA"})'
|
f'({self.vintage if self.vintage else "BSA"})'
|
||||||
if self.establishment.name:
|
if self.establishment and self.establishment.name:
|
||||||
name = f'{self.establishment.name} - ' + name
|
name = f'{self.establishment.name} - ' + name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def main_image(self):
|
||||||
|
qs = self.product_gallery.main_image()
|
||||||
|
if qs.exists():
|
||||||
|
return qs.first().image
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_url(self):
|
||||||
|
return self.main_image.image.url if self.main_image else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preview_image_url(self):
|
||||||
|
if self.main_image:
|
||||||
|
return self.main_image.get_image_url(thumbnail_key='product_preview')
|
||||||
|
|
||||||
|
|
||||||
class OnlineProductManager(ProductManager):
|
class OnlineProductManager(ProductManager):
|
||||||
"""Extended manger for OnlineProduct model."""
|
"""Extended manger for OnlineProduct model."""
|
||||||
|
|
@ -304,6 +326,26 @@ class OnlineProduct(Product):
|
||||||
verbose_name_plural = _('Online products')
|
verbose_name_plural = _('Online products')
|
||||||
|
|
||||||
|
|
||||||
|
class PurchasedProduct(models.Model):
|
||||||
|
"""Model for storing establishment purchased plaques."""
|
||||||
|
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE,
|
||||||
|
related_name='purchased_plaques',
|
||||||
|
verbose_name=_('establishment'))
|
||||||
|
product = models.ForeignKey('product.Product', on_delete=models.CASCADE,
|
||||||
|
related_name='purchased_by_establishments',
|
||||||
|
verbose_name=_('plaque'))
|
||||||
|
is_gifted = models.NullBooleanField(default=None,
|
||||||
|
verbose_name=_('is gifted'))
|
||||||
|
quantity = models.PositiveSmallIntegerField(verbose_name=_('quantity'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('purchased plaque')
|
||||||
|
verbose_name_plural = _('purchased plaques')
|
||||||
|
unique_together = ('establishment', 'product')
|
||||||
|
|
||||||
|
|
||||||
class Unit(models.Model):
|
class Unit(models.Model):
|
||||||
"""Product unit model."""
|
"""Product unit model."""
|
||||||
name = models.CharField(max_length=255,
|
name = models.CharField(max_length=255,
|
||||||
|
|
@ -353,15 +395,8 @@ class ProductStandard(models.Model):
|
||||||
verbose_name = _('wine standard')
|
verbose_name = _('wine standard')
|
||||||
|
|
||||||
|
|
||||||
class ProductGalleryQuerySet(models.QuerySet):
|
class ProductGallery(IntermediateGalleryModelMixin):
|
||||||
"""QuerySet for model Product"""
|
"""Gallery for model Product."""
|
||||||
|
|
||||||
def main_image(self):
|
|
||||||
"""Return objects with flag is_main is True"""
|
|
||||||
return self.filter(is_main=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ProductGallery(models.Model):
|
|
||||||
product = models.ForeignKey(Product, null=True,
|
product = models.ForeignKey(Product, null=True,
|
||||||
related_name='product_gallery',
|
related_name='product_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
@ -369,11 +404,7 @@ class ProductGallery(models.Model):
|
||||||
image = models.ForeignKey('gallery.Image', null=True,
|
image = models.ForeignKey('gallery.Image', null=True,
|
||||||
related_name='product_gallery',
|
related_name='product_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('gallery'))
|
verbose_name=_('image'))
|
||||||
is_main = models.BooleanField(default=False,
|
|
||||||
verbose_name=_('Is the main image'))
|
|
||||||
|
|
||||||
objects = ProductGalleryQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""ProductGallery meta class."""
|
"""ProductGallery meta class."""
|
||||||
|
|
@ -439,7 +470,7 @@ class ProductNote(ProjectBaseMixin):
|
||||||
old_id = models.PositiveIntegerField(null=True, blank=True)
|
old_id = models.PositiveIntegerField(null=True, blank=True)
|
||||||
text = models.TextField(verbose_name=_('text'))
|
text = models.TextField(verbose_name=_('text'))
|
||||||
product = models.ForeignKey(Product, on_delete=models.PROTECT,
|
product = models.ForeignKey(Product, on_delete=models.PROTECT,
|
||||||
related_name='product_notes',
|
related_name='notes',
|
||||||
verbose_name=_('product'))
|
verbose_name=_('product'))
|
||||||
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
|
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from product import models
|
||||||
from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializer, \
|
from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializer, \
|
||||||
ProductSubTypeBaseSerializer
|
ProductSubTypeBaseSerializer
|
||||||
from tag.models import TagCategory
|
from tag.models import TagCategory
|
||||||
|
from account.serializers.common import UserShortSerializer
|
||||||
|
|
||||||
|
|
||||||
class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
|
class ProductBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -127,3 +128,55 @@ class ProductSubTypeBackOfficeDetailSerializer(ProductSubTypeBaseSerializer):
|
||||||
'name',
|
'name',
|
||||||
'index_name',
|
'index_name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductNoteBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model ProductNote."""
|
||||||
|
|
||||||
|
user_detail = UserShortSerializer(read_only=True, source='user')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.ProductNote
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
'modified',
|
||||||
|
'text',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
|
'product',
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'created': {'read_only': True},
|
||||||
|
'modified': {'read_only': True},
|
||||||
|
'product': {'required': False, 'write_only': True},
|
||||||
|
'user': {'required': False, 'write_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serializer_view(self):
|
||||||
|
"""Return view instance."""
|
||||||
|
return self.context.get('view')
|
||||||
|
|
||||||
|
|
||||||
|
class ProductNoteListCreateSerializer(ProductNoteBaseSerializer):
|
||||||
|
"""Serializer for List|Create action for model ProductNote."""
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Overridden create method."""
|
||||||
|
validated_data['user'] = self.user
|
||||||
|
validated_data['product'] = self.product
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
"""Return user instance from view."""
|
||||||
|
if self.serializer_view:
|
||||||
|
return self.serializer_view.request.user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def product(self):
|
||||||
|
"""Return product instance from view."""
|
||||||
|
if self.serializer_view:
|
||||||
|
return self.serializer_view.get_object()
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,21 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from comment.serializers import CommentSerializer
|
from comment.serializers import CommentSerializer
|
||||||
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer
|
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer, EstablishmentProductShortSerializer
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
from product import models
|
from product import models
|
||||||
from review.serializers import ReviewShortSerializer
|
from review.serializers import ReviewShortSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import TranslatedField, FavoritesCreateSerializer
|
from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer
|
||||||
from main.serializers import AwardSerializer
|
from main.serializers import AwardSerializer
|
||||||
from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer
|
from location.serializers import WineRegionBaseSerializer, WineSubRegionBaseSerializer
|
||||||
from tag.serializers import TagBaseSerializer, TagCategoryShortSerializer
|
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
|
||||||
|
|
||||||
|
|
||||||
class ProductTagSerializer(TagBaseSerializer):
|
class ProductTagSerializer(TagBaseSerializer):
|
||||||
"""Serializer for model Tag."""
|
"""Serializer for model Tag."""
|
||||||
|
|
||||||
category = TagCategoryShortSerializer(read_only=True)
|
category = TagCategoryProductSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(TagBaseSerializer.Meta):
|
class Meta(TagBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -88,12 +88,11 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
name = serializers.CharField(source='display_name', read_only=True)
|
name = serializers.CharField(source='display_name', read_only=True)
|
||||||
product_type = ProductTypeBaseSerializer(read_only=True)
|
product_type = ProductTypeBaseSerializer(read_only=True)
|
||||||
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
|
subtypes = ProductSubTypeBaseSerializer(many=True, read_only=True)
|
||||||
establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True)
|
establishment_detail = EstablishmentProductShortSerializer(source='establishment', read_only=True)
|
||||||
tags = ProductTagSerializer(source='related_tags', many=True, read_only=True)
|
tags = ProductTagSerializer(source='related_tags', many=True, read_only=True)
|
||||||
wine_region = WineRegionBaseSerializer(read_only=True)
|
wine_region = WineRegionBaseSerializer(read_only=True)
|
||||||
wine_colors = TagBaseSerializer(many=True, read_only=True)
|
wine_colors = TagBaseSerializer(many=True, read_only=True)
|
||||||
preview_image_url = serializers.URLField(source='preview_main_image_url',
|
preview_image_url = serializers.URLField(allow_null=True,
|
||||||
allow_null=True,
|
|
||||||
read_only=True)
|
read_only=True)
|
||||||
in_favorites = serializers.BooleanField(allow_null=True)
|
in_favorites = serializers.BooleanField(allow_null=True)
|
||||||
|
|
||||||
|
|
@ -120,6 +119,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
||||||
class ProductDetailSerializer(ProductBaseSerializer):
|
class ProductDetailSerializer(ProductBaseSerializer):
|
||||||
"""Product detail serializer."""
|
"""Product detail serializer."""
|
||||||
description_translated = TranslatedField()
|
description_translated = TranslatedField()
|
||||||
|
establishment_detail = EstablishmentShortSerializer(source='establishment', read_only=True)
|
||||||
review = ReviewShortSerializer(source='last_published_review', read_only=True)
|
review = ReviewShortSerializer(source='last_published_review', read_only=True)
|
||||||
awards = AwardSerializer(many=True, read_only=True)
|
awards = AwardSerializer(many=True, read_only=True)
|
||||||
classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
|
classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
|
||||||
|
|
@ -127,9 +127,12 @@ class ProductDetailSerializer(ProductBaseSerializer):
|
||||||
wine_sub_region = WineSubRegionBaseSerializer(read_only=True)
|
wine_sub_region = WineSubRegionBaseSerializer(read_only=True)
|
||||||
bottles_produced = TagBaseSerializer(many=True, read_only=True)
|
bottles_produced = TagBaseSerializer(many=True, read_only=True)
|
||||||
sugar_contents = TagBaseSerializer(many=True, read_only=True)
|
sugar_contents = TagBaseSerializer(many=True, read_only=True)
|
||||||
image_url = serializers.ImageField(source='main_image_url',
|
grape_variety = TagBaseSerializer(many=True, read_only=True)
|
||||||
allow_null=True,
|
bottle_sizes = TagBaseSerializer(many=True, read_only=True)
|
||||||
read_only=True)
|
alcohol_percentage = TagBaseSerializer(read_only=True)
|
||||||
|
image_url = serializers.URLField(allow_null=True,
|
||||||
|
read_only=True)
|
||||||
|
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta(ProductBaseSerializer.Meta):
|
class Meta(ProductBaseSerializer.Meta):
|
||||||
fields = ProductBaseSerializer.Meta.fields + [
|
fields = ProductBaseSerializer.Meta.fields + [
|
||||||
|
|
@ -142,6 +145,11 @@ class ProductDetailSerializer(ProductBaseSerializer):
|
||||||
'bottles_produced',
|
'bottles_produced',
|
||||||
'sugar_contents',
|
'sugar_contents',
|
||||||
'image_url',
|
'image_url',
|
||||||
|
'new_image',
|
||||||
|
'grape_variety',
|
||||||
|
'average_price',
|
||||||
|
'bottle_sizes',
|
||||||
|
'alcohol_percentage',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -175,78 +183,6 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
# class CropImageSerializer(serializers.Serializer):
|
|
||||||
# """Serializer for crop images for News object."""
|
|
||||||
#
|
|
||||||
# preview_url = serializers.SerializerMethodField()
|
|
||||||
# promo_horizontal_web_url = serializers.SerializerMethodField()
|
|
||||||
# promo_horizontal_mobile_url = serializers.SerializerMethodField()
|
|
||||||
# tile_horizontal_web_url = serializers.SerializerMethodField()
|
|
||||||
# tile_horizontal_mobile_url = serializers.SerializerMethodField()
|
|
||||||
# tile_vertical_web_url = serializers.SerializerMethodField()
|
|
||||||
# highlight_vertical_web_url = serializers.SerializerMethodField()
|
|
||||||
# editor_web_url = serializers.SerializerMethodField()
|
|
||||||
# editor_mobile_url = serializers.SerializerMethodField()
|
|
||||||
#
|
|
||||||
# def get_preview_url(self, obj):
|
|
||||||
# """Get crop preview."""
|
|
||||||
# return obj.instance.get_image_url('news_preview')
|
|
||||||
#
|
|
||||||
# def get_promo_horizontal_web_url(self, obj):
|
|
||||||
# """Get crop promo_horizontal_web."""
|
|
||||||
# return obj.instance.get_image_url('news_promo_horizontal_web')
|
|
||||||
#
|
|
||||||
# def get_promo_horizontal_mobile_url(self, obj):
|
|
||||||
# """Get crop promo_horizontal_mobile."""
|
|
||||||
# return obj.instance.get_image_url('news_promo_horizontal_mobile')
|
|
||||||
#
|
|
||||||
# def get_tile_horizontal_web_url(self, obj):
|
|
||||||
# """Get crop tile_horizontal_web."""
|
|
||||||
# return obj.instance.get_image_url('news_tile_horizontal_web')
|
|
||||||
#
|
|
||||||
# def get_tile_horizontal_mobile_url(self, obj):
|
|
||||||
# """Get crop tile_horizontal_mobile."""
|
|
||||||
# return obj.instance.get_image_url('news_tile_horizontal_mobile')
|
|
||||||
#
|
|
||||||
# def get_tile_vertical_web_url(self, obj):
|
|
||||||
# """Get crop tile_vertical_web."""
|
|
||||||
# return obj.instance.get_image_url('news_tile_vertical_web')
|
|
||||||
#
|
|
||||||
# def get_highlight_vertical_web_url(self, obj):
|
|
||||||
# """Get crop highlight_vertical_web."""
|
|
||||||
# return obj.instance.get_image_url('news_highlight_vertical_web')
|
|
||||||
#
|
|
||||||
# def get_editor_web_url(self, obj):
|
|
||||||
# """Get crop editor_web."""
|
|
||||||
# return obj.instance.get_image_url('news_editor_web')
|
|
||||||
#
|
|
||||||
# def get_editor_mobile_url(self, obj):
|
|
||||||
# """Get crop editor_mobile."""
|
|
||||||
# return obj.instance.get_image_url('news_editor_mobile')
|
|
||||||
|
|
||||||
|
|
||||||
class ProductImageSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for returning crop images of product image."""
|
|
||||||
|
|
||||||
orientation_display = serializers.CharField(source='get_orientation_display',
|
|
||||||
read_only=True)
|
|
||||||
original_url = serializers.URLField(source='image.url')
|
|
||||||
# auto_crop_images = CropImageSerializer(source='image', allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Image
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'title',
|
|
||||||
'orientation_display',
|
|
||||||
'original_url',
|
|
||||||
# 'auto_crop_images',
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
'orientation': {'write_only': True}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ProductCommentCreateSerializer(CommentSerializer):
|
class ProductCommentCreateSerializer(CommentSerializer):
|
||||||
"""Create comment serializer"""
|
"""Create comment serializer"""
|
||||||
mark = serializers.IntegerField()
|
mark = serializers.IntegerField()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ from product import views
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'),
|
path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'),
|
||||||
path('<int:pk>/', views.ProductDetailBackOfficeView.as_view(), name='rud'),
|
path('<int:pk>/', views.ProductDetailBackOfficeView.as_view(), name='rud'),
|
||||||
|
path('<int:pk>/notes/', views.ProductNoteListCreateView.as_view(), name='note-list-create'),
|
||||||
|
path('<int:pk>/notes/<int:note_pk>/', views.ProductNoteRUDView.as_view(), name='note-rud'),
|
||||||
path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(),
|
path('<int:pk>/gallery/', views.ProductBackOfficeGalleryListView.as_view(),
|
||||||
name='gallery-list'),
|
name='gallery-list'),
|
||||||
path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(),
|
path('<int:pk>/gallery/<int:image_id>/', views.ProductBackOfficeGalleryCreateDestroyView.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from product import serializers, models
|
from product import serializers, models
|
||||||
from product.views import ProductBaseView
|
from product.views import ProductBaseView
|
||||||
|
from utils.serializers import ImageBaseSerializer
|
||||||
from utils.views import CreateDestroyGalleryViewMixin
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
|
|
||||||
|
|
||||||
class ProductBackOfficeMixinView(ProductBaseView):
|
class ProductBackOfficeMixinView(ProductBaseView):
|
||||||
"""Product back-office mixin view."""
|
"""Product back-office mixin view."""
|
||||||
|
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
|
|
@ -56,8 +57,8 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
|
||||||
"""
|
"""
|
||||||
product_qs = self.filter_queryset(self.get_queryset())
|
product_qs = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
product = get_object_or_404(product_qs, pk=self.kwargs['pk'])
|
product = get_object_or_404(product_qs, pk=self.kwargs.get('pk'))
|
||||||
gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs['image_id'])
|
gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs.get('image_id'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, gallery)
|
self.check_object_permissions(self.request, gallery)
|
||||||
|
|
@ -65,14 +66,16 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
|
||||||
return gallery
|
return gallery
|
||||||
|
|
||||||
|
|
||||||
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView):
|
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView,
|
||||||
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for product for back-office users."""
|
"""Resource for returning gallery for product for back-office users."""
|
||||||
serializer_class = serializers.ProductImageSerializer
|
serializer_class = ImageBaseSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method."""
|
"""Override get_object method."""
|
||||||
qs = super(ProductBackOfficeGalleryListView, self).get_queryset()
|
qs = super(ProductBackOfficeGalleryListView, self).get_queryset()
|
||||||
product = get_object_or_404(qs, pk=self.kwargs['pk'])
|
product = get_object_or_404(qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, product)
|
self.check_object_permissions(self.request, product)
|
||||||
|
|
@ -81,10 +84,11 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.List
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
return self.get_object().gallery.all()
|
return self.get_object().crop_gallery
|
||||||
|
|
||||||
|
|
||||||
class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView):
|
class ProductDetailBackOfficeView(ProductBackOfficeMixinView,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Product back-office R/U/D view."""
|
"""Product back-office R/U/D view."""
|
||||||
serializer_class = serializers.ProductBackOfficeDetailSerializer
|
serializer_class = serializers.ProductBackOfficeDetailSerializer
|
||||||
|
|
||||||
|
|
@ -131,3 +135,48 @@ class ProductSubTypeRUDBackOfficeView(BackOfficeListCreateMixin,
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Product sub type back-office retrieve-update-destroy view."""
|
"""Product sub type back-office retrieve-update-destroy view."""
|
||||||
serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer
|
serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ProductNoteListCreateView(ProductBackOfficeMixinView,
|
||||||
|
BackOfficeListCreateMixin,
|
||||||
|
generics.ListCreateAPIView):
|
||||||
|
"""Retrieve|Update|Destroy product note view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.ProductNoteListCreateSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
product_qs = models.Product.objects.all()
|
||||||
|
filtered_product_qs = self.filter_queryset(product_qs)
|
||||||
|
|
||||||
|
product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, product)
|
||||||
|
|
||||||
|
return product
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
return self.get_object().notes.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductNoteRUDView(ProductBackOfficeMixinView,
|
||||||
|
BackOfficeListCreateMixin,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""Create|Retrieve|Update|Destroy product note view."""
|
||||||
|
|
||||||
|
serializer_class = serializers.ProductNoteBaseSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Returns the object the view is displaying."""
|
||||||
|
product_qs = models.Product.objects.all()
|
||||||
|
filtered_product_qs = self.filter_queryset(product_qs)
|
||||||
|
|
||||||
|
product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk'))
|
||||||
|
note = get_object_or_404(product.notes.all(), pk=self.kwargs.get('note_pk'))
|
||||||
|
|
||||||
|
# May raise a permission denied
|
||||||
|
self.check_object_permissions(self.request, note)
|
||||||
|
|
||||||
|
return note
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ class ProductBaseView(generics.GenericAPIView):
|
||||||
return Product.objects.published() \
|
return Product.objects.published() \
|
||||||
.with_base_related() \
|
.with_base_related() \
|
||||||
.annotate_in_favorites(self.request.user) \
|
.annotate_in_favorites(self.request.user) \
|
||||||
.by_country_code(self.request.country_code) \
|
|
||||||
.order_by('-created')
|
.order_by('-created')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,6 +25,11 @@ class ProductListView(ProductBaseView, generics.ListAPIView):
|
||||||
serializer_class = serializers.ProductBaseSerializer
|
serializer_class = serializers.ProductBaseSerializer
|
||||||
filter_class = filters.ProductFilterSet
|
filter_class = filters.ProductFilterSet
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset().with_extended_related() \
|
||||||
|
.by_country_code(self.request.country_code)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
|
class ProductDetailView(ProductBaseView, generics.RetrieveAPIView):
|
||||||
"""Detail view fro model Product."""
|
"""Detail view fro model Product."""
|
||||||
|
|
|
||||||
30
apps/review/filters.py
Normal file
30
apps/review/filters.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from review import models
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewFilter(filters.FilterSet):
|
||||||
|
"""Review filter set."""
|
||||||
|
|
||||||
|
establishment_id = filters.NumberFilter(field_name='object_id', )
|
||||||
|
product_id = filters.NumberFilter(field_name='object_id', )
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Review
|
||||||
|
fields = (
|
||||||
|
'establishment_id',
|
||||||
|
'product_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
def by_establishment_id(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_establishment_id(value, content_type='establishment')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def by_product_id(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_product_id(value, content_type='product')
|
||||||
|
return queryset
|
||||||
19
apps/review/migrations/0018_auto_20191117_1117.py
Normal file
19
apps/review/migrations/0018_auto_20191117_1117.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-11-17 11:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('review', '0017_auto_20191115_0737'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inquiriesgallery',
|
||||||
|
name='image',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inquiries_gallery', to='gallery.Image', verbose_name='image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -4,8 +4,9 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from utils.models import BaseAttributes, TranslatedFieldsMixin, ProjectBaseMixin
|
from utils.models import (BaseAttributes, TranslatedFieldsMixin,
|
||||||
from utils.models import TJSONField
|
ProjectBaseMixin, GalleryModelMixin,
|
||||||
|
TJSONField, IntermediateGalleryModelMixin)
|
||||||
|
|
||||||
|
|
||||||
class ReviewQuerySet(models.QuerySet):
|
class ReviewQuerySet(models.QuerySet):
|
||||||
|
|
@ -92,7 +93,7 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
|
||||||
verbose_name_plural = _('Reviews')
|
verbose_name_plural = _('Reviews')
|
||||||
|
|
||||||
|
|
||||||
class Inquiries(ProjectBaseMixin):
|
class Inquiries(GalleryModelMixin, ProjectBaseMixin):
|
||||||
NONE = 0
|
NONE = 0
|
||||||
DINER = 1
|
DINER = 1
|
||||||
LUNCH = 2
|
LUNCH = 2
|
||||||
|
|
@ -145,15 +146,7 @@ class GridItems(ProjectBaseMixin):
|
||||||
return f'inquiry: {self.inquiry.id}, grid id: {self.id}'
|
return f'inquiry: {self.inquiry.id}, grid id: {self.id}'
|
||||||
|
|
||||||
|
|
||||||
class InquiriesGalleryQuerySet(models.QuerySet):
|
class InquiriesGallery(IntermediateGalleryModelMixin):
|
||||||
"""QuerySet for model Inquiries"""
|
|
||||||
|
|
||||||
def main_image(self):
|
|
||||||
"""Return objects with flag is_main is True"""
|
|
||||||
return self.filter(is_main=True)
|
|
||||||
|
|
||||||
|
|
||||||
class InquiriesGallery(models.Model):
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
inquiry = models.ForeignKey(
|
inquiry = models.ForeignKey(
|
||||||
Inquiries,
|
Inquiries,
|
||||||
|
|
@ -167,11 +160,8 @@ class InquiriesGallery(models.Model):
|
||||||
null=True,
|
null=True,
|
||||||
related_name='inquiries_gallery',
|
related_name='inquiries_gallery',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('gallery'),
|
verbose_name=_('image'),
|
||||||
)
|
)
|
||||||
is_main = models.BooleanField(default=False, verbose_name=_('Is the main image'))
|
|
||||||
|
|
||||||
objects = InquiriesGalleryQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('inquiry gallery')
|
verbose_name = _('inquiry gallery')
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ class ReviewBaseSerializer(serializers.ModelSerializer):
|
||||||
'child',
|
'child',
|
||||||
'published_at',
|
'published_at',
|
||||||
'vintage',
|
'vintage',
|
||||||
'country'
|
'country',
|
||||||
|
'content_type',
|
||||||
|
'object_id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class BaseTestCase(APITestCase):
|
||||||
username=self.username,
|
username=self.username,
|
||||||
email=self.email,
|
email=self.email,
|
||||||
password=self.password,
|
password=self.password,
|
||||||
|
is_staff=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
tokens = User.create_jwt_tokens(self.user)
|
tokens = User.create_jwt_tokens(self.user)
|
||||||
|
|
@ -61,6 +62,49 @@ class BaseTestCase(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewTestCase(BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_review_list(self):
|
||||||
|
response = self.client.get('/api/back/review/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_review_post(self):
|
||||||
|
test_review = {
|
||||||
|
'reviewer': self.user.id,
|
||||||
|
'status': Review.READY,
|
||||||
|
'vintage': 2019,
|
||||||
|
'country': self.country_ru.id,
|
||||||
|
'object_id': 1,
|
||||||
|
'content_type': 1,
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/back/review/', data=test_review)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def test_review_detail(self):
|
||||||
|
response = self.client.get(f'/api/back/review/{self.test_review.id}/', format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_review_detail_put(self):
|
||||||
|
data = {
|
||||||
|
'id': self.test_review.id,
|
||||||
|
'vintage': 2018,
|
||||||
|
'reviewer': self.user.id,
|
||||||
|
'status': Review.READY,
|
||||||
|
'country': self.country_ru.id,
|
||||||
|
'object_id': 1,
|
||||||
|
'content_type': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.put(f'/api/back/review/{self.test_review.id}/', data=data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_review_delete(self):
|
||||||
|
response = self.client.delete(f'/api/back/review/{self.test_review.id}/')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class InquiriesTestCase(BaseTestCase):
|
class InquiriesTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ from pprint import pprint
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from product.models import Product
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from account.transfer_data import STOP_LIST
|
from account.transfer_data import STOP_LIST
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
|
from product.models import Product
|
||||||
from review.models import Inquiries as NewInquiries, Review
|
from review.models import Inquiries as NewInquiries, Review
|
||||||
from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos
|
from transfer.models import Reviews, ReviewTexts, Inquiries, GridItems, InquiryPhotos
|
||||||
from transfer.serializers.grid import GridItemsSerializer
|
from transfer.serializers.grid import GridItemsSerializer
|
||||||
|
|
@ -34,11 +34,11 @@ def transfer_languages():
|
||||||
|
|
||||||
def transfer_reviews():
|
def transfer_reviews():
|
||||||
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
establishments = Establishment.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
||||||
queryset = Reviews.objects.filter(
|
queryset = Reviews.objects.exclude(product_id__isnull=False).filter(
|
||||||
establishment_id__in=list(establishments),
|
establishment_id__in=list(establishments),
|
||||||
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'establishment_id', 'mark', 'vintage')
|
).values('id', 'reviewer_id', 'aasm_state', 'created_at', 'establishment_id', 'mark', 'vintage')
|
||||||
|
|
||||||
serialized_data = ReviewSerializer(data=list(queryset.values()), many=True)
|
serialized_data = ReviewSerializer(data=list(queryset), many=True)
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
|
|
@ -48,17 +48,23 @@ def transfer_reviews():
|
||||||
def transfer_text_review():
|
def transfer_text_review():
|
||||||
reviews = Review.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
reviews = Review.objects.filter(old_id__isnull=False).values_list('old_id', flat=True)
|
||||||
queryset = ReviewTexts.objects.filter(
|
queryset = ReviewTexts.objects.filter(
|
||||||
review_id__in=list(reviews),
|
review_id__in=list(reviews)
|
||||||
).exclude(
|
).exclude(
|
||||||
Q(text__isnull=True) | Q(text='')
|
id__in=(23183, 25348, 43930, 23199, 26226, 34006) # пробелы вместо текста
|
||||||
|
).exclude(
|
||||||
|
text__isnull=True
|
||||||
|
).exclude(
|
||||||
|
text__iexact=''
|
||||||
).values('review_id', 'locale', 'text')
|
).values('review_id', 'locale', 'text')
|
||||||
|
|
||||||
serialized_data = ReviewTextSerializer(data=list(queryset.values()), many=True)
|
serialized_data = ReviewTextSerializer(data=list(queryset), many=True)
|
||||||
if serialized_data.is_valid():
|
if serialized_data.is_valid():
|
||||||
serialized_data.save()
|
serialized_data.save()
|
||||||
else:
|
else:
|
||||||
pprint(f"ReviewTextSerializer serializer errors: {serialized_data.errors}")
|
pprint(f"ReviewTextSerializer serializer errors: {serialized_data.errors}")
|
||||||
|
|
||||||
|
|
||||||
|
def make_en_text_review():
|
||||||
for review in Review.objects.filter(old_id__isnull=False):
|
for review in Review.objects.filter(old_id__isnull=False):
|
||||||
text = review.text
|
text = review.text
|
||||||
if text and 'en-GB' not in text:
|
if text and 'en-GB' not in text:
|
||||||
|
|
@ -106,7 +112,6 @@ def transfer_inquiry_photos():
|
||||||
|
|
||||||
|
|
||||||
def transfer_product_reviews():
|
def transfer_product_reviews():
|
||||||
|
|
||||||
products = Product.objects.filter(
|
products = Product.objects.filter(
|
||||||
old_id__isnull=False).values_list('old_id', flat=True)
|
old_id__isnull=False).values_list('old_id', flat=True)
|
||||||
|
|
||||||
|
|
@ -130,6 +135,7 @@ data_types = {
|
||||||
# transfer_languages,
|
# transfer_languages,
|
||||||
transfer_reviews,
|
transfer_reviews,
|
||||||
transfer_text_review,
|
transfer_text_review,
|
||||||
|
make_en_text_review,
|
||||||
],
|
],
|
||||||
'inquiries': [
|
'inquiries': [
|
||||||
transfer_inquiries,
|
transfer_inquiries,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
|
from review import filters
|
||||||
from review import models
|
from review import models
|
||||||
from review import serializers
|
from review import serializers
|
||||||
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
|
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
|
||||||
|
|
@ -10,13 +11,14 @@ class ReviewLstView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.ReviewBaseSerializer
|
serializer_class = serializers.ReviewBaseSerializer
|
||||||
queryset = models.Review.objects.all()
|
queryset = models.Review.objects.all()
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
|
||||||
|
filterset_class = filters.ReviewFilter
|
||||||
|
|
||||||
|
|
||||||
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Comment RUD view."""
|
"""Comment RUD view."""
|
||||||
serializer_class = serializers.ReviewBaseSerializer
|
serializer_class = serializers.ReviewBaseSerializer
|
||||||
queryset = models.Review.objects.all()
|
queryset = models.Review.objects.all()
|
||||||
permission_classes = [IsReviewerManager | IsRestaurantReviewer]
|
permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer]
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,34 @@ class EstablishmentDocument(Document):
|
||||||
properties=OBJECT_FIELD_PROPERTIES),
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
|
visible_tags = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
},
|
||||||
|
multi=True)
|
||||||
|
products = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'wine_region': fields.ObjectField(properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.KeywordField(),
|
||||||
|
'country': fields.ObjectField(properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.ObjectField(attr='name_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'code': fields.KeywordField(),
|
||||||
|
}),
|
||||||
|
# 'coordinates': fields.GeoPointField(),
|
||||||
|
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
}),
|
||||||
|
'wine_sub_region': fields.ObjectField(properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.KeywordField(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
multi=True
|
||||||
|
)
|
||||||
schedule = fields.ListField(fields.ObjectField(
|
schedule = fields.ListField(fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(attr='id'),
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,13 @@ class NewsDocument(Document):
|
||||||
'value': fields.KeywordField()
|
'value': fields.KeywordField()
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True)
|
||||||
|
visible_tags = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(attr='id'),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
},
|
||||||
|
multi=True)
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,43 @@ class ProductDocument(Document):
|
||||||
},
|
},
|
||||||
multi=True
|
multi=True
|
||||||
)
|
)
|
||||||
|
preview_image_url = fields.KeywordField(attr='preview_image_url')
|
||||||
establishment = fields.ObjectField(
|
establishment = fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.KeywordField(),
|
'name': fields.KeywordField(),
|
||||||
|
'index_name': fields.KeywordField(),
|
||||||
'slug': fields.KeywordField(),
|
'slug': fields.KeywordField(),
|
||||||
# 'city' TODO: city indexing
|
'city': fields.ObjectField(
|
||||||
|
attr='address.city',
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.KeywordField(),
|
||||||
|
'code': fields.KeywordField(),
|
||||||
|
'country': fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.ObjectField(attr='name_indexing',
|
||||||
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'code': fields.KeywordField(),
|
||||||
|
'svg_image': fields.KeywordField(attr='svg_image_indexing')
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'address': fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'city': fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'country': fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'code': fields.KeywordField()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
wine_colors = fields.ObjectField(
|
wine_colors = fields.ObjectField(
|
||||||
|
|
@ -44,6 +75,14 @@ class ProductDocument(Document):
|
||||||
},
|
},
|
||||||
multi=True,
|
multi=True,
|
||||||
)
|
)
|
||||||
|
grape_variety = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'value': fields.KeywordField(),
|
||||||
|
},
|
||||||
|
multi=True,
|
||||||
|
)
|
||||||
wine_region = fields.ObjectField(properties={
|
wine_region = fields.ObjectField(properties={
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.KeywordField(),
|
'name': fields.KeywordField(),
|
||||||
|
|
@ -56,7 +95,10 @@ class ProductDocument(Document):
|
||||||
# 'coordinates': fields.GeoPointField(),
|
# 'coordinates': fields.GeoPointField(),
|
||||||
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
'description': fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||||
})
|
})
|
||||||
wine_sub_region = fields.ObjectField(properties={'name': fields.KeywordField()})
|
wine_sub_region = fields.ObjectField(properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'name': fields.KeywordField(),
|
||||||
|
})
|
||||||
classifications = fields.ObjectField( # TODO
|
classifications = fields.ObjectField( # TODO
|
||||||
properties={
|
properties={
|
||||||
'classification_type': fields.ObjectField(properties={}),
|
'classification_type': fields.ObjectField(properties={}),
|
||||||
|
|
@ -95,13 +137,23 @@ class ProductDocument(Document):
|
||||||
},
|
},
|
||||||
multi=True
|
multi=True
|
||||||
)
|
)
|
||||||
|
related_tags = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES),
|
||||||
|
'value': fields.KeywordField(),
|
||||||
|
},
|
||||||
|
multi=True
|
||||||
|
)
|
||||||
|
name = fields.TextField(attr='display_name', analyzer='english')
|
||||||
|
name_ru = fields.TextField(attr='display_name', analyzer='russian')
|
||||||
|
name_fr = fields.TextField(attr='display_name', analyzer='french')
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
model = models.Product
|
model = models.Product
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'category',
|
'category',
|
||||||
'name',
|
|
||||||
'available',
|
'available',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
'slug',
|
'slug',
|
||||||
|
|
@ -109,6 +161,7 @@ class ProductDocument(Document):
|
||||||
'state',
|
'state',
|
||||||
'old_unique_key',
|
'old_unique_key',
|
||||||
'vintage',
|
'vintage',
|
||||||
|
'average_price',
|
||||||
)
|
)
|
||||||
related_models = [models.ProductType]
|
related_models = [models.ProductType]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
"""Search indexes filters."""
|
"""Search indexes filters."""
|
||||||
from elasticsearch_dsl.query import Q
|
from elasticsearch_dsl.query import Q
|
||||||
from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend
|
from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend
|
||||||
from utils.models import get_current_locale
|
from search_indexes.utils import OBJECT_FIELD_PROPERTIES
|
||||||
|
|
||||||
|
|
||||||
class CustomSearchFilterBackend(SearchFilterBackend):
|
class CustomSearchFilterBackend(SearchFilterBackend):
|
||||||
"""Custom SearchFilterBackend."""
|
"""Custom SearchFilterBackend."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_field_name(view, field):
|
def search_among_all_locales(view, search_kwargs: dict):
|
||||||
field_name = field
|
|
||||||
if hasattr(view, 'search_fields') and hasattr(view, 'translated_search_fields'):
|
if hasattr(view, 'search_fields') and hasattr(view, 'translated_search_fields'):
|
||||||
if field in view.translated_search_fields:
|
all_supported_locales = OBJECT_FIELD_PROPERTIES.keys()
|
||||||
field_name = f'{field}.{get_current_locale()}'
|
fields = search_kwargs.copy().keys()
|
||||||
return field_name
|
for field in fields:
|
||||||
|
if field in view.translated_search_fields:
|
||||||
|
value = search_kwargs.pop(field)
|
||||||
|
search_kwargs.update({f'{field}.{locale}': value for locale in all_supported_locales})
|
||||||
|
|
||||||
|
|
||||||
def construct_search(self, request, view):
|
def construct_search(self, request, view):
|
||||||
"""Construct search.
|
"""Construct search.
|
||||||
|
|
@ -54,29 +57,69 @@ class CustomSearchFilterBackend(SearchFilterBackend):
|
||||||
field, value = __values
|
field, value = __values
|
||||||
if field in view.search_fields:
|
if field in view.search_fields:
|
||||||
# Initial kwargs for the match query
|
# Initial kwargs for the match query
|
||||||
field_kwargs = {self.get_field_name(view, field): {'query': value}}
|
field_kwargs = {field: {'query': value}}
|
||||||
# In case if we deal with structure 2
|
# In case if we deal with structure 2
|
||||||
if isinstance(view.search_fields, dict):
|
if isinstance(view.search_fields, dict):
|
||||||
extra_field_kwargs = view.search_fields[field]
|
extra_field_kwargs = view.search_fields[field]
|
||||||
if extra_field_kwargs:
|
if extra_field_kwargs:
|
||||||
field_kwargs[self.get_field_name(view, field)].update(extra_field_kwargs)
|
field_kwargs[field].update(extra_field_kwargs)
|
||||||
# The match query
|
# The match query
|
||||||
__queries.append(
|
self.search_among_all_locales(view, field_kwargs)
|
||||||
Q("match", **field_kwargs)
|
if len(field_kwargs.keys()) > 1:
|
||||||
)
|
for k, v in field_kwargs.items():
|
||||||
|
__queries.append(
|
||||||
|
Q("match", **{k: v})
|
||||||
|
)
|
||||||
|
__queries.append(
|
||||||
|
Q('wildcard',
|
||||||
|
**{k: {
|
||||||
|
'value': f'*{search_term.lower()}*',
|
||||||
|
'boost': v.get('boost', 1) + 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
__queries.append(
|
||||||
|
Q("match", **field_kwargs)
|
||||||
|
)
|
||||||
|
__queries.append(
|
||||||
|
Q('wildcard', **{field: {'value': f'*{search_term.lower()}*',
|
||||||
|
'boost': field_kwargs[field].get('boost', 1) + 30}})
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for field in view.search_fields:
|
for field in view.search_fields:
|
||||||
# Initial kwargs for the match query
|
# Initial kwargs for the match query
|
||||||
field_kwargs = {self.get_field_name(view, field): {'query': search_term}}
|
field_kwargs = {field: {'query': search_term}}
|
||||||
|
|
||||||
# In case if we deal with structure 2
|
# In case if we deal with structure 2
|
||||||
if isinstance(view.search_fields, dict):
|
if isinstance(view.search_fields, dict):
|
||||||
extra_field_kwargs = view.search_fields[field]
|
extra_field_kwargs = view.search_fields[field]
|
||||||
if extra_field_kwargs:
|
if extra_field_kwargs:
|
||||||
field_kwargs[self.get_field_name(view, field)].update(extra_field_kwargs)
|
field_kwargs[field].update(extra_field_kwargs)
|
||||||
|
|
||||||
# The match query
|
# The match query
|
||||||
__queries.append(
|
self.search_among_all_locales(view, field_kwargs)
|
||||||
Q("match", **field_kwargs)
|
if len(field_kwargs.keys()) > 1:
|
||||||
)
|
for k, v in field_kwargs.items():
|
||||||
|
__queries.append(
|
||||||
|
Q("match", **{k: v})
|
||||||
|
)
|
||||||
|
__queries.append(
|
||||||
|
Q('wildcard',
|
||||||
|
**{k: {
|
||||||
|
'value': f'*{search_term.lower()}*',
|
||||||
|
'boost': v.get('boost', 1) + 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
__queries.append(
|
||||||
|
Q("match", **field_kwargs)
|
||||||
|
)
|
||||||
|
__queries.append(
|
||||||
|
Q('wildcard', **{field: {'value': f'*{search_term.lower()}*',
|
||||||
|
'boost': field_kwargs[field].get('boost', 1) + 30}})
|
||||||
|
)
|
||||||
return __queries
|
return __queries
|
||||||
|
|
@ -39,7 +39,8 @@ class ProductSubtypeDocumentSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
name_translated = serializers.SerializerMethodField()
|
name_translated = serializers.SerializerMethodField()
|
||||||
|
|
||||||
get_name_translated = lambda obj: get_translated_value(obj.name)
|
def get_name_translated(self, obj):
|
||||||
|
return get_translated_value(obj.name)
|
||||||
|
|
||||||
|
|
||||||
class WineRegionCountryDocumentSerialzer(serializers.Serializer):
|
class WineRegionCountryDocumentSerialzer(serializers.Serializer):
|
||||||
|
|
@ -64,9 +65,12 @@ class WineRegionDocumentSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
country = WineRegionCountryDocumentSerialzer(allow_null=True)
|
country = WineRegionCountryDocumentSerialzer(allow_null=True)
|
||||||
|
|
||||||
|
def get_attribute(self, instance):
|
||||||
|
return instance.wine_region if instance and instance.wine_region else None
|
||||||
|
|
||||||
class WineColorDocumentSerializer(serializers.Serializer):
|
|
||||||
"""Wine color ES document serializer,"""
|
class TagDocumentSerializer(serializers.Serializer):
|
||||||
|
"""Tag ES document serializer,"""
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
label_translated = serializers.SerializerMethodField()
|
label_translated = serializers.SerializerMethodField()
|
||||||
|
|
@ -79,12 +83,16 @@ class WineColorDocumentSerializer(serializers.Serializer):
|
||||||
return get_translated_value(obj.label)
|
return get_translated_value(obj.label)
|
||||||
|
|
||||||
|
|
||||||
class ProductEstablishmentDocumentSerializer(serializers.Serializer):
|
class ProductTypeDocumentSerializer(serializers.Serializer):
|
||||||
"""Related to Product Establishment ES document serializer."""
|
"""Product type ES document serializer."""
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
name = serializers.CharField()
|
index_name = serializers.CharField()
|
||||||
slug = serializers.CharField()
|
name_translated = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_name_translated(obj):
|
||||||
|
return get_translated_value(obj.name)
|
||||||
|
|
||||||
|
|
||||||
class CityDocumentShortSerializer(serializers.Serializer):
|
class CityDocumentShortSerializer(serializers.Serializer):
|
||||||
|
|
@ -95,6 +103,39 @@ class CityDocumentShortSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class CountryDocumentSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
id = serializers.IntegerField()
|
||||||
|
code = serializers.CharField(allow_null=True)
|
||||||
|
svg_image = serializers.CharField()
|
||||||
|
name_translated = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_name_translated(obj):
|
||||||
|
return get_translated_value(obj.name)
|
||||||
|
|
||||||
|
|
||||||
|
class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer):
|
||||||
|
|
||||||
|
country = CountryDocumentSerializer()
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
if instance != AttrDict(d={}) or \
|
||||||
|
(isinstance(instance, dict) and len(instance) != 0):
|
||||||
|
return super().to_representation(instance)
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
class AddressDocumentSerializer(serializers.Serializer):
|
class AddressDocumentSerializer(serializers.Serializer):
|
||||||
"""Address serializer for ES Document."""
|
"""Address serializer for ES Document."""
|
||||||
|
|
||||||
|
|
@ -132,7 +173,7 @@ class NewsDocumentSerializer(DocumentSerializer):
|
||||||
title_translated = serializers.SerializerMethodField(allow_null=True)
|
title_translated = serializers.SerializerMethodField(allow_null=True)
|
||||||
subtitle_translated = serializers.SerializerMethodField(allow_null=True)
|
subtitle_translated = serializers.SerializerMethodField(allow_null=True)
|
||||||
news_type = NewsTypeSerializer()
|
news_type = NewsTypeSerializer()
|
||||||
tags = TagsDocumentSerializer(many=True)
|
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -165,7 +206,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
|
||||||
establishment_type = EstablishmentTypeSerializer()
|
establishment_type = EstablishmentTypeSerializer()
|
||||||
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
||||||
address = AddressDocumentSerializer(allow_null=True)
|
address = AddressDocumentSerializer(allow_null=True)
|
||||||
tags = TagsDocumentSerializer(many=True)
|
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||||
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -198,17 +239,14 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
|
||||||
class ProductDocumentSerializer(DocumentSerializer):
|
class ProductDocumentSerializer(DocumentSerializer):
|
||||||
"""Product document serializer"""
|
"""Product document serializer"""
|
||||||
|
|
||||||
tags = TagsDocumentSerializer(many=True)
|
tags = TagsDocumentSerializer(many=True, source='related_tags')
|
||||||
subtypes = ProductSubtypeDocumentSerializer(many=True)
|
subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True)
|
||||||
wine_region = WineRegionDocumentSerializer(allow_null=True)
|
wine_region = WineRegionDocumentSerializer(allow_null=True)
|
||||||
wine_colors = WineColorDocumentSerializer(many=True)
|
wine_colors = TagDocumentSerializer(many=True)
|
||||||
product_type = serializers.SerializerMethodField()
|
grape_variety = TagDocumentSerializer(many=True)
|
||||||
|
product_type = ProductTypeDocumentSerializer(allow_null=True)
|
||||||
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
|
establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_product_type(obj):
|
|
||||||
return get_translated_value(obj.product_type.name if obj.product_type else {})
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
@ -216,6 +254,7 @@ class ProductDocumentSerializer(DocumentSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'category',
|
'category',
|
||||||
|
'preview_image_url',
|
||||||
'name',
|
'name',
|
||||||
'available',
|
'available',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
|
|
@ -229,5 +268,7 @@ class ProductDocumentSerializer(DocumentSerializer):
|
||||||
'subtypes',
|
'subtypes',
|
||||||
'wine_region',
|
'wine_region',
|
||||||
'wine_colors',
|
'wine_colors',
|
||||||
|
'grape_variety',
|
||||||
'establishment_detail',
|
'establishment_detail',
|
||||||
|
'average_price',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,10 @@ class NewsDocumentViewSet(BaseDocumentViewSet):
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = {
|
search_fields = {
|
||||||
'title': {'fuzziness': 'auto:2,5'},
|
'title': {'fuzziness': 'auto:2,5',
|
||||||
'subtitle': {'fuzziness': 'auto:2,5'},
|
'boost': 3},
|
||||||
|
'subtitle': {'fuzziness': 'auto:2,5',
|
||||||
|
'boost': 2},
|
||||||
'description': {'fuzziness': 'auto:2,5'},
|
'description': {'fuzziness': 'auto:2,5'},
|
||||||
}
|
}
|
||||||
translated_search_fields = (
|
translated_search_fields = (
|
||||||
|
|
@ -86,11 +88,9 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
|
|
||||||
search_fields = {
|
search_fields = {
|
||||||
'name': {'fuzziness': 'auto:2,5',
|
'name': {'fuzziness': 'auto:2,5',
|
||||||
'boost': '2'},
|
'boost': 4},
|
||||||
'transliterated_name': {'fuzziness': 'auto:2,5',
|
'transliterated_name': {'fuzziness': 'auto:2,5',
|
||||||
'boost': '2'},
|
'boost': 3},
|
||||||
'index_name': {'fuzziness': 'auto:2,5',
|
|
||||||
'boost': '2'},
|
|
||||||
'description': {'fuzziness': 'auto:2,5'},
|
'description': {'fuzziness': 'auto:2,5'},
|
||||||
}
|
}
|
||||||
translated_search_fields = (
|
translated_search_fields = (
|
||||||
|
|
@ -124,6 +124,20 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
|
||||||
constants.LOOKUP_QUERY_IN,
|
constants.LOOKUP_QUERY_IN,
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
'wine_region_id': {
|
||||||
|
'field': 'products.wine_region.id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'wine_sub_region_id': {
|
||||||
|
'field': 'products.wine_sub_region_id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
'country_id': {
|
'country_id': {
|
||||||
'field': 'address.city.country.id'
|
'field': 'address.city.country.id'
|
||||||
},
|
},
|
||||||
|
|
@ -197,32 +211,27 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
"""Product document ViewSet."""
|
"""Product document ViewSet."""
|
||||||
|
|
||||||
document = ProductDocument
|
document = ProductDocument
|
||||||
lookup_field = 'slug'
|
|
||||||
pagination_class = ProjectMobilePagination
|
pagination_class = ProjectMobilePagination
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.ProductDocumentSerializer
|
serializer_class = serializers.ProductDocumentSerializer
|
||||||
|
|
||||||
# def get_queryset(self):
|
|
||||||
# qs = super(ProductDocumentViewSet, self).get_queryset()
|
|
||||||
# qs = qs.filter('match', is_publish=True)
|
|
||||||
# return qs
|
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
FilteringFilterBackend,
|
FilteringFilterBackend,
|
||||||
filters.CustomSearchFilterBackend,
|
filters.CustomSearchFilterBackend,
|
||||||
GeoSpatialFilteringFilterBackend,
|
|
||||||
DefaultOrderingFilterBackend,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = {
|
search_fields = {
|
||||||
'name': {'fuzziness': 'auto:2,5',
|
'name': {'fuzziness': 'auto:2,5',
|
||||||
'boost': '2'},
|
'boost': 8},
|
||||||
|
'name_ru': {'fuzziness': 'auto:2,5',
|
||||||
|
'boost': 6},
|
||||||
|
'name_fr': {'fuzziness': 'auto:2,5',
|
||||||
|
'boost': 7},
|
||||||
'transliterated_name': {'fuzziness': 'auto:2,5',
|
'transliterated_name': {'fuzziness': 'auto:2,5',
|
||||||
'boost': '2'},
|
'boost': 3},
|
||||||
'index_name': {'fuzziness': 'auto:2,5',
|
|
||||||
'boost': '2'},
|
|
||||||
'description': {'fuzziness': 'auto:2,5'},
|
'description': {'fuzziness': 'auto:2,5'},
|
||||||
}
|
}
|
||||||
|
|
||||||
translated_search_fields = (
|
translated_search_fields = (
|
||||||
'description',
|
'description',
|
||||||
)
|
)
|
||||||
|
|
@ -231,10 +240,34 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
'slug': 'slug',
|
'slug': 'slug',
|
||||||
'tags_id': {
|
'tags_id': {
|
||||||
'field': 'tags.id',
|
'field': 'tags.id',
|
||||||
'lookups': [constants.LOOKUP_QUERY_IN]
|
'lookups': [constants.LOOKUP_QUERY_IN],
|
||||||
|
},
|
||||||
|
'country': {
|
||||||
|
'field': 'establishment.address.city.country.code',
|
||||||
},
|
},
|
||||||
'wine_colors_id': {
|
'wine_colors_id': {
|
||||||
'field': 'wine_colors.id',
|
'field': 'wine_colors.id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'wine_region_id': {
|
||||||
|
'field': 'wine_region.id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'wine_sub_region_id': {
|
||||||
|
'field': 'wine_sub_region_id',
|
||||||
|
'lookups': [
|
||||||
|
constants.LOOKUP_QUERY_IN,
|
||||||
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'grape_variety_id': {
|
||||||
|
'field': 'grape_variety.id',
|
||||||
'lookups': [
|
'lookups': [
|
||||||
constants.LOOKUP_QUERY_IN,
|
constants.LOOKUP_QUERY_IN,
|
||||||
constants.LOOKUP_QUERY_EXCLUDE,
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
|
|
@ -246,7 +279,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
'for_establishment': {
|
'for_establishment': {
|
||||||
'field': 'establishment.slug',
|
'field': 'establishment.slug',
|
||||||
},
|
},
|
||||||
'type': {
|
'product_type': {
|
||||||
'field': 'product_type.index_name',
|
'field': 'product_type.index_name',
|
||||||
},
|
},
|
||||||
'subtype': {
|
'subtype': {
|
||||||
|
|
@ -254,8 +287,6 @@ class ProductDocumentViewSet(BaseDocumentViewSet):
|
||||||
'lookups': [
|
'lookups': [
|
||||||
constants.LOOKUP_QUERY_IN,
|
constants.LOOKUP_QUERY_IN,
|
||||||
constants.LOOKUP_QUERY_EXCLUDE,
|
constants.LOOKUP_QUERY_EXCLUDE,
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
|
||||||
geo_spatial_filter_fields = {
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ from django_filters import rest_framework as filters
|
||||||
from establishment.models import EstablishmentType
|
from establishment.models import EstablishmentType
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from tag import models
|
from tag import models
|
||||||
|
from product import models as product_models
|
||||||
|
|
||||||
class TagsBaseFilterSet(filters.FilterSet):
|
class TagsBaseFilterSet(filters.FilterSet):
|
||||||
|
|
||||||
|
|
@ -31,27 +32,49 @@ class TagCategoryFilterSet(TagsBaseFilterSet):
|
||||||
"""TagCategory filterset."""
|
"""TagCategory filterset."""
|
||||||
|
|
||||||
establishment_type = filters.CharFilter(method='by_establishment_type')
|
establishment_type = filters.CharFilter(method='by_establishment_type')
|
||||||
|
product_type = filters.CharFilter(method='by_product_type')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.TagCategory
|
model = models.TagCategory
|
||||||
fields = ('type',
|
fields = ('type',
|
||||||
'establishment_type', )
|
'establishment_type',
|
||||||
|
'product_type', )
|
||||||
|
|
||||||
|
def by_product_type(self, queryset, name, value):
|
||||||
|
# if value == product_models.ProductType.WINE:
|
||||||
|
# queryset = queryset.filter(index_name='wine-color').filter(tags__products__isnull=False)
|
||||||
|
queryset = queryset.by_product_type(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
# todo: filter by establishment type
|
# todo: filter by establishment type
|
||||||
def by_establishment_type(self, queryset, name, value):
|
def by_establishment_type(self, queryset, name, value):
|
||||||
return queryset.by_establishment_type(value)
|
if value == EstablishmentType.ARTISAN:
|
||||||
|
qs = models.TagCategory.objects.filter(index_name='shop_category')
|
||||||
|
else:
|
||||||
|
qs = queryset.by_establishment_type(value)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class TagsFilterSet(TagsBaseFilterSet):
|
class TagsFilterSet(TagsBaseFilterSet):
|
||||||
"""Chosen tags filterset."""
|
"""Chosen tags filterset."""
|
||||||
|
|
||||||
|
establishment_type = filters.CharFilter(method='by_establishment_type')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Tag
|
model = models.Tag
|
||||||
fields = ('type',)
|
fields = (
|
||||||
|
'type',
|
||||||
|
'establishment_type',
|
||||||
|
)
|
||||||
|
|
||||||
|
def by_establishment_type(self, queryset, name, value):
|
||||||
|
if value == EstablishmentType.ARTISAN:
|
||||||
|
return models.Tag.objects.by_category_index_name('shop_category')[0:8]
|
||||||
|
return queryset.by_establishment_type(value)
|
||||||
|
|
||||||
# TMP TODO remove it later
|
# TMP TODO remove it later
|
||||||
# Временный хардкод для демонстрации 4 ноября, потом удалить!
|
# Временный хардкод для демонстрации 4 ноября, потом удалить!
|
||||||
|
|
@ -66,3 +89,4 @@ class TagsFilterSet(TagsBaseFilterSet):
|
||||||
queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
|
queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
|
||||||
'value')
|
'value')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user