Merge branch 'develop' into feature/similar-wines
This commit is contained in:
commit
439fb17778
|
|
@ -1,3 +1,3 @@
|
|||
FROM mdillon/postgis:latest
|
||||
FROM mdillon/postgis:10
|
||||
RUN localedef -i ru_RU -c -f UTF-8 -A /usr/share/locale/locale.alias ru_RU.UTF-8
|
||||
ENV LANG ru_RU.utf8
|
||||
|
|
|
|||
20
apps/account/migrations/0024_role_establishment_subtype.py
Normal file
20
apps/account/migrations/0024_role_establishment_subtype.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 2.2.7 on 2019-12-06 06:55
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0067_auto_20191122_1244'),
|
||||
('account', '0023_auto_20191204_0916'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='establishment_subtype',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.EstablishmentSubType', verbose_name='Establishment subtype'),
|
||||
),
|
||||
]
|
||||
23
apps/account/migrations/0025_auto_20191210_0623.py
Normal file
23
apps/account/migrations/0025_auto_20191210_0623.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.7 on 2019-12-10 06:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0024_role_establishment_subtype'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='city',
|
||||
field=models.TextField(blank=True, default=None, null=True, verbose_name='User last visited from city'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='locale',
|
||||
field=models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='User last used locale'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
"""Account models"""
|
||||
from datetime import datetime
|
||||
from tabnanny import verbose
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
||||
|
|
@ -15,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from authorization.models import Application
|
||||
from establishment.models import Establishment
|
||||
from establishment.models import Establishment, EstablishmentSubType
|
||||
from location.models import Country
|
||||
from main.models import SiteSettings
|
||||
from utils.models import GMTokenGenerator
|
||||
|
|
@ -33,7 +34,7 @@ class Role(ProjectBaseMixin):
|
|||
REVIEWER_MANGER = 6
|
||||
RESTAURANT_REVIEWER = 7
|
||||
SALES_MAN = 8
|
||||
WINERY_REVIEWER = 9
|
||||
WINERY_REVIEWER = 9 # Establishments subtype "winery"
|
||||
SELLER = 10
|
||||
|
||||
ROLE_CHOICES = (
|
||||
|
|
@ -54,6 +55,9 @@ class Role(ProjectBaseMixin):
|
|||
null=True, blank=True, on_delete=models.SET_NULL)
|
||||
site = models.ForeignKey(SiteSettings, verbose_name=_('Site settings'),
|
||||
null=True, blank=True, on_delete=models.SET_NULL)
|
||||
establishment_subtype = models.ForeignKey(EstablishmentSubType,
|
||||
verbose_name=_('Establishment subtype'),
|
||||
null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
|
|
@ -103,6 +107,10 @@ class User(AbstractUser):
|
|||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||
newsletter = models.NullBooleanField(default=True)
|
||||
old_id = models.IntegerField(null=True, blank=True, default=None)
|
||||
locale = models.CharField(max_length=10, blank=True, default=None, null=True,
|
||||
verbose_name=_('User last used locale'))
|
||||
city = models.TextField(default=None, blank=True, null=True,
|
||||
verbose_name=_('User last visited from city'))
|
||||
|
||||
EMAIL_FIELD = 'email'
|
||||
USERNAME_FIELD = 'username'
|
||||
|
|
|
|||
|
|
@ -16,11 +16,31 @@ class RoleSerializer(serializers.ModelSerializer):
|
|||
class BackUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'id',
|
||||
'last_login',
|
||||
'is_superuser',
|
||||
'username',
|
||||
'last_name',
|
||||
'first_name',
|
||||
'is_active',
|
||||
'date_joined',
|
||||
'image_url',
|
||||
'cropped_image_url',
|
||||
'email',
|
||||
'email_confirmed',
|
||||
'unconfirmed_email',
|
||||
'email_confirmed',
|
||||
'newsletter',
|
||||
'roles',
|
||||
'password',
|
||||
'city',
|
||||
'locale',
|
||||
)
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': True}
|
||||
'password': {'write_only': True},
|
||||
}
|
||||
read_only_fields = ('old_password', 'last_login', 'date_joined')
|
||||
read_only_fields = ('old_password', 'last_login', 'date_joined', 'city', 'locale')
|
||||
|
||||
def create(self, validated_data):
|
||||
user = super().create(validated_data)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ app_name = 'account'
|
|||
urlpatterns = [
|
||||
path('role/', views.RoleLstView.as_view(), name='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/', views.UserLstView.as_view(), name='user-create-list'),
|
||||
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import generics, permissions
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
from account import models
|
||||
from account.models import User
|
||||
|
|
@ -18,10 +19,10 @@ class UserRoleLstView(generics.ListCreateAPIView):
|
|||
|
||||
class UserLstView(generics.ListCreateAPIView):
|
||||
"""User list create view."""
|
||||
queryset = User.objects.all()
|
||||
queryset = User.objects.prefetch_related('roles')
|
||||
serializer_class = serializers.BackUserSerializer
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filterset_fields = (
|
||||
'email_confirmed',
|
||||
'is_staff',
|
||||
|
|
@ -29,6 +30,14 @@ class UserLstView(generics.ListCreateAPIView):
|
|||
'is_superuser',
|
||||
'roles',
|
||||
)
|
||||
ordering_fields = (
|
||||
'email_confirmed',
|
||||
'is_staff',
|
||||
'is_active',
|
||||
'is_superuser',
|
||||
'roles',
|
||||
'last_login'
|
||||
)
|
||||
|
||||
|
||||
class UserRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ class AdvertisementQuerySet(models.QuerySet):
|
|||
"""Filter Advertisement by page type."""
|
||||
return self.filter(page_type__name=page_type)
|
||||
|
||||
def by_country(self, code: str):
|
||||
"""Filter Advertisement by country code."""
|
||||
return self.filter(sites__country__code=code)
|
||||
|
||||
def by_locale(self, locale):
|
||||
"""Filter by locale."""
|
||||
return self.filter(target_languages__locale=locale)
|
||||
|
|
|
|||
|
|
@ -11,14 +11,11 @@ from main.models import SiteSettings
|
|||
class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
||||
"""Base serializer for model Advertisement."""
|
||||
|
||||
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)
|
||||
target_sites = serializers.PrimaryKeyRelatedField(
|
||||
queryset=SiteSettings.objects.all(),
|
||||
many=True,
|
||||
|
|
@ -33,9 +30,7 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer):
|
|||
'uuid',
|
||||
'url',
|
||||
'block_level',
|
||||
'languages',
|
||||
'target_languages',
|
||||
'sites',
|
||||
'target_sites',
|
||||
'start',
|
||||
'end',
|
||||
|
|
|
|||
|
|
@ -28,5 +28,8 @@ class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView)
|
|||
product_type = self.kwargs.get('page_type')
|
||||
qs = super(AdvertisementPageTypeListView, self).get_queryset()
|
||||
if product_type:
|
||||
return qs.by_page_type(product_type)
|
||||
return qs.by_page_type(product_type) \
|
||||
.by_country(self.request.country_code) \
|
||||
.by_locale(self.request.locale) \
|
||||
.distinct('id')
|
||||
return qs.none()
|
||||
|
|
|
|||
|
|
@ -7,3 +7,4 @@ class AdvertisementPageTypeWebListView(AdvertisementPageTypeListView):
|
|||
"""Advertisement mobile list view."""
|
||||
|
||||
serializer_class = AdvertisementPageTypeWebListSerializer
|
||||
|
||||
|
|
|
|||
0
apps/booking/urls/__init__.py
Normal file
0
apps/booking/urls/__init__.py
Normal file
8
apps/booking/urls/mobile.py
Normal file
8
apps/booking/urls/mobile.py
Normal 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
8
apps/booking/urls/web.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from booking.urls import common as common_views
|
||||
app = 'booking'
|
||||
|
||||
|
||||
urlpatterns_api = []
|
||||
|
||||
urlpatterns = urlpatterns_api + \
|
||||
common_views.urlpatterns
|
||||
|
|
@ -87,6 +87,42 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
|||
verbose_name = _('collection')
|
||||
verbose_name_plural = _('collections')
|
||||
|
||||
@property
|
||||
def _related_objects(self) -> list:
|
||||
"""Return list of related objects."""
|
||||
related_objects = []
|
||||
# get related objects
|
||||
for related_object in self._meta.related_objects:
|
||||
related_objects.append(related_object)
|
||||
return related_objects
|
||||
|
||||
@property
|
||||
def count_related_objects(self) -> int:
|
||||
"""Return count of related objects."""
|
||||
counter = 0
|
||||
# count of related objects
|
||||
for related_object in [related_object.name for related_object in self._related_objects]:
|
||||
counter += getattr(self, f'{related_object}').count()
|
||||
return counter
|
||||
|
||||
@property
|
||||
def related_object_names(self) -> list:
|
||||
"""Return related object names."""
|
||||
raw_object_names = []
|
||||
for related_object in [related_object.name for related_object in self._related_objects]:
|
||||
instances = getattr(self, f'{related_object}')
|
||||
if instances.exists():
|
||||
for instance in instances.all():
|
||||
raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None)
|
||||
|
||||
# parse slugs
|
||||
object_names = []
|
||||
re_pattern = r'[\w]+'
|
||||
for raw_name in raw_object_names:
|
||||
result = re.findall(re_pattern, raw_name)
|
||||
if result: object_names.append(' '.join(result).capitalize())
|
||||
return set(object_names)
|
||||
|
||||
|
||||
class GuideTypeQuerySet(models.QuerySet):
|
||||
"""QuerySet for model GuideType."""
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
|||
collection_type_display = serializers.CharField(
|
||||
source='get_collection_type_display', read_only=True)
|
||||
country = CountrySimpleSerializer(read_only=True)
|
||||
count_related_objects = serializers.IntegerField(read_only=True)
|
||||
related_object_names = serializers.ListField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Collection
|
||||
|
|
@ -36,6 +38,8 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer):
|
|||
'slug',
|
||||
'start',
|
||||
'end',
|
||||
'count_related_objects',
|
||||
'related_object_names',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -498,9 +498,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin,
|
|||
def visible_tags(self):
|
||||
return super().visible_tags \
|
||||
.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
|
||||
|
||||
@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):
|
||||
toque_number = 0
|
||||
if self.address and self.public_mark:
|
||||
|
|
@ -865,25 +871,6 @@ class ContactEmail(models.Model):
|
|||
return f'{self.email}'
|
||||
|
||||
|
||||
#
|
||||
# class Wine(TranslatedFieldsMixin, models.Model):
|
||||
# """Wine model."""
|
||||
# establishment = models.ForeignKey(
|
||||
# 'establishment.Establishment', verbose_name=_('establishment'),
|
||||
# on_delete=models.CASCADE)
|
||||
# bottles = models.IntegerField(_('bottles'))
|
||||
# price_min = models.DecimalField(
|
||||
# _('price min'), max_digits=14, decimal_places=2)
|
||||
# price_max = models.DecimalField(
|
||||
# _('price max'), max_digits=14, decimal_places=2)
|
||||
# by_glass = models.BooleanField(_('by glass'))
|
||||
# price_glass_min = models.DecimalField(
|
||||
# _('price min'), max_digits=14, decimal_places=2)
|
||||
# price_glass_max = models.DecimalField(
|
||||
# _('price max'), max_digits=14, decimal_places=2)
|
||||
#
|
||||
|
||||
|
||||
class Plate(TranslatedFieldsMixin, models.Model):
|
||||
"""Plate model."""
|
||||
STR_FIELD_NAME = 'name'
|
||||
|
|
|
|||
|
|
@ -232,9 +232,13 @@ class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
|||
def validate(self, attrs):
|
||||
"""Override validate method."""
|
||||
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')
|
||||
|
||||
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)
|
||||
|
||||
if not establishment_qs.exists():
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
|||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||
FavoritesCreateSerializer)
|
||||
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
||||
EstablishmentWineOriginBaseSerializer
|
||||
EstablishmentWineOriginBaseSerializer
|
||||
|
||||
|
||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -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):
|
||||
"""SHORT Serializer for displaying info about an establishment on product page."""
|
||||
establishment_type = EstablishmentTypeGeoSerializer()
|
||||
|
|
@ -369,7 +393,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
|||
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
|
||||
many=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)
|
||||
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)
|
||||
|
|
@ -426,14 +450,18 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
|||
|
||||
address = AddressDetailSerializer(read_only=True)
|
||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||
establishment_type = EstablishmentTypeGeoSerializer()
|
||||
artisan_category = TagBaseSerializer(many=True, allow_null=True)
|
||||
type = EstablishmentTypeGeoSerializer(source='establishment_type')
|
||||
artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||
restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||
restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||
|
||||
class Meta(EstablishmentBaseSerializer.Meta):
|
||||
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||
'schedule',
|
||||
'establishment_type',
|
||||
'type',
|
||||
'artisan_category',
|
||||
'restaurant_category',
|
||||
'restaurant_cuisine',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -522,7 +550,9 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer):
|
|||
"""Serializer to carousel object w/ model News."""
|
||||
|
||||
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:
|
||||
raise serializers.ValidationError({'detail': _('Object not found.')})
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ from account.models import User
|
|||
from rest_framework import status
|
||||
from http.cookies import SimpleCookie
|
||||
from main.models import Currency
|
||||
from establishment.models import Establishment, EstablishmentType, Menu, SocialChoice, SocialNetwork
|
||||
from establishment.models import Establishment, EstablishmentType, EstablishmentSubType,\
|
||||
Menu, SocialChoice, SocialNetwork
|
||||
# Create your tests here.
|
||||
from translation.models import Language
|
||||
from account.models import Role, UserRole
|
||||
|
|
@ -87,7 +88,7 @@ class BaseTestCase(APITestCase):
|
|||
)
|
||||
|
||||
|
||||
class EstablishmentBTests(BaseTestCase):
|
||||
class EstablishmentBackTests(BaseTestCase):
|
||||
def test_establishment_CRUD(self):
|
||||
params = {'page': 1, 'page_size': 1, }
|
||||
response = self.client.get('/api/back/establishments/', params, format='json')
|
||||
|
|
@ -104,18 +105,18 @@ class EstablishmentBTests(BaseTestCase):
|
|||
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
||||
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)
|
||||
|
||||
update_data = {
|
||||
'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)
|
||||
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')
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -372,22 +373,22 @@ class EstablishmentShedulerTests(ChildTestCase):
|
|||
'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)
|
||||
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)
|
||||
|
||||
update_data = {
|
||||
'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)
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -484,8 +485,8 @@ class EstablishmentCarouselTests(ChildTestCase):
|
|||
"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)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -8,25 +8,25 @@ app_name = 'establishment'
|
|||
|
||||
urlpatterns = [
|
||||
path('', views.EstablishmentListCreateView.as_view(), name='list'),
|
||||
path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||
path('<int:pk>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
|
||||
path('slug/<slug:slug>/', views.EstablishmentRUDView.as_view(), name='detail'),
|
||||
path('slug/<slug:slug>/carousels/', views.EstablishmentCarouselCreateDestroyView.as_view(),
|
||||
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'),
|
||||
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||
path('slug/<slug:slug>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
|
||||
name='schedule-create'),
|
||||
path('<int:pk>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||
path('slug/<slug:slug>/gallery/', views.EstablishmentGalleryListView.as_view(),
|
||||
name='gallery-list'),
|
||||
path('<int:pk>/gallery/<int:image_id>/',
|
||||
path('slug/<slug:slug>/gallery/<int:image_id>/',
|
||||
views.EstablishmentGalleryCreateDestroyView.as_view(),
|
||||
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'),
|
||||
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'),
|
||||
path('<int:pk>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||
path('slug/<slug:slug>/notes/', views.EstablishmentNoteListCreateView.as_view(),
|
||||
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'),
|
||||
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
||||
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ from django.http import Http404, HttpResponse
|
|||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics, permissions, status
|
||||
|
||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||
from establishment import filters, models, serializers
|
||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer
|
||||
from utils.views import CreateDestroyGalleryViewMixin
|
||||
from timetable.models import Timetable
|
||||
from rest_framework import status
|
||||
|
|
@ -25,31 +24,34 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
|||
"""Establishment list/create view."""
|
||||
|
||||
filter_class = filters.EstablishmentFilter
|
||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
||||
|
||||
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
||||
queryset = models.Establishment.objects.all()
|
||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||
|
||||
|
||||
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
lookup_field = 'slug'
|
||||
queryset = models.Establishment.objects.all()
|
||||
serializer_class = serializers.EstablishmentRUDSerializer
|
||||
permission_classes = [IsCountryAdmin | IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
||||
|
||||
|
||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment schedule RUD view"""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = ScheduleRUDSerializer
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer |IsEstablishmentManager]
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
Returns the object the view is displaying.
|
||||
"""
|
||||
establishment_pk = self.kwargs['pk']
|
||||
establishment_slug = self.kwargs['slug']
|
||||
schedule_id = self.kwargs['schedule_id']
|
||||
|
||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||
pk=establishment_pk)
|
||||
slug=establishment_slug)
|
||||
schedule = get_object_or_404(klass=establishment.schedule,
|
||||
id=schedule_id)
|
||||
|
||||
|
|
@ -62,23 +64,24 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
|
||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||
"""Establishment schedule Create view"""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = ScheduleCreateSerializer
|
||||
queryset = Timetable.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class MenuListCreateView(generics.ListCreateAPIView):
|
||||
"""Menu list create view."""
|
||||
serializer_class = serializers.MenuSerializers
|
||||
queryset = models.Menu.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Menu RUD view."""
|
||||
serializer_class = serializers.MenuRUDSerializers
|
||||
queryset = models.Menu.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class SocialChoiceListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -116,14 +119,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.PlatesSerializers
|
||||
queryset = models.Plate.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Plate RUD view."""
|
||||
serializer_class = serializers.PlatesSerializers
|
||||
queryset = models.Plate.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class PhonesListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -131,14 +134,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.ContactPhoneBackSerializers
|
||||
queryset = models.ContactPhone.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Phones RUD view."""
|
||||
serializer_class = serializers.ContactPhoneBackSerializers
|
||||
queryset = models.ContactPhone.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmailListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -146,14 +149,14 @@ class EmailListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.ContactEmailBackSerializers
|
||||
queryset = models.ContactEmail.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Email RUD view."""
|
||||
serializer_class = serializers.ContactEmailBackSerializers
|
||||
queryset = models.ContactEmail.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -210,6 +213,7 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||
CreateDestroyGalleryViewMixin):
|
||||
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -218,7 +222,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
|||
"""
|
||||
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,
|
||||
image_id=self.kwargs.get('image_id'))
|
||||
|
||||
|
|
@ -231,12 +235,13 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
|||
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||
generics.ListAPIView):
|
||||
"""Resource for returning gallery for establishment for back-office users."""
|
||||
lookup_field = 'slug'
|
||||
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'))
|
||||
establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -252,6 +257,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
|||
generics.ListCreateAPIView):
|
||||
"""List|Create establishment company view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentCompanyListCreateSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -259,7 +265,7 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews,
|
|||
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'))
|
||||
establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug'))
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -275,6 +281,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
|||
generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Create|Retrieve|Update|Destroy establishment company view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.CompanyBaseSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -282,7 +289,7 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews,
|
|||
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'))
|
||||
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'))
|
||||
|
||||
# May raise a permission denied
|
||||
|
|
@ -295,6 +302,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
|||
generics.ListCreateAPIView):
|
||||
"""Retrieve|Update|Destroy establishment note view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentNoteListCreateSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -302,7 +310,7 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews,
|
|||
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'))
|
||||
establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug'))
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
|
|
@ -318,6 +326,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
|||
generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Create|Retrieve|Update|Destroy establishment note view."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
serializer_class = serializers.EstablishmentNoteBaseSerializer
|
||||
|
||||
def get_object(self):
|
||||
|
|
@ -325,7 +334,7 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews,
|
|||
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'))
|
||||
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'])
|
||||
|
||||
# May raise a permission denied
|
||||
|
|
|
|||
|
|
@ -126,10 +126,7 @@ class EstablishmentCommentListView(generics.ListAPIView):
|
|||
"""Override get_queryset method"""
|
||||
|
||||
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
||||
return comment_models.Comment.objects.by_content_type(app_label='establishment',
|
||||
model='establishment') \
|
||||
.by_object_id(object_id=establishment.pk) \
|
||||
.order_by('-created')
|
||||
return establishment.comments.order_by('-created')
|
||||
|
||||
|
||||
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
@ -164,6 +161,7 @@ class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
|
|||
class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
|
||||
"""View for create/destroy establishment from carousel."""
|
||||
|
||||
lookup_field = 'slug'
|
||||
_model = models.Establishment
|
||||
serializer_class = serializers.EstablishmentCarouselCreateSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
|||
"""Override get_queryset method"""
|
||||
return Establishment.objects.filter(favorites__user=self.request.user) \
|
||||
.order_by('-favorites').with_base_related() \
|
||||
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
||||
|
||||
|
||||
|
|
|
|||
24
apps/location/filters.py
Normal file
24
apps/location/filters.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from django.core.validators import EMPTY_VALUES
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from location import models
|
||||
|
||||
|
||||
class CityBackFilter(filters.FilterSet):
|
||||
"""Employee filter set."""
|
||||
|
||||
search = filters.CharFilter(method='search_by_name')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.City
|
||||
fields = (
|
||||
'search',
|
||||
)
|
||||
|
||||
def search_by_name(self, queryset, name, value):
|
||||
"""Search by name or last name."""
|
||||
if value not in EMPTY_VALUES:
|
||||
return queryset.search_by_name(value)
|
||||
return queryset
|
||||
|
|
@ -5,6 +5,9 @@ from django.db.models.signals import post_save
|
|||
from django.db.transaction import on_commit
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from functools import reduce
|
||||
from typing import List
|
||||
|
||||
|
||||
from translation.models import Language
|
||||
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
||||
|
|
@ -92,6 +95,18 @@ class Region(models.Model):
|
|||
class CityQuerySet(models.QuerySet):
|
||||
"""Extended queryset for City model."""
|
||||
|
||||
def _generic_search(self, value, filter_fields_names: List[str]):
|
||||
"""Generic method for searching value in specified fields"""
|
||||
filters = [
|
||||
{f'{field}__icontains': value}
|
||||
for field in filter_fields_names
|
||||
]
|
||||
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
||||
|
||||
def search_by_name(self, value):
|
||||
"""Search by name or last_name."""
|
||||
return self._generic_search(value, ['name', 'code', 'postal_code'])
|
||||
|
||||
def by_country_code(self, code):
|
||||
"""Return establishments by country code"""
|
||||
return self.filter(country__code=code)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
|||
from django.shortcuts import get_object_or_404
|
||||
from utils.serializers import ImageBaseSerializer
|
||||
|
||||
from location import filters
|
||||
|
||||
# Address
|
||||
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
|||
"""Create view for model City."""
|
||||
serializer_class = serializers.CitySerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
queryset = models.City.objects.all()
|
||||
filter_class = filters.CityBackFilter
|
||||
|
||||
|
||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
|
|||
32
apps/main/management/commands/add_footers.py
Normal file
32
apps/main/management/commands/add_footers.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from tqdm import tqdm
|
||||
|
||||
from main.models import SiteSettings, Footer
|
||||
from transfer.models import Footers
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Add footers from legacy DB.'''
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
objects = []
|
||||
deleted = 0
|
||||
footers_list = Footers.objects.all()
|
||||
|
||||
for old_footer in tqdm(footers_list, desc='Add footers'):
|
||||
site = SiteSettings.objects.filter(old_id=old_footer.site_id).first()
|
||||
if site:
|
||||
if site.footers.exists():
|
||||
site.footers.all().delete()
|
||||
deleted += 1
|
||||
footer = Footer(
|
||||
site=site,
|
||||
about_us=old_footer.about_us,
|
||||
copyright=old_footer.copyright,
|
||||
created=old_footer.created_at,
|
||||
modified=old_footer.updated_at
|
||||
)
|
||||
objects.append(footer)
|
||||
Footer.objects.bulk_create(objects)
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.'))
|
||||
37
apps/main/management/commands/add_home_page_carousel.py
Normal file
37
apps/main/management/commands/add_home_page_carousel.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from tqdm import tqdm
|
||||
|
||||
from establishment.models import Establishment
|
||||
from main.models import Carousel
|
||||
from transfer.models import HomePages
|
||||
from location.models import Country
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Add establishment form HomePage to Carousel!'''
|
||||
|
||||
@staticmethod
|
||||
def get_country(country_code):
|
||||
return Country.objects.filter(code__iexact=country_code).first()
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
objects = []
|
||||
deleted = 0
|
||||
hp_list = HomePages.objects.annotate(
|
||||
country=F('site__country_code_2'),
|
||||
).all()
|
||||
for hm in tqdm(hp_list, desc='Add home_page.establishments to carousel'):
|
||||
est = Establishment.objects.filter(old_id=hm.selection_of_week).first()
|
||||
if est:
|
||||
if est.carousels.exists():
|
||||
est.carousels.all().delete()
|
||||
deleted += 1
|
||||
carousel = Carousel(
|
||||
content_object=est,
|
||||
country=self.get_country(hm.country)
|
||||
)
|
||||
objects.append(carousel)
|
||||
Carousel.objects.bulk_create(objects)
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} carousel objects.'))
|
||||
29
apps/main/migrations/0040_footer.py
Normal file
29
apps/main/migrations/0040_footer.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 2.2.7 on 2019-12-09 13:21
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0039_sitefeature_old_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Footer',
|
||||
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')),
|
||||
('about_us', models.TextField(verbose_name='about_us')),
|
||||
('copyright', models.TextField(verbose_name='copyright')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='footers', to='main.SiteSettings', verbose_name='footer')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -351,3 +351,12 @@ class PageType(ProjectBaseMixin):
|
|||
def __str__(self):
|
||||
"""Overridden dunder method."""
|
||||
return self.name
|
||||
|
||||
|
||||
class Footer(ProjectBaseMixin):
|
||||
site = models.ForeignKey(
|
||||
'main.SiteSettings', related_name='footers', verbose_name=_('footer'),
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
about_us = models.TextField(_('about_us'))
|
||||
copyright = models.TextField(_('copyright'))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class FeatureSerializer(serializers.ModelSerializer):
|
|||
'site_settings',
|
||||
)
|
||||
|
||||
|
||||
class CurrencySerializer(ProjectModelSerializer):
|
||||
"""Currency serializer."""
|
||||
|
||||
|
|
@ -36,6 +37,33 @@ class CurrencySerializer(ProjectModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class FooterSerializer(serializers.ModelSerializer):
|
||||
"""Footer serializer."""
|
||||
|
||||
class Meta:
|
||||
model = models.Footer
|
||||
fields = [
|
||||
'id',
|
||||
'about_us',
|
||||
'copyright',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
class FooterBackSerializer(FooterSerializer):
|
||||
site_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=models.SiteSettings.objects.all(),
|
||||
source='site'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.Footer
|
||||
fields = FooterSerializer.Meta.fields + [
|
||||
'site_id'
|
||||
]
|
||||
|
||||
|
||||
class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(source='feature.id')
|
||||
slug = serializers.CharField(source='feature.slug')
|
||||
|
|
@ -68,6 +96,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
|||
|
||||
country_name = serializers.CharField(source='country.name_translated', read_only=True)
|
||||
time_format = serializers.CharField(source='country.time_format', read_only=True)
|
||||
footers = FooterSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -87,6 +116,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
|||
'published_features',
|
||||
'currency',
|
||||
'country_name',
|
||||
'footers',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ urlpatterns = [
|
|||
name='site-feature-list-create'),
|
||||
path('site-feature/<int:id>/', views.SiteFeatureRUDBackView.as_view(),
|
||||
name='site-feature-rud'),
|
||||
path('footer/', views.FooterBackView.as_view(), name='footer-list-create'),
|
||||
path('footer/<int:pk>/', views.FooterRUDBackView.as_view(), name='footer-rud'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from rest_framework import generics, permissions
|
|||
|
||||
from main import serializers
|
||||
from main.filters import AwardFilter
|
||||
from main.models import Award
|
||||
from main.models import Award, Footer
|
||||
from main.views import SiteSettingsView, SiteListView
|
||||
|
||||
|
||||
|
|
@ -67,3 +67,17 @@ class SiteSettingsBackOfficeView(SiteSettingsView):
|
|||
class SiteListBackOfficeView(SiteListView):
|
||||
"""Site settings View."""
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
||||
|
||||
class FooterBackView(generics.ListCreateAPIView):
|
||||
"""Footer back list/create view."""
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
serializer_class = serializers.FooterBackSerializer
|
||||
queryset = Footer.objects.all()
|
||||
|
||||
|
||||
class FooterRUDBackView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Footer back RUD view."""
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
serializer_class = serializers.FooterBackSerializer
|
||||
queryset = Footer.objects.all()
|
||||
|
|
|
|||
17
apps/news/management/commands/rm_empty_images.py
Normal file
17
apps/news/management/commands/rm_empty_images.py
Normal 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()
|
||||
|
|
@ -8,7 +8,7 @@ from partner.serializers import common as serializers
|
|||
# Mixins
|
||||
class PartnerViewMixin(generics.GenericAPIView):
|
||||
"""View mixin for Partner views"""
|
||||
queryset = models.Partner.objects.all()
|
||||
queryset = models.Partner.objects.distinct("name")
|
||||
|
||||
|
||||
# Views
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class ProductFilterSet(filters.FilterSet):
|
|||
"""Product filter set."""
|
||||
|
||||
establishment_id = filters.NumberFilter()
|
||||
current_product = filters.CharFilter(method='without_current_product')
|
||||
product_type = filters.CharFilter(method='by_product_type')
|
||||
product_subtype = filters.CharFilter(method='by_product_subtype')
|
||||
|
||||
|
|
@ -21,6 +22,11 @@ class ProductFilterSet(filters.FilterSet):
|
|||
'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):
|
||||
if value not in EMPTY_VALUES:
|
||||
return queryset.by_product_type(value)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from tqdm import tqdm
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Add add product tags networks from old db to new db.
|
||||
help = '''Add product tags networks from old db to new db.
|
||||
Run after add_product!!!'''
|
||||
|
||||
def category_sql(self):
|
||||
|
|
@ -101,14 +101,12 @@ class Command(BaseCommand):
|
|||
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):
|
||||
with connections['legacy'].cursor() as cursor:
|
||||
cursor.execute('''
|
||||
|
|
|
|||
20
apps/product/management/commands/check_serial_number.py
Normal file
20
apps/product/management/commands/check_serial_number.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from tag.models import Tag, TagCategory
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Check product serial number from old db to new db.
|
||||
Run after add_product_tag!!!'''
|
||||
|
||||
def check_serial_number(self):
|
||||
category = TagCategory.objects.get(index_name='serial_number')
|
||||
tags = Tag.objects.filter(category=category, products__isnull=False)
|
||||
for tag in tqdm(tags, desc='Update serial number for product'):
|
||||
tag.products.all().update(serial_number=tag.value)
|
||||
tag.products.clear()
|
||||
|
||||
self.stdout.write(self.style.WARNING(f'Check serial number product end.'))
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.check_serial_number()
|
||||
18
apps/product/migrations/0019_product_serial_number.py
Normal file
18
apps/product/migrations/0019_product_serial_number.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.7 on 2019-11-21 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0018_purchasedproduct'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='serial_number',
|
||||
field=models.CharField(default=None, max_length=255, null=True, verbose_name='Serial number'),
|
||||
),
|
||||
]
|
||||
14
apps/product/migrations/0020_merge_20191209_0911.py
Normal file
14
apps/product/migrations/0020_merge_20191209_0911.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.7 on 2019-12-09 09:11
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('product', '0019_auto_20191204_1420'),
|
||||
('product', '0019_product_serial_number'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
|
|
@ -102,6 +102,11 @@ class ProductQuerySet(models.QuerySet):
|
|||
def wines(self):
|
||||
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):
|
||||
"""Filter by type."""
|
||||
return self.filter(product_type__index_name__icontains=product_type)
|
||||
|
|
@ -213,6 +218,10 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes,
|
|||
comments = generic.GenericRelation(to='comment.Comment')
|
||||
awards = generic.GenericRelation(to='main.Award', related_query_name='product')
|
||||
|
||||
serial_number = models.CharField(max_length=255,
|
||||
default=None, null=True,
|
||||
verbose_name=_('Serial number'))
|
||||
|
||||
objects = ProductManager.from_queryset(ProductQuerySet)()
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ from rest_framework import serializers
|
|||
|
||||
from comment.models import Comment
|
||||
from comment.serializers import CommentSerializer
|
||||
from establishment.serializers import EstablishmentShortSerializer, EstablishmentProductSerializer, EstablishmentProductShortSerializer
|
||||
from gallery.models import Image
|
||||
from establishment.serializers import EstablishmentProductShortSerializer
|
||||
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
||||
from location.serializers import WineOriginRegionBaseSerializer,\
|
||||
WineOriginBaseSerializer, EstablishmentWineOriginBaseSerializer
|
||||
from main.serializers import AwardSerializer
|
||||
from product import models
|
||||
from review.serializers import ReviewShortSerializer
|
||||
from tag.serializers import TagBaseSerializer, TagCategoryProductSerializer
|
||||
from utils import exceptions as utils_exceptions
|
||||
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):
|
||||
|
|
@ -95,6 +96,7 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
|||
preview_image_url = serializers.URLField(allow_null=True,
|
||||
read_only=True)
|
||||
in_favorites = serializers.BooleanField(allow_null=True)
|
||||
wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -113,13 +115,14 @@ class ProductBaseSerializer(serializers.ModelSerializer):
|
|||
'wine_regions',
|
||||
'wine_colors',
|
||||
'in_favorites',
|
||||
'wine_origins',
|
||||
]
|
||||
|
||||
|
||||
class ProductDetailSerializer(ProductBaseSerializer):
|
||||
"""Product detail serializer."""
|
||||
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)
|
||||
awards = AwardSerializer(many=True, read_only=True)
|
||||
classifications = ProductClassificationBaseSerializer(many=True, read_only=True)
|
||||
|
|
|
|||
|
|
@ -60,10 +60,7 @@ class ProductCommentListView(generics.ListAPIView):
|
|||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
product = get_object_or_404(Product, slug=self.kwargs['slug'])
|
||||
return Comment.objects.by_content_type(app_label='product',
|
||||
model='product') \
|
||||
.by_object_id(object_id=product.pk) \
|
||||
.order_by('-created')
|
||||
return product.comments.order_by('-created')
|
||||
|
||||
|
||||
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
from django.conf import settings
|
||||
from django_elasticsearch_dsl import Document, Index, fields
|
||||
from tag import models
|
||||
from news.models import News
|
||||
|
||||
TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category'))
|
||||
TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2)
|
||||
|
|
@ -26,8 +27,16 @@ class TagCategoryDocument(Document):
|
|||
'public',
|
||||
'value_type'
|
||||
)
|
||||
related_models = [models.Tag]
|
||||
related_models = [models.Tag, News]
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().with_base_related()
|
||||
|
||||
def get_instances_from_related(self, related_instance):
|
||||
"""If related_models is set, define how to retrieve the Car instance(s) from the related model.
|
||||
The related_models option should be used with caution because it can lead in the index
|
||||
to the updating of a lot of items.
|
||||
"""
|
||||
if isinstance(related_instance, News):
|
||||
return related_instance.tags
|
||||
|
|
@ -243,8 +243,8 @@ class WineOriginSerializer(serializers.Serializer):
|
|||
class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||
"""Establishment document serializer."""
|
||||
|
||||
establishment_type = EstablishmentTypeSerializer()
|
||||
establishment_subtypes = EstablishmentTypeSerializer(many=True)
|
||||
type = EstablishmentTypeSerializer(source='establishment_type')
|
||||
subtypes = EstablishmentTypeSerializer(many=True, source='establishment_subtypes')
|
||||
address = AddressDocumentSerializer(allow_null=True)
|
||||
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||
|
|
@ -280,8 +280,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
|||
'wine_origins',
|
||||
# 'works_now',
|
||||
# 'collections',
|
||||
# 'establishment_type',
|
||||
# 'establishment_subtypes',
|
||||
'type',
|
||||
'subtypes',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -45,8 +45,14 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
|||
.parser_context.get('view')\
|
||||
.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.
|
||||
establishment_qs = Establishment.objects.filter(pk=establishment_pk)
|
||||
establishment_qs = Establishment.objects.filter(**search_kwargs)
|
||||
if not establishment_qs.exists():
|
||||
raise serializers.ValidationError({'detail': _('Establishment not found.')})
|
||||
attrs['establishment'] = establishment_qs.first()
|
||||
|
|
|
|||
|
|
@ -1000,7 +1000,7 @@ class ProductNotes(MigrateMixin):
|
|||
db_table = 'product_notes'
|
||||
|
||||
|
||||
class HomePages(models.Model):
|
||||
class HomePages(MigrateMixin):
|
||||
using = 'legacy'
|
||||
|
||||
site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True)
|
||||
|
|
@ -1208,3 +1208,17 @@ class NewsletterSubscriber(MigrateMixin):
|
|||
class Meta:
|
||||
managed = False
|
||||
db_table = 'newsletter_subscriptions'
|
||||
|
||||
|
||||
class Footers(MigrateMixin):
|
||||
using = 'legacy'
|
||||
|
||||
about_us = models.TextField(blank=True, null=True)
|
||||
copyright = models.TextField(blank=True, null=True)
|
||||
site = models.ForeignKey('Sites', models.DO_NOTHING, blank=True, null=True)
|
||||
created_at = models.DateTimeField()
|
||||
updated_at = models.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = 'footers'
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class GuideSerializer(TransferSerializerMixin):
|
|||
class GuideFilterSerializer(TransferSerializerMixin):
|
||||
id = serializers.IntegerField()
|
||||
year = serializers.CharField(allow_null=True)
|
||||
establishment_type = serializers.CharField(allow_null=True)
|
||||
type = serializers.CharField(allow_null=True, source='establishment_type')
|
||||
countries = serializers.CharField(allow_null=True)
|
||||
regions = serializers.CharField(allow_null=True)
|
||||
subregions = serializers.CharField(allow_null=True)
|
||||
|
|
@ -86,7 +86,7 @@ class GuideFilterSerializer(TransferSerializerMixin):
|
|||
fields = (
|
||||
'id',
|
||||
'year',
|
||||
'establishment_type',
|
||||
'type',
|
||||
'countries',
|
||||
'regions',
|
||||
'subregions',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
"""Custom middleware."""
|
||||
from django.utils import translation
|
||||
"""Custom middlewares."""
|
||||
from django.utils import translation, timezone
|
||||
|
||||
from account.models import User
|
||||
from configuration.models import TranslationSettings
|
||||
from main.methods import determine_user_city
|
||||
from translation.models import Language
|
||||
|
||||
|
||||
|
|
@ -12,6 +14,18 @@ def get_locale(cookie_dict):
|
|||
def get_country_code(cookie_dict):
|
||||
return cookie_dict.get('country_code')
|
||||
|
||||
def user_last_visit(get_response):
|
||||
"""Updates user last visit w/ current"""
|
||||
def middleware(request):
|
||||
response = get_response(request)
|
||||
if request.user.is_authenticated:
|
||||
User.objects.filter(pk=request.user.pk).update(**{
|
||||
'last_login': timezone.now(),
|
||||
'locale': request.locale,
|
||||
'city': determine_user_city(request),
|
||||
})
|
||||
return response
|
||||
return middleware
|
||||
|
||||
def parse_cookies(get_response):
|
||||
"""Parse cookies."""
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import AccessToken
|
|||
from account.models import UserRole, Role
|
||||
from authorization.models import JWTRefreshToken
|
||||
from utils.tokens import GMRefreshToken
|
||||
|
||||
from establishment.models import EstablishmentSubType
|
||||
from location.models import Address
|
||||
|
||||
class IsAuthenticatedAndTokenIsValid(permissions.BasePermission):
|
||||
"""
|
||||
|
|
@ -56,8 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly):
|
|||
"""
|
||||
Object-level permission to only allow owners of an object to edit it.
|
||||
"""
|
||||
|
||||
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
|
||||
def has_permission(self, request, view):
|
||||
|
||||
rules = [
|
||||
request.user.is_superuser,
|
||||
request.method in permissions.SAFE_METHODS
|
||||
|
|
@ -306,7 +308,6 @@ class IsEstablishmentManager(IsStandardUser):
|
|||
rules = [
|
||||
# special!
|
||||
super().has_permission(request, view)
|
||||
# super().has_object_permission(request, view, obj)
|
||||
]
|
||||
|
||||
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \
|
||||
|
|
@ -319,7 +320,6 @@ class IsEstablishmentManager(IsStandardUser):
|
|||
).exists(),
|
||||
# special!
|
||||
super().has_permission(request, view)
|
||||
# super().has_object_permission(request, view, obj)
|
||||
]
|
||||
|
||||
return any(rules)
|
||||
|
|
@ -368,7 +368,7 @@ class IsRestaurantReviewer(IsStandardUser):
|
|||
# and request.user.email_confirmed,
|
||||
if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'):
|
||||
role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \
|
||||
.first() # 'Comments moderator'
|
||||
.first()
|
||||
|
||||
rules = [
|
||||
UserRole.objects.filter(user=request.user, role=role,
|
||||
|
|
@ -394,3 +394,58 @@ class IsRestaurantReviewer(IsStandardUser):
|
|||
]
|
||||
|
||||
return any(rules)
|
||||
|
||||
|
||||
class IsWineryReviewer(IsStandardUser):
|
||||
|
||||
def has_permission(self, request, view):
|
||||
rules = [
|
||||
super().has_permission(request, view)
|
||||
]
|
||||
|
||||
if 'type_id' in request.data and 'address_id' in request.data and request.user:
|
||||
countries = Address.objects.filter(id=request.data['address_id'])
|
||||
|
||||
est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id'])
|
||||
if est.exists():
|
||||
role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est],
|
||||
role=Role.WINERY_REVIEWER,
|
||||
country_id__in=[country.id for country in countries]) \
|
||||
.first()
|
||||
|
||||
rules.append(
|
||||
UserRole.objects.filter(user=request.user, role=role).exists()
|
||||
)
|
||||
|
||||
return any(rules)
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
rules = [
|
||||
super().has_object_permission(request, view, obj)
|
||||
]
|
||||
|
||||
if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'):
|
||||
type_id: int
|
||||
if hasattr(obj, 'type_id'):
|
||||
type_id = obj.type_id
|
||||
else:
|
||||
type_id = obj.establishment_type_id
|
||||
|
||||
est = EstablishmentSubType.objects.filter(establishment_type_id=type_id)
|
||||
role = Role.objects.filter(role=Role.WINERY_REVIEWER,
|
||||
establishment_subtype_id__in=[id for type.id in est],
|
||||
country_id=obj.country_id).first()
|
||||
|
||||
object_id: int
|
||||
if hasattr(obj, 'object_id'):
|
||||
object_id = obj.object_id
|
||||
else:
|
||||
object_id = obj.establishment_id
|
||||
|
||||
rules = [
|
||||
UserRole.objects.filter(user=request.user, role=role,
|
||||
establishment_id=object_id
|
||||
).exists(),
|
||||
super().has_object_permission(request, view, obj)
|
||||
]
|
||||
return any(rules)
|
||||
|
|
@ -118,6 +118,10 @@ class CarouselCreateSerializer(serializers.ModelSerializer):
|
|||
def pk(self):
|
||||
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):
|
||||
def to_representation(self, value):
|
||||
|
|
|
|||
|
|
@ -158,7 +158,11 @@ class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
|
|||
lookup_field = 'id'
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ services:
|
|||
MYSQL_ROOT_PASSWORD: rootPassword
|
||||
volumes:
|
||||
- gm-mysql_db:/var/lib/mysql
|
||||
- .:/code
|
||||
|
||||
|
||||
|
||||
|
||||
# PostgreSQL database
|
||||
|
|
@ -29,6 +32,7 @@ services:
|
|||
- "5436:5432"
|
||||
volumes:
|
||||
- gm-db:/var/lib/postgresql/data/
|
||||
- .:/code
|
||||
|
||||
|
||||
elasticsearch:
|
||||
|
|
|
|||
|
|
@ -2,14 +2,21 @@
|
|||
./manage.py transfer -a
|
||||
./manage.py transfer -d
|
||||
./manage.py transfer -e
|
||||
./manage.py transfer -n
|
||||
./manage.py rm_empty_images # команда для удаления картинок с относительным урлом из news.description
|
||||
./manage.py upd_transportation
|
||||
./manage.py transfer --fill_city_gallery
|
||||
./manage.py transfer -l
|
||||
./manage.py transfer --product
|
||||
# Утеряна четкая связь между последовательностью миграций для импорта тегов продуктов,
|
||||
# что может привести к удалению уже импортированных тегов командой выше.
|
||||
./manage.py transfer --souvenir
|
||||
./manage.py transfer --establishment_note
|
||||
./manage.py transfer --product_note
|
||||
./manage.py transfer --check_serial_number
|
||||
./manage.py transfer --wine_characteristics
|
||||
./manage.py transfer --inquiries
|
||||
./manage.py transfer --assemblage
|
||||
./manage.py transfer --purchased_plaques
|
||||
./manage.py transfer --purchased_plaques
|
||||
./manage.py rm_empty_images
|
||||
./manage.py add_artisan_subtype # добавляет подтипы для заведений артизанов
|
||||
|
|
@ -118,6 +118,7 @@ MIDDLEWARE = [
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'utils.middleware.parse_cookies',
|
||||
'utils.middleware.user_last_visit',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'project.urls'
|
||||
|
|
@ -414,10 +415,10 @@ SORL_THUMBNAIL_ALIASES = {
|
|||
SIMPLE_JWT = {
|
||||
# Increase access token lifetime b.c. front-end dev's cant send multiple
|
||||
# requests to API in one HTTP request.
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=30),
|
||||
'ACCESS_TOKEN_LIFETIME_SECONDS': 21600, # 6 hours in seconds
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
|
||||
'REFRESH_TOKEN_LIFETIME_SECONDS': 2592000, # 30 days in seconds
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=182),
|
||||
'ACCESS_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=182),
|
||||
'REFRESH_TOKEN_LIFETIME_SECONDS': 15770000, # 6 months
|
||||
'ROTATE_REFRESH_TOKENS': True,
|
||||
'BLACKLIST_AFTER_ROTATION': True,
|
||||
|
||||
|
|
@ -453,7 +454,7 @@ NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html'
|
|||
|
||||
|
||||
# COOKIES
|
||||
COOKIES_MAX_AGE = 2628000 # 30 days
|
||||
COOKIES_MAX_AGE = 15730000 # 6 months
|
||||
SESSION_COOKIE_SAMESITE = None
|
||||
|
||||
|
||||
|
|
@ -524,3 +525,6 @@ INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
|
|||
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
|
||||
|
||||
COOKIE_DOMAIN = None
|
||||
|
||||
ELASTICSEARCH_DSL = {}
|
||||
ELASTICSEARCH_INDEX_NAMES = {}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from django.urls import path, include
|
|||
app_name = 'mobile'
|
||||
|
||||
urlpatterns = [
|
||||
path('booking/', include('booking.urls.web')),
|
||||
path('establishments/', include('establishment.urls.mobile')),
|
||||
path('location/', include('location.urls.mobile')),
|
||||
path('main/', include('main.urls.mobile')),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ app_name = 'web'
|
|||
|
||||
urlpatterns = [
|
||||
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('collections/', include('collection.urls.web')),
|
||||
path('establishments/', include('establishment.urls.web')),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user