Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop
This commit is contained in:
commit
893eb6e877
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -19,9 +19,11 @@ logs/
|
||||||
/datadir/
|
/datadir/
|
||||||
/_files/
|
/_files/
|
||||||
/geoip_db/
|
/geoip_db/
|
||||||
|
/venv
|
||||||
|
|
||||||
# dev
|
# dev
|
||||||
./docker-compose.override.yml
|
./docker-compose.override.yml
|
||||||
|
docker-compose.override.yml
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
local_files
|
local_files
|
||||||
celerybeat.pid
|
celerybeat.pid
|
||||||
|
|
|
||||||
23
apps/establishment/migrations/0099_auto_20200207_1136.py
Normal file
23
apps/establishment/migrations/0099_auto_20200207_1136.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-02-07 11:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0098_auto_20200204_1205'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='last_update_by_gm',
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='last_update_by_manager',
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -33,7 +33,7 @@ from utils.models import (
|
||||||
BaseAttributes, FavoritesMixin, FileMixin, GalleryMixin, HasTagsMixin,
|
BaseAttributes, FavoritesMixin, FileMixin, GalleryMixin, HasTagsMixin,
|
||||||
IntermediateGalleryModelMixin, ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
|
IntermediateGalleryModelMixin, ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
|
||||||
TypeDefaultImageMixin, URLImageMixin, default_menu_bool_array, PhoneModelMixin,
|
TypeDefaultImageMixin, URLImageMixin, default_menu_bool_array, PhoneModelMixin,
|
||||||
AwardsModelMixin)
|
AwardsModelMixin, CarouselMixin, UpdateByMixin)
|
||||||
|
|
||||||
|
|
||||||
# todo: establishment type&subtypes check
|
# todo: establishment type&subtypes check
|
||||||
|
|
@ -137,6 +137,14 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
"""Return qs with related reviews."""
|
"""Return qs with related reviews."""
|
||||||
return self.prefetch_related('reviews')
|
return self.prefetch_related('reviews')
|
||||||
|
|
||||||
|
def with_reviews_sorted(self):
|
||||||
|
return self.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'reviews',
|
||||||
|
queryset=Review.objects.published().order_by('-published_at'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def with_currency_related(self):
|
def with_currency_related(self):
|
||||||
"""Return qs with related """
|
"""Return qs with related """
|
||||||
return self.prefetch_related('currency')
|
return self.prefetch_related('currency')
|
||||||
|
|
@ -547,8 +555,15 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
return self.prefetch_related('menu_set', 'menu_set__plates', 'back_office_wine')
|
return self.prefetch_related('menu_set', 'menu_set__plates', 'back_office_wine')
|
||||||
|
|
||||||
|
|
||||||
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
class Establishment(GalleryMixin,
|
||||||
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin, AwardsModelMixin):
|
ProjectBaseMixin,
|
||||||
|
URLImageMixin,
|
||||||
|
TranslatedFieldsMixin,
|
||||||
|
HasTagsMixin,
|
||||||
|
FavoritesMixin,
|
||||||
|
AwardsModelMixin,
|
||||||
|
CarouselMixin,
|
||||||
|
UpdateByMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
||||||
ABANDONED = 0
|
ABANDONED = 0
|
||||||
|
|
@ -565,12 +580,12 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
(ABANDONED, _('Abandoned')),
|
(ABANDONED, _('Abandoned')),
|
||||||
(CLOSED, _('Closed')),
|
(CLOSED, _('Closed')),
|
||||||
(PUBLISHED, _('Published')),
|
(PUBLISHED, _('Published')),
|
||||||
(UNPICKED, _('Unpicked')),
|
# (UNPICKED, _('Unpicked')),
|
||||||
(WAITING, _('Waiting')),
|
(WAITING, _('Waiting')),
|
||||||
(HIDDEN, _('Hidden')),
|
# (HIDDEN, _('Hidden')),
|
||||||
(DELETED, _('Deleted')),
|
# (DELETED, _('Deleted')),
|
||||||
(OUT_OF_SELECTION, _('Out of selection')),
|
(OUT_OF_SELECTION, _('Out of selection')),
|
||||||
(UNPUBLISHED, _('Unpublished')),
|
# (UNPUBLISHED, _('Unpublished')),
|
||||||
)
|
)
|
||||||
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
|
@ -717,9 +732,13 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
raise ValidationError('Establishment type of subtype does not match')
|
raise ValidationError('Establishment type of subtype does not match')
|
||||||
self.establishment_subtypes.add(establishment_subtype)
|
self.establishment_subtypes.add(establishment_subtype)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_review(self):
|
||||||
|
return self.reviews.by_status(Review.READY).last()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vintage_year(self):
|
def vintage_year(self):
|
||||||
last_review = self.reviews.by_status(Review.READY).last()
|
last_review = self.last_review
|
||||||
if last_review:
|
if last_review:
|
||||||
return last_review.vintage
|
return last_review.vintage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from datetime import datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
@ -9,17 +10,20 @@ from rest_framework import serializers
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
|
|
||||||
from account import models as account_models
|
from account import models as account_models
|
||||||
|
from account.models import Role
|
||||||
from account.serializers.common import UserShortSerializer
|
from account.serializers.common import UserShortSerializer
|
||||||
from collection.models import Guide
|
from collection.models import Guide
|
||||||
from establishment import models, serializers as model_serializers
|
from establishment import models, serializers as model_serializers
|
||||||
from establishment.models import ContactEmail, ContactPhone, EstablishmentEmployee
|
from establishment.models import ContactEmail, ContactPhone, EstablishmentEmployee
|
||||||
from establishment.serializers.common import ContactPhonesSerializer
|
from establishment.serializers.common import ContactPhonesSerializer
|
||||||
|
from review.serializers.common import ReviewBaseSerializer
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
from location.serializers import AddressDetailSerializer, TranslatedField, AddressBaseSerializer, \
|
from location.serializers import AddressDetailSerializer, TranslatedField, AddressBaseSerializer, \
|
||||||
AddressEstablishmentSerializer
|
AddressEstablishmentSerializer
|
||||||
from main import models as main_models
|
from main import models as main_models
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
from main.serializers import AwardSerializer
|
from main.serializers import AwardSerializer
|
||||||
|
from review.serializers import ReviewBaseSerializer, User
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from utils.decorators import with_base_attributes
|
from utils.decorators import with_base_attributes
|
||||||
from utils.methods import string_random
|
from utils.methods import string_random
|
||||||
|
|
@ -92,12 +96,13 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
)
|
)
|
||||||
subtypes = model_serializers.EstablishmentSubTypeBaseSerializer(source='establishment_subtypes',
|
subtypes = model_serializers.EstablishmentSubTypeBaseSerializer(source='establishment_subtypes',
|
||||||
read_only=True, many=True)
|
read_only=True, many=True)
|
||||||
|
reviews = ReviewBaseSerializer(allow_null=True, read_only=True, many=True)
|
||||||
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
distillery_type = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
distillery_type = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
food_producer = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
food_producer = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
vintage_year = serializers.IntegerField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
|
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -137,6 +142,8 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
'artisan_category',
|
'artisan_category',
|
||||||
'distillery_type',
|
'distillery_type',
|
||||||
'food_producer',
|
'food_producer',
|
||||||
|
'reviews',
|
||||||
|
'vintage_year',
|
||||||
]
|
]
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
|
|
@ -167,13 +174,9 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
validated_data['slug'] = slug
|
validated_data['slug'] = slug
|
||||||
|
|
||||||
if 'address' in validated_data:
|
if 'address' in validated_data:
|
||||||
address_fields = validated_data.pop('address')
|
address = models.Address(**validated_data.pop('address'))
|
||||||
address_instance = get_object_or_404(models.Address, id=address_fields['id'] or None)
|
address.save()
|
||||||
address_id = getattr(address_instance, 'id')
|
validated_data['address_id'] = address.id
|
||||||
|
|
||||||
models.Address.objects.filter(id=address_id).update(**address_fields)
|
|
||||||
|
|
||||||
validated_data['address_id'] = address_id
|
|
||||||
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
|
|
||||||
|
|
@ -215,6 +218,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
subtypes = model_serializers.EstablishmentSubTypeBaseSerializer(source='establishment_subtypes',
|
subtypes = model_serializers.EstablishmentSubTypeBaseSerializer(source='establishment_subtypes',
|
||||||
read_only=True, many=True)
|
read_only=True, many=True)
|
||||||
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
|
|
||||||
phones = serializers.ListField(
|
phones = serializers.ListField(
|
||||||
source='contact_phones',
|
source='contact_phones',
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
|
|
@ -223,8 +227,11 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
required=False,
|
required=False,
|
||||||
write_only=True,
|
write_only=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
contact_phones = ContactPhonesSerializer(source='phones', read_only=True, many=True)
|
contact_phones = ContactPhonesSerializer(source='phones', read_only=True, many=True)
|
||||||
|
|
||||||
|
last_review = ReviewBaseSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
|
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
|
|
@ -253,6 +260,10 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
'tags',
|
'tags',
|
||||||
'status',
|
'status',
|
||||||
'status_display',
|
'status_display',
|
||||||
|
'last_review',
|
||||||
|
'must_of_the_week',
|
||||||
|
'last_update_by_gm',
|
||||||
|
'last_update_by_manager',
|
||||||
]
|
]
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
|
|
@ -260,7 +271,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
data['phones'] = data.pop('contact_phones', None)
|
data['phones'] = data.pop('contact_phones', None)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance: models.Establishment, validated_data):
|
||||||
phones_list = []
|
phones_list = []
|
||||||
if 'contact_phones' in validated_data:
|
if 'contact_phones' in validated_data:
|
||||||
phones_list = validated_data.pop('contact_phones')
|
phones_list = validated_data.pop('contact_phones')
|
||||||
|
|
@ -269,9 +280,30 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
if 'contact_emails' in validated_data:
|
if 'contact_emails' in validated_data:
|
||||||
emails_list = validated_data.pop('contact_emails')
|
emails_list = validated_data.pop('contact_emails')
|
||||||
|
|
||||||
|
request = self.context.get('request')
|
||||||
|
if request and hasattr(request, 'user'):
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
if isinstance(user, User):
|
||||||
|
is_by_manager = user.userrole_set.filter(
|
||||||
|
pk=user.pk,
|
||||||
|
role__in=(
|
||||||
|
Role.ESTABLISHMENT_MANAGER,
|
||||||
|
Role.ESTABLISHMENT_ADMINISTRATOR,
|
||||||
|
Role.COUNTRY_ADMIN
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
if is_by_manager:
|
||||||
|
instance.last_update_by_manager = datetime.now()
|
||||||
|
else:
|
||||||
|
''' by gm. '''
|
||||||
|
instance.last_update_by_gm = datetime.now()
|
||||||
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
phones_handler(phones_list, instance)
|
phones_handler(phones_list, instance)
|
||||||
emails_handler(emails_list, instance)
|
emails_handler(emails_list, instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -359,6 +391,17 @@ class PositionBackSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AdminEmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Employee
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'last_name',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# TODO: test decorator
|
# TODO: test decorator
|
||||||
@with_base_attributes
|
@with_base_attributes
|
||||||
class EmployeeBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer):
|
class EmployeeBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer):
|
||||||
|
|
@ -972,6 +1015,7 @@ class CardAndWinesSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class TeamMemberSerializer(serializers.ModelSerializer):
|
class TeamMemberSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for team establishment BO section"""
|
"""Serializer for team establishment BO section"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = account_models.User
|
model = account_models.User
|
||||||
fields = (
|
fields = (
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ urlpatterns = [
|
||||||
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
|
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
|
||||||
name='establishment-employees'),
|
name='establishment-employees'),
|
||||||
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
||||||
|
path('employees/for_admin/', views.AdminEmployeeListView.as_view(), name='employees-list-for-admin'),
|
||||||
path('employees/search/', views.EmployeesListSearchViews.as_view(), name='employees-search'),
|
path('employees/search/', views.EmployeesListSearchViews.as_view(), name='employees-search'),
|
||||||
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
||||||
path('employees/<int:pk>/<int:award_id>', views.RemoveAwardView.as_view(), name='employees-award-delete'),
|
path('employees/<int:pk>/<int:award_id>', views.RemoveAwardView.as_view(), name='employees-award-delete'),
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
||||||
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||||
.with_certain_tag_category_related('shop_category', 'artisan_category') \
|
.with_certain_tag_category_related('shop_category', 'artisan_category') \
|
||||||
.with_certain_tag_category_related('distillery_type', 'distillery_type') \
|
.with_certain_tag_category_related('distillery_type', 'distillery_type') \
|
||||||
.with_certain_tag_category_related('producer_type', 'food_producer')
|
.with_certain_tag_category_related('producer_type', 'food_producer') \
|
||||||
|
.with_reviews_sorted()
|
||||||
|
|
||||||
|
|
||||||
class EmployeeEstablishmentPositionsView(generics.ListAPIView):
|
class EmployeeEstablishmentPositionsView(generics.ListAPIView):
|
||||||
|
|
@ -209,7 +210,7 @@ class EstablishmentRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestr
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method."""
|
"""An overridden get_queryset method."""
|
||||||
qs = super(EstablishmentRUDView, self).get_queryset()
|
qs = super(EstablishmentRUDView, self).get_queryset()
|
||||||
return qs.prefetch_related(
|
return qs.prefetch_related(
|
||||||
'establishmentemployee_set',
|
'establishmentemployee_set',
|
||||||
|
|
@ -270,9 +271,36 @@ class EstablishmentScheduleRUDView(EstablishmentMixinViews, generics.RetrieveUpd
|
||||||
|
|
||||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
Establishment schedule Create view
|
## Create establishment schedule
|
||||||
|
### *POST*
|
||||||
Implement creating Establishment shedule.
|
#### Description
|
||||||
|
Create schedule for establishment by establishment `slug`.
|
||||||
|
##### Request
|
||||||
|
Required:
|
||||||
|
* weekday (`enum`)
|
||||||
|
```
|
||||||
|
0 (Monday),
|
||||||
|
1 (Tuesday),
|
||||||
|
2 (Wednesday),
|
||||||
|
3 (Thursday),
|
||||||
|
4 (Friday),
|
||||||
|
5 (Saturday),
|
||||||
|
6 (Sunday)
|
||||||
|
```
|
||||||
|
Non-required:
|
||||||
|
* lunch_start (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
* lunch_end (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
* dinner_start (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
* dinner_end (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
* opening_at (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
* closed_at (str) - time in a format (`ISO-8601`, e.g. - `hh:mm:ss`)
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = ScheduleCreateSerializer
|
serializer_class = ScheduleCreateSerializer
|
||||||
|
|
@ -292,8 +320,8 @@ class CardAndWinesListView(generics.RetrieveAPIView):
|
||||||
queryset = models.Establishment.objects.with_base_related()
|
queryset = models.Establishment.objects.with_base_related()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
establishment = models.Establishment.objects.prefetch_plates()\
|
establishment = models.Establishment.objects.prefetch_plates() \
|
||||||
.filter(pk=self.kwargs['establishment_id'])\
|
.filter(pk=self.kwargs['establishment_id']) \
|
||||||
.first()
|
.first()
|
||||||
if establishment is None:
|
if establishment is None:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
@ -809,7 +837,7 @@ class EmployeesListSearchViews(generics.ListAPIView):
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
queryset = (
|
queryset = (
|
||||||
models.Employee.objects.with_back_office_related()
|
models.Employee.objects.with_back_office_related()
|
||||||
.select_related('photo')
|
.select_related('photo')
|
||||||
)
|
)
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
|
|
@ -931,6 +959,43 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminEmployeeListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
## Employee list view, where request user is ESTABLISHMENT_ADMINISTRATOR.
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Return paginated list of employees.
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1324,
|
||||||
|
"name": "Alex",
|
||||||
|
"last_name": "Wolf",
|
||||||
|
{
|
||||||
|
```
|
||||||
|
|
||||||
|
"""
|
||||||
|
serializer_class = serializers.AdminEmployeeBackSerializers
|
||||||
|
permission_classes = get_permission_classes(IsEstablishmentAdministrator, )
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.is_anonymous:
|
||||||
|
return None
|
||||||
|
est_ids = models.Establishment.objects.filter(
|
||||||
|
userrole__user=user,
|
||||||
|
userrole__role__role=Role.ESTABLISHMENT_ADMINISTRATOR,
|
||||||
|
).values_list('id', flat=True)
|
||||||
|
|
||||||
|
qs = models.Employee.objects.filter(establishments__in=est_ids).distinct().with_back_office_related()
|
||||||
|
|
||||||
|
if self.request.country_code:
|
||||||
|
qs = qs.filter(establishments__address__city__country__code=self.request.country_code)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class RemoveAwardView(generics.DestroyAPIView):
|
class RemoveAwardView(generics.DestroyAPIView):
|
||||||
"""
|
"""
|
||||||
## Remove award view.
|
## Remove award view.
|
||||||
|
|
@ -1060,7 +1125,38 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
CreateDestroyGalleryViewMixin):
|
CreateDestroyGalleryViewMixin):
|
||||||
"""Resource for a create|destroy gallery for establishment for back-office users."""
|
"""
|
||||||
|
## Establishment gallery image Create/Destroy view
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Attaching existing **image** by `image identifier` to **establishment** by `establishment slug`
|
||||||
|
in request kwargs.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
|
||||||
|
### *DELETE*
|
||||||
|
#### Description
|
||||||
|
Delete existing **gallery image** from **establishment** gallery, by `image identifier`
|
||||||
|
and `establishment slug` in request kwargs.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
> Image wouldn't be deleted after all.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
"""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
serializer_class = serializers.EstablishmentBackOfficeGallerySerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
@ -1083,7 +1179,28 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
|
|
||||||
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||||
generics.ListAPIView):
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for establishment for back-office users."""
|
"""
|
||||||
|
## Establishment gallery image list view
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Returning paginated list of establishment images by `establishment slug`,
|
||||||
|
with cropped images.
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.ImageBaseSerializer
|
serializer_class = serializers.ImageBaseSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
@ -1093,7 +1210,7 @@ class EstablishmentGalleryListView(EstablishmentMixinViews,
|
||||||
qs = super(EstablishmentGalleryListView, self).get_queryset()
|
qs = super(EstablishmentGalleryListView, self).get_queryset()
|
||||||
establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
|
establishment = get_object_or_404(qs, slug=self.kwargs.get('slug'))
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raises a permission denied
|
||||||
self.check_object_permissions(self.request, establishment)
|
self.check_object_permissions(self.request, establishment)
|
||||||
|
|
||||||
return establishment
|
return establishment
|
||||||
|
|
@ -1330,10 +1447,10 @@ class EstablishmentGuideCreateDestroyView(generics.GenericAPIView):
|
||||||
lookup_url_kwarg = getattr(self, 'establishment_lookup_url_kwarg', None)
|
lookup_url_kwarg = getattr(self, 'establishment_lookup_url_kwarg', None)
|
||||||
|
|
||||||
assert lookup_url_kwarg in self.kwargs, (
|
assert lookup_url_kwarg in self.kwargs, (
|
||||||
'Expected view %s to be called with a URL keyword argument '
|
'Expected view %s to be called with a URL keyword argument '
|
||||||
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
||||||
'attribute on the view correctly.' %
|
'attribute on the view correctly.' %
|
||||||
(self.__class__.__name__, lookup_url_kwarg)
|
(self.__class__.__name__, lookup_url_kwarg)
|
||||||
)
|
)
|
||||||
|
|
||||||
filters = {'klass': queryset, lookup_url_kwarg: self.kwargs.get(lookup_url_kwarg)}
|
filters = {'klass': queryset, lookup_url_kwarg: self.kwargs.get(lookup_url_kwarg)}
|
||||||
|
|
@ -1351,10 +1468,10 @@ class EstablishmentGuideCreateDestroyView(generics.GenericAPIView):
|
||||||
lookup_url_kwarg = getattr(self, 'guide_lookup_url_kwarg', None)
|
lookup_url_kwarg = getattr(self, 'guide_lookup_url_kwarg', None)
|
||||||
|
|
||||||
assert lookup_url_kwarg in self.kwargs, (
|
assert lookup_url_kwarg in self.kwargs, (
|
||||||
'Expected view %s to be called with a URL keyword argument '
|
'Expected view %s to be called with a URL keyword argument '
|
||||||
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
||||||
'attribute on the view correctly.' %
|
'attribute on the view correctly.' %
|
||||||
(self.__class__.__name__, lookup_url_kwarg)
|
(self.__class__.__name__, lookup_url_kwarg)
|
||||||
)
|
)
|
||||||
|
|
||||||
obj = get_object_or_404(klass=queryset, id=self.kwargs.get(lookup_url_kwarg))
|
obj = get_object_or_404(klass=queryset, id=self.kwargs.get(lookup_url_kwarg))
|
||||||
|
|
@ -1535,4 +1652,4 @@ class EstablishmentAwardCreateAndBind(generics.CreateAPIView, generics.DestroyAP
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
establishment = get_object_or_404(models.Establishment, id=kwargs['establishment_id'])
|
establishment = get_object_or_404(models.Establishment, id=kwargs['establishment_id'])
|
||||||
establishment.remove_award(kwargs['award_id'])
|
establishment.remove_award(kwargs['award_id'])
|
||||||
return self._award_list_for_establishment(kwargs['establishment_id'], status.HTTP_200_OK)
|
return self._award_list_for_establishment(kwargs['establishment_id'], status.HTTP_200_OK)
|
||||||
|
|
|
||||||
18
apps/gallery/migrations/0010_auto_20200206_1944.py
Normal file
18
apps/gallery/migrations/0010_auto_20200206_1944.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-02-06 19:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0009_auto_20200206_1749'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='image',
|
||||||
|
name='is_public',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Is media source public'),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
apps/gallery/migrations/0011_image_cropbox.py
Normal file
20
apps/gallery/migrations/0011_image_cropbox.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-02-08 19:00
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0010_auto_20200206_1944'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='image',
|
||||||
|
name='cropbox',
|
||||||
|
field=models.CharField(default=None, max_length=500, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='x1,y1,x2,y2 crop settings'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core import validators
|
||||||
|
from sorl.thumbnail import get_thumbnail
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from project.storage_backends import PublicMediaStorage
|
from project.storage_backends import PublicMediaStorage
|
||||||
|
|
@ -43,6 +45,8 @@ class Image(BaseAttributes, SORLImageMixin, PlatformMixin):
|
||||||
default=None)
|
default=None)
|
||||||
link = models.URLField(blank=True, null=True, default=None, verbose_name=_('mp4 or youtube video link'))
|
link = models.URLField(blank=True, null=True, default=None, verbose_name=_('mp4 or youtube video link'))
|
||||||
order = models.PositiveIntegerField(default=0, verbose_name=_('Sorting order'))
|
order = models.PositiveIntegerField(default=0, verbose_name=_('Sorting order'))
|
||||||
|
cropbox = models.CharField(max_length=500, validators=[validators.validate_comma_separated_integer_list], null=True,
|
||||||
|
default=None, verbose_name=_('x1,y1,x2,y2 crop settings'))
|
||||||
objects = ImageQuerySet.as_manager()
|
objects = ImageQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -55,6 +59,16 @@ class Image(BaseAttributes, SORLImageMixin, PlatformMixin):
|
||||||
"""String representation"""
|
"""String representation"""
|
||||||
return f'{self.id}'
|
return f'{self.id}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_by_cropbox(self):
|
||||||
|
"""Returns cropped image if cropbox is set"""
|
||||||
|
if self.cropbox and self.image:
|
||||||
|
x1, y1, x2, y2 = map(int, self.cropbox.split(','))
|
||||||
|
return get_thumbnail(self.image,
|
||||||
|
geometry_string=f'{round(x2 - x1)}x{round(y2 - y1)}',
|
||||||
|
cropbox=self.cropbox,
|
||||||
|
quality=100)
|
||||||
|
|
||||||
def set_pubic(self, is_public=True):
|
def set_pubic(self, is_public=True):
|
||||||
if not settings.AWS_STORAGE_BUCKET_NAME:
|
if not settings.AWS_STORAGE_BUCKET_NAME:
|
||||||
"""Backend doesn't use aws s3"""
|
"""Backend doesn't use aws s3"""
|
||||||
|
|
@ -69,6 +83,12 @@ class Image(BaseAttributes, SORLImageMixin, PlatformMixin):
|
||||||
else:
|
else:
|
||||||
file_object.Acl().put(ACL='authenticated-read')
|
file_object.Acl().put(ACL='authenticated-read')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_main(self) -> bool:
|
||||||
|
establishment_gallery_list = list(self.establishment_gallery.all())
|
||||||
|
if establishment_gallery_list and len(establishment_gallery_list):
|
||||||
|
return establishment_gallery_list[0].is_main
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
if self.image:
|
if self.image:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment, EstablishmentGallery
|
||||||
from account.serializers.common import UserBaseSerializer
|
from account.serializers.common import UserBaseSerializer
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
@ -53,6 +53,8 @@ class EstablishmentGallerySerializer(serializers.ModelSerializer):
|
||||||
type = serializers.ChoiceField(read_only=True, choices=models.Image.MEDIA_TYPES)
|
type = serializers.ChoiceField(read_only=True, choices=models.Image.MEDIA_TYPES)
|
||||||
created_by = UserBaseSerializer(read_only=True, allow_null=True)
|
created_by = UserBaseSerializer(read_only=True, allow_null=True)
|
||||||
image_size_in_KB = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=20)
|
image_size_in_KB = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=20)
|
||||||
|
is_main = serializers.BooleanField()
|
||||||
|
cropped_image = serializers.ImageField(source='image_by_cropbox', allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Image
|
model = models.Image
|
||||||
|
|
@ -65,11 +67,16 @@ class EstablishmentGallerySerializer(serializers.ModelSerializer):
|
||||||
'preview',
|
'preview',
|
||||||
'is_public',
|
'is_public',
|
||||||
'title',
|
'title',
|
||||||
|
'is_main',
|
||||||
'created_by',
|
'created_by',
|
||||||
|
'created',
|
||||||
'image_size_in_KB',
|
'image_size_in_KB',
|
||||||
|
'cropbox',
|
||||||
|
'cropped_image',
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'created': {'read_only': True},
|
'created': {'read_only': True},
|
||||||
|
'created_by': {'read_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
@ -78,24 +85,43 @@ class EstablishmentGallerySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
if image and image.size >= settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
|
if image and image.size >= settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
|
||||||
raise serializers.ValidationError({'detail': _('File size too large: %s bytes') % image.size})
|
raise serializers.ValidationError({'detail': _('File size too large: %s bytes') % image.size})
|
||||||
|
if attrs.get('cropbox'):
|
||||||
|
if len(attrs['cropbox'].split(',')) != 4:
|
||||||
|
raise serializers.ValidationError({'detail': _('Cropbox contains 4 integer values separated by comma.')})
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
is_main = validated_data.pop('is_main')
|
||||||
establishment = get_object_or_404(klass=Establishment, pk=self.context['view'].kwargs['establishment_id'])
|
establishment = get_object_or_404(klass=Establishment, pk=self.context['view'].kwargs['establishment_id'])
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
instance.created_by = self.context['request'].user
|
instance.created_by = self.context['request'].user
|
||||||
instance.establishment_set.add(establishment)
|
instance.establishment_set.add(establishment)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
if is_main:
|
||||||
|
EstablishmentGallery.objects.filter(
|
||||||
|
establishment=establishment
|
||||||
|
).update(is_main=False) # reset all before setting True on some instance
|
||||||
|
EstablishmentGallery.objects.filter(
|
||||||
|
image=instance
|
||||||
|
).update(is_main=is_main)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance: models.Image, validated_data):
|
def update(self, instance: models.Image, validated_data):
|
||||||
if instance.is_public != validated_data.get('is_public'):
|
if instance.is_public != validated_data.get('is_public'):
|
||||||
instance.set_pubic(validated_data.get('is_public', True))
|
instance.set_pubic(validated_data.get('is_public', True))
|
||||||
|
if 'is_main' in validated_data:
|
||||||
|
is_main = validated_data.pop('is_main')
|
||||||
|
if is_main:
|
||||||
|
establishment = instance.establishment_gallery.all()[0].establishment
|
||||||
|
EstablishmentGallery.objects.filter(
|
||||||
|
establishment=establishment
|
||||||
|
).update(is_main=False) # reset all before setting True on some instance
|
||||||
|
EstablishmentGallery.objects.filter(
|
||||||
|
image=instance
|
||||||
|
).update(is_main=is_main)
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CropImageSerializer(ImageSerializer):
|
class CropImageSerializer(ImageSerializer):
|
||||||
"""Serializers for image crops."""
|
"""Serializers for image crops."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.transaction import on_commit
|
from django.db.transaction import on_commit
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from utils.methods import get_permission_classes
|
from utils.methods import get_permission_classes
|
||||||
|
|
@ -8,6 +9,7 @@ from utils.permissions import IsContentPageManager, IsCountryAdmin, IsEstablishm
|
||||||
IsProducerFoodInspector, IsEstablishmentAdministrator
|
IsProducerFoodInspector, IsEstablishmentAdministrator
|
||||||
from . import tasks, models, serializers
|
from . import tasks, models, serializers
|
||||||
|
|
||||||
|
|
||||||
class ImageBaseView(generics.GenericAPIView):
|
class ImageBaseView(generics.GenericAPIView):
|
||||||
"""Base Image view."""
|
"""Base Image view."""
|
||||||
model = models.Image
|
model = models.Image
|
||||||
|
|
@ -19,24 +21,83 @@ class ImageBaseView(generics.GenericAPIView):
|
||||||
|
|
||||||
|
|
||||||
class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView):
|
class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView):
|
||||||
"""List/Create Image view."""
|
"""
|
||||||
|
## List/Create view
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Get paginated list of images, with ordering by field `modified` (descending)
|
||||||
|
#### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"count": 40595,
|
||||||
|
"next": 2,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 47336,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Upload an image on a server.
|
||||||
|
##### Request
|
||||||
|
Required:
|
||||||
|
* file (`file`) - download file
|
||||||
|
Available:
|
||||||
|
* orientation (`enum`) - default: `null`
|
||||||
|
```
|
||||||
|
0 (Horizontal)
|
||||||
|
1 (Vertical)
|
||||||
|
```
|
||||||
|
* title (`str`) - title of image file (default - `''`)
|
||||||
|
* is_public (`bool`) - flag that responds for availability
|
||||||
|
for displaying (default - `True`)
|
||||||
|
* preview (`file`) - download preview file (default - `null`)
|
||||||
|
* link (`str`) - mp4 or youtube video link (default - `null`)
|
||||||
|
* order (`int`) - order number (default - `0`)
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 47336,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MediaForEstablishmentView(ImageBaseView, generics.ListCreateAPIView):
|
class MediaForEstablishmentView(ImageBaseView, generics.ListCreateAPIView):
|
||||||
"""View for creating and retrieving certain establishment media."""
|
"""View for creating and retrieving certain establishment media."""
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = (IsCountryAdmin, IsEstablishmentAdministrator, IsEstablishmentManager, IsProducerFoodInspector)
|
# permission_classes = (IsCountryAdmin, IsEstablishmentAdministrator, IsEstablishmentManager, IsProducerFoodInspector)
|
||||||
|
permission_classes = (AllowAny, )
|
||||||
serializer_class = serializers.EstablishmentGallerySerializer
|
serializer_class = serializers.EstablishmentGallerySerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().filter(establishment__pk=self.kwargs['establishment_id'])\
|
return super().get_queryset().filter(establishment__pk=self.kwargs['establishment_id'])\
|
||||||
.order_by('-order').prefetch_related('created_by')
|
.order_by('-establishment_gallery__is_main', '-order').prefetch_related('created_by',
|
||||||
|
'establishment_gallery')
|
||||||
|
|
||||||
|
|
||||||
class MediaUpdateView(ImageBaseView, generics.UpdateAPIView):
|
class MediaUpdateView(ImageBaseView, generics.UpdateAPIView, generics.DestroyAPIView):
|
||||||
"""View for updating media data"""
|
"""View for updating media data"""
|
||||||
serializer_class = serializers.EstablishmentGallerySerializer
|
serializer_class = serializers.EstablishmentGallerySerializer
|
||||||
permission_classes = ()
|
# permission_classes = (IsCountryAdmin, IsEstablishmentAdministrator, IsEstablishmentManager, IsProducerFoodInspector)
|
||||||
|
permission_classes = (AllowAny, )
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
"""Override destroy view"""
|
||||||
|
instance = self.get_object()
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
on_commit(lambda: tasks.delete_image.delay(image_id=instance.id))
|
||||||
|
else:
|
||||||
|
on_commit(lambda: tasks.delete_image(image_id=instance.id))
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
||||||
|
|
|
||||||
|
|
@ -223,11 +223,18 @@ class AddressBaseSerializer(serializers.ModelSerializer):
|
||||||
class AddressEstablishmentSerializer(AddressBaseSerializer):
|
class AddressEstablishmentSerializer(AddressBaseSerializer):
|
||||||
"""Address serializer."""
|
"""Address serializer."""
|
||||||
|
|
||||||
id = serializers.IntegerField(required=True)
|
id = serializers.IntegerField(required=False)
|
||||||
street_name_1 = serializers.CharField(required=False, default='')
|
street_name_1 = serializers.CharField(required=False, allow_blank=True, default='')
|
||||||
street_name_2 = serializers.CharField(required=False, default='')
|
street_name_2 = serializers.CharField(required=False, allow_blank=True, default='')
|
||||||
number = serializers.IntegerField(required=False, default=0)
|
number = serializers.IntegerField(required=False, default=0)
|
||||||
postal_code = serializers.CharField(required=False, default='')
|
postal_code = serializers.CharField(required=False, default='')
|
||||||
|
city_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
source='city',
|
||||||
|
queryset=models.City.objects.all(),
|
||||||
|
write_only=True,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
city = CityBaseSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(AddressBaseSerializer.Meta):
|
class Meta(AddressBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -238,6 +245,8 @@ class AddressEstablishmentSerializer(AddressBaseSerializer):
|
||||||
'street_name_2',
|
'street_name_2',
|
||||||
'number',
|
'number',
|
||||||
'postal_code',
|
'postal_code',
|
||||||
|
'city_id',
|
||||||
|
'city',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,23 @@ class AwardFilter(filters.FilterSet):
|
||||||
if value not in EMPTY_VALUES:
|
if value not in EMPTY_VALUES:
|
||||||
return queryset.by_employee_id(value, content_type='establishmentemployee')
|
return queryset.by_employee_id(value, content_type='establishmentemployee')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class AwardTypeFilterSet(filters.FilterSet):
|
||||||
|
"""Award type FilterSet."""
|
||||||
|
|
||||||
|
id = filters.NumberFilter(help_text='Filter by AwardType identifier.')
|
||||||
|
name = filters.CharFilter(method='by_name', help_text='Filter by AwardType name.')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.AwardType
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
|
||||||
|
def by_name(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_name(value)
|
||||||
|
return queryset
|
||||||
|
|
@ -223,6 +223,10 @@ class AwardTypeQuerySet(models.QuerySet):
|
||||||
"""Filter QuerySet by country code."""
|
"""Filter QuerySet by country code."""
|
||||||
return self.filter(country__code=country_code)
|
return self.filter(country__code=country_code)
|
||||||
|
|
||||||
|
def by_name(self, name: str):
|
||||||
|
"""Filter by name field."""
|
||||||
|
return self.filter(name__icontains=name)
|
||||||
|
|
||||||
|
|
||||||
class AwardType(models.Model):
|
class AwardType(models.Model):
|
||||||
"""AwardType model."""
|
"""AwardType model."""
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from rest_framework import serializers
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from account.serializers import BackUserSerializer
|
from account.serializers import BackUserSerializer
|
||||||
from main import models
|
from main import models
|
||||||
|
from main.serializers import CarouselListSerializer
|
||||||
|
|
||||||
|
|
||||||
class PanelSerializer(serializers.ModelSerializer):
|
class PanelSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -27,3 +28,15 @@ class PanelSerializer(serializers.ModelSerializer):
|
||||||
'user',
|
'user',
|
||||||
'user_id'
|
'user_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BackCarouselListSerializer(CarouselListSerializer):
|
||||||
|
"""Serializer for retrieving list of carousel items."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Carousel
|
||||||
|
fields = CarouselListSerializer.Meta.fields + [
|
||||||
|
'active',
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
"""Main app serializers."""
|
"""Main app serializers."""
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from establishment.models import Employee
|
||||||
from location.serializers import CountrySerializer
|
from location.serializers import CountrySerializer
|
||||||
from main import models
|
from main import models
|
||||||
from establishment.models import Employee
|
|
||||||
from tag.serializers import TagBackOfficeSerializer
|
from tag.serializers import TagBackOfficeSerializer
|
||||||
|
from utils.exceptions import EmployeeNotFoundError
|
||||||
from utils.serializers import ProjectModelSerializer, RecursiveFieldSerializer, TranslatedField
|
from utils.serializers import ProjectModelSerializer, RecursiveFieldSerializer, TranslatedField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -207,6 +210,7 @@ class AwardBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Award base serializer."""
|
"""Award base serializer."""
|
||||||
|
|
||||||
title_translated = serializers.CharField(read_only=True, allow_null=True)
|
title_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||||
|
title = serializers.CharField(write_only=True, help_text='Title text')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Award
|
model = models.Award
|
||||||
|
|
@ -215,45 +219,63 @@ class AwardBaseSerializer(serializers.ModelSerializer):
|
||||||
'title_translated',
|
'title_translated',
|
||||||
'vintage_year',
|
'vintage_year',
|
||||||
'image_url',
|
'image_url',
|
||||||
|
'title',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request(self):
|
||||||
|
"""Return a request object"""
|
||||||
|
return self.context.get('request')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context_kwargs(self) -> Union[dict, None]:
|
||||||
|
"""Return a request kwargs."""
|
||||||
|
if hasattr(self.request, 'parser_context'):
|
||||||
|
return self.request.parser_context.get('kwargs')
|
||||||
|
|
||||||
|
def validate_title(self, value) -> dict:
|
||||||
|
"""Construct title str to JSON that contains locale from request."""
|
||||||
|
return {self.request.locale: value}
|
||||||
|
|
||||||
|
|
||||||
class AwardSerializer(AwardBaseSerializer):
|
class AwardSerializer(AwardBaseSerializer):
|
||||||
"""Award serializer."""
|
"""Award serializer."""
|
||||||
|
|
||||||
award_type = AwardTypeBaseSerializer(read_only=True)
|
award_type = AwardTypeBaseSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta(AwardBaseSerializer.Meta):
|
||||||
model = models.Award
|
|
||||||
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
|
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
|
||||||
|
|
||||||
|
|
||||||
class BackAwardSerializer(AwardBaseSerializer):
|
class BackAwardSerializer(AwardBaseSerializer):
|
||||||
"""Award serializer."""
|
"""Award serializer."""
|
||||||
|
award_type_display = AwardTypeBaseSerializer(read_only=True,
|
||||||
|
source='award_type')
|
||||||
|
award_type = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=models.AwardType.objects.all(),
|
||||||
|
write_only=True,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
award_type = AwardTypeBaseSerializer(read_only=True)
|
class Meta(AwardBaseSerializer.Meta):
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Award
|
|
||||||
fields = AwardBaseSerializer.Meta.fields + [
|
fields = AwardBaseSerializer.Meta.fields + [
|
||||||
'award_type',
|
'award_type',
|
||||||
|
'award_type_display',
|
||||||
'state',
|
'state',
|
||||||
'content_type',
|
'content_type',
|
||||||
'object_id',
|
'object_id',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
data = super(BackAwardSerializer, self).to_representation(instance)
|
||||||
|
data['award_type'] = data.pop('award_type_display', None)
|
||||||
|
return data
|
||||||
|
|
||||||
class BackAwardEmployeeCreateSerializer(serializers.ModelSerializer):
|
|
||||||
|
class BackAwardEmployeeCreateSerializer(AwardBaseSerializer):
|
||||||
"""Award, The Creator."""
|
"""Award, The Creator."""
|
||||||
|
|
||||||
award_type = serializers.PrimaryKeyRelatedField(required=True, queryset=models.AwardType.objects.all())
|
award_type = serializers.PrimaryKeyRelatedField(required=True, queryset=models.AwardType.objects.all())
|
||||||
title = serializers.CharField(write_only=True)
|
|
||||||
|
|
||||||
def get_title(self, obj):
|
class Meta(AwardBaseSerializer.Meta):
|
||||||
pass
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Award
|
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'award_type',
|
'award_type',
|
||||||
|
|
@ -262,9 +284,15 @@ class BackAwardEmployeeCreateSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs['object_id'] = self.context.get('request').parser_context.get('kwargs')['employee_id']
|
"""An overridden validate method."""
|
||||||
|
employee_id = self.context_kwargs.get('employee_id')
|
||||||
|
employee_qs = Employee.objects.filter(id=employee_id)
|
||||||
|
|
||||||
|
if not employee_qs.exists():
|
||||||
|
raise EmployeeNotFoundError()
|
||||||
|
|
||||||
|
attrs['object_id'] = employee_id
|
||||||
attrs['content_type'] = ContentType.objects.get_for_model(Employee)
|
attrs['content_type'] = ContentType.objects.get_for_model(Employee)
|
||||||
attrs['title'] = {self.context.get('request').locale: attrs['title']}
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ app_name = 'main'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
|
path('awards/', views.AwardLstView.as_view(), name='awards-list-create'),
|
||||||
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
|
path('awards/<int:id>/', views.AwardRUDView.as_view(), name='awards-rud'),
|
||||||
path('awards/create-and-bind/<int:employee_id>/', views.AwardCreateAndBind.as_view(), name='award-employee-create'),
|
path('awards/create-and-bind/<int:employee_id>/', views.AwardCreateAndBind.as_view(),
|
||||||
|
name='award-employee-create'),
|
||||||
path('award-types/', views.AwardTypesListView.as_view(), name='awards-types-list'),
|
path('award-types/', views.AwardTypesListView.as_view(), name='awards-types-list'),
|
||||||
path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'),
|
path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'),
|
||||||
path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'),
|
path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'),
|
||||||
|
|
@ -28,7 +29,6 @@ urlpatterns = [
|
||||||
path('panels/<int:pk>/', views.PanelsRUDView.as_view(), name='panels-rud'),
|
path('panels/<int:pk>/', views.PanelsRUDView.as_view(), name='panels-rud'),
|
||||||
path('panels/<int:pk>/execute/', views.PanelsExecuteView.as_view(), name='panels-execute'),
|
path('panels/<int:pk>/execute/', views.PanelsExecuteView.as_view(), name='panels-execute'),
|
||||||
path('panels/<int:pk>/csv/', views.PanelsExportCSVView.as_view(), name='panels-csv'),
|
path('panels/<int:pk>/csv/', views.PanelsExportCSVView.as_view(), name='panels-csv'),
|
||||||
path('panels/<int:pk>/xls/', views.PanelsExecuteXLSView.as_view(), name='panels-xls')
|
path('panels/<int:pk>/xls/', views.PanelsExecuteXLSView.as_view(), name='panels-xls'),
|
||||||
|
path('carousel/', views.BackCarouselListView.as_view(), name='carousel-list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,61 @@ from establishment.models import Employee
|
||||||
from establishment.serializers.back import EmployeeBackSerializers
|
from establishment.serializers.back import EmployeeBackSerializers
|
||||||
from main import serializers
|
from main import serializers
|
||||||
from main import tasks
|
from main import tasks
|
||||||
from main.filters import AwardFilter
|
from main.filters import AwardFilter, AwardTypeFilterSet
|
||||||
from main.models import Award, Footer, PageType, Panel, SiteFeature, Feature, AwardType
|
from main.models import Award, Footer, PageType, Panel, SiteFeature, Feature, AwardType, Carousel
|
||||||
from main.serializers.back import PanelSerializer
|
from main.serializers.back import PanelSerializer, BackCarouselListSerializer
|
||||||
from main.views import SiteSettingsView, SiteListView
|
from main.views import SiteSettingsView, SiteListView
|
||||||
from utils.methods import get_permission_classes
|
from utils.methods import get_permission_classes
|
||||||
|
|
||||||
|
|
||||||
class AwardLstView(generics.ListCreateAPIView):
|
class AwardLstView(generics.ListCreateAPIView):
|
||||||
"""Award list create view."""
|
"""
|
||||||
|
## List of awards
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Return paginated list of awards.
|
||||||
|
Available filters:
|
||||||
|
* establishment_id (`int`) - Filter by establishment identifier
|
||||||
|
* product_id (`int`) - Filter by product identifier
|
||||||
|
* employee_id (`int`) - Filter by employee identifier
|
||||||
|
* state (`enum`) - `0 (Waiting)`, `1 (Published)`
|
||||||
|
* award_type (`str`) - Filter by award type identifier
|
||||||
|
* vintage_year (`str`) - Filter by a vintage year
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"count": 58,
|
||||||
|
"next": 2,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Create a record in Award table.
|
||||||
|
##### Request
|
||||||
|
Required:
|
||||||
|
* content_type (`int`) - identifier of content type entity
|
||||||
|
* object_id (`int`) - identifier of content object
|
||||||
|
* award_type (`int`) - identifier of award type
|
||||||
|
* title (`str`) - title of an award
|
||||||
|
Non required:
|
||||||
|
* vintage_year (str) - vintage year in a format - `yyyy`
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
queryset = Award.objects.all().with_base_related()
|
queryset = Award.objects.all().with_base_related()
|
||||||
serializer_class = serializers.BackAwardSerializer
|
serializer_class = serializers.BackAwardSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
@ -25,7 +71,35 @@ class AwardLstView(generics.ListCreateAPIView):
|
||||||
|
|
||||||
|
|
||||||
class AwardCreateAndBind(generics.CreateAPIView):
|
class AwardCreateAndBind(generics.CreateAPIView):
|
||||||
"""Award create and bind to employee by id"""
|
"""
|
||||||
|
## Creating an Award for an Employee.
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Creating an Award for an Employee and return in response
|
||||||
|
serialized Employee object.
|
||||||
|
##### Response
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### Request
|
||||||
|
Required:
|
||||||
|
* award_type (`int`) - identifier of award type
|
||||||
|
* title (`str`) - title of an award
|
||||||
|
Non required:
|
||||||
|
* vintage_year (str) - vintage year in a format - `yyyy`
|
||||||
|
##### Response
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
queryset = Award.objects.all().with_base_related()
|
queryset = Award.objects.all().with_base_related()
|
||||||
serializer_class = serializers.BackAwardEmployeeCreateSerializer
|
serializer_class = serializers.BackAwardEmployeeCreateSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
@ -41,7 +115,52 @@ class AwardCreateAndBind(generics.CreateAPIView):
|
||||||
|
|
||||||
|
|
||||||
class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Award RUD view."""
|
"""
|
||||||
|
## Retrieve/Update/Destroy Award view
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Retrieving serialized object of an Award by an identifier
|
||||||
|
#### Response
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### *PATCH*
|
||||||
|
#### Description
|
||||||
|
Partially update Award object by identifier
|
||||||
|
##### Request
|
||||||
|
Available:
|
||||||
|
* content_type (`int`) - identifier of content type entity
|
||||||
|
* object_id (`int`) - identifier of content object
|
||||||
|
* award_type (`int`) - identifier of award type
|
||||||
|
* title (`str`) - title of an award
|
||||||
|
* vintage_year (str) - vintage year in a format - `yyyy`
|
||||||
|
##### Response
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### *DELETE*
|
||||||
|
#### Description
|
||||||
|
Delete an Award instance by award identifier
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No request data
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
"""
|
||||||
queryset = Award.objects.all().with_base_related()
|
queryset = Award.objects.all().with_base_related()
|
||||||
serializer_class = serializers.BackAwardSerializer
|
serializer_class = serializers.BackAwardSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
@ -49,20 +168,32 @@ class AwardRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
|
|
||||||
class AwardTypesListView(generics.ListAPIView):
|
class AwardTypesListView(generics.ListAPIView):
|
||||||
"""AwardType List view."""
|
"""
|
||||||
|
## List of Award types view.
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Return non paginated list of Award types filtered by request country code.
|
||||||
|
Available filters:
|
||||||
|
* id (`int`) - award type identifier
|
||||||
|
* name (`str`) - award type name
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
"""
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
serializer_class = serializers.AwardTypeBaseSerializer
|
serializer_class = serializers.AwardTypeBaseSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
ordering_fields = '__all__'
|
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
filterset_fields = (
|
filter_class = AwardTypeFilterSet
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method."""
|
"""An overridden get_queryset method."""
|
||||||
if hasattr(self, 'request') and hasattr(self.request, 'country_code'):
|
if hasattr(self, 'request') and hasattr(self.request, 'country_code'):
|
||||||
return AwardType.objects.by_country_code(self.request.country_code)
|
return AwardType.objects.by_country_code(self.request.country_code)
|
||||||
return AwardType.objects.none()
|
return AwardType.objects.none()
|
||||||
|
|
@ -200,3 +331,40 @@ class PanelsExecuteXLSView(PanelsExecuteView):
|
||||||
{"success": _('The file will be sent to your email.')},
|
{"success": _('The file will be sent to your email.')},
|
||||||
status=status.HTTP_200_OK
|
status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BackCarouselListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
## List of carousel.
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Return list of carousel items.
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"model_name": "model_name",
|
||||||
|
"name": "name",
|
||||||
|
...
|
||||||
|
"awards": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = Carousel.objects.all()
|
||||||
|
serializer_class = BackCarouselListSerializer
|
||||||
|
permission_classes = get_permission_classes()
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
country_code = self.request.country_code
|
||||||
|
qs = Carousel.objects.all()
|
||||||
|
if country_code:
|
||||||
|
qs = qs.by_country_code(country_code)
|
||||||
|
return qs
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ from utils.models import (
|
||||||
BaseAttributes, FavoritesMixin, GalleryMixin, HasTagsMixin, IntermediateGalleryModelMixin,
|
BaseAttributes, FavoritesMixin, GalleryMixin, HasTagsMixin, IntermediateGalleryModelMixin,
|
||||||
ProjectBaseMixin,
|
ProjectBaseMixin,
|
||||||
TJSONField, TranslatedFieldsMixin, TypeDefaultImageMixin,
|
TJSONField, TranslatedFieldsMixin, TypeDefaultImageMixin,
|
||||||
)
|
CarouselMixin)
|
||||||
from utils.querysets import TranslationQuerysetMixin
|
from utils.querysets import TranslationQuerysetMixin
|
||||||
|
from location.models import Country
|
||||||
|
from utils.parsers import NewsSlug
|
||||||
|
|
||||||
|
|
||||||
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
|
|
@ -257,8 +259,12 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
return self.filter(site__country__code=country_code) if not user.is_superuser else self
|
return self.filter(site__country__code=country_code) if not user.is_superuser else self
|
||||||
|
|
||||||
|
|
||||||
class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
class News(GalleryMixin,
|
||||||
FavoritesMixin):
|
BaseAttributes,
|
||||||
|
TranslatedFieldsMixin,
|
||||||
|
HasTagsMixin,
|
||||||
|
FavoritesMixin,
|
||||||
|
CarouselMixin):
|
||||||
"""News model."""
|
"""News model."""
|
||||||
|
|
||||||
STR_FIELD_NAME = 'title'
|
STR_FIELD_NAME = 'title'
|
||||||
|
|
@ -358,24 +364,41 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
return f'news: {next(iter(self.slugs.values()))}'
|
return f'news: {next(iter(self.slugs.values()))}'
|
||||||
|
|
||||||
def create_duplicate(self, new_country, view_count_model):
|
def create_duplicate(self, new_country, view_count_model):
|
||||||
|
country_codes = list(Country.objects.all().values_list('code', flat=True))
|
||||||
|
|
||||||
|
# Get all existed slugs
|
||||||
|
all_slugs = {slug_value
|
||||||
|
for slug_dict in News.objects.all().values_list('slugs', flat=True)
|
||||||
|
for slug_value in slug_dict.values()}
|
||||||
|
|
||||||
|
new_slugs = {}
|
||||||
|
for locale, raw_slug in self.slugs.items():
|
||||||
|
slug = NewsSlug.parse(raw_slug, country_codes)
|
||||||
|
|
||||||
|
# all slugs LIKE% slug
|
||||||
|
similar_slugs = sorted(x for x in all_slugs if NewsSlug.parse(x, country_codes).value == slug.value)
|
||||||
|
|
||||||
|
if len(similar_slugs) == 0:
|
||||||
|
# It is impossible because at least current instance has slug
|
||||||
|
raise ValueError('Duplicating unsaved object')
|
||||||
|
else:
|
||||||
|
# The last slug in similar_slugs is slug with largest count
|
||||||
|
last_slug = NewsSlug.parse(similar_slugs[-1], country_codes)
|
||||||
|
|
||||||
|
new_slug = NewsSlug(slug.value, new_country.code, last_slug.count)
|
||||||
|
if last_slug.country_code is not None:
|
||||||
|
new_slug.count += 1
|
||||||
|
|
||||||
|
new_slugs[locale] = str(new_slug)
|
||||||
|
|
||||||
self.pk = None
|
self.pk = None
|
||||||
self.state = self.UNPUBLISHED
|
self.state = self.UNPUBLISHED
|
||||||
self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()}
|
self.slugs = new_slugs
|
||||||
self.country = new_country
|
self.country = new_country
|
||||||
self.views_count = view_count_model
|
self.views_count = view_count_model
|
||||||
self.duplication_date = timezone.now()
|
self.duplication_date = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
|
||||||
def must_of_the_week(self) -> bool:
|
|
||||||
"""Detects whether current item in carousel"""
|
|
||||||
kwargs = {
|
|
||||||
'content_type': ContentType.objects.get_for_model(self),
|
|
||||||
'object_id': self.pk,
|
|
||||||
'country': self.country,
|
|
||||||
}
|
|
||||||
return Carousel.objects.filter(**kwargs).exists()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def publication_datetime(self):
|
def publication_datetime(self):
|
||||||
"""Represents datetime object combined from `publication_date` & `publication_time` fields"""
|
"""Represents datetime object combined from `publication_date` & `publication_time` fields"""
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,38 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
|
||||||
|
|
||||||
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
CreateDestroyGalleryViewMixin):
|
CreateDestroyGalleryViewMixin):
|
||||||
"""Resource for a create gallery for news for back-office users."""
|
"""
|
||||||
|
## News gallery image Create/Destroy view
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Attaching existing **image** by `image identifier` to **news** by `news identifier`
|
||||||
|
in request kwargs.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
|
||||||
|
### *DELETE*
|
||||||
|
#### Description
|
||||||
|
Delete existing **gallery image** from **news** gallery, by `image identifier`
|
||||||
|
and `news identifier` in request kwargs.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
> Image wouldn't be deleted after all.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
"""
|
||||||
serializer_class = serializers.NewsBackOfficeGallerySerializer
|
serializer_class = serializers.NewsBackOfficeGallerySerializer
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
|
@ -203,7 +234,28 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
|
||||||
|
|
||||||
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView,
|
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView,
|
||||||
generics.ListAPIView):
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for news for back-office users."""
|
"""
|
||||||
|
## News gallery image list view
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Returning paginated list of news images by `news identifier`,
|
||||||
|
with cropped images.
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
serializer_class = ImageBaseSerializer
|
serializer_class = ImageBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,38 @@ class ProductSubTypeBackOfficeMixinView:
|
||||||
|
|
||||||
class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
|
class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
|
||||||
CreateDestroyGalleryViewMixin):
|
CreateDestroyGalleryViewMixin):
|
||||||
"""Resource for a create gallery for product for back-office users."""
|
"""
|
||||||
|
## Product gallery image Create/Destroy view
|
||||||
|
### *POST*
|
||||||
|
#### Description
|
||||||
|
Attaching existing **image** by `image identifier` to **product** by `product identifier`
|
||||||
|
in request kwargs.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
|
||||||
|
### *DELETE*
|
||||||
|
#### Description
|
||||||
|
Delete existing **gallery image** from **product** gallery, by `image identifier`
|
||||||
|
and `product identifier` in request kwargs.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
> Image wouldn't be deleted after all.
|
||||||
|
##### Request
|
||||||
|
```
|
||||||
|
No body
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```
|
||||||
|
No content
|
||||||
|
```
|
||||||
|
"""
|
||||||
serializer_class = serializers.ProductBackOfficeGallerySerializer
|
serializer_class = serializers.ProductBackOfficeGallerySerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -66,7 +97,28 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView,
|
||||||
|
|
||||||
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView,
|
class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView,
|
||||||
generics.ListAPIView):
|
generics.ListAPIView):
|
||||||
"""Resource for returning gallery for product for back-office users."""
|
"""
|
||||||
|
## Product gallery image list view
|
||||||
|
### *GET*
|
||||||
|
#### Description
|
||||||
|
Returning paginated list of product images by `product identifier`,
|
||||||
|
with cropped images.
|
||||||
|
##### Response
|
||||||
|
E.g.:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
serializer_class = ImageBaseSerializer
|
serializer_class = ImageBaseSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,10 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
|
||||||
|
|
||||||
objects = ReviewQuerySet.as_manager()
|
objects = ReviewQuerySet.as_manager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_display(self):
|
||||||
|
return self.REVIEW_STATUSES[self.status][1]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
verbose_name = _('Review')
|
verbose_name = _('Review')
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,19 @@ from review.models import Review, Inquiries, GridItems
|
||||||
|
|
||||||
|
|
||||||
class ReviewBaseSerializer(serializers.ModelSerializer):
|
class ReviewBaseSerializer(serializers.ModelSerializer):
|
||||||
|
text_translated = serializers.CharField(read_only=True)
|
||||||
|
status_display = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Review
|
model = Review
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'reviewer',
|
'reviewer',
|
||||||
'text',
|
'text',
|
||||||
|
'text_translated',
|
||||||
'priority',
|
'priority',
|
||||||
'status',
|
'status',
|
||||||
|
'status_display',
|
||||||
'child',
|
'child',
|
||||||
'published_at',
|
'published_at',
|
||||||
'vintage',
|
'vintage',
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class TagCategoryFilterSet(TagsBaseFilterSet):
|
||||||
if value == EstablishmentType.ARTISAN:
|
if value == EstablishmentType.ARTISAN:
|
||||||
qs = models.TagCategory.objects.with_base_related().filter(index_name='shop_category')
|
qs = models.TagCategory.objects.with_base_related().filter(index_name='shop_category')
|
||||||
else:
|
else:
|
||||||
qs = queryset.by_establishment_type(value)
|
qs = queryset.by_establishment_type(value).exclude(index_name__in=['guide', 'collection'])
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
"""Serializer for app timetable"""
|
"""Serializer for app timetable"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
@ -23,8 +21,6 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
dinner_end = serializers.TimeField(required=False)
|
dinner_end = serializers.TimeField(required=False)
|
||||||
opening_at = serializers.TimeField(required=False)
|
opening_at = serializers.TimeField(required=False)
|
||||||
closed_at = serializers.TimeField(required=False)
|
closed_at = serializers.TimeField(required=False)
|
||||||
# For permission!!
|
|
||||||
establishment_id = serializers.ReadOnlyField(source='establishment.id')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -40,7 +36,6 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
'dinner_end',
|
'dinner_end',
|
||||||
'opening_at',
|
'opening_at',
|
||||||
'closed_at',
|
'closed_at',
|
||||||
'establishment_id'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ class UserNotFoundError(AuthErrorMixin, ProjectBaseException):
|
||||||
default_detail = _('User not found')
|
default_detail = _('User not found')
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeNotFoundError(ProjectBaseException):
|
||||||
|
"""The exception should be thrown when the employee cannot get"""
|
||||||
|
default_detail = _('Employee not found')
|
||||||
|
|
||||||
|
|
||||||
class EmailSendingError(exceptions.APIException):
|
class EmailSendingError(exceptions.APIException):
|
||||||
"""The exception should be thrown when unable to send an email"""
|
"""The exception should be thrown when unable to send an email"""
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from os.path import exists
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
|
@ -517,6 +518,7 @@ def default_menu_bool_array():
|
||||||
|
|
||||||
class PhoneModelMixin:
|
class PhoneModelMixin:
|
||||||
"""Mixin for PhoneNumberField."""
|
"""Mixin for PhoneNumberField."""
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def country_calling_code(self):
|
def country_calling_code(self):
|
||||||
"""Return phone code from PhonеNumberField."""
|
"""Return phone code from PhonеNumberField."""
|
||||||
|
|
@ -537,3 +539,32 @@ class AwardsModelMixin:
|
||||||
|
|
||||||
if hasattr(self, 'awards'):
|
if hasattr(self, 'awards'):
|
||||||
self.awards.remove(award)
|
self.awards.remove(award)
|
||||||
|
|
||||||
|
|
||||||
|
class CarouselMixin:
|
||||||
|
@property
|
||||||
|
def must_of_the_week(self) -> bool:
|
||||||
|
"""Detects whether current item in carousel"""
|
||||||
|
from main.models import Carousel
|
||||||
|
|
||||||
|
if hasattr(self, 'pk') and (hasattr(self, 'country') or hasattr(self, 'country_id')):
|
||||||
|
kwargs = {
|
||||||
|
'content_type': ContentType.objects.get_for_model(self),
|
||||||
|
'object_id': self.pk,
|
||||||
|
'country': getattr(self, 'country', getattr(self, 'country_id', None)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Carousel.objects.filter(**kwargs).exists()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateByMixin(models.Model):
|
||||||
|
"""Modify by mixin"""
|
||||||
|
last_update_by_manager = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
last_update_by_gm = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
abstract = True
|
||||||
|
|
|
||||||
49
apps/utils/parsers.py
Normal file
49
apps/utils/parsers.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
|
||||||
|
class NewsSlug:
|
||||||
|
def __init__(self, value=None, country_code=None, count=0):
|
||||||
|
self.value = value
|
||||||
|
self.country_code = country_code
|
||||||
|
self.count = count
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, raw_slug, country_codes):
|
||||||
|
slug, *rest = raw_slug.split('-')
|
||||||
|
instance = NewsSlug()
|
||||||
|
|
||||||
|
if len(rest) >= 1 and rest[-1] in country_codes:
|
||||||
|
# It is like 'slug-en'
|
||||||
|
|
||||||
|
instance.value = '-'.join([slug, *rest[:-1]])
|
||||||
|
instance.country_code = rest[-1]
|
||||||
|
elif len(rest) >= 2 and rest[-1].isdigit() and rest[-2] in country_codes:
|
||||||
|
# It is like 'slug-en-1'
|
||||||
|
|
||||||
|
instance.value = '-'.join([slug, *rest[:-2]])
|
||||||
|
instance.country_code = rest[-2]
|
||||||
|
instance.count = int(rest[-1])
|
||||||
|
else:
|
||||||
|
# It is like 'slug'
|
||||||
|
|
||||||
|
instance.value = '-'.join([slug, *rest])
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.value is None:
|
||||||
|
raise ValueError('No value for slug')
|
||||||
|
|
||||||
|
slug_parts = [self.value]
|
||||||
|
if self.country_code is not None:
|
||||||
|
slug_parts.append(self.country_code)
|
||||||
|
|
||||||
|
if self.count != 0:
|
||||||
|
slug_parts.append(str(self.count))
|
||||||
|
|
||||||
|
return '-'.join(slug_parts)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<{self.__class__.__name__} {self.value}, {self.country_code}, {self.count}>'
|
||||||
|
|
@ -108,6 +108,7 @@ class CarouselCreateSerializer(serializers.ModelSerializer):
|
||||||
model = Carousel
|
model = Carousel
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
|
'active',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
"""Development settings."""
|
"""Development settings."""
|
||||||
from .amazon_s3 import *
|
|
||||||
from .base import *
|
from .base import *
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0']
|
ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0']
|
||||||
|
|
@ -15,7 +14,6 @@ DEFAULT_SUBDOMAIN = 'www'
|
||||||
SITE_DOMAIN_URI = 'id-east.ru'
|
SITE_DOMAIN_URI = 'id-east.ru'
|
||||||
DOMAIN_URI = 'gm.id-east.ru'
|
DOMAIN_URI = 'gm.id-east.ru'
|
||||||
|
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
|
@ -26,7 +24,6 @@ CACHES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ELASTICSEARCH SETTINGS
|
# ELASTICSEARCH SETTINGS
|
||||||
ELASTICSEARCH_DSL = {
|
ELASTICSEARCH_DSL = {
|
||||||
'default': {
|
'default': {
|
||||||
|
|
@ -35,7 +32,6 @@ ELASTICSEARCH_DSL = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ELASTICSEARCH_INDEX_NAMES = {
|
ELASTICSEARCH_INDEX_NAMES = {
|
||||||
'search_indexes.documents.news': 'development_news',
|
'search_indexes.documents.news': 'development_news',
|
||||||
'search_indexes.documents.establishment': 'development_establishment',
|
'search_indexes.documents.establishment': 'development_establishment',
|
||||||
|
|
@ -45,7 +41,6 @@ ELASTICSEARCH_INDEX_NAMES = {
|
||||||
|
|
||||||
# ELASTICSEARCH_DSL_AUTOSYNC = False
|
# ELASTICSEARCH_DSL_AUTOSYNC = False
|
||||||
|
|
||||||
|
|
||||||
# DATABASE
|
# DATABASE
|
||||||
DATABASES.update({
|
DATABASES.update({
|
||||||
'legacy': {
|
'legacy': {
|
||||||
|
|
@ -75,7 +70,6 @@ EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com'
|
||||||
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
|
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE.append('utils.middleware.log_db_queries_per_API_request')
|
MIDDLEWARE.append('utils.middleware.log_db_queries_per_API_request')
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
|
@ -107,3 +101,7 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EMAIL_TECHNICAL_SUPPORT = 'n.malinova@octopod.ru'
|
||||||
|
|
||||||
|
from .amazon_s3 import *
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user