Merge branch 'develop' into feature/permission_winery

This commit is contained in:
Виктор Гладких 2019-12-06 16:58:44 +03:00
commit 8e63b969bf
25 changed files with 159 additions and 49 deletions

View File

View File

@ -0,0 +1,8 @@
from booking.urls import common as common_views
app = 'booking'
urlpatterns_api = []
urlpatterns = urlpatterns_api + \
common_views.urlpatterns

8
apps/booking/urls/web.py Normal file
View File

@ -0,0 +1,8 @@
from booking.urls import common as common_views
app = 'booking'
urlpatterns_api = []
urlpatterns = urlpatterns_api + \
common_views.urlpatterns

View File

@ -457,9 +457,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
def visible_tags(self): def visible_tags(self):
return super().visible_tags \ return super().visible_tags \
.exclude(category__index_name__in=['guide', 'collection', 'purchased_item', .exclude(category__index_name__in=['guide', 'collection', 'purchased_item',
'business_tag', 'business_tags_de', 'tag']) 'business_tag', 'business_tags_de']) \
.exclude(value__in=['rss', 'rss_selection'])
# todo: recalculate toque_number # todo: recalculate toque_number
@property
def visible_tags_detail(self):
"""Removes some tags from detail Establishment representation"""
return self.visible_tags.exclude(category__index_name__in=['tag'])
def recalculate_toque_number(self): def recalculate_toque_number(self):
toque_number = 0 toque_number = 0
if self.address and self.public_mark: if self.address and self.public_mark:

View File

@ -232,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
def validate(self, attrs): def validate(self, attrs):
"""Override validate method.""" """Override validate method."""
establishment_pk = self.get_request_kwargs().get('pk') establishment_pk = self.get_request_kwargs().get('pk')
establishment_slug = self.get_request_kwargs().get('slug')
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
image_id = self.get_request_kwargs().get('image_id') image_id = self.get_request_kwargs().get('image_id')
establishment_qs = models.Establishment.objects.filter(pk=establishment_pk) establishment_qs = models.Establishment.objects.filter(**search_kwargs)
image_qs = Image.objects.filter(id=image_id) image_qs = Image.objects.filter(id=image_id)
if not establishment_qs.exists(): if not establishment_qs.exists():

View File

@ -239,6 +239,30 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
] ]
class _EstablishmentAddressShortSerializer(serializers.ModelSerializer):
"""Short serializer for establishment."""
city = CitySerializer(source='address.city', allow_null=True)
establishment_type = EstablishmentTypeGeoSerializer()
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
currency = CurrencySerializer(read_only=True)
address = AddressBaseSerializer(read_only=True)
class Meta:
"""Meta class."""
model = models.Establishment
fields = [
'id',
'name',
'index_name',
'slug',
'city',
'establishment_type',
'establishment_subtypes',
'currency',
'address',
]
class EstablishmentProductShortSerializer(serializers.ModelSerializer): class EstablishmentProductShortSerializer(serializers.ModelSerializer):
"""SHORT Serializer for displaying info about an establishment on product page.""" """SHORT Serializer for displaying info about an establishment on product page."""
establishment_type = EstablishmentTypeGeoSerializer() establishment_type = EstablishmentTypeGeoSerializer()
@ -369,7 +393,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=True) many=True)
address = AddressDetailSerializer(read_only=True) address = AddressDetailSerializer(read_only=True)
tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags_detail')
menu = MenuSerializers(source='menu_set', many=True, read_only=True) menu = MenuSerializers(source='menu_set', many=True, read_only=True)
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
@ -522,7 +546,9 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
"""Serializer to carousel object w/ model News.""" """Serializer to carousel object w/ model News."""
def validate(self, attrs): def validate(self, attrs):
establishment = models.Establishment.objects.filter(pk=self.pk).first() search_kwargs = {'pk': self.pk} if self.pk else {'slug': self.slug}
establishment = models.Establishment.objects.filter(**search_kwargs).first()
if not establishment: if not establishment:
raise serializers.ValidationError({'detail': _('Object not found.')}) raise serializers.ValidationError({'detail': _('Object not found.')})

View File

