Merge branch 'develop' into feature/roles
# Conflicts: # apps/account/models.py # apps/collection/views/back.py # apps/comment/views/back.py # apps/establishment/models.py # apps/establishment/views/back.py # apps/news/views.py # apps/partner/views/back.py
This commit is contained in:
commit
25c63b8097
|
|
@ -15,6 +15,8 @@ from django.utils.http import urlsafe_base64_encode
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
from collections import Counter
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from authorization.models import Application
|
from authorization.models import Application
|
||||||
from establishment.models import Establishment, EstablishmentSubType
|
from establishment.models import Establishment, EstablishmentSubType
|
||||||
|
|
@ -475,6 +477,15 @@ class User(AbstractUser):
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_roles(self, ids: List[int]):
|
||||||
|
"""
|
||||||
|
Set user roles
|
||||||
|
:param ids: list of role ids
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
self.roles.set(Role.objects.filter(id__in=ids))
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class UserRoleQueryset(models.QuerySet):
|
class UserRoleQueryset(models.QuerySet):
|
||||||
"""QuerySet for model UserRole."""
|
"""QuerySet for model UserRole."""
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class _SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
class BackUserSerializer(UserSerializer):
|
class BackUserSerializer(UserSerializer):
|
||||||
|
|
||||||
last_country = _SiteSettingsSerializer(read_only=True)
|
last_country = _SiteSettingsSerializer(read_only=True)
|
||||||
roles = RoleBaseSerializer(many=True, read_only=True)
|
roles = RoleBaseSerializer(many=True)
|
||||||
|
|
||||||
class Meta(UserSerializer.Meta):
|
class Meta(UserSerializer.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
|
|
@ -115,6 +115,7 @@ class BackDetailUserSerializer(BackUserSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
subscriptions_list = []
|
subscriptions_list = []
|
||||||
|
|
||||||
if 'subscription_types' in validated_data:
|
if 'subscription_types' in validated_data:
|
||||||
subscriptions_list = validated_data.pop('subscription_types')
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
|
|
@ -127,11 +128,17 @@ class BackDetailUserSerializer(BackUserSerializer):
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
subscriptions_list = []
|
subscriptions_list = []
|
||||||
|
|
||||||
if 'subscription_types' in validated_data:
|
if 'subscription_types' in validated_data:
|
||||||
subscriptions_list = validated_data.pop('subscription_types')
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
|
if 'roles' in validated_data:
|
||||||
|
roles_ids = [role['id'] for role in validated_data.pop('roles') if 'id' in role]
|
||||||
|
instance.set_roles(roles_ids)
|
||||||
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
subscriptions_handler(subscriptions_list, instance)
|
subscriptions_handler(subscriptions_list, instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from rest_framework import serializers
|
||||||
from rest_framework import validators as rest_validators
|
from rest_framework import validators as rest_validators
|
||||||
|
|
||||||
from account import models, tasks
|
from account import models, tasks
|
||||||
|
from account.models import User, Role
|
||||||
from main.serializers.common import NavigationBarPermissionBaseSerializer
|
from main.serializers.common import NavigationBarPermissionBaseSerializer
|
||||||
from notification.models import Subscribe, Subscriber
|
from notification.models import Subscribe, Subscriber
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
|
|
@ -27,7 +28,7 @@ def subscriptions_handler(subscriptions_list, user):
|
||||||
'user': user,
|
'user': user,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'ip_address': user.last_ip,
|
'ip_address': user.last_ip,
|
||||||
'country_code': user.last_country.country.code if user.last_country else None,
|
'country_code': user.last_country.country.code if user.last_country and user.last_country.country else None,
|
||||||
'locale': user.locale,
|
'locale': user.locale,
|
||||||
'update_code': generate_string_code(),
|
'update_code': generate_string_code(),
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +43,7 @@ def subscriptions_handler(subscriptions_list, user):
|
||||||
|
|
||||||
class RoleBaseSerializer(serializers.ModelSerializer):
|
class RoleBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model Role."""
|
"""Serializer for model Role."""
|
||||||
|
id = serializers.IntegerField()
|
||||||
role_display = serializers.CharField(source='get_role_display', read_only=True)
|
role_display = serializers.CharField(source='get_role_display', read_only=True)
|
||||||
navigation_bar_permission = NavigationBarPermissionBaseSerializer(read_only=True)
|
navigation_bar_permission = NavigationBarPermissionBaseSerializer(read_only=True)
|
||||||
country_code = serializers.CharField(source='country.code', read_only=True, allow_null=True)
|
country_code = serializers.CharField(source='country.code', read_only=True, allow_null=True)
|
||||||
|
|
@ -87,6 +89,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = [
|
fields = [
|
||||||
|
'id',
|
||||||
'username',
|
'username',
|
||||||
'first_name',
|
'first_name',
|
||||||
'last_name',
|
'last_name',
|
||||||
|
|
@ -122,12 +125,6 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
subscriptions_handler(subscriptions_list, user)
|
subscriptions_handler(subscriptions_list, user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def validate_email(self, value):
|
|
||||||
"""Validate email value"""
|
|
||||||
if hasattr(self.instance, 'email') and self.instance.email and value == self.instance.email:
|
|
||||||
raise serializers.ValidationError(detail='Equal email address.')
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate_username(self, value):
|
def validate_username(self, value):
|
||||||
"""Custom username validation"""
|
"""Custom username validation"""
|
||||||
valid = utils_methods.username_validator(username=value)
|
valid = utils_methods.username_validator(username=value)
|
||||||
|
|
@ -138,29 +135,39 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
"""Override update method"""
|
"""Override update method"""
|
||||||
subscriptions_list = []
|
subscriptions_list = []
|
||||||
|
|
||||||
if 'subscription_types' in validated_data:
|
if 'subscription_types' in validated_data:
|
||||||
subscriptions_list = validated_data.pop('subscription_types')
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
|
new_email = validated_data.get('email')
|
||||||
old_email = instance.email
|
old_email = instance.email
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
if 'email' in validated_data:
|
request = self.context['request']
|
||||||
instance.email_confirmed = False
|
user = request.user
|
||||||
instance.email = old_email
|
if not user.is_superuser and not user.is_staff and \
|
||||||
instance.unconfirmed_email = validated_data['email']
|
not user.roles.filter(country__code=request.country_code, role=models.Role.COUNTRY_ADMIN).exists():
|
||||||
instance.save()
|
"""
|
||||||
# Send verification link on user email for change email address
|
superuser or country admin changes email immediately!
|
||||||
if settings.USE_CELERY:
|
"""
|
||||||
tasks.change_email_address.delay(
|
if new_email and new_email != old_email:
|
||||||
user_id=instance.id,
|
instance.email_confirmed = False
|
||||||
country_code=self.context.get('request').country_code,
|
instance.email = old_email
|
||||||
emails=[validated_data['email'], ])
|
instance.unconfirmed_email = new_email
|
||||||
else:
|
instance.save()
|
||||||
tasks.change_email_address(
|
# Send verification link on user email for change email address
|
||||||
user_id=instance.id,
|
if settings.USE_CELERY:
|
||||||
country_code=self.context.get('request').country_code,
|
tasks.change_email_address.delay(
|
||||||
emails=[validated_data['email'], ])
|
user_id=instance.id,
|
||||||
|
country_code=self.context.get('request').country_code,
|
||||||
|
emails=[validated_data['email'], ])
|
||||||
|
else:
|
||||||
|
tasks.change_email_address(
|
||||||
|
user_id=instance.id,
|
||||||
|
country_code=self.context.get('request').country_code,
|
||||||
|
emails=[validated_data['email'], ])
|
||||||
|
|
||||||
subscriptions_handler(subscriptions_list, instance)
|
subscriptions_handler(subscriptions_list, instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -195,6 +202,7 @@ class UserShortSerializer(UserSerializer):
|
||||||
'id',
|
'id',
|
||||||
'fullname',
|
'fullname',
|
||||||
'email',
|
'email',
|
||||||
|
'username',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from utils.views import JWTGenericViewMixin
|
||||||
|
|
||||||
|
|
||||||
# User views
|
# User views
|
||||||
class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
|
class UserRetrieveUpdateView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""User update view."""
|
"""User update view."""
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
queryset = models.User.objects.active()
|
queryset = models.User.objects.active()
|
||||||
|
|
@ -25,6 +25,10 @@ class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
"""Overridden behavior of DELETE method."""
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordView(generics.GenericAPIView):
|
class ChangePasswordView(generics.GenericAPIView):
|
||||||
"""Change password view"""
|
"""Change password view"""
|
||||||
|
|
|
||||||
56
apps/collection/filters.py
Normal file
56
apps/collection/filters.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
"""Collection app filters."""
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
|
||||||
|
from collection import models
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionFilterSet(filters.FilterSet):
|
||||||
|
"""Collection filter set."""
|
||||||
|
establishment_id = filters.NumberFilter(
|
||||||
|
field_name='establishments__id',
|
||||||
|
help_text='Establishment id. Allows to filter list of collections by choosen estblishment. '
|
||||||
|
'Use for Establishment detail\'s sheet to content display within '
|
||||||
|
'"Collections & Guides" tab.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# "ordering" instead of "o" is for backward compatibility
|
||||||
|
ordering = filters.OrderingFilter(
|
||||||
|
# tuple-mapping retains order
|
||||||
|
fields=(
|
||||||
|
('rank', 'rank'),
|
||||||
|
('start', 'start'),
|
||||||
|
),
|
||||||
|
help_text='Ordering by fields - rank, start',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Collection
|
||||||
|
fields = (
|
||||||
|
'ordering',
|
||||||
|
'establishment_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GuideFilterSet(filters.FilterSet):
|
||||||
|
"""Guide filter set."""
|
||||||
|
establishment_id = filters.NumberFilter(
|
||||||
|
method='by_establishment_id',
|
||||||
|
help_text='Establishment id. Allows to filter list of guides by choosen establishment. '
|
||||||
|
'Use for Establishment detail\'s sheet to content display within '
|
||||||
|
'"Collections & Guides" tab.'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Guide
|
||||||
|
fields = (
|
||||||
|
'establishment_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
def by_establishment_id(self, queryset, name, value):
|
||||||
|
"""Filter by establishment id."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_establishment_id(value)
|
||||||
|
return queryset
|
||||||
|
|
@ -57,6 +57,10 @@ class CollectionQuerySet(RelatedObjectsCountMixin):
|
||||||
"""Returned only published collection"""
|
"""Returned only published collection"""
|
||||||
return self.filter(is_publish=True)
|
return self.filter(is_publish=True)
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
"""Select relate objects"""
|
||||||
|
return self.select_related('country')
|
||||||
|
|
||||||
|
|
||||||
class Collection(ProjectBaseMixin, CollectionDateMixin,
|
class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
TranslatedFieldsMixin, URLImageMixin):
|
TranslatedFieldsMixin, URLImageMixin):
|
||||||
|
|
@ -106,7 +110,7 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
"""Return list of related objects."""
|
"""Return list of related objects."""
|
||||||
related_objects = []
|
related_objects = []
|
||||||
# get related objects
|
# get related objects
|
||||||
for related_object in self._meta.related_objects:
|
for related_object in self._meta.related_objects.with_base_related():
|
||||||
related_objects.append(related_object)
|
related_objects.append(related_object)
|
||||||
return related_objects
|
return related_objects
|
||||||
|
|
||||||
|
|
@ -167,17 +171,26 @@ class GuideQuerySet(models.QuerySet):
|
||||||
"""Return QuerySet with related."""
|
"""Return QuerySet with related."""
|
||||||
return self.select_related('site', )
|
return self.select_related('site', )
|
||||||
|
|
||||||
|
def with_extended_related(self):
|
||||||
|
"""Return QuerySet with extended related."""
|
||||||
|
return self.with_base_related().prefetch_related('guideelement_set')
|
||||||
|
|
||||||
def by_country_id(self, country_id):
|
def by_country_id(self, country_id):
|
||||||
"""Return QuerySet filtered by country code."""
|
"""Return QuerySet filtered by country code."""
|
||||||
return self.filter(country_json__id__contains=country_id)
|
return self.filter(country_json__id__contains=country_id)
|
||||||
|
|
||||||
def annotate_in_restaurant_section(self):
|
def annotate_in_restaurant_section(self):
|
||||||
"""Annotate flag if GuideElement in RestaurantSectionNode."""
|
"""Annotate flag if GuideElement in RestaurantSectionNode."""
|
||||||
|
restaurant_guides = models.Subquery(
|
||||||
|
self.filter(
|
||||||
|
guideelement__guide_element_type__name='EstablishmentNode',
|
||||||
|
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
|
||||||
|
).values_list('id', flat=True).distinct()
|
||||||
|
)
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
in_restaurant_section=models.Case(
|
in_restaurant_section=models.Case(
|
||||||
models.When(
|
models.When(
|
||||||
guideelement__guide_element_type__name='EstablishmentNode',
|
id__in=restaurant_guides,
|
||||||
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
|
|
||||||
then=True),
|
then=True),
|
||||||
default=False,
|
default=False,
|
||||||
output_field=models.BooleanField(default=False)
|
output_field=models.BooleanField(default=False)
|
||||||
|
|
@ -186,11 +199,16 @@ class GuideQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def annotate_in_shop_section(self):
|
def annotate_in_shop_section(self):
|
||||||
"""Annotate flag if GuideElement in ShopSectionNode."""
|
"""Annotate flag if GuideElement in ShopSectionNode."""
|
||||||
|
shop_guides = models.Subquery(
|
||||||
|
self.filter(
|
||||||
|
guideelement__guide_element_type__name='EstablishmentNode',
|
||||||
|
guideelement__parent__guide_element_type__name='ShopSectionNode',
|
||||||
|
).values_list('guideelement__id', flat=True).distinct()
|
||||||
|
)
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
in_shop_section=models.Case(
|
in_shop_section=models.Case(
|
||||||
models.When(
|
models.When(
|
||||||
guideelement__guide_element_type__name='EstablishmentNode',
|
id__in=shop_guides,
|
||||||
guideelement__parent__guide_element_type__name='ShopSectionNode',
|
|
||||||
then=True),
|
then=True),
|
||||||
default=False,
|
default=False,
|
||||||
output_field=models.BooleanField(default=False)
|
output_field=models.BooleanField(default=False)
|
||||||
|
|
@ -201,37 +219,60 @@ class GuideQuerySet(models.QuerySet):
|
||||||
"""Return QuerySet with annotated field - restaurant_counter."""
|
"""Return QuerySet with annotated field - restaurant_counter."""
|
||||||
return self.annotate_in_restaurant_section().annotate(
|
return self.annotate_in_restaurant_section().annotate(
|
||||||
restaurant_counter=models.Count(
|
restaurant_counter=models.Count(
|
||||||
'guideelement',
|
'guideelement__establishment',
|
||||||
filter=models.Q(in_restaurant_section=True) &
|
filter=models.Q(in_restaurant_section=True) &
|
||||||
models.Q(guideelement__parent_id__isnull=False),
|
models.Q(guideelement__parent_id__isnull=True),
|
||||||
distinct=True))
|
distinct=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def annotate_shop_counter(self):
|
def annotate_shop_counter(self):
|
||||||
"""Return QuerySet with annotated field - shop_counter."""
|
"""Return QuerySet with annotated field - shop_counter."""
|
||||||
return self.annotate_in_shop_section().annotate(
|
return self.annotate_in_shop_section().annotate(
|
||||||
shop_counter=models.Count(
|
shop_counter=models.Count(
|
||||||
'guideelement',
|
'guideelement__establishment',
|
||||||
filter=models.Q(in_shop_section=True) &
|
filter=models.Q(in_shop_section=True) &
|
||||||
models.Q(guideelement__parent_id__isnull=False),
|
models.Q(guideelement__parent_id__isnull=True),
|
||||||
distinct=True))
|
distinct=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def annotate_wine_counter(self):
|
def annotate_wine_counter(self):
|
||||||
"""Return QuerySet with annotated field - shop_counter."""
|
"""Return QuerySet with annotated field - shop_counter."""
|
||||||
return self.annotate_in_restaurant_section().annotate(
|
return self.annotate_in_restaurant_section().annotate(
|
||||||
wine_counter=models.Count(
|
wine_counter=models.Count(
|
||||||
'guideelement',
|
'guideelement__product',
|
||||||
filter=models.Q(guideelement__guide_element_type__name='WineNode') &
|
filter=models.Q(guideelement__guide_element_type__name='WineNode') &
|
||||||
models.Q(guideelement__parent_id__isnull=False),
|
models.Q(guideelement__parent_id__isnull=False),
|
||||||
distinct=True))
|
distinct=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def annotate_present_objects_counter(self):
|
def annotate_present_objects_counter(self):
|
||||||
"""Return QuerySet with annotated field - present_objects_counter."""
|
"""Return QuerySet with annotated field - present_objects_counter."""
|
||||||
return self.annotate_in_restaurant_section().annotate(
|
return (
|
||||||
present_objects_counter=models.Count(
|
self.annotate_restaurant_counter()
|
||||||
'guideelement',
|
.annotate_shop_counter()
|
||||||
filter=models.Q(guideelement__guide_element_type__name__in=['EstablishmentNode', 'WineNode']) &
|
.annotate_wine_counter()
|
||||||
models.Q(guideelement__parent_id__isnull=False),
|
.annotate(
|
||||||
distinct=True))
|
present_objects_counter=(
|
||||||
|
models.F('restaurant_counter') +
|
||||||
|
models.F('shop_counter') +
|
||||||
|
models.F('wine_counter')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def annotate_counters(self):
|
||||||
|
return (
|
||||||
|
self.annotate_restaurant_counter()
|
||||||
|
.annotate_shop_counter()
|
||||||
|
.annotate_wine_counter()
|
||||||
|
.annotate_present_objects_counter()
|
||||||
|
)
|
||||||
|
|
||||||
|
def by_establishment_id(self, establishment_id: int):
|
||||||
|
return self.filter(guideelement__establishment=establishment_id).distinct()
|
||||||
|
|
||||||
|
|
||||||
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
|
||||||
restaurant_counter = serializers.IntegerField(read_only=True)
|
restaurant_counter = serializers.IntegerField(read_only=True)
|
||||||
shop_counter = serializers.IntegerField(read_only=True)
|
shop_counter = serializers.IntegerField(read_only=True)
|
||||||
wine_counter = serializers.IntegerField(read_only=True)
|
wine_counter = serializers.IntegerField(read_only=True)
|
||||||
|
present_objects_counter = serializers.IntegerField(read_only=True)
|
||||||
count_objects_during_init = serializers.IntegerField(read_only=True,
|
count_objects_during_init = serializers.IntegerField(read_only=True,
|
||||||
source='count_related_objects')
|
source='count_related_objects')
|
||||||
|
|
||||||
|
|
@ -131,6 +132,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
|
||||||
'restaurant_counter',
|
'restaurant_counter',
|
||||||
'shop_counter',
|
'shop_counter',
|
||||||
'wine_counter',
|
'wine_counter',
|
||||||
|
'present_objects_counter',
|
||||||
'count_objects_during_init',
|
'count_objects_during_init',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework import mixins, permissions, viewsets
|
from rest_framework import mixins, permissions, viewsets
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.filters import OrderingFilter
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from collection import models, serializers
|
from collection import models, serializers, filters, tasks
|
||||||
from collection import tasks
|
|
||||||
from utils.methods import get_permission_classes
|
from utils.methods import get_permission_classes
|
||||||
from utils.views import BindObjectMixin
|
from utils.views import BindObjectMixin
|
||||||
|
|
||||||
|
|
@ -22,7 +19,7 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
qs = models.Collection.objects.all().order_by('-created')
|
qs = models.Collection.objects.all().order_by('-created').with_base_related()
|
||||||
if self.request.country_code:
|
if self.request.country_code:
|
||||||
qs = qs.by_country_code(self.request.country_code)
|
qs = qs.by_country_code(self.request.country_code)
|
||||||
return qs
|
return qs
|
||||||
|
|
@ -35,12 +32,7 @@ class GuideBaseView(generics.GenericAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden get_queryset method."""
|
"""Overridden get_queryset method."""
|
||||||
return models.Guide.objects.with_base_related() \
|
return models.Guide.objects.with_extended_related().annotate_counters()
|
||||||
.annotate_restaurant_counter() \
|
|
||||||
.annotate_shop_counter() \
|
|
||||||
.annotate_wine_counter() \
|
|
||||||
.annotate_present_objects_counter() \
|
|
||||||
.distinct()
|
|
||||||
|
|
||||||
|
|
||||||
class GuideFilterBaseView(generics.GenericAPIView):
|
class GuideFilterBaseView(generics.GenericAPIView):
|
||||||
|
|
@ -73,17 +65,14 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
BindObjectMixin,
|
BindObjectMixin,
|
||||||
CollectionViewSet):
|
CollectionViewSet):
|
||||||
"""ViewSet for Collection model for BackOffice users."""
|
"""ViewSet for Collections list for BackOffice users and Collection create."""
|
||||||
|
|
||||||
queryset = models.Collection.objects.all()
|
queryset = models.Collection.objects.with_base_related().order_by('-start')
|
||||||
filter_backends = [DjangoFilterBackend, OrderingFilter]
|
filter_class = filters.CollectionFilterSet
|
||||||
serializer_class = serializers.CollectionBackOfficeSerializer
|
serializer_class = serializers.CollectionBackOfficeSerializer
|
||||||
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
|
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
|
||||||
permission_classes = get_permission_classes()
|
permission_classes = get_permission_classes()
|
||||||
|
|
||||||
ordering_fields = ('rank', 'start')
|
|
||||||
ordering = ('-start', )
|
|
||||||
|
|
||||||
def perform_binding(self, serializer):
|
def perform_binding(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
collection = data.pop('collection')
|
collection = data.pop('collection')
|
||||||
|
|
@ -107,7 +96,8 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
|
|
||||||
class GuideListCreateView(GuideBaseView,
|
class GuideListCreateView(GuideBaseView,
|
||||||
generics.ListCreateAPIView):
|
generics.ListCreateAPIView):
|
||||||
"""View for Guide model for BackOffice users."""
|
"""View for Guides list for BackOffice users and Guide create."""
|
||||||
|
filter_class = filters.GuideFilterSet
|
||||||
|
|
||||||
|
|
||||||
class GuideFilterCreateView(GuideFilterBaseView,
|
class GuideFilterCreateView(GuideFilterBaseView,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ class CommentQuerySet(ContentTypeQuerySetMixin):
|
||||||
qs = self.filter(id__in=tuple(waiting_ids))
|
qs = self.filter(id__in=tuple(waiting_ids))
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
return self.prefetch_related("content_object").select_related("user", "content_type")
|
||||||
|
|
||||||
|
|
||||||
class Comment(ProjectBaseMixin):
|
class Comment(ProjectBaseMixin):
|
||||||
"""Comment model."""
|
"""Comment model."""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
"""Common serializers for app comment."""
|
"""Common serializers for app comment."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
import establishment.serializers.common as establishment_serializers
|
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from establishment.models import EstablishmentType
|
|
||||||
|
|
||||||
|
|
||||||
class CommentBaseSerializer(serializers.ModelSerializer):
|
class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -22,6 +20,8 @@ class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
user_email = serializers.CharField(read_only=True, source='user.email')
|
user_email = serializers.CharField(read_only=True, source='user.email')
|
||||||
|
|
||||||
|
slug = serializers.CharField(read_only=True, source='content_object.slug')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Serializer for model Comment"""
|
"""Serializer for model Comment"""
|
||||||
model = Comment
|
model = Comment
|
||||||
|
|
@ -39,18 +39,29 @@ class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
'status_display',
|
'status_display',
|
||||||
'last_ip',
|
'last_ip',
|
||||||
'content_type',
|
'content_type',
|
||||||
'content_name'
|
'content_name',
|
||||||
|
'slug',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
# 'status': {'read_only': True},
|
# 'status': {'read_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_content_type(self, instance: Comment):
|
def get_content_type(self, instance: Comment):
|
||||||
if instance.content_object.establishment_type == EstablishmentType.PRODUCER:
|
import establishment.serializers.common as establishment_serializers
|
||||||
return establishment_serializers.EstablishmentSubTypeBaseSerializer(
|
from establishment.models import EstablishmentType, Establishment
|
||||||
instance.content_object.establishment_subtypes, many=True
|
from product.models import Product
|
||||||
).data
|
from product.serializers import ProductTypeBaseSerializer
|
||||||
|
|
||||||
return establishment_serializers.EstablishmentTypeBaseSerializer(
|
if isinstance(instance.content_object, Establishment):
|
||||||
instance.content_object.establishment_type
|
if instance.content_object.establishment_type == EstablishmentType.PRODUCER:
|
||||||
).data
|
return establishment_serializers.EstablishmentSubTypeBaseSerializer(
|
||||||
|
instance.content_object.establishment_subtypes, many=True
|
||||||
|
).data
|
||||||
|
|
||||||
|
return establishment_serializers.EstablishmentTypeBaseSerializer(
|
||||||
|
instance.content_object.establishment_type
|
||||||
|
).data
|
||||||
|
if isinstance(instance.content_object, Product):
|
||||||
|
return ProductTypeBaseSerializer(
|
||||||
|
instance.content_object.product_type
|
||||||
|
).data
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,6 @@ app_name = 'comment'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.CommentLstView.as_view(), name='comment-list-create'),
|
path('', views.CommentLstView.as_view(), name='comment-list-create'),
|
||||||
path('<int:id>/', views.CommentRUDView.as_view(), name='comment-crud'),
|
path('<int:id>/', views.CommentRUDView.as_view(), name='comment-crud'),
|
||||||
|
path('<str:type>/', views.CommentLstView.as_view(), name='comment-list-by-type-create'),
|
||||||
|
path('<str:type>/<int:object>', views.CommentLstView.as_view(), name='comment-list-by-type-object-create'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,26 @@ from utils.permissions import IsModerator
|
||||||
|
|
||||||
class CommentLstView(generics.ListCreateAPIView):
|
class CommentLstView(generics.ListCreateAPIView):
|
||||||
"""Comment list create view."""
|
"""Comment list create view."""
|
||||||
|
def get_queryset(self):
|
||||||
|
from product.models import Product
|
||||||
|
from establishment.models import Establishment
|
||||||
|
|
||||||
|
allowed = {
|
||||||
|
"product": Product.__name__.lower(),
|
||||||
|
"establishment": Establishment.__name__.lower()
|
||||||
|
}
|
||||||
|
|
||||||
|
qs = models.Comment.objects.with_base_related()
|
||||||
|
|
||||||
|
if "object" in self.kwargs:
|
||||||
|
qs = qs.by_object_id(self.kwargs["object"])
|
||||||
|
|
||||||
|
if "type" in self.kwargs and self.kwargs["type"] in allowed:
|
||||||
|
model = allowed[self.kwargs["type"]]
|
||||||
|
qs = qs.by_content_type(self.kwargs["type"], model)
|
||||||
|
|
||||||
|
return qs.order_by('-created')
|
||||||
|
|
||||||
serializer_class = CommentBaseSerializer
|
serializer_class = CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
permission_classes = get_permission_classes(IsModerator)
|
permission_classes = get_permission_classes(IsModerator)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
"""Establishment app filters."""
|
"""Establishment app filters."""
|
||||||
from django.core.validators import EMPTY_VALUES
|
from django.core.validators import EMPTY_VALUES
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django_filters import rest_framework as filters, Filter
|
from django_filters import rest_framework as filters
|
||||||
from django_filters.fields import Lookup
|
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
|
|
|
||||||
40
apps/establishment/migrations/0080_auto_20200128_0904.py
Normal file
40
apps/establishment/migrations/0080_auto_20200128_0904.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 09:04
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0008_merge_20191212_0752'),
|
||||||
|
('establishment', '0079_auto_20200124_0720'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='file',
|
||||||
|
field=models.FileField(upload_to='', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=('doc', 'docx', 'pdf'))], verbose_name='File'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='menu',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='menu_uploads', to='establishment.Menu', verbose_name='menu'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MenuGallery',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_main', models.BooleanField(default=False, verbose_name='Is the main image')),
|
||||||
|
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='menu_gallery', to='gallery.Image', verbose_name='image')),
|
||||||
|
('menu', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='menu_gallery', to='establishment.Menu', verbose_name='menu')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'menu gallery',
|
||||||
|
'verbose_name_plural': 'menu galleries',
|
||||||
|
'unique_together': {('menu', 'image'), ('menu', 'is_main')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0081_menuuploads_title.py
Normal file
18
apps/establishment/migrations/0081_menuuploads_title.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 09:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0080_auto_20200128_0904'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(default='', max_length=255, verbose_name='title'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 12:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0081_menuuploads_title'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='western_name',
|
||||||
|
field=models.CharField(default='', max_length=255, verbose_name='Western name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 12:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0082_establishment_western_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='instagram',
|
||||||
|
field=models.URLField(blank=True, default=None, max_length=255, null=True, verbose_name='Instagram URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
69
apps/establishment/migrations/0084_auto_20200129_1113.py
Normal file
69
apps/establishment/migrations/0084_auto_20200129_1113.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-29 11:13
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import utils.methods
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('establishment', '0083_establishment_instagram'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MenuFiles',
|
||||||
|
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')),
|
||||||
|
('file', models.FileField(blank=True, default=None, null=True, upload_to=utils.methods.file_path, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=('jpg', 'jpeg', 'png', 'doc', 'docx', 'pdf'))], verbose_name='File')),
|
||||||
|
('name', models.CharField(default='', max_length=255, verbose_name='name')),
|
||||||
|
('type', models.CharField(default='', max_length=65, verbose_name='type')),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menufiles_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
|
||||||
|
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menufiles_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'menu upload',
|
||||||
|
'verbose_name_plural': 'menu uploads',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='created_by',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='menu',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='menuuploads',
|
||||||
|
name='modified_by',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='menu',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default='', max_length=255, verbose_name='name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='menu',
|
||||||
|
name='schedule',
|
||||||
|
field=models.ManyToManyField(blank=True, to='timetable.Timetable', verbose_name='Menu schedule'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='MenuGallery',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='MenuUploads',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='menu',
|
||||||
|
name='uploads',
|
||||||
|
field=models.ManyToManyField(blank=True, to='establishment.MenuFiles', verbose_name='Menu files'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/establishment/migrations/0085_menu_price.py
Normal file
18
apps/establishment/migrations/0085_menu_price.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-29 11:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0084_auto_20200129_1113'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='menu',
|
||||||
|
name='price',
|
||||||
|
field=models.IntegerField(blank=True, default=None, null=True, verbose_name='price'),
|
||||||
|
),
|
||||||
|
]
|
||||||
47
apps/establishment/migrations/0086_auto_20200129_1301.py
Normal file
47
apps/establishment/migrations/0086_auto_20200129_1301.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-29 13:01
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('establishment', '0085_menu_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='menufiles',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('image', 'Image'), ('file', 'File')], default='', max_length=65, verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MenuDish',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(default='', max_length=255, verbose_name='name')),
|
||||||
|
('category', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='category')),
|
||||||
|
('price', models.IntegerField(blank=True, default=None, null=True, verbose_name='price')),
|
||||||
|
('signature', models.BooleanField(default=False, verbose_name='signature')),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menudish_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
|
||||||
|
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menudish_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'dish',
|
||||||
|
'verbose_name_plural': 'dishes',
|
||||||
|
'ordering': ('-created',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='menu',
|
||||||
|
name='dishes',
|
||||||
|
field=models.ManyToManyField(blank=True, to='establishment.MenuDish', verbose_name='Menu dishes'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -33,7 +33,7 @@ from utils.methods import transform_into_readable_str
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes, GalleryMixin,
|
TranslatedFieldsMixin, BaseAttributes, GalleryMixin,
|
||||||
IntermediateGalleryModelMixin, HasTagsMixin,
|
IntermediateGalleryModelMixin, HasTagsMixin,
|
||||||
FavoritesMixin, TypeDefaultImageMixin)
|
FavoritesMixin, TypeDefaultImageMixin, FileMixin)
|
||||||
|
|
||||||
|
|
||||||
# todo: establishment type&subtypes check
|
# todo: establishment type&subtypes check
|
||||||
|
|
@ -541,6 +541,9 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
self.filter(id__in=administrator_establishment_ids)
|
self.filter(id__in=administrator_establishment_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def with_contacts(self):
|
||||||
|
return self.prefetch_related('emails', 'phones')
|
||||||
|
|
||||||
|
|
||||||
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
||||||
|
|
@ -552,6 +555,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
name = models.CharField(_('name'), max_length=255, default='')
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
transliterated_name = models.CharField(default='', max_length=255,
|
transliterated_name = models.CharField(default='', max_length=255,
|
||||||
verbose_name=_('Transliterated name'))
|
verbose_name=_('Transliterated name'))
|
||||||
|
western_name = models.CharField(default='', max_length=255,
|
||||||
|
verbose_name=_('Western name'))
|
||||||
index_name = models.CharField(_('Index name'), max_length=255, default='')
|
index_name = models.CharField(_('Index name'), max_length=255, default='')
|
||||||
description = TJSONField(blank=True, null=True, default=None,
|
description = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
|
|
@ -583,6 +588,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
verbose_name=_('Facebook URL'))
|
verbose_name=_('Facebook URL'))
|
||||||
twitter = models.URLField(blank=True, null=True, default=None, max_length=255,
|
twitter = models.URLField(blank=True, null=True, default=None, max_length=255,
|
||||||
verbose_name=_('Twitter URL'))
|
verbose_name=_('Twitter URL'))
|
||||||
|
instagram = models.URLField(blank=True, null=True, default=None, max_length=255,
|
||||||
|
verbose_name=_('Instagram URL'))
|
||||||
lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255,
|
lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255,
|
||||||
verbose_name=_('Lafourchette URL'))
|
verbose_name=_('Lafourchette URL'))
|
||||||
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
|
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
|
||||||
|
|
@ -754,7 +761,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
now_at_est_tz = datetime.now(tz=self.tz)
|
now_at_est_tz = datetime.now(tz=self.tz)
|
||||||
current_week = now_at_est_tz.weekday()
|
current_week = now_at_est_tz.weekday()
|
||||||
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
||||||
if schedule_for_today is None or schedule_for_today.opening_time is None or schedule_for_today.ending_time is None:
|
if schedule_for_today is None or schedule_for_today.opening_time is None or schedule_for_today.ending_time is \
|
||||||
|
None:
|
||||||
return False
|
return False
|
||||||
time_at_est_tz = now_at_est_tz.time()
|
time_at_est_tz = now_at_est_tz.time()
|
||||||
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
|
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
|
||||||
|
|
@ -766,8 +774,10 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tags_indexing(self):
|
def tags_indexing(self):
|
||||||
return [{'id': tag.metadata.id,
|
return [{
|
||||||
'label': tag.metadata.label} for tag in self.tags.all()]
|
'id': tag.metadata.id,
|
||||||
|
'label': tag.metadata.label
|
||||||
|
} for tag in self.tags.all()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_published_review(self):
|
def last_published_review(self):
|
||||||
|
|
@ -837,6 +847,11 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
if self.phones:
|
if self.phones:
|
||||||
return [phone.phone.as_e164 for phone in self.phones.all()]
|
return [phone.phone.as_e164 for phone in self.phones.all()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contact_emails(self):
|
||||||
|
if self.phones:
|
||||||
|
return [email.email for email in self.emails.all()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def establishment_subtype_labels(self):
|
def establishment_subtype_labels(self):
|
||||||
if self.establishment_subtypes:
|
if self.establishment_subtypes:
|
||||||
|
|
@ -1249,6 +1264,24 @@ class Plate(TranslatedFieldsMixin, models.Model):
|
||||||
verbose_name_plural = _('plates')
|
verbose_name_plural = _('plates')
|
||||||
|
|
||||||
|
|
||||||
|
class MenuDish(BaseAttributes):
|
||||||
|
"""Dish model."""
|
||||||
|
|
||||||
|
STR_FIELD_NAME = 'category'
|
||||||
|
|
||||||
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
|
category = TJSONField(
|
||||||
|
blank=True, null=True, default=None, verbose_name=_('category'),
|
||||||
|
help_text='{"en-GB":"some text"}')
|
||||||
|
price = models.IntegerField(blank=True, null=True, default=None, verbose_name=_('price'))
|
||||||
|
signature = models.BooleanField(_('signature'), default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('dish')
|
||||||
|
verbose_name_plural = _('dishes')
|
||||||
|
ordering = ('-created',)
|
||||||
|
|
||||||
|
|
||||||
class MenuQuerySet(models.QuerySet):
|
class MenuQuerySet(models.QuerySet):
|
||||||
def with_schedule_plates_establishment(self):
|
def with_schedule_plates_establishment(self):
|
||||||
return self.select_related(
|
return self.select_related(
|
||||||
|
|
@ -1258,6 +1291,11 @@ class MenuQuerySet(models.QuerySet):
|
||||||
'plates',
|
'plates',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def with_gallery(self):
|
||||||
|
return self.prefetch_related(
|
||||||
|
'menu_gallery'
|
||||||
|
)
|
||||||
|
|
||||||
def dishes(self):
|
def dishes(self):
|
||||||
return self.filter(
|
return self.filter(
|
||||||
Q(category__icontains='starter') |
|
Q(category__icontains='starter') |
|
||||||
|
|
@ -1269,25 +1307,38 @@ class MenuQuerySet(models.QuerySet):
|
||||||
"""Search by category."""
|
"""Search by category."""
|
||||||
return self.filter(category__icontains=value)
|
return self.filter(category__icontains=value)
|
||||||
|
|
||||||
|
def with_dishes(self):
|
||||||
|
return self.filter(~Q(dishes=None))
|
||||||
|
|
||||||
|
|
||||||
class Menu(TranslatedFieldsMixin, BaseAttributes):
|
class Menu(TranslatedFieldsMixin, BaseAttributes):
|
||||||
"""Menu model."""
|
"""Menu model."""
|
||||||
|
|
||||||
STR_FIELD_NAME = 'category'
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
|
|
||||||
category = TJSONField(
|
|
||||||
blank=True, null=True, default=None, verbose_name=_('category'),
|
|
||||||
help_text='{"en-GB":"some text"}')
|
|
||||||
establishment = models.ForeignKey(
|
establishment = models.ForeignKey(
|
||||||
'establishment.Establishment', verbose_name=_('establishment'),
|
'establishment.Establishment', verbose_name=_('establishment'),
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
is_drinks_included = models.BooleanField(_('is drinks included'), default=False)
|
is_drinks_included = models.BooleanField(_('is drinks included'), default=False)
|
||||||
|
price = models.IntegerField(blank=True, null=True, default=None, verbose_name=_('price'))
|
||||||
schedule = models.ManyToManyField(
|
schedule = models.ManyToManyField(
|
||||||
to='timetable.Timetable',
|
to='timetable.Timetable',
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('Establishment schedule'),
|
verbose_name=_('Menu schedule'),
|
||||||
related_name='menus',
|
|
||||||
)
|
)
|
||||||
|
uploads = models.ManyToManyField(
|
||||||
|
to='MenuFiles',
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Menu files'),
|
||||||
|
)
|
||||||
|
dishes = models.ManyToManyField(
|
||||||
|
to='MenuDish',
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Menu dishes')
|
||||||
|
)
|
||||||
|
category = TJSONField(
|
||||||
|
blank=True, null=True, default=None, verbose_name=_('category'),
|
||||||
|
help_text='{"en-GB":"some text"}')
|
||||||
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
|
|
||||||
objects = MenuQuerySet.as_manager()
|
objects = MenuQuerySet.as_manager()
|
||||||
|
|
@ -1298,15 +1349,16 @@ class Menu(TranslatedFieldsMixin, BaseAttributes):
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
||||||
|
|
||||||
class MenuUploads(BaseAttributes):
|
class MenuFiles(FileMixin, BaseAttributes):
|
||||||
"""Menu files"""
|
"""Menu files"""
|
||||||
|
TYPES = (
|
||||||
menu = models.ForeignKey(Menu, verbose_name=_('Menu'), on_delete=models.CASCADE, related_name='uploads')
|
('image', 'Image'),
|
||||||
file = models.FileField(
|
('file', 'File')
|
||||||
_('File'),
|
|
||||||
validators=[FileExtensionValidator(allowed_extensions=('jpg', 'jpeg', 'png', 'doc', 'docx', 'pdf')), ],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
name = models.CharField(_('name'), max_length=255, default='')
|
||||||
|
type = models.CharField(_('type'), choices=TYPES, max_length=65, default='')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('menu upload')
|
verbose_name = _('menu upload')
|
||||||
verbose_name_plural = _('menu uploads')
|
verbose_name_plural = _('menu uploads')
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
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
|
||||||
|
|
||||||
from account.serializers.common import UserShortSerializer
|
from account.serializers.common import UserShortSerializer
|
||||||
from establishment import models, serializers as model_serializers
|
from establishment import models, serializers as model_serializers
|
||||||
from establishment.models import ContactPhone, EstablishmentEmployee
|
from establishment.models import ContactEmail, ContactPhone, EstablishmentEmployee
|
||||||
|
from establishment.serializers import MenuDishSerializer
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from location.serializers import AddressDetailSerializer, TranslatedField
|
from location.serializers import AddressDetailSerializer, TranslatedField
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
from main.serializers import AwardSerializer
|
from main.serializers import AwardSerializer
|
||||||
from timetable.serialziers import ScheduleRUDSerializer
|
|
||||||
from utils.decorators import with_base_attributes
|
from utils.decorators import with_base_attributes
|
||||||
from utils.serializers import ImageBaseSerializer, TimeZoneChoiceField, ProjectModelSerializer
|
from utils.serializers import ImageBaseSerializer, ProjectModelSerializer, TimeZoneChoiceField
|
||||||
|
|
||||||
|
|
||||||
def phones_handler(phones_list, establishment):
|
def phones_handler(phones_list, establishment):
|
||||||
|
|
@ -27,6 +29,16 @@ def phones_handler(phones_list, establishment):
|
||||||
ContactPhone.objects.create(establishment=establishment, phone=new_phone)
|
ContactPhone.objects.create(establishment=establishment, phone=new_phone)
|
||||||
|
|
||||||
|
|
||||||
|
def emails_handler(emails_list, establishment):
|
||||||
|
"""
|
||||||
|
create or update emails for establishment
|
||||||
|
"""
|
||||||
|
ContactEmail.objects.filter(establishment=establishment).delete()
|
||||||
|
|
||||||
|
for new_email in emails_list:
|
||||||
|
ContactEmail.objects.create(establishment=establishment, email=new_email)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
|
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
"""Establishment create serializer"""
|
"""Establishment create serializer"""
|
||||||
|
|
||||||
|
|
@ -40,14 +52,14 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
queryset=models.Address.objects.all(),
|
queryset=models.Address.objects.all(),
|
||||||
write_only=True
|
write_only=True
|
||||||
)
|
)
|
||||||
emails = model_serializers.ContactEmailsSerializer(read_only=True,
|
socials = model_serializers.SocialNetworkRelatedSerializers(
|
||||||
many=True, )
|
read_only=True,
|
||||||
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
|
many=True,
|
||||||
many=True, )
|
)
|
||||||
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type',
|
type = model_serializers.EstablishmentTypeBaseSerializer(
|
||||||
read_only=True)
|
source='establishment_type',
|
||||||
address_id = serializers.PrimaryKeyRelatedField(write_only=True, source='address',
|
read_only=True,
|
||||||
queryset=Address.objects.all())
|
)
|
||||||
tz = TimeZoneChoiceField()
|
tz = TimeZoneChoiceField()
|
||||||
phones = serializers.ListField(
|
phones = serializers.ListField(
|
||||||
source='contact_phones',
|
source='contact_phones',
|
||||||
|
|
@ -56,6 +68,13 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
child=serializers.CharField(max_length=128),
|
child=serializers.CharField(max_length=128),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
emails = serializers.ListField(
|
||||||
|
source='contact_emails',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
|
|
@ -81,6 +100,7 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
'tags',
|
'tags',
|
||||||
'tz',
|
'tz',
|
||||||
'address_id',
|
'address_id',
|
||||||
|
'western_name',
|
||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|
@ -88,11 +108,29 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
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')
|
||||||
|
|
||||||
|
emails_list = []
|
||||||
|
if 'contact_emails' in validated_data:
|
||||||
|
emails_list = validated_data.pop('contact_emails')
|
||||||
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
phones_handler(phones_list, instance)
|
phones_handler(phones_list, instance)
|
||||||
|
emails_handler(emails_list, instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentPositionListSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
|
"""Establishment position serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Establishment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'transliterated_name',
|
||||||
|
'index_name',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
"""Establishment create serializer"""
|
"""Establishment create serializer"""
|
||||||
|
|
||||||
|
|
@ -100,9 +138,14 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
source='establishment_type',
|
source='establishment_type',
|
||||||
queryset=models.EstablishmentType.objects.all(), write_only=True
|
queryset=models.EstablishmentType.objects.all(), write_only=True
|
||||||
)
|
)
|
||||||
address = AddressDetailSerializer()
|
address = AddressDetailSerializer(read_only=True)
|
||||||
emails = model_serializers.ContactEmailsSerializer(read_only=False,
|
emails = serializers.ListField(
|
||||||
many=True, )
|
source='contact_emails',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
|
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
|
||||||
many=True, )
|
many=True, )
|
||||||
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
|
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
|
||||||
|
|
@ -119,6 +162,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'slug',
|
'slug',
|
||||||
|
'western_name',
|
||||||
'name',
|
'name',
|
||||||
'website',
|
'website',
|
||||||
'phones',
|
'phones',
|
||||||
|
|
@ -132,6 +176,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
# TODO: check in admin filters
|
# TODO: check in admin filters
|
||||||
'is_publish',
|
'is_publish',
|
||||||
'address',
|
'address',
|
||||||
|
'transportation',
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -140,8 +185,13 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
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')
|
||||||
|
|
||||||
|
emails_list = []
|
||||||
|
if 'contact_emails' in validated_data:
|
||||||
|
emails_list = validated_data.pop('contact_emails')
|
||||||
|
|
||||||
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)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -516,52 +566,79 @@ class EstablishmentAdminListSerializer(UserShortSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class _PlateSerializer(ProjectModelSerializer):
|
class EstablishmentEmployeePositionsSerializer(serializers.ModelSerializer):
|
||||||
name_translated = TranslatedField()
|
"""Establishments from employee serializer"""
|
||||||
|
|
||||||
|
restaurant_name = serializers.CharField(read_only=True, source='establishment.name')
|
||||||
|
position = PositionBackSerializer(read_only=True)
|
||||||
|
state = serializers.CharField(read_only=True, source='status')
|
||||||
|
start = serializers.DateTimeField(read_only=True, source='from_date')
|
||||||
|
end = serializers.DateTimeField(read_only=True, source='to_date')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Plate
|
model = models.EstablishmentEmployee
|
||||||
fields = [
|
fields = [
|
||||||
'name_translated',
|
'restaurant_name',
|
||||||
'price',
|
'position',
|
||||||
|
'state',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MenuDishesSerializer(ProjectModelSerializer):
|
class MenuDishesSerializer(ProjectModelSerializer):
|
||||||
"""for dessert, main_course and starter category"""
|
"""for dessert, main_course and starter category"""
|
||||||
|
|
||||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
establishment_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
|
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
|
||||||
category_translated = serializers.CharField(read_only=True)
|
dishes = MenuDishSerializer(many=True, read_only=True)
|
||||||
last_update = serializers.DateTimeField(source='created')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Menu
|
model = models.Menu
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'category',
|
'establishment_id',
|
||||||
'category_translated',
|
'establishment_slug',
|
||||||
'establishment',
|
'dishes',
|
||||||
'is_drinks_included',
|
|
||||||
'schedule',
|
|
||||||
'plates',
|
|
||||||
'last_update',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MenuDishesCreateSerializer(ProjectModelSerializer):
|
||||||
|
"""Menu dishes create serializer"""
|
||||||
|
|
||||||
|
menu_id = serializers.IntegerField(write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.MenuDish
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'category',
|
||||||
|
'price',
|
||||||
|
'signature',
|
||||||
|
'menu_id'
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
menu_id = validated_data.pop('menu_id')
|
||||||
|
menu = get_object_or_404(models.Menu, pk=menu_id)
|
||||||
|
instance = models.MenuDish.objects.create(**validated_data)
|
||||||
|
menu.dishes.add(instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class MenuDishesRUDSerializers(ProjectModelSerializer):
|
class MenuDishesRUDSerializers(ProjectModelSerializer):
|
||||||
"""for dessert, main_course and starter category"""
|
"""for dessert, main_course and starter category"""
|
||||||
|
|
||||||
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
|
establishment_id = serializers.PrimaryKeyRelatedField(queryset=models.Establishment.objects.all())
|
||||||
schedule = ScheduleRUDSerializer(read_only=True, many=True, allow_null=True)
|
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
|
||||||
|
dishes = MenuDishSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Menu
|
model = models.Menu
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'category',
|
'establishment_id',
|
||||||
'plates',
|
'establishment_slug',
|
||||||
'establishment',
|
'dishes',
|
||||||
'is_drinks_included',
|
|
||||||
'schedule',
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
|
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
@ -9,18 +10,19 @@ from rest_framework import serializers
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from comment.serializers import common as comment_serializers
|
from comment.serializers import common as comment_serializers
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.serializers import AddressBaseSerializer, CityBaseSerializer, AddressDetailSerializer, \
|
from location.serializers import (
|
||||||
CityShortSerializer
|
AddressBaseSerializer, AddressDetailSerializer, CityBaseSerializer,
|
||||||
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
CityShortSerializer, EstablishmentWineOriginBaseSerializer, EstablishmentWineRegionBaseSerializer,
|
||||||
EstablishmentWineOriginBaseSerializer
|
)
|
||||||
from main.serializers import AwardSerializer, CurrencySerializer
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
from review.serializers import ReviewShortSerializer
|
from review.serializers import ReviewShortSerializer
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
from timetable.serialziers import ScheduleRUDSerializer
|
from timetable.serialziers import ScheduleRUDSerializer
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
from utils.serializers import (
|
||||||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
CarouselCreateSerializer, FavoritesCreateSerializer, ImageBaseSerializer,
|
||||||
FavoritesCreateSerializer)
|
ProjectModelSerializer, TranslatedField,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -70,31 +72,87 @@ class PlateSerializer(ProjectModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MenuSerializers(ProjectModelSerializer):
|
class MenuDishSerializer(ProjectModelSerializer):
|
||||||
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
|
||||||
category_translated = serializers.CharField(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Menu
|
model = models.MenuDish
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
|
'name',
|
||||||
'category',
|
'category',
|
||||||
'category_translated',
|
'price',
|
||||||
'plates',
|
'signature'
|
||||||
'establishment'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MenuRUDSerializers(ProjectModelSerializer):
|
class MenuFilesSerializers(ProjectModelSerializer):
|
||||||
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
|
menu_id = serializers.IntegerField(write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.MenuFiles
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'file',
|
||||||
|
'menu_id'
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
menu_id = validated_data.pop('menu_id')
|
||||||
|
menu = get_object_or_404(models.Menu, pk=menu_id)
|
||||||
|
instance = models.MenuFiles.objects.create(**validated_data)
|
||||||
|
menu.uploads.add(instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class MenuSerializers(ProjectModelSerializer):
|
||||||
|
name = serializers.CharField()
|
||||||
|
establishment_id = serializers.PrimaryKeyRelatedField(queryset=models.Establishment.objects.all())
|
||||||
|
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
|
||||||
|
price = serializers.IntegerField(required=False)
|
||||||
|
drinks_included = serializers.BooleanField(source='is_drinks_included', required=False)
|
||||||
|
schedules = ScheduleRUDSerializer(many=True, allow_null=True, required=False)
|
||||||
|
uploads = MenuFilesSerializers(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Menu
|
model = models.Menu
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'category',
|
'name',
|
||||||
'plates',
|
'establishment_id',
|
||||||
'establishment'
|
'establishment_slug',
|
||||||
|
'price',
|
||||||
|
'drinks_included',
|
||||||
|
'schedules',
|
||||||
|
'uploads',
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
validated_data['establishment'] = validated_data.pop('establishment_id')
|
||||||
|
instance = models.Menu.objects.create(**validated_data)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class MenuRUDSerializers(ProjectModelSerializer):
|
||||||
|
name = serializers.CharField()
|
||||||
|
establishment_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
|
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
|
||||||
|
price = serializers.IntegerField(required=False)
|
||||||
|
drinks_included = serializers.BooleanField(source='is_drinks_included', required=False)
|
||||||
|
schedules = ScheduleRUDSerializer(many=True, allow_null=True, required=False)
|
||||||
|
uploads = MenuFilesSerializers(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Menu
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'establishment_id',
|
||||||
|
'establishment_slug',
|
||||||
|
'price',
|
||||||
|
'drinks_included',
|
||||||
|
'schedules',
|
||||||
|
'uploads',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -464,6 +522,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
'website',
|
'website',
|
||||||
'facebook',
|
'facebook',
|
||||||
'twitter',
|
'twitter',
|
||||||
|
'instagram',
|
||||||
'lafourchette',
|
'lafourchette',
|
||||||
'booking',
|
'booking',
|
||||||
'phones',
|
'phones',
|
||||||
|
|
@ -527,7 +586,7 @@ class EstablishmentCommentBaseSerializer(comment_serializers.CommentBaseSerializ
|
||||||
'created',
|
'created',
|
||||||
'text',
|
'text',
|
||||||
'mark',
|
'mark',
|
||||||
'nickname',
|
# 'nickname',
|
||||||
'profile_pic',
|
'profile_pic',
|
||||||
'status',
|
'status',
|
||||||
'status_display',
|
'status_display',
|
||||||
|
|
@ -567,7 +626,7 @@ class EstablishmentCommentRUDSerializer(comment_serializers.CommentBaseSerialize
|
||||||
'created',
|
'created',
|
||||||
'text',
|
'text',
|
||||||
'mark',
|
'mark',
|
||||||
'nickname',
|
# 'nickname',
|
||||||
'profile_pic',
|
'profile_pic',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,15 @@ urlpatterns = [
|
||||||
name='note-rud'),
|
name='note-rud'),
|
||||||
path('slug/<slug:slug>/admin/', views.EstablishmentAdminView.as_view(),
|
path('slug/<slug:slug>/admin/', views.EstablishmentAdminView.as_view(),
|
||||||
name='establishment-admin-list'),
|
name='establishment-admin-list'),
|
||||||
path('menus/dishes/', views.MenuDishesListCreateView.as_view(), name='menu-dishes-list'),
|
path('menus/dishes/', views.MenuDishesListView.as_view(), name='menu-dishes-list'),
|
||||||
|
path('menus/dishes/create/', views.MenuDishesCreateView.as_view(), name='menu-dishes-create'),
|
||||||
path('menus/dishes/<int:pk>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
|
path('menus/dishes/<int:pk>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
|
||||||
|
path('menus/dishes/slug/<slug:slug>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
|
||||||
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
|
||||||
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||||
|
path('menus/slug/<slug:slug>/', views.MenuRUDView.as_view(), name='menu-rud'),
|
||||||
|
path('menus/uploads/', views.MenuFilesListCreateView.as_view(), name='menu-files-list'),
|
||||||
|
path('menus/uploads/<int:pk>/', views.MenuFilesRUDView.as_view(), name='menu-files-rud'),
|
||||||
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
|
||||||
path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
|
path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
|
||||||
path('social_choice/', views.SocialChoiceListCreateView.as_view(), name='socials_choice'),
|
path('social_choice/', views.SocialChoiceListCreateView.as_view(), name='socials_choice'),
|
||||||
|
|
@ -60,6 +65,10 @@ urlpatterns = [
|
||||||
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
||||||
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
||||||
path('positions/', views.EstablishmentPositionListView.as_view(), name='position-list'),
|
path('positions/', views.EstablishmentPositionListView.as_view(), name='position-list'),
|
||||||
|
path('employee_positions/<int:pk>/', views.EmployeePositionsListView.as_view(),
|
||||||
|
name='employee-positions-list'),
|
||||||
path('employee_establishments/<int:pk>/', views.EmployeeEstablishmentsListView.as_view(),
|
path('employee_establishments/<int:pk>/', views.EmployeeEstablishmentsListView.as_view(),
|
||||||
name='employee-establishments-list')
|
name='employee-establishments-list'),
|
||||||
|
path('employee_establishment_positions/<int:pk>/', views.EmployeeEstablishmentPositionsView.as_view(),
|
||||||
|
name='employee-establishment-positions')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
"""Establishment app views."""
|
"""Establishment app views."""
|
||||||
|
from django.db.models.query_utils import Q
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status, permissions
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
|
@ -16,6 +18,20 @@ from utils.permissions import (
|
||||||
from utils.views import CreateDestroyGalleryViewMixin
|
from utils.views import CreateDestroyGalleryViewMixin
|
||||||
|
|
||||||
|
|
||||||
|
class MenuRUDMixinViews:
|
||||||
|
"""Menu mixin"""
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
instance = self.get_queryset().filter(
|
||||||
|
Q(establishment__slug=self.kwargs.get('slug')) | Q(establishment__id=self.kwargs.get('pk'))
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if instance is None:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentMixinViews:
|
class EstablishmentMixinViews:
|
||||||
"""Establishment mixin."""
|
"""Establishment mixin."""
|
||||||
|
|
||||||
|
|
@ -34,12 +50,27 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
||||||
filter_class = filters.EstablishmentFilter
|
filter_class = filters.EstablishmentFilter
|
||||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsReviewManager,
|
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
IsEstablishmentAdministrator,
|
IsEstablishmentAdministrator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeEstablishmentPositionsView(generics.ListAPIView):
|
||||||
|
"""Establishment by employee view."""
|
||||||
|
|
||||||
|
queryset = models.EstablishmentEmployee.objects.all()
|
||||||
|
serializer_class = serializers.EstablishmentEmployeePositionsSerializer
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
employee_pk = self.kwargs.get('pk')
|
||||||
|
return super().get_queryset().filter(employee__id=employee_pk).all().prefetch_related(
|
||||||
|
'establishment').select_related('position')
|
||||||
|
|
||||||
|
|
||||||
class EmployeeEstablishmentsListView(generics.ListAPIView):
|
class EmployeeEstablishmentsListView(generics.ListAPIView):
|
||||||
"""Establishment by employee list view."""
|
"""Establishment by employee list view."""
|
||||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||||
|
|
@ -54,6 +85,22 @@ class EmployeeEstablishmentsListView(generics.ListAPIView):
|
||||||
return employee.establishments.with_extended_related()
|
return employee.establishments.with_extended_related()
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeePositionsListView(generics.ListAPIView):
|
||||||
|
"""Establishment position by employee list view."""
|
||||||
|
|
||||||
|
queryset = models.Establishment.objects.all()
|
||||||
|
serializer_class = serializers.EstablishmentPositionListSerializer
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
employee = get_object_or_404(models.Employee, pk=pk)
|
||||||
|
return employee.establishments.with_extended_related()
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView):
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
serializer_class = serializers.EstablishmentRUDSerializer
|
serializer_class = serializers.EstablishmentRUDSerializer
|
||||||
|
|
@ -120,13 +167,17 @@ class MenuListCreateView(generics.ListCreateAPIView):
|
||||||
IsEstablishmentAdministrator,
|
IsEstablishmentAdministrator,
|
||||||
)
|
)
|
||||||
filterset_fields = (
|
filterset_fields = (
|
||||||
'establishment',
|
'establishment__id',
|
||||||
'establishment__slug',
|
'establishment__slug',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().prefetch_related('establishment')
|
||||||
|
|
||||||
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
|
class MenuRUDView(MenuRUDMixinViews, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Menu RUD view."""
|
"""Menu RUD view."""
|
||||||
|
lookup_field = None
|
||||||
serializer_class = serializers.MenuRUDSerializers
|
serializer_class = serializers.MenuRUDSerializers
|
||||||
queryset = models.Menu.objects.all()
|
queryset = models.Menu.objects.all()
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
|
|
@ -135,6 +186,26 @@ class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MenuFilesListCreateView(generics.ListCreateAPIView):
|
||||||
|
"""Menu files list create view."""
|
||||||
|
serializer_class = serializers.MenuFilesSerializers
|
||||||
|
queryset = models.MenuFiles.objects.all()
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MenuFilesRUDView(generics.RetrieveDestroyAPIView):
|
||||||
|
"""Menu files RUD view."""
|
||||||
|
serializer_class = serializers.MenuFilesSerializers
|
||||||
|
queryset = models.MenuFiles.objects.all()
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SocialChoiceListCreateView(generics.ListCreateAPIView):
|
class SocialChoiceListCreateView(generics.ListCreateAPIView):
|
||||||
"""SocialChoice list create view."""
|
"""SocialChoice list create view."""
|
||||||
serializer_class = serializers.SocialChoiceSerializers
|
serializer_class = serializers.SocialChoiceSerializers
|
||||||
|
|
@ -523,10 +594,10 @@ class EstablishmentAdminView(generics.ListAPIView):
|
||||||
return User.objects.establishment_admin(establishment).distinct()
|
return User.objects.establishment_admin(establishment).distinct()
|
||||||
|
|
||||||
|
|
||||||
class MenuDishesListCreateView(generics.ListCreateAPIView):
|
class MenuDishesListView(generics.ListAPIView):
|
||||||
"""Menu (dessert, main_course, starter) list create view."""
|
"""Menu (dessert, main_course, starter) list create view."""
|
||||||
serializer_class = serializers.MenuDishesSerializer
|
serializer_class = serializers.MenuDishesSerializer
|
||||||
queryset = models.Menu.objects.with_schedule_plates_establishment().dishes().distinct()
|
queryset = models.Menu.objects.with_dishes()
|
||||||
filter_class = filters.MenuDishesBackFilter
|
filter_class = filters.MenuDishesBackFilter
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
|
|
@ -534,10 +605,21 @@ class MenuDishesListCreateView(generics.ListCreateAPIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MenuDishesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class MenuDishesRUDView(MenuRUDMixinViews, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Menu (dessert, main_course, starter) RUD view."""
|
"""Menu (dessert, main_course, starter) RUD view."""
|
||||||
|
lookup_field = None
|
||||||
serializer_class = serializers.MenuDishesRUDSerializers
|
serializer_class = serializers.MenuDishesRUDSerializers
|
||||||
queryset = models.Menu.objects.dishes().distinct()
|
queryset = models.Menu.objects.with_dishes()
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
class MenuDishesCreateView(generics.CreateAPIView):
|
||||||
|
"""Menu (dessert, main_course, starter) list create view."""
|
||||||
|
serializer_class = serializers.MenuDishesCreateSerializer
|
||||||
|
queryset = models.MenuDish.objects.all()
|
||||||
|
filter_class = filters.MenuDishesBackFilter
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
IsEstablishmentAdministrator
|
IsEstablishmentAdministrator
|
||||||
|
|
|
||||||
45
apps/location/migrations/0036_auto_20200127_2004.py
Normal file
45
apps/location/migrations/0036_auto_20200127_2004.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-27 20:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
def clear_data(apps, schema_editor):
|
||||||
|
City = apps.get_model('location', 'City')
|
||||||
|
for city in City.objects.all():
|
||||||
|
city.name = None
|
||||||
|
city.save()
|
||||||
|
|
||||||
|
|
||||||
|
def preserve_field_data(apps, schema_editor):
|
||||||
|
City = apps.get_model('location', 'City')
|
||||||
|
for city in City.objects.all():
|
||||||
|
city.name = city.name_translated
|
||||||
|
city.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0035_auto_20200115_1117'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='city',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=250, null=True, verbose_name='name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(clear_data, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='city',
|
||||||
|
name='name',
|
||||||
|
field=utils.models.TJSONField(default=None, help_text='{"en-GB":"some city name"}', null=True, verbose_name='City name json'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(preserve_field_data, migrations.RunPython.noop),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='city',
|
||||||
|
name='name_translated',
|
||||||
|
)
|
||||||
|
]
|
||||||
18
apps/location/migrations/0037_address_district_name.py
Normal file
18
apps/location/migrations/0037_address_district_name.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 12:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0036_auto_20200127_2004'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='address',
|
||||||
|
name='district_name',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=500, verbose_name='District name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
"""Location app models."""
|
"""Location app models."""
|
||||||
|
from functools import reduce
|
||||||
|
from json import dumps
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.db.transaction import on_commit
|
from django.db.transaction import on_commit
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from functools import reduce
|
|
||||||
from typing import List
|
|
||||||
from django.contrib.postgres.fields.jsonb import KeyTextTransform
|
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
|
||||||
|
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
||||||
TranslatedFieldsMixin, get_current_locale,
|
TranslatedFieldsMixin, get_current_locale,
|
||||||
IntermediateGalleryModelMixin, GalleryMixin)
|
IntermediateGalleryModelMixin)
|
||||||
|
|
||||||
|
|
||||||
class CountryQuerySet(models.QuerySet):
|
class CountryQuerySet(models.QuerySet):
|
||||||
|
|
@ -139,13 +139,14 @@ class CityQuerySet(models.QuerySet):
|
||||||
"""Return establishments by country code"""
|
"""Return establishments by country code"""
|
||||||
return self.filter(country__code=code)
|
return self.filter(country__code=code)
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
return self.prefetch_related('country', 'region', 'region__country')
|
||||||
|
|
||||||
class City(models.Model):
|
|
||||||
|
class City(models.Model, TranslatedFieldsMixin):
|
||||||
"""Region model."""
|
"""Region model."""
|
||||||
name = models.CharField(_('name'), max_length=250)
|
name = TJSONField(default=None, null=True, help_text='{"en-GB":"some city name"}',
|
||||||
name_translated = TJSONField(blank=True, null=True, default=None,
|
verbose_name=_('City name json'))
|
||||||
verbose_name=_('Translated name'),
|
|
||||||
help_text='{"en-GB":"some text"}')
|
|
||||||
code = models.CharField(_('code'), max_length=250)
|
code = models.CharField(_('code'), max_length=250)
|
||||||
region = models.ForeignKey(Region, on_delete=models.CASCADE,
|
region = models.ForeignKey(Region, on_delete=models.CASCADE,
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
|
|
@ -177,7 +178,12 @@ class City(models.Model):
|
||||||
verbose_name = _('city')
|
verbose_name = _('city')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name_dumped
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name_dumped(self):
|
||||||
|
"""Used for indexing as string"""
|
||||||
|
return f'{self.id}: {dumps(self.name)}'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_object(self):
|
def image_object(self):
|
||||||
|
|
@ -217,6 +223,8 @@ class Address(models.Model):
|
||||||
default='', help_text=_('Ex.: 350018'))
|
default='', help_text=_('Ex.: 350018'))
|
||||||
coordinates = models.PointField(
|
coordinates = models.PointField(
|
||||||
_('Coordinates'), blank=True, null=True, default=None)
|
_('Coordinates'), blank=True, null=True, default=None)
|
||||||
|
district_name = models.CharField(
|
||||||
|
_('District name'), max_length=500, blank=True, default='')
|
||||||
old_id = models.IntegerField(null=True, blank=True, default=None)
|
old_id = models.IntegerField(null=True, blank=True, default=None)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from location import models
|
from location import models
|
||||||
from location.serializers import common
|
from location.serializers import common
|
||||||
|
|
||||||
|
from utils.serializers import TranslatedField
|
||||||
|
|
||||||
|
|
||||||
class AddressCreateSerializer(common.AddressDetailSerializer):
|
class AddressCreateSerializer(common.AddressDetailSerializer):
|
||||||
"""Address create serializer."""
|
"""Address create serializer."""
|
||||||
|
|
@ -9,6 +11,8 @@ class AddressCreateSerializer(common.AddressDetailSerializer):
|
||||||
class CountryBackSerializer(common.CountrySerializer):
|
class CountryBackSerializer(common.CountrySerializer):
|
||||||
"""Country back-office serializer."""
|
"""Country back-office serializer."""
|
||||||
|
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Country
|
model = models.Country
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -16,5 +20,6 @@ class CountryBackSerializer(common.CountrySerializer):
|
||||||
'code',
|
'code',
|
||||||
'svg_image',
|
'svg_image',
|
||||||
'name',
|
'name',
|
||||||
|
'name_translated',
|
||||||
'country_id'
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,27 @@ class CountrySimpleSerializer(serializers.ModelSerializer):
|
||||||
fields = ('id', 'code', 'name_translated')
|
fields = ('id', 'code', 'name_translated')
|
||||||
|
|
||||||
|
|
||||||
|
class ParentRegionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Region serializer"""
|
||||||
|
|
||||||
|
country = CountrySerializer(read_only=True)
|
||||||
|
country_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
source='country',
|
||||||
|
queryset=models.Country.objects.all(),
|
||||||
|
write_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Region
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'code',
|
||||||
|
'country',
|
||||||
|
'country_id'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class RegionSerializer(serializers.ModelSerializer):
|
class RegionSerializer(serializers.ModelSerializer):
|
||||||
"""Region serializer"""
|
"""Region serializer"""
|
||||||
|
|
||||||
|
|
@ -43,6 +64,7 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||||
queryset=models.Country.objects.all(),
|
queryset=models.Country.objects.all(),
|
||||||
write_only=True
|
write_only=True
|
||||||
)
|
)
|
||||||
|
parent_region = ParentRegionSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Region
|
model = models.Region
|
||||||
|
|
@ -59,13 +81,14 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||||
class CityShortSerializer(serializers.ModelSerializer):
|
class CityShortSerializer(serializers.ModelSerializer):
|
||||||
"""Short city serializer"""
|
"""Short city serializer"""
|
||||||
country = CountrySerializer(read_only=True)
|
country = CountrySerializer(read_only=True)
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class"""
|
"""Meta class"""
|
||||||
model = models.City
|
model = models.City
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name_translated',
|
||||||
'code',
|
'code',
|
||||||
'country',
|
'country',
|
||||||
)
|
)
|
||||||
|
|
@ -91,12 +114,14 @@ class CityBaseSerializer(serializers.ModelSerializer):
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
country = CountrySerializer(read_only=True)
|
country = CountrySerializer(read_only=True)
|
||||||
|
name_translated = TranslatedField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.City
|
model = models.City
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
|
'name_translated',
|
||||||
'region',
|
'region',
|
||||||
'region_id',
|
'region_id',
|
||||||
'country_id',
|
'country_id',
|
||||||
|
|
@ -146,6 +171,7 @@ class AddressBaseSerializer(serializers.ModelSerializer):
|
||||||
'postal_code',
|
'postal_code',
|
||||||
'latitude',
|
'latitude',
|
||||||
'longitude',
|
'longitude',
|
||||||
|
'district_name',
|
||||||
|
|
||||||
# todo: remove this fields (backward compatibility)
|
# todo: remove this fields (backward compatibility)
|
||||||
'geo_lon',
|
'geo_lon',
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Overridden method 'get_queryset'."""
|
"""Overridden method 'get_queryset'."""
|
||||||
qs = models.City.objects.all().annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\
|
qs = models.City.objects.all().annotate(locale_name=KeyTextTransform(get_current_locale(), 'name'))\
|
||||||
.order_by('locale_name')
|
.order_by('locale_name').with_base_related()
|
||||||
if self.request.country_code:
|
if self.request.country_code:
|
||||||
qs = qs.by_country_code(self.request.country_code)
|
qs = qs.by_country_code(self.request.country_code)
|
||||||
return qs
|
return qs
|
||||||
|
|
@ -61,7 +61,7 @@ class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CityBaseSerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
queryset = models.City.objects.all()\
|
queryset = models.City.objects.all()\
|
||||||
.annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\
|
.annotate(locale_name=KeyTextTransform(get_current_locale(), 'name'))\
|
||||||
.order_by('locale_name')
|
.order_by('locale_name')
|
||||||
filter_class = filters.CityBackFilter
|
filter_class = filters.CityBackFilter
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class RegionViewMixin(generics.GenericAPIView):
|
||||||
class CityViewMixin(generics.GenericAPIView):
|
class CityViewMixin(generics.GenericAPIView):
|
||||||
"""View Mixin for model City"""
|
"""View Mixin for model City"""
|
||||||
model = models.City
|
model = models.City
|
||||||
queryset = models.City.objects.all()
|
queryset = models.City.objects.with_base_related()
|
||||||
|
|
||||||
|
|
||||||
class AddressViewMixin(generics.GenericAPIView):
|
class AddressViewMixin(generics.GenericAPIView):
|
||||||
|
|
@ -101,7 +101,7 @@ class CityListView(CityViewMixin, generics.ListAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
if self.request.country_code:
|
if self.request.country_code:
|
||||||
qs = qs.by_country_code(self.request.country_code)
|
qs = qs.by_country_code(self.request.country_code).with_base_related()
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
|
|
||||||
state = filters.NumberFilter()
|
state = filters.NumberFilter()
|
||||||
|
|
||||||
|
state__in = filters.CharFilter(method='by_states_list')
|
||||||
|
|
||||||
SORT_BY_CREATED_CHOICE = "created"
|
SORT_BY_CREATED_CHOICE = "created"
|
||||||
SORT_BY_START_CHOICE = "start"
|
SORT_BY_START_CHOICE = "start"
|
||||||
SORT_BY_CHOICES = (
|
SORT_BY_CHOICES = (
|
||||||
|
|
@ -54,6 +56,10 @@ class NewsListFilterSet(filters.FilterSet):
|
||||||
return queryset.es_search(value, relevance_order='ordering' not in self.request.query_params)
|
return queryset.es_search(value, relevance_order='ordering' not in self.request.query_params)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def by_states_list(self, queryset, name, value):
|
||||||
|
states = value.split('__')
|
||||||
|
return queryset.filter(state__in=states)
|
||||||
|
|
||||||
def in_tags(self, queryset, name, value):
|
def in_tags(self, queryset, name, value):
|
||||||
tags = value.split('__')
|
tags = value.split('__')
|
||||||
return queryset.filter(tags__value__in=tags)
|
return queryset.filter(tags__value__in=tags)
|
||||||
|
|
|
||||||
18
apps/news/migrations/0053_auto_20200128_1431.py
Normal file
18
apps/news/migrations/0053_auto_20200128_1431.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 14:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('news', '0052_auto_20200121_0940'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='state',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'remove'), (1, 'hidden'), (2, 'published'), (3, 'not published')], default=3, verbose_name='State'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -101,6 +101,10 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
return self.select_related('created_by', 'agenda', 'banner')
|
return self.select_related('created_by', 'agenda', 'banner')
|
||||||
|
|
||||||
|
def visible(self):
|
||||||
|
"""Narrows qs by excluding invisible for API (at all) news"""
|
||||||
|
return self.exclude(state=self.model.REMOVE)
|
||||||
|
|
||||||
def by_type(self, news_type):
|
def by_type(self, news_type):
|
||||||
"""Filter News by type"""
|
"""Filter News by type"""
|
||||||
return self.filter(news_type__name=news_type)
|
return self.filter(news_type__name=news_type)
|
||||||
|
|
@ -128,14 +132,21 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
return self.exclude(models.Q(publication_date__isnull=True) | models.Q(publication_time__isnull=True)). \
|
return self.exclude(models.Q(publication_date__isnull=True) | models.Q(publication_time__isnull=True)). \
|
||||||
filter(models.Q(models.Q(end__gte=now) |
|
filter(models.Q(models.Q(end__gte=now) |
|
||||||
models.Q(end__isnull=True)),
|
models.Q(end__isnull=True)),
|
||||||
state__in=self.model.PUBLISHED_STATES, publication_date__lte=date_now,
|
state__in=self.model.PUBLISHED_STATES)\
|
||||||
publication_time__lte=time_now)
|
.annotate(visible_now=Case(
|
||||||
|
When(publication_date__gt=date_now, then=False),
|
||||||
|
When(Q(publication_date=date_now) & Q(publication_time__gt=time_now), then=False),
|
||||||
|
default=True,
|
||||||
|
output_field=models.BooleanField()
|
||||||
|
))\
|
||||||
|
.exclude(visible_now=False)
|
||||||
|
|
||||||
# todo: filter by best score
|
# todo: filter by best score
|
||||||
# todo: filter by country?
|
# todo: filter by country?
|
||||||
def should_read(self, news, user):
|
def should_read(self, news, user):
|
||||||
return self.model.objects.exclude(pk=news.pk).published(). \
|
return self.model.objects.exclude(pk=news.pk).published(). \
|
||||||
annotate_in_favorites(user). \
|
annotate_in_favorites(user). \
|
||||||
|
filter(country=news.country). \
|
||||||
with_base_related().by_type(news.news_type).distinct().order_by('?')
|
with_base_related().by_type(news.news_type).distinct().order_by('?')
|
||||||
|
|
||||||
def same_theme(self, news, user):
|
def same_theme(self, news, user):
|
||||||
|
|
@ -252,18 +263,18 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
)
|
)
|
||||||
|
|
||||||
# STATE CHOICES
|
# STATE CHOICES
|
||||||
WAITING = 0
|
REMOVE = 0
|
||||||
HIDDEN = 1
|
HIDDEN = 1
|
||||||
PUBLISHED = 2
|
PUBLISHED = 2
|
||||||
PUBLISHED_EXCLUSIVE = 3
|
UNPUBLISHED = 3
|
||||||
|
|
||||||
PUBLISHED_STATES = [PUBLISHED, PUBLISHED_EXCLUSIVE]
|
PUBLISHED_STATES = [PUBLISHED]
|
||||||
|
|
||||||
STATE_CHOICES = (
|
STATE_CHOICES = (
|
||||||
(WAITING, _('Waiting')),
|
(REMOVE, _('remove')), # simply stored in DB news. not shown anywhere
|
||||||
(HIDDEN, _('Hidden')),
|
(HIDDEN, _('hidden')), # not shown in api/web or api/mobile
|
||||||
(PUBLISHED, _('Published')),
|
(PUBLISHED, _('published')), # shown everywhere
|
||||||
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
|
(UNPUBLISHED, _('not published')), # newly created news
|
||||||
)
|
)
|
||||||
|
|
||||||
INTERNATIONAL_TAG_VALUE = 'international'
|
INTERNATIONAL_TAG_VALUE = 'international'
|
||||||
|
|
@ -295,7 +306,7 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
slugs = HStoreField(null=True, blank=True, default=dict,
|
slugs = HStoreField(null=True, blank=True, default=dict,
|
||||||
verbose_name=_('Slugs for current news obj'),
|
verbose_name=_('Slugs for current news obj'),
|
||||||
help_text='{"en-GB":"some slug"}')
|
help_text='{"en-GB":"some slug"}')
|
||||||
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
|
state = models.PositiveSmallIntegerField(default=UNPUBLISHED, choices=STATE_CHOICES,
|
||||||
verbose_name=_('State'))
|
verbose_name=_('State'))
|
||||||
is_highlighted = models.BooleanField(default=False,
|
is_highlighted = models.BooleanField(default=False,
|
||||||
verbose_name=_('Is highlighted'))
|
verbose_name=_('Is highlighted'))
|
||||||
|
|
@ -340,7 +351,7 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
|
|
||||||
def create_duplicate(self, new_country, view_count_model):
|
def create_duplicate(self, new_country, view_count_model):
|
||||||
self.pk = None
|
self.pk = None
|
||||||
self.state = self.WAITING
|
self.state = self.UNPUBLISHED
|
||||||
self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()}
|
self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()}
|
||||||
self.country = new_country
|
self.country = new_country
|
||||||
self.views_count = view_count_model
|
self.views_count = view_count_model
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,9 @@ class NewsDetailSerializer(NewsBaseSerializer):
|
||||||
'gallery',
|
'gallery',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class NewsDetailWebSerializer(NewsDetailSerializer):
|
class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
"""News detail serializer for web users.."""
|
"""News detail serializer for web users.."""
|
||||||
|
|
@ -207,7 +210,8 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
"""News back office base serializer."""
|
"""News back office base serializer."""
|
||||||
is_published = serializers.BooleanField(source='is_publish', read_only=True)
|
is_published = serializers.BooleanField(source='is_publish', read_only=True)
|
||||||
descriptions = serializers.ListField(required=False)
|
descriptions = serializers.ListField(required=False)
|
||||||
agenda = AgendaSerializer()
|
agenda = AgendaSerializer(required=False, allow_null=True)
|
||||||
|
state_display = serializers.CharField(source='get_state_display', read_only=True)
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -226,7 +230,9 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
'created',
|
'created',
|
||||||
'modified',
|
'modified',
|
||||||
'descriptions',
|
'descriptions',
|
||||||
'agenda'
|
'agenda',
|
||||||
|
'state',
|
||||||
|
'state_display',
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'created': {'read_only': True},
|
'created': {'read_only': True},
|
||||||
|
|
@ -234,6 +240,8 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
'duplication_date': {'read_only': True},
|
'duplication_date': {'read_only': True},
|
||||||
'locale_to_description_is_active': {'allow_null': False},
|
'locale_to_description_is_active': {'allow_null': False},
|
||||||
'must_of_the_week': {'read_only': True},
|
'must_of_the_week': {'read_only': True},
|
||||||
|
# 'state': {'read_only': True},
|
||||||
|
'state_display': {'read_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
@ -255,7 +263,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
instance = models.News.objects.get(pk=self.context['request'].data['id'])
|
instance = models.News.objects.get(pk=self.context['request'].data['id'])
|
||||||
for key in ['slugs', 'title', 'locale_to_description_is_active', 'description']:
|
for key in ['slugs', 'title', 'locale_to_description_is_active', 'description']:
|
||||||
for locale in locales:
|
for locale in locales:
|
||||||
if not attrs[key].get(locale):
|
if locale not in attrs[key]:
|
||||||
attrs[key][locale] = getattr(instance, key).get(locale)
|
attrs[key][locale] = getattr(instance, key).get(locale)
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
@ -299,7 +307,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
slugs_set = set(slugs_list)
|
slugs_set = set(slugs_list)
|
||||||
if models.News.objects.filter(
|
if models.News.objects.filter(
|
||||||
slugs__values__contains=list(slugs.values())
|
slugs__values__contains=list(slugs.values())
|
||||||
).exists() or len(slugs_list) != len(slugs_set):
|
).exclude(pk=instance.pk).exists() or len(slugs_list) != len(slugs_set):
|
||||||
raise serializers.ValidationError({'slugs': _('Slug should be unique')})
|
raise serializers.ValidationError({'slugs': _('Slug should be unique')})
|
||||||
|
|
||||||
agenda_data = validated_data.get('agenda')
|
agenda_data = validated_data.get('agenda')
|
||||||
|
|
@ -357,8 +365,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
template_display = serializers.CharField(source='get_template_display',
|
template_display = serializers.CharField(source='get_template_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True)
|
duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
agenda = AgendaSerializer(required=False, allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
|
class Meta(NewsDetailSerializer.Meta, NewsBackOfficeBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
fields = NewsBackOfficeBaseSerializer.Meta.fields + \
|
fields = NewsBackOfficeBaseSerializer.Meta.fields + \
|
||||||
|
|
@ -373,6 +382,13 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
|
||||||
'duplicates',
|
'duplicates',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Overridden validate method."""
|
||||||
|
return super().validate(attrs)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
"""Serializer class for model NewsGallery."""
|
"""Serializer class for model NewsGallery."""
|
||||||
|
|
@ -503,3 +519,8 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer,
|
||||||
view_count_model = rating_models.ViewCount.objects.create(count=0)
|
view_count_model = rating_models.ViewCount.objects.create(count=0)
|
||||||
instance.create_duplicate(new_country, view_count_model)
|
instance.create_duplicate(new_country, view_count_model)
|
||||||
return get_object_or_404(models.News, pk=kwargs['pk'])
|
return get_object_or_404(models.News, pk=kwargs['pk'])
|
||||||
|
|
||||||
|
|
||||||
|
class NewsStatesSerializer(serializers.Serializer):
|
||||||
|
value = serializers.IntegerField()
|
||||||
|
state_translated = serializers.CharField()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ app_name = 'news'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.NewsBackOfficeLCView.as_view(), name='list-create'),
|
path('', views.NewsBackOfficeLCView.as_view(), name='list-create'),
|
||||||
|
path('states/', views.NewsStatesView.as_view(), name='possible-news-states-list'),
|
||||||
path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'),
|
path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'),
|
||||||
path('<int:pk>/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'),
|
path('<int:pk>/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'),
|
||||||
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ class NewsMixinView:
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
qs = models.News.objects.published() \
|
qs = models.News.objects.published() \
|
||||||
.with_base_related() \
|
.with_base_related() \
|
||||||
|
.visible() \
|
||||||
.annotate_in_favorites(self.request.user) \
|
.annotate_in_favorites(self.request.user) \
|
||||||
.order_by('-is_highlighted', '-publication_date', '-publication_time')
|
.order_by('-is_highlighted', '-publication_date', '-publication_time')
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ class NewsMixinView:
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
instance = self.get_queryset().filter(
|
instance = self.get_queryset().visible().with_base_related().filter(
|
||||||
slugs__values__contains=[self.kwargs['slug']]
|
slugs__values__contains=[self.kwargs['slug']]
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
|
@ -53,6 +54,23 @@ class NewsMixinView:
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class NewsStatesView(generics.ListAPIView):
|
||||||
|
"""Possible project news states"""
|
||||||
|
pagination_class = None
|
||||||
|
serializer_class = serializers.NewsStatesSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
mutated_for_serializer = [{
|
||||||
|
'value': state[0],
|
||||||
|
'state_translated': state[1],
|
||||||
|
} for state in models.News.STATE_CHOICES]
|
||||||
|
serializer = self.get_serializer(mutated_for_serializer, many=True)
|
||||||
|
return response.Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class NewsListView(NewsMixinView, generics.ListAPIView):
|
class NewsListView(NewsMixinView, generics.ListAPIView):
|
||||||
"""News list view."""
|
"""News list view."""
|
||||||
|
|
||||||
|
|
@ -137,7 +155,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method."""
|
"""Override get_queryset method."""
|
||||||
qs = super().get_queryset().with_extended_related()
|
qs = super().get_queryset().with_extended_related().visible()
|
||||||
if 'ordering' in self.request.query_params:
|
if 'ordering' in self.request.query_params:
|
||||||
self.request.GET._mutable = True
|
self.request.GET._mutable = True
|
||||||
if '-publication_datetime' in self.request.query_params['ordering']:
|
if '-publication_datetime' in self.request.query_params['ordering']:
|
||||||
|
|
|
||||||
39
apps/notification/migrations/0011_auto_20200124_1351.py
Normal file
39
apps/notification/migrations/0011_auto_20200124_1351.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-24 13:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('notification', '0010_auto_20191231_0135'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='old_subscriber_id',
|
||||||
|
field=models.PositiveIntegerField(null=True, verbose_name='Old subscriber id'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='old_subscription_type_id',
|
||||||
|
field=models.PositiveIntegerField(null=True, verbose_name='Old subscription type id'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='subscribe_date',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Last subscribe date'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='subscriber',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='notification.Subscriber'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='subscription_type',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='notification.SubscriptionType'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-27 16:37
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('notification', '0011_auto_20200124_1351'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='subscribe',
|
||||||
|
name='subscribe_date',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import F
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
@ -10,6 +11,7 @@ from location.models import Country
|
||||||
from notification.tasks import send_unsubscribe_email
|
from notification.tasks import send_unsubscribe_email
|
||||||
from utils.methods import generate_string_code
|
from utils.methods import generate_string_code
|
||||||
from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin
|
from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin
|
||||||
|
from notification.tasks import send_unsubscribe_email
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin):
|
class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
|
|
@ -131,14 +133,23 @@ class Subscriber(ProjectBaseMixin):
|
||||||
self.update_code = generate_string_code()
|
self.update_code = generate_string_code()
|
||||||
return super(Subscriber, self).save(*args, **kwargs)
|
return super(Subscriber, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def unsubscribe(self, query: dict):
|
def unsubscribe(self):
|
||||||
"""Unsubscribe user."""
|
"""Unsubscribe user."""
|
||||||
self.subscribe_set.update(unsubscribe_date=now())
|
|
||||||
|
self.subscribe_set.update(
|
||||||
|
unsubscribe_date=now(),
|
||||||
|
old_subscriber_id=F('subscriber_id'),
|
||||||
|
old_subscription_type_id=F('subscription_type_id')
|
||||||
|
)
|
||||||
|
self.subscribe_set.update(
|
||||||
|
subscriber_id=None,
|
||||||
|
subscription_type_id=None
|
||||||
|
)
|
||||||
|
|
||||||
if settings.USE_CELERY:
|
if settings.USE_CELERY:
|
||||||
send_unsubscribe_email.delay(self.email)
|
send_unsubscribe_email.delay(self.pk)
|
||||||
else:
|
else:
|
||||||
send_unsubscribe_email(self.email)
|
send_unsubscribe_email(self.pk)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def send_to(self):
|
def send_to(self):
|
||||||
|
|
@ -159,6 +170,10 @@ class Subscriber(ProjectBaseMixin):
|
||||||
def active_subscriptions(self):
|
def active_subscriptions(self):
|
||||||
return self.subscription_types.exclude(subscriber__subscribe__unsubscribe_date__isnull=False)
|
return self.subscription_types.exclude(subscriber__subscribe__unsubscribe_date__isnull=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscription_history(self):
|
||||||
|
return Subscribe.objects.subscription_history(self.pk)
|
||||||
|
|
||||||
|
|
||||||
class SubscribeQuerySet(models.QuerySet):
|
class SubscribeQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
@ -166,18 +181,26 @@ class SubscribeQuerySet(models.QuerySet):
|
||||||
"""Fetches active subscriptions."""
|
"""Fetches active subscriptions."""
|
||||||
return self.exclude(unsubscribe_date__isnull=not switcher)
|
return self.exclude(unsubscribe_date__isnull=not switcher)
|
||||||
|
|
||||||
|
def subscription_history(self, subscriber_id: int):
|
||||||
|
return self.filter(old_subscriber_id=subscriber_id)
|
||||||
|
|
||||||
|
|
||||||
class Subscribe(ProjectBaseMixin):
|
class Subscribe(ProjectBaseMixin):
|
||||||
"""Subscribe model."""
|
"""Subscribe model."""
|
||||||
|
|
||||||
subscribe_date = models.DateTimeField(_('Last subscribe date'), blank=True, null=True, default=now)
|
|
||||||
unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None)
|
unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None)
|
||||||
|
|
||||||
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE)
|
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, null=True)
|
||||||
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE)
|
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE, null=True)
|
||||||
|
|
||||||
|
old_subscriber_id = models.PositiveIntegerField(_("Old subscriber id"), null=True)
|
||||||
|
old_subscription_type_id = models.PositiveIntegerField(_("Old subscription type id"), null=True)
|
||||||
|
|
||||||
objects = SubscribeQuerySet.as_manager()
|
objects = SubscribeQuerySet.as_manager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscribe_date(self):
|
||||||
|
return self.created
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
model = models.Subscriber
|
model = models.Subscriber
|
||||||
fields = (
|
fields = (
|
||||||
|
'id',
|
||||||
'email',
|
'email',
|
||||||
'subscription_types',
|
'subscription_types',
|
||||||
'link_to_unsubscribe',
|
'link_to_unsubscribe',
|
||||||
|
|
@ -54,7 +55,7 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
# validate email
|
# validate email
|
||||||
email = attrs.get('send_to')
|
email = attrs.pop('send_to')
|
||||||
|
|
||||||
if attrs.get('email'):
|
if attrs.get('email'):
|
||||||
email = attrs.get('email')
|
email = attrs.get('email')
|
||||||
|
|
@ -89,12 +90,20 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
|
||||||
subscriber = models.Subscriber.objects.make_subscriber(**validated_data)
|
subscriber = models.Subscriber.objects.make_subscriber(**validated_data)
|
||||||
|
|
||||||
if settings.USE_CELERY:
|
if settings.USE_CELERY:
|
||||||
send_subscribes_update_email.delay(subscriber.email)
|
send_subscribes_update_email.delay(subscriber.pk)
|
||||||
else:
|
else:
|
||||||
send_subscribes_update_email(subscriber.email)
|
send_subscribes_update_email(subscriber.pk)
|
||||||
|
|
||||||
return subscriber
|
return subscriber
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
send_subscribes_update_email.delay(instance.pk)
|
||||||
|
else:
|
||||||
|
send_subscribes_update_email(instance.pk)
|
||||||
|
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class UpdateSubscribeSerializer(serializers.ModelSerializer):
|
class UpdateSubscribeSerializer(serializers.ModelSerializer):
|
||||||
"""Update with code Subscribe serializer."""
|
"""Update with code Subscribe serializer."""
|
||||||
|
|
@ -141,14 +150,27 @@ class UpdateSubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class SubscribeObjectSerializer(serializers.ModelSerializer):
|
class SubscribeObjectSerializer(serializers.ModelSerializer):
|
||||||
"""Subscribe serializer."""
|
"""Subscription type serializer."""
|
||||||
|
|
||||||
|
subscription_type = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Subscriber
|
model = models.Subscribe
|
||||||
fields = ('subscriber',)
|
fields = (
|
||||||
read_only_fields = ('subscribe_date', 'unsubscribe_date',)
|
'subscribe_date',
|
||||||
|
'unsubscribe_date',
|
||||||
|
'subscription_type'
|
||||||
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'subscribe_date': {'read_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_subscription_type(self, instance):
|
||||||
|
return SubscriptionTypeSerializer(
|
||||||
|
models.SubscriptionType.objects.get(pk=instance.old_subscription_type_id)
|
||||||
|
).data
|
||||||
|
|
||||||
|
|
||||||
class SubscribeSerializer(serializers.ModelSerializer):
|
class SubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -156,6 +178,7 @@ class SubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
email = serializers.EmailField(required=False, source='send_to')
|
email = serializers.EmailField(required=False, source='send_to')
|
||||||
subscription_types = SubscriptionTypeSerializer(source='active_subscriptions', read_only=True, many=True)
|
subscription_types = SubscriptionTypeSerializer(source='active_subscriptions', read_only=True, many=True)
|
||||||
|
history = SubscribeObjectSerializer(source='subscription_history', many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -165,4 +188,16 @@ class SubscribeSerializer(serializers.ModelSerializer):
|
||||||
'email',
|
'email',
|
||||||
'subscription_types',
|
'subscription_types',
|
||||||
'link_to_unsubscribe',
|
'link_to_unsubscribe',
|
||||||
|
'history',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
email = serializers.EmailField(read_only=True, required=False, source='send_to')
|
||||||
|
subscription_types = SubscriptionTypeSerializer(source='active_subscriptions', read_only=True, many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Subscriber
|
||||||
|
fields = SubscribeSerializer.Meta.fields
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@ from datetime import datetime
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.template.loader import get_template, render_to_string
|
from django.template.loader import get_template, render_to_string
|
||||||
|
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings
|
||||||
from notification import models
|
from notification import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def send_subscribes_update_email(email):
|
def send_subscribes_update_email(subscriber_id):
|
||||||
subscriber = models.Subscriber.objects.filter(email=email).first()
|
subscriber = models.Subscriber.objects.get(pk=subscriber_id)
|
||||||
|
|
||||||
if subscriber is None:
|
if subscriber is None:
|
||||||
return
|
return
|
||||||
|
|
@ -53,8 +53,8 @@ def send_subscribes_update_email(email):
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def send_unsubscribe_email(email):
|
def send_unsubscribe_email(subscriber_id):
|
||||||
subscriber = models.Subscriber.objects.filter(email=email).first()
|
subscriber = models.Subscriber.objects.get(pk=subscriber_id)
|
||||||
|
|
||||||
if subscriber is None:
|
if subscriber is None:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Notification app common views."""
|
"""Notification app common views."""
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from notification import models
|
from notification import models
|
||||||
|
|
@ -15,6 +15,18 @@ class CreateSubscribeView(generics.CreateAPIView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.CreateAndUpdateSubscribeSerializer
|
serializer_class = serializers.CreateAndUpdateSubscribeSerializer
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
data = request.data
|
||||||
|
instance = None
|
||||||
|
if 'email' in request.data:
|
||||||
|
# we shouldn't create new subscriber if we have one
|
||||||
|
instance = models.Subscriber.objects.filter(email=request.data['email']).first()
|
||||||
|
serializer = self.get_serializer(data=data) if instance is None else self.get_serializer(instance, data=data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
self.perform_create(serializer)
|
||||||
|
headers = self.get_success_headers(serializer.data)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
class UpdateSubscribeView(generics.UpdateAPIView):
|
class UpdateSubscribeView(generics.UpdateAPIView):
|
||||||
"""Subscribe info view."""
|
"""Subscribe info view."""
|
||||||
|
|
@ -41,20 +53,7 @@ class SubscribeInfoAuthUserView(generics.RetrieveAPIView):
|
||||||
lookup_field = None
|
lookup_field = None
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
user = self.request.user
|
return get_object_or_404(models.Subscriber, user=self.request.user)
|
||||||
|
|
||||||
subscriber = models.Subscriber.objects.filter(user=user).first()
|
|
||||||
|
|
||||||
if subscriber is None:
|
|
||||||
subscriber = models.Subscriber.objects.make_subscriber(
|
|
||||||
email=user.email, user=user, ip_address=get_user_ip(self.request),
|
|
||||||
country_code=self.request.country_code, locale=self.request.locale
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return get_object_or_404(models.Subscriber, user=user)
|
|
||||||
|
|
||||||
return subscriber
|
|
||||||
|
|
||||||
|
|
||||||
class UnsubscribeView(generics.UpdateAPIView):
|
class UnsubscribeView(generics.UpdateAPIView):
|
||||||
|
|
@ -65,9 +64,9 @@ class UnsubscribeView(generics.UpdateAPIView):
|
||||||
queryset = models.Subscriber.objects.all()
|
queryset = models.Subscriber.objects.all()
|
||||||
serializer_class = serializers.SubscribeSerializer
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
def patch(self, request, *args, **kw):
|
def put(self, request, *args, **kw):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
obj.unsubscribe(request.query_params)
|
obj.unsubscribe()
|
||||||
serializer = self.get_serializer(instance=obj)
|
serializer = self.get_serializer(instance=obj)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
@ -81,7 +80,7 @@ class UnsubscribeAuthUserView(generics.GenericAPIView):
|
||||||
def patch(self, request, *args, **kw):
|
def patch(self, request, *args, **kw):
|
||||||
user = request.user
|
user = request.user
|
||||||
obj = models.Subscriber.objects.filter(user=user).first()
|
obj = models.Subscriber.objects.filter(user=user).first()
|
||||||
obj.unsubscribe(request.query_params)
|
obj.unsubscribe()
|
||||||
serializer = self.get_serializer(instance=obj)
|
serializer = self.get_serializer(instance=obj)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
|
||||||
34
apps/partner/filters.py
Normal file
34
apps/partner/filters.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""Partner app filters."""
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from partner.models import Partner
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerFilterSet(filters.FilterSet):
|
||||||
|
"""Establishment filter set."""
|
||||||
|
|
||||||
|
establishment = filters.NumberFilter(
|
||||||
|
help_text='Allows to get partner list by establishment ID.')
|
||||||
|
type = filters.ChoiceFilter(
|
||||||
|
choices=Partner.MODEL_TYPES,
|
||||||
|
help_text=f'Allows to filter partner list by partner type. '
|
||||||
|
f'Enum: {dict(Partner.MODEL_TYPES)}')
|
||||||
|
ordering = filters.CharFilter(method='sort_partner')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = Partner
|
||||||
|
fields = (
|
||||||
|
'establishment',
|
||||||
|
'type',
|
||||||
|
'ordering',
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sort_partner(queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
if 'date_bind' in value:
|
||||||
|
value = value.replace('date_bind', 'partnertoestablishment__partner_bind_date')
|
||||||
|
queryset = queryset.order_by(value)
|
||||||
|
return queryset
|
||||||
44
apps/partner/migrations/0004_auto_20200128_1746.py
Normal file
44
apps/partner/migrations/0004_auto_20200128_1746.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 17:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
def make_relations(apps, schemaeditor):
|
||||||
|
PartnerToEstablishment = apps.get_model('partner', 'PartnerToEstablishment')
|
||||||
|
Partner = apps.get_model('partner', 'Partner')
|
||||||
|
Establishment = apps.get_model('establishment', 'Establishment')
|
||||||
|
touched_partners_ids = []
|
||||||
|
for establishment in Establishment.objects.filter(partners__isnull=False):
|
||||||
|
for related_partner in establishment.partners.all():
|
||||||
|
real_partner = Partner.objects.filter(name=related_partner.name, type=related_partner.type,
|
||||||
|
url=related_partner.url).first()
|
||||||
|
touched_partners_ids.append(related_partner.pk)
|
||||||
|
PartnerToEstablishment(establishment=establishment, partner=real_partner, partner_bind_date=real_partner.created).save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0083_establishment_instagram'),
|
||||||
|
('partner', '0003_auto_20191121_1059'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PartnerToEstablishment',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('partner_bind_date', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date partner binded')),
|
||||||
|
('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='establishment.Establishment')),
|
||||||
|
('partner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='partner.Partner')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='establishments',
|
||||||
|
field=models.ManyToManyField(null=True, related_name='new_partners', through='partner.PartnerToEstablishment', to='establishment.Establishment', verbose_name='Establishments'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(make_relations, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
45
apps/partner/migrations/0005_auto_20200128_1754.py
Normal file
45
apps/partner/migrations/0005_auto_20200128_1754.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-28 17:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def delete_unused_partners(apps, schema_editor):
|
||||||
|
PartnerToEstablishment = apps.get_model('partner', 'PartnerToEstablishment')
|
||||||
|
Partner = apps.get_model('partner', 'Partner')
|
||||||
|
ids_to_preserve = []
|
||||||
|
for p_t_e in PartnerToEstablishment.objects.all():
|
||||||
|
ids_to_preserve.append(p_t_e.partner.pk)
|
||||||
|
Partner.objects.exclude(id__in=ids_to_preserve).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('location', '0037_address_district_name'),
|
||||||
|
('establishment', '0083_establishment_instagram'),
|
||||||
|
('partner', '0004_auto_20200128_1746'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='partner',
|
||||||
|
name='establishments',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='country',
|
||||||
|
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to='location.Country'),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='partner',
|
||||||
|
name='establishment',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='establishment',
|
||||||
|
field=models.ManyToManyField(related_name='partners', through='partner.PartnerToEstablishment',
|
||||||
|
to='establishment.Establishment', verbose_name='Establishments'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(delete_unused_partners, migrations.RunPython.noop, atomic=True)
|
||||||
|
]
|
||||||
37
apps/partner/migrations/0006_auto_20200129_1201.py
Normal file
37
apps/partner/migrations/0006_auto_20200129_1201.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-29 12:01
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('partner', '0005_auto_20200128_1754'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='partner',
|
||||||
|
name='image',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='partner',
|
||||||
|
name='url',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partner',
|
||||||
|
name='images',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.URLField(verbose_name='Partner image URL'), blank=True, default=None, null=True, size=None),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partnertoestablishment',
|
||||||
|
name='image',
|
||||||
|
field=models.URLField(null=True, verbose_name='Partner image URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partnertoestablishment',
|
||||||
|
name='url',
|
||||||
|
field=models.URLField(blank=True, default=None, null=True, verbose_name='Establishment to Partner URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,10 +1,25 @@
|
||||||
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.utils import timezone
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from utils.models import ImageMixin, ProjectBaseMixin
|
from utils.models import ImageMixin, ProjectBaseMixin
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerQueryset(models.QuerySet):
|
||||||
|
|
||||||
|
def with_base_related(self):
|
||||||
|
return self.prefetch_related('establishment__establishment_type', 'establishment__establishment_subtypes',
|
||||||
|
'establishment__awards', 'establishment__schedule', 'establishment__phones',
|
||||||
|
'establishment__gallery', 'establishment__menu_set',
|
||||||
|
'establishment__menu_set__plates', 'establishment__menu_set__plates__currency',
|
||||||
|
'establishment__currency', 'establishment__address__city',
|
||||||
|
'establishment__address__city__region',
|
||||||
|
'establishment__address__city__region__country',
|
||||||
|
'establishment__address__city__country', 'country')
|
||||||
|
|
||||||
|
|
||||||
class Partner(ProjectBaseMixin):
|
class Partner(ProjectBaseMixin):
|
||||||
"""Partner model."""
|
"""Partner model."""
|
||||||
|
|
||||||
|
|
@ -17,24 +32,36 @@ class Partner(ProjectBaseMixin):
|
||||||
|
|
||||||
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
||||||
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
|
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
|
||||||
url = models.URLField(verbose_name=_('Partner URL'))
|
images = ArrayField(
|
||||||
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
|
models.URLField(verbose_name=_('Partner image URL')), blank=True, null=True, default=None,
|
||||||
establishment = models.ForeignKey(
|
|
||||||
Establishment,
|
|
||||||
verbose_name=_('Establishment'),
|
|
||||||
related_name='partners',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
)
|
)
|
||||||
|
establishment = models.ManyToManyField('establishment.Establishment', related_name='partners',
|
||||||
|
through='PartnerToEstablishment',
|
||||||
|
verbose_name=_('Establishments'))
|
||||||
type = models.PositiveSmallIntegerField(choices=MODEL_TYPES, default=PARTNER)
|
type = models.PositiveSmallIntegerField(choices=MODEL_TYPES, default=PARTNER)
|
||||||
starting_date = models.DateField(_('starting date'), blank=True, null=True)
|
starting_date = models.DateField(_('starting date'), blank=True, null=True)
|
||||||
expiry_date = models.DateField(_('expiry date'), blank=True, null=True)
|
expiry_date = models.DateField(_('expiry date'), blank=True, null=True)
|
||||||
price_per_month = models.DecimalField(_('price per month'), max_digits=10, decimal_places=2, blank=True, null=True)
|
price_per_month = models.DecimalField(_('price per month'), max_digits=10, decimal_places=2, blank=True, null=True)
|
||||||
|
country = models.ForeignKey('location.Country', null=True, default=None, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
objects = PartnerQueryset.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('partner')
|
verbose_name = _('partner')
|
||||||
verbose_name_plural = _('partners')
|
verbose_name_plural = _('partners')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.url}'
|
return f'{self.name}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_display(self):
|
||||||
|
return self.MODEL_TYPES[self.type][1]
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerToEstablishment(models.Model):
|
||||||
|
partner_bind_date = models.DateTimeField(default=timezone.now, editable=False,
|
||||||
|
verbose_name=_('Date partner binded'))
|
||||||
|
url = models.URLField(verbose_name=_('Establishment to Partner URL'), null=True, blank=True, default=None)
|
||||||
|
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
|
||||||
|
partner = models.ForeignKey(Partner, on_delete=models.CASCADE, null=True)
|
||||||
|
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE, null=True)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,85 @@
|
||||||
"""Back account serializers"""
|
"""Back account serializers"""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from partner.models import Partner
|
from partner.models import Partner, PartnerToEstablishment
|
||||||
|
from establishment.serializers import EstablishmentShortSerializer
|
||||||
|
from location.serializers import CountrySimpleSerializer
|
||||||
|
from location.models import Country
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from establishment.models import Establishment
|
||||||
|
|
||||||
|
|
||||||
class BackPartnerSerializer(serializers.ModelSerializer):
|
class BackPartnerSerializer(serializers.ModelSerializer):
|
||||||
|
# establishments = EstablishmentShortSerializer(many=True, read_only=True, source='establishment')
|
||||||
|
country = CountrySimpleSerializer(read_only=True)
|
||||||
|
type_display = serializers.CharField(read_only=True)
|
||||||
|
country_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=Country.objects.all(),
|
||||||
|
required=False,
|
||||||
|
write_only=True,
|
||||||
|
source='country'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Partner
|
model = Partner
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'old_id',
|
|
||||||
'name',
|
'name',
|
||||||
'url',
|
'images',
|
||||||
'image',
|
|
||||||
'establishment',
|
|
||||||
'establishment_id',
|
|
||||||
'type',
|
'type',
|
||||||
|
'type_display',
|
||||||
'starting_date',
|
'starting_date',
|
||||||
'expiry_date',
|
'expiry_date',
|
||||||
'price_per_month',
|
'price_per_month',
|
||||||
|
'country',
|
||||||
|
'country_id',
|
||||||
|
# 'establishments',
|
||||||
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'type': {'write_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PartnersForEstablishmentSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.IntegerField(source='partner.pk', read_only=True)
|
||||||
|
name = serializers.CharField(source='partner.name', read_only=True)
|
||||||
|
type = serializers.IntegerField(source='partner.type', read_only=True)
|
||||||
|
type_display = serializers.CharField(source='partner.type_display', read_only=True)
|
||||||
|
starting_date = serializers.DateField(source='partner.starting_date', read_only=True)
|
||||||
|
expiry_date = serializers.DateField(source='partner.expiry_date', read_only=True)
|
||||||
|
price_per_month = serializers.DecimalField(source='partner.price_per_month', max_digits=10, decimal_places=2,
|
||||||
|
read_only=True)
|
||||||
|
country = CountrySimpleSerializer(read_only=True, source='partner.country')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PartnerToEstablishment
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'type_display',
|
||||||
|
'starting_date',
|
||||||
|
'expiry_date',
|
||||||
|
'price_per_month',
|
||||||
|
'country',
|
||||||
|
'url', # own field
|
||||||
|
'image', # own field
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
establishment_id = self.context['view'].kwargs['establishment_id']
|
||||||
|
partner_id = self.context['view'].kwargs['partner_id']
|
||||||
|
establishment = get_object_or_404(Establishment, pk=establishment_id)
|
||||||
|
partner = get_object_or_404(Partner, pk=partner_id)
|
||||||
|
instance = PartnerToEstablishment.objects.create(**validated_data,
|
||||||
|
establishment=establishment,
|
||||||
|
partner=partner)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerPicturesSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Partner
|
||||||
|
fields = (
|
||||||
|
'images',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ class PartnerSerializer(serializers.ModelSerializer):
|
||||||
model = models.Partner
|
model = models.Partner
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'image',
|
# 'image',
|
||||||
'url'
|
'url'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from partner.models import Partner
|
from partner.models import Partner, PartnerToEstablishment
|
||||||
from transfer.models import EstablishmentBacklinks
|
from transfer.models import EstablishmentBacklinks
|
||||||
from transfer.serializers.partner import PartnerSerializer
|
from transfer.serializers.partner import PartnerSerializer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,11 @@ app_name = 'partner'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.PartnerLstView.as_view(), name='partner-list-create'),
|
path('', views.PartnerLstView.as_view(), name='partner-list-create'),
|
||||||
path('<int:id>/', views.PartnerRUDView.as_view(), name='partner-rud'),
|
path('<int:id>/', views.PartnerRUDView.as_view(), name='partner-rud'),
|
||||||
|
path('for_establishment/<int:establishment_id>/', views.EstablishmentPartners.as_view(),
|
||||||
|
name='partners-for-establishment'),
|
||||||
|
path('pictures/<int:id>/', views.PartnerPicturesListView.as_view(), name='partner-pictures-get'),
|
||||||
|
path('bind/<int:partner_id>/<int:establishment_id>/', views.BindPartnerToEstablishmentView.as_view(),
|
||||||
|
name='bind-partner-to-establishment'),
|
||||||
|
path('unbind/<int:partner_id>/<int:establishment_id>/', views.UnbindPartnerFromEstablishmentView.as_view(),
|
||||||
|
name='unbind-partner-from-establishment'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,81 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
from partner.models import Partner
|
from partner import filters
|
||||||
|
from partner.models import Partner, PartnerToEstablishment
|
||||||
from partner.serializers import back as serializers
|
from partner.serializers import back as serializers
|
||||||
from utils.methods import get_permission_classes
|
from utils.methods import get_permission_classes
|
||||||
from utils.permissions import IsEstablishmentManager, IsEstablishmentAdministrator
|
from utils.permissions import IsEstablishmentManager, IsEstablishmentAdministrator
|
||||||
|
|
||||||
|
|
||||||
class PartnerLstView(generics.ListCreateAPIView):
|
class PartnerLstView(generics.ListCreateAPIView):
|
||||||
"""Partner list create view."""
|
"""Partner list/create view.
|
||||||
queryset = Partner.objects.all()
|
Allows to get partners for current country, or create a new one.
|
||||||
|
"""
|
||||||
|
queryset = Partner.objects.with_base_related()
|
||||||
serializer_class = serializers.BackPartnerSerializer
|
serializer_class = serializers.BackPartnerSerializer
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
IsEstablishmentAdministrator
|
IsEstablishmentAdministrator
|
||||||
)
|
)
|
||||||
filterset_fields = (
|
filter_class = filters.PartnerFilterSet
|
||||||
'establishment',
|
|
||||||
'type',
|
|
||||||
|
class EstablishmentPartners(generics.ListAPIView):
|
||||||
|
queryset = PartnerToEstablishment.objects.prefetch_related('partner', 'partner__country')
|
||||||
|
serializer_class = serializers.PartnersForEstablishmentSerializer
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
)
|
)
|
||||||
|
filter_backends = (OrderingFilter, DjangoFilterBackend)
|
||||||
|
ordering_fields = '__all__'
|
||||||
|
ordering = '-partner_bind_date'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(establishment=self.kwargs['establishment_id'])
|
||||||
|
|
||||||
|
|
||||||
class PartnerRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class PartnerRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Partner RUD view."""
|
"""Partner RUD view."""
|
||||||
queryset = Partner.objects.all()
|
queryset = Partner.objects.with_base_related()
|
||||||
serializer_class = serializers.BackPartnerSerializer
|
serializer_class = serializers.BackPartnerSerializer
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
permission_classes = get_permission_classes(
|
permission_classes = get_permission_classes(
|
||||||
IsEstablishmentManager,
|
IsEstablishmentManager,
|
||||||
IsEstablishmentAdministrator
|
IsEstablishmentAdministrator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerPicturesListView(generics.RetrieveAPIView):
|
||||||
|
lookup_field = 'id'
|
||||||
|
serializer_class = serializers.PartnerPicturesSerializer
|
||||||
|
queryset = Partner.objects.with_base_related()
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BindPartnerToEstablishmentView(generics.CreateAPIView):
|
||||||
|
serializer_class = serializers.PartnersForEstablishmentSerializer
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UnbindPartnerFromEstablishmentView(generics.DestroyAPIView):
|
||||||
|
serializer_class = serializers.PartnersForEstablishmentSerializer
|
||||||
|
permission_classes = get_permission_classes(
|
||||||
|
IsEstablishmentManager,
|
||||||
|
IsEstablishmentAdministrator
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return get_object_or_404(PartnerToEstablishment, establishment_id=self.kwargs['establishment_id'],
|
||||||
|
partner_id=self.kwargs['partner_id'])
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,8 @@ class EstablishmentDocument(Document):
|
||||||
'city': fields.ObjectField(
|
'city': fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.KeywordField(),
|
'name': fields.KeywordField(attr='name_dumped'),
|
||||||
|
'name_translated': fields.KeywordField(),
|
||||||
'is_island': fields.BooleanField(),
|
'is_island': fields.BooleanField(),
|
||||||
'country': fields.ObjectField(
|
'country': fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ class ProductDocument(Document):
|
||||||
attr='address.city',
|
attr='address.city',
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
'name': fields.KeywordField(),
|
'name': fields.KeywordField(attr='name_dumped'),
|
||||||
|
'name_translated': fields.KeywordField(),
|
||||||
'code': fields.KeywordField(),
|
'code': fields.KeywordField(),
|
||||||
'country': fields.ObjectField(
|
'country': fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,13 @@ class CityDocumentShortSerializer(serializers.Serializer):
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
code = serializers.CharField(allow_null=True)
|
code = serializers.CharField(allow_null=True)
|
||||||
name = serializers.CharField()
|
# todo: index and use name dict field
|
||||||
|
name_translated = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_name_translated(obj):
|
||||||
|
return get_translated_value(loads(obj.name))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CountryDocumentSerializer(serializers.Serializer):
|
class CountryDocumentSerializer(serializers.Serializer):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def update_document(sender, **kwargs):
|
||||||
|
|
||||||
app_label_model_name_to_filter = {
|
app_label_model_name_to_filter = {
|
||||||
('location', 'country'): 'address__city__country',
|
('location', 'country'): 'address__city__country',
|
||||||
('location', 'city'): 'address__city',
|
# ('location', 'city'): 'address__city',
|
||||||
('location', 'address'): 'address',
|
('location', 'address'): 'address',
|
||||||
# todo: remove after migration
|
# todo: remove after migration
|
||||||
('establishment', 'establishmenttype'): 'establishment_type',
|
('establishment', 'establishmenttype'): 'establishment_type',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
from partner.models import Partner
|
from partner.models import Partner, PartnerToEstablishment
|
||||||
|
|
||||||
|
|
||||||
class PartnerSerializer(serializers.Serializer):
|
class PartnerSerializer(serializers.Serializer):
|
||||||
|
|
@ -43,10 +43,32 @@ class PartnerSerializer(serializers.Serializer):
|
||||||
return establishment
|
return establishment
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
obj, _ = Partner.objects.update_or_create(
|
establishment = validated_data.pop('establishment')
|
||||||
old_id=validated_data['old_id'],
|
url = validated_data.pop('url')
|
||||||
defaults=validated_data,
|
image = validated_data.pop('image')
|
||||||
|
|
||||||
|
old_id = validated_data.pop('old_id')
|
||||||
|
created = validated_data.pop('created')
|
||||||
|
|
||||||
|
obj, is_created = Partner.objects.update_or_create(
|
||||||
|
# old_id=validated_data['old_id'],
|
||||||
|
**validated_data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
obj.old_id = old_id
|
||||||
|
obj.created = created
|
||||||
|
|
||||||
|
obj.establishment.add(establishment)
|
||||||
|
if is_created:
|
||||||
|
obj.images = [image]
|
||||||
|
elif image not in obj.images:
|
||||||
|
obj.images.append(image)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
p_t_e = PartnerToEstablishment.objects.filter(establishment=establishment, partner=obj).first()
|
||||||
|
p_t_e.url = url
|
||||||
|
p_t_e.image = image
|
||||||
|
p_t_e.save()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import string
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import pathlib
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -69,6 +69,15 @@ def username_random():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def file_path(instance, filename):
|
||||||
|
"""Determine file path method."""
|
||||||
|
filename = '%s.%s' % (generate_code(), pathlib.Path(filename).suffix.lstrip('.'))
|
||||||
|
return 'files/%s/%s/%s' % (
|
||||||
|
instance._meta.model_name,
|
||||||
|
datetime.now().strftime(settings.REST_DATE_FORMAT),
|
||||||
|
filename)
|
||||||
|
|
||||||
|
|
||||||
def image_path(instance, filename):
|
def image_path(instance, filename):
|
||||||
"""Determine avatar path method."""
|
"""Determine avatar path method."""
|
||||||
filename = '%s.jpeg' % generate_code()
|
filename = '%s.jpeg' % generate_code()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ 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
|
||||||
from django.contrib.postgres.fields.jsonb import KeyTextTransform
|
from django.contrib.postgres.fields.jsonb import KeyTextTransform
|
||||||
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _, get_language
|
from django.utils.translation import ugettext_lazy as _, get_language
|
||||||
|
|
@ -16,7 +17,7 @@ from sorl.thumbnail import get_thumbnail
|
||||||
from sorl.thumbnail.fields import ImageField as SORLImageField
|
from sorl.thumbnail.fields import ImageField as SORLImageField
|
||||||
|
|
||||||
from configuration.models import TranslationSettings
|
from configuration.models import TranslationSettings
|
||||||
from utils.methods import image_path, svg_image_path
|
from utils.methods import image_path, svg_image_path, file_path
|
||||||
from utils.validators import svg_image_validator
|
from utils.validators import svg_image_validator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -86,6 +87,7 @@ def translate_field(self, field_name, toggle_field_name=None):
|
||||||
return None
|
return None
|
||||||
return value
|
return value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return translate
|
return translate
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -159,6 +161,33 @@ class BaseAttributes(ProjectBaseMixin):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class FileMixin(models.Model):
|
||||||
|
"""File model."""
|
||||||
|
|
||||||
|
file = models.FileField(upload_to=file_path,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('File'),
|
||||||
|
validators=[FileExtensionValidator(
|
||||||
|
allowed_extensions=('jpg', 'jpeg', 'png', 'doc', 'docx', 'pdf')
|
||||||
|
)])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def get_file_url(self):
|
||||||
|
"""Get file url."""
|
||||||
|
return self.file.url if self.file else None
|
||||||
|
|
||||||
|
def get_full_file_url(self, request):
|
||||||
|
"""Get full file url"""
|
||||||
|
if self.file and exists(self.file.path):
|
||||||
|
return request.build_absolute_uri(self.file.url)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ImageMixin(models.Model):
|
class ImageMixin(models.Model):
|
||||||
"""Avatar model."""
|
"""Avatar model."""
|
||||||
|
|
||||||
|
|
@ -230,7 +259,7 @@ class SORLImageMixin(models.Model):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_cropped_image(self, geometry: str, quality: int, cropbox:str) -> dict:
|
def get_cropped_image(self, geometry: str, quality: int, cropbox: str) -> dict:
|
||||||
cropped_image = get_thumbnail(self.image,
|
cropped_image = get_thumbnail(self.image,
|
||||||
geometry_string=geometry,
|
geometry_string=geometry,
|
||||||
# crop=crop,
|
# crop=crop,
|
||||||
|
|
|
||||||
BIN
project/locale/de/LC_MESSAGES/django.mo
Normal file
BIN
project/locale/de/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
BIN
project/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
project/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
project/locale/ru/LC_MESSAGES/django.mo
Normal file
BIN
project/locale/ru/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user