@ -157,18 +157,18 @@ class EstablishmentBackTests(BaseTestCase):
response = self.client.post('/api/back/establishments/', data=data, format='json') response = self.client.post('/api/back/establishments/', data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'name': 'Test new establishment' 'name': 'Test new establishment'
} }
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/',
data=update_data) data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/',
format='json') format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -425,22 +425,22 @@ class EstablishmentShedulerTests(ChildTestCase):
'weekday': 1 'weekday': 1
} }
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data) response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
schedule = response.data schedule = response.data
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') response = self.client.get(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'weekday': 2 'weekday': 2
} }
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/', response = self.client.patch(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/',
data=update_data) data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/{schedule["id"]}/') response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/schedule/{schedule["id"]}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -537,8 +537,8 @@ class EstablishmentCarouselTests(ChildTestCase):
"object_id": self.establishment.id "object_id": self.establishment.id
} }
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/carousels/', data=data) response = self.client.post(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/carousels/') response = self.client.delete(f'/api/back/establishments/slug/{self.establishment.slug}/carousels/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

View File

@ -8,25 +8,25 @@ app_name = 'establishment'
urlpatterns = [ urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'), path('', views.EstablishmentListCreateView.as_view(), name='list'),
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'), path('slug/<slug:slug>/', views.EstablishmentRUDView.as_view(), name='detail'),
path('<int:pk>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(), path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
name='create-destroy-carousels'), name='create-destroy-carousels'),
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(), path('slug/<slug:slug>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
name='schedule-rud'), name='schedule-rud'),
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(), path('slug/<slug:slug>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
name='schedule-create'), name='schedule-create'),
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(), path('slug/<slug:slug>/gallery/', views.EstablishmentGalleryListView.as_view(),
name='gallery-list'), name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', path('slug/<slug:slug>/gallery/<int:image_id>/',
views.EstablishmentGalleryCreateDestroyView.as_view(), views.EstablishmentGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
path('<int:pk>/companies/', views.EstablishmentCompanyListCreateView.as_view(), path('slug/<slug:slug>/companies/', views.EstablishmentCompanyListCreateView.as_view(),
name='company-list-create'), name='company-list-create'),
path('<int:pk>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(), path('slug/<slug:slug>/companies/<int:company_pk>/', views.EstablishmentCompanyRUDView.as_view(),
name='company-rud'), name='company-rud'),
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(), path('slug/<slug:slug>/notes/', views.EstablishmentNoteListCreateView.as_view(),
name='note-list-create'), name='note-list-create'),
path('<int:pk>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(), path('slug/<slug:slug>/notes/<int:note_pk>/', views.EstablishmentNoteRUDView.as_view(),
name='note-rud'), 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'),

View File

@ -31,6 +31,7 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'slug'
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentRUDSerializer serializer_class = serializers.EstablishmentRUDSerializer
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
@ -38,6 +39,7 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view""" """Establishment schedule RUD view"""
lookup_field = 'slug'
serializer_class = ScheduleRUDSerializer serializer_class = ScheduleRUDSerializer
permission_classes = [IsWineryReviewer |IsEstablishmentManager] permission_classes = [IsWineryReviewer |IsEstablishmentManager]
@ -45,11 +47,11 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
""" """
Returns the object the view is displaying. Returns the object the view is displaying.
""" """
establishment_pk = self.kwargs['pk'] establishment_slug = self.kwargs['slug']
schedule_id = self.kwargs['schedule_id'] schedule_id = self.kwargs['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) slug=establishment_slug)
schedule = get_object_or_404(klass=establishment.schedule, schedule = get_object_or_404(klass=establishment.schedule,
id=schedule_id) id=schedule_id)
@ -62,6 +64,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentScheduleCreateView(generics.CreateAPIView): class EstablishmentScheduleCreateView(generics.CreateAPIView):
"""Establishment schedule Create view""" """Establishment schedule Create view"""
lookup_field = 'slug'
serializer_class = ScheduleCreateSerializer serializer_class = ScheduleCreateSerializer
queryset = Timetable.objects.all() queryset = Timetable.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
@ -210,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
CreateDestroyGalleryViewMixin): CreateDestroyGalleryViewMixin):
"""Resource for a create|destroy gallery for establishment for back-office users.""" """Resource for a create|destroy gallery for establishment for back-office users."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
def get_object(self): def get_object(self):
@ -218,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
""" """
establishment_qs = self.filter_queryset(self.get_queryset()) establishment_qs = self.filter_queryset(self.get_queryset())
establishment = get_object_or_404(establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(establishment_qs, slug=self.kwargs.get('slug'))
gallery = get_object_or_404(establishment.establishment_gallery, gallery = get_object_or_404(establishment.establishment_gallery,
image_id=self.kwargs.get('image_id')) image_id=self.kwargs.get('image_id'))
@ -231,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
class EstablishmentGalleryListView(EstablishmentMixinViews, class EstablishmentGalleryListView(EstablishmentMixinViews,
generics.ListAPIView): generics.ListAPIView):
"""Resource for returning gallery for establishment for back-office users.""" """Resource for returning gallery for establishment for back-office users."""
lookup_field = 'slug'
serializer_class = serializers.ImageBaseSerializer serializer_class = serializers.ImageBaseSerializer
def get_object(self): def get_object(self):
"""Override get_object method.""" """Override get_object method."""
qs = super(EstablishmentGalleryListView, self).get_queryset() qs = super(EstablishmentGalleryListView, self).get_queryset()
establishment = get_object_or_404(qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -252,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView): generics.ListCreateAPIView):
"""List|Create establishment company view.""" """List|Create establishment company view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentCompanyListCreateSerializer serializer_class = serializers.EstablishmentCompanyListCreateSerializer
def get_object(self): def get_object(self):
@ -259,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -275,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
generics.RetrieveUpdateDestroyAPIView): generics.RetrieveUpdateDestroyAPIView):
"""Create|Retrieve|Update|Destroy establishment company view.""" """Create|Retrieve|Update|Destroy establishment company view."""
lookup_field = 'slug'
serializer_class = serializers.CompanyBaseSerializer serializer_class = serializers.CompanyBaseSerializer
def get_object(self): def get_object(self):
@ -282,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_ad_qs = self.filter_queryset(establishment_qs) filtered_ad_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_ad_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk')) company = get_object_or_404(establishment.companies.all(), pk=self.kwargs.get('company_pk'))
# May raise a permission denied # May raise a permission denied
@ -295,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
generics.ListCreateAPIView): generics.ListCreateAPIView):
"""Retrieve|Update|Destroy establishment note view.""" """Retrieve|Update|Destroy establishment note view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentNoteListCreateSerializer serializer_class = serializers.EstablishmentNoteListCreateSerializer
def get_object(self): def get_object(self):
@ -302,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
# May raise a permission denied # May raise a permission denied
self.check_object_permissions(self.request, establishment) self.check_object_permissions(self.request, establishment)
@ -318,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
generics.RetrieveUpdateDestroyAPIView): generics.RetrieveUpdateDestroyAPIView):
"""Create|Retrieve|Update|Destroy establishment note view.""" """Create|Retrieve|Update|Destroy establishment note view."""
lookup_field = 'slug'
serializer_class = serializers.EstablishmentNoteBaseSerializer serializer_class = serializers.EstablishmentNoteBaseSerializer
def get_object(self): def get_object(self):
@ -325,7 +334,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
establishment_qs = models.Establishment.objects.all() establishment_qs = models.Establishment.objects.all()
filtered_establishment_qs = self.filter_queryset(establishment_qs) filtered_establishment_qs = self.filter_queryset(establishment_qs)
establishment = get_object_or_404(filtered_establishment_qs, pk=self.kwargs.get('pk')) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk']) note = get_object_or_404(establishment.notes.all(), pk=self.kwargs['note_pk'])
# May raise a permission denied # May raise a permission denied

View File

@ -152,6 +152,7 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView): class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
"""View for create/destroy establishment from carousel.""" """View for create/destroy establishment from carousel."""
lookup_field = 'slug'
_model = models.Establishment _model = models.Establishment
serializer_class = serializers.EstablishmentCarouselCreateSerializer serializer_class = serializers.EstablishmentCarouselCreateSerializer

View File

@ -22,8 +22,9 @@ class Command(BaseCommand):
new_address = Address.objects.filter(old_id=idx).first() new_address = Address.objects.filter(old_id=idx).first()
if new_address: if new_address:
new_address.number = 0 new_address.number = 0
new_address.street_name_2 = ''
new_address.street_name_1 = address new_address.street_name_1 = address
update_address.append(new_address) update_address.append(new_address)
Address.objects.bulk_update(update_address, ['number', 'street_name_1']) Address.objects.bulk_update(update_address, ['number', 'street_name_1', 'street_name_2'])
self.stdout.write(self.style.WARNING(f'Updated addresses: {len(update_address)}')) self.stdout.write(self.style.WARNING(f'Updated addresses: {len(update_address)}'))

View File

@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from news.models import News
import re
class Command(BaseCommand):
help = 'Removes empty img html tags from news description'
relative_img_regex = re.compile(r'\<img.+src=(?!https?:\/\/)([^\/].+?)[\"|\']>', re.I)
def handle(self, *args, **kwargs):
for news in News.objects.all():
if isinstance(news.description, dict):
news.description = {locale: self.relative_img_regex.sub('', rich_text)
for locale, rich_text in news.description.items()}
self.stdout.write(self.style.WARNING(f'Replaced {news} empty img html tags...\n'))
news.save()

View File

@ -8,7 +8,7 @@ from partner.serializers import common as serializers
# Mixins # Mixins
class PartnerViewMixin(generics.GenericAPIView): class PartnerViewMixin(generics.GenericAPIView):
"""View mixin for Partner views""" """View mixin for Partner views"""
queryset = models.Partner.objects.all() queryset = models.Partner.objects.distinct("name")
# Views # Views

View File

@ -9,6 +9,7 @@ class ProductFilterSet(filters.FilterSet):
"""Product filter set.""" """Product filter set."""
establishment_id = filters.NumberFilter() establishment_id = filters.NumberFilter()
current_product = filters.CharFilter(method='without_current_product')
product_type = filters.CharFilter(method='by_product_type') product_type = filters.CharFilter(method='by_product_type')
product_subtype = filters.CharFilter(method='by_product_subtype') product_subtype = filters.CharFilter(method='by_product_subtype')
@ -21,6 +22,11 @@ class ProductFilterSet(filters.FilterSet):
'product_subtype', 'product_subtype',
] ]
def without_current_product(self, queryset, name, value):
if value not in EMPTY_VALUES:
return queryset.without_current_product(value)
return queryset
def by_product_type(self, queryset, name, value): def by_product_type(self, queryset, name, value):
if value not in EMPTY_VALUES: if value not in EMPTY_VALUES:
return queryset.by_product_type(value) return queryset.by_product_type(value)

View File

@ -102,6 +102,11 @@ class ProductQuerySet(models.QuerySet):
def wines(self): def wines(self):
return self.filter(type__index_name__icontains=ProductType.WINE) return self.filter(type__index_name__icontains=ProductType.WINE)
def without_current_product(self, current_product: str):
"""Exclude by current product."""
kwargs = {'pk': int(current_product)} if current_product.isdigit() else {'slug': current_product}
return self.exclude(**kwargs)
def by_product_type(self, product_type: str): def by_product_type(self, product_type: str):
"""Filter by type.""" """Filter by type."""
return self.filter(product_type__index_name__icontains=product_type) return self.filter(product_type__index_name__icontains=product_type)

View File

@ -4,15 +4,15 @@ 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, EstablishmentProductShortSerializer from establishment.serializers import EstablishmentProductShortSerializer
from gallery.models import Image from establishment.serializers.common import _EstablishmentAddressShortSerializer
from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer
from main.serializers import AwardSerializer
from product import models from product import models
from review.serializers import ReviewShortSerializer from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer from utils.serializers import TranslatedField, FavoritesCreateSerializer, ImageBaseSerializer
from main.serializers import AwardSerializer
from location.serializers import WineOriginRegionBaseSerializer, WineOriginBaseSerializer
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
class ProductTagSerializer(TagBaseSerializer): class ProductTagSerializer(TagBaseSerializer):
@ -119,7 +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) establishment_detail = _EstablishmentAddressShortSerializer(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)

View File

@ -45,8 +45,14 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
.parser_context.get('view')\ .parser_context.get('view')\
.kwargs.get('pk') .kwargs.get('pk')
establishment_slug = self.context.get('request')\
.parser_context.get('view')\
.kwargs.get('slug')
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
# Check if establishment exists. # Check if establishment exists.
establishment_qs = Establishment.objects.filter(pk=establishment_pk) establishment_qs = Establishment.objects.filter(**search_kwargs)
if not establishment_qs.exists(): if not establishment_qs.exists():
raise serializers.ValidationError({'detail': _('Establishment not found.')}) raise serializers.ValidationError({'detail': _('Establishment not found.')})
attrs['establishment'] = establishment_qs.first() attrs['establishment'] = establishment_qs.first()

View File

@ -118,6 +118,10 @@ class CarouselCreateSerializer(serializers.ModelSerializer):
def pk(self): def pk(self):
return self.request.parser_context.get('kwargs').get('pk') return self.request.parser_context.get('kwargs').get('pk')
@property
def slug(self):
return self.request.parser_context.get('kwargs').get('slug')
class RecursiveFieldSerializer(serializers.Serializer): class RecursiveFieldSerializer(serializers.Serializer):
def to_representation(self, value): def to_representation(self, value):

View File

@ -158,7 +158,11 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
lookup_field = 'id' lookup_field = 'id'
def get_base_object(self): def get_base_object(self):
return get_object_or_404(self._model, id=self.kwargs['pk']) establishment_pk = self.kwargs.get('pk')
establishment_slug = self.kwargs.get('slug')
search_kwargs = {'id': establishment_pk} if establishment_pk else {'slug': establishment_slug}
return get_object_or_404(self._model, **search_kwargs)
def get_object(self): def get_object(self):
""" """

View File

@ -13,6 +13,9 @@ services:
MYSQL_ROOT_PASSWORD: rootPassword MYSQL_ROOT_PASSWORD: rootPassword
volumes: volumes:
- gm-mysql_db:/var/lib/mysql - gm-mysql_db:/var/lib/mysql
- .:/code
# PostgreSQL database # PostgreSQL database

View File

@ -13,3 +13,4 @@
./manage.py transfer --inquiries ./manage.py transfer --inquiries
./manage.py transfer --assemblage ./manage.py transfer --assemblage
./manage.py transfer --purchased_plaques ./manage.py transfer --purchased_plaques
./manage.py rm_empty_images

View File

@ -414,10 +414,10 @@ SORL_THUMBNAIL_ALIASES = {
SIMPLE_JWT = { SIMPLE_JWT = {
# Increase access token lifetime b.c. front-end dev's cant send multiple # Increase access token lifetime b.c. front-end dev's cant send multiple
# requests to API in one HTTP request. # requests to API in one HTTP request.
'ACCESS_TOKEN_LIFETIME': timedelta(days=30), 'ACCESS_TOKEN_LIFETIME': timedelta(days=182),
'ACCESS_TOKEN_LIFETIME_SECONDS': 21600, # 6 hours in seconds 'ACCESS_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
'REFRESH_TOKEN_LIFETIME': timedelta(days=30), 'REFRESH_TOKEN_LIFETIME': timedelta(days=182),
'REFRESH_TOKEN_LIFETIME_SECONDS': 2592000, # 30 days in seconds 'REFRESH_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
'ROTATE_REFRESH_TOKENS': True, 'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True, 'BLACKLIST_AFTER_ROTATION': True,
@ -453,7 +453,7 @@ NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html'
# COOKIES # COOKIES
COOKIES_MAX_AGE = 2628000 # 30 days COOKIES_MAX_AGE = 15730000 # 6 months
SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_SAMESITE = None

View File

@ -3,6 +3,7 @@ from django.urls import path, include
app_name = 'mobile' app_name = 'mobile'
urlpatterns = [ urlpatterns = [
path('booking/', include('booking.urls.web')),
path('establishments/', include('establishment.urls.mobile')), path('establishments/', include('establishment.urls.mobile')),
path('location/', include('location.urls.mobile')), path('location/', include('location.urls.mobile')),
path('main/', include('main.urls.mobile')), path('main/', include('main.urls.mobile')),

View File

@ -19,7 +19,7 @@ app_name = 'web'
urlpatterns = [ urlpatterns = [
path('account/', include('account.urls.web')), path('account/', include('account.urls.web')),
path('booking/', include('booking.urls')), path('booking/', include('booking.urls.web')),
path('re_blocks/', include('advertisement.urls.web')), path('re_blocks/', include('advertisement.urls.web')),
path('collections/', include('collection.urls.web')), path('collections/', include('collection.urls.web')),
path('establishments/', include('establishment.urls.web')), path('establishments/', include('establishment.urls.web')),