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:
Anatoly 2020-01-30 10:56:46 +03:00
commit 25c63b8097
66 changed files with 5754 additions and 2602 deletions

View File

@ -15,6 +15,8 @@ from django.utils.http import urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from rest_framework.authtoken.models import Token
from collections import Counter
from typing import List
from authorization.models import Application
from establishment.models import Establishment, EstablishmentSubType
@ -475,6 +477,15 @@ class User(AbstractUser):
.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):
"""QuerySet for model UserRole."""

View File

@ -19,7 +19,7 @@ class _SiteSettingsSerializer(serializers.ModelSerializer):
class BackUserSerializer(UserSerializer):
last_country = _SiteSettingsSerializer(read_only=True)
roles = RoleBaseSerializer(many=True, read_only=True)
roles = RoleBaseSerializer(many=True)
class Meta(UserSerializer.Meta):
fields = (
@ -115,6 +115,7 @@ class BackDetailUserSerializer(BackUserSerializer):
def create(self, validated_data):
subscriptions_list = []
if 'subscription_types' in validated_data:
subscriptions_list = validated_data.pop('subscription_types')
@ -127,11 +128,17 @@ class BackDetailUserSerializer(BackUserSerializer):
def update(self, instance, validated_data):
subscriptions_list = []
if 'subscription_types' in validated_data:
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)
subscriptions_handler(subscriptions_list, instance)
return instance

View File

@ -8,6 +8,7 @@ from rest_framework import serializers
from rest_framework import validators as rest_validators
from account import models, tasks
from account.models import User, Role
from main.serializers.common import NavigationBarPermissionBaseSerializer
from notification.models import Subscribe, Subscriber
from utils import exceptions as utils_exceptions
@ -27,7 +28,7 @@ def subscriptions_handler(subscriptions_list, user):
'user': user,
'email': user.email,
'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,
'update_code': generate_string_code(),
}
@ -42,6 +43,7 @@ def subscriptions_handler(subscriptions_list, user):
class RoleBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Role."""
id = serializers.IntegerField()
role_display = serializers.CharField(source='get_role_display', read_only=True)
navigation_bar_permission = NavigationBarPermissionBaseSerializer(read_only=True)
country_code = serializers.CharField(source='country.code', read_only=True, allow_null=True)
@ -87,6 +89,7 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = [
'id',
'username',
'first_name',
'last_name',
@ -122,12 +125,6 @@ class UserSerializer(serializers.ModelSerializer):
subscriptions_handler(subscriptions_list, 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):
"""Custom username validation"""
valid = utils_methods.username_validator(username=value)
@ -138,15 +135,24 @@ class UserSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
"""Override update method"""
subscriptions_list = []
if 'subscription_types' in validated_data:
subscriptions_list = validated_data.pop('subscription_types')
new_email = validated_data.get('email')
old_email = instance.email
instance = super().update(instance, validated_data)
if 'email' in validated_data:
request = self.context['request']
user = request.user
if not user.is_superuser and not user.is_staff and \
not user.roles.filter(country__code=request.country_code, role=models.Role.COUNTRY_ADMIN).exists():
"""
superuser or country admin changes email immediately!
"""
if new_email and new_email != old_email:
instance.email_confirmed = False
instance.email = old_email
instance.unconfirmed_email = validated_data['email']
instance.unconfirmed_email = new_email
instance.save()
# Send verification link on user email for change email address
if settings.USE_CELERY:
@ -161,6 +167,7 @@ class UserSerializer(serializers.ModelSerializer):
emails=[validated_data['email'], ])
subscriptions_handler(subscriptions_list, instance)
return instance
@ -195,6 +202,7 @@ class UserShortSerializer(UserSerializer):
'id',
'fullname',
'email',
'username',
]

View File

@ -17,7 +17,7 @@ from utils.views import JWTGenericViewMixin
# User views
class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
class UserRetrieveUpdateView(generics.RetrieveUpdateDestroyAPIView):
"""User update view."""
serializer_class = serializers.UserSerializer
queryset = models.User.objects.active()
@ -25,6 +25,10 @@ class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
def get_object(self):
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):
"""Change password view"""

View 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

View File

@ -57,6 +57,10 @@ class CollectionQuerySet(RelatedObjectsCountMixin):
"""Returned only published collection"""
return self.filter(is_publish=True)
def with_base_related(self):
"""Select relate objects"""
return self.select_related('country')
class Collection(ProjectBaseMixin, CollectionDateMixin,
TranslatedFieldsMixin, URLImageMixin):
@ -106,7 +110,7 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
"""Return list of related objects."""
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)
return related_objects
@ -167,17 +171,26 @@ class GuideQuerySet(models.QuerySet):
"""Return QuerySet with related."""
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):
"""Return QuerySet filtered by country code."""
return self.filter(country_json__id__contains=country_id)
def annotate_in_restaurant_section(self):
"""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(
in_restaurant_section=models.Case(
models.When(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
id__in=restaurant_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
@ -186,11 +199,16 @@ class GuideQuerySet(models.QuerySet):
def annotate_in_shop_section(self):
"""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(
in_shop_section=models.Case(
models.When(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='ShopSectionNode',
id__in=shop_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
@ -201,37 +219,60 @@ class GuideQuerySet(models.QuerySet):
"""Return QuerySet with annotated field - restaurant_counter."""
return self.annotate_in_restaurant_section().annotate(
restaurant_counter=models.Count(
'guideelement',
'guideelement__establishment',
filter=models.Q(in_restaurant_section=True) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_shop_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_shop_section().annotate(
shop_counter=models.Count(
'guideelement',
'guideelement__establishment',
filter=models.Q(in_shop_section=True) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_wine_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_restaurant_section().annotate(
wine_counter=models.Count(
'guideelement',
'guideelement__product',
filter=models.Q(guideelement__guide_element_type__name='WineNode') &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
distinct=True
)
)
def annotate_present_objects_counter(self):
"""Return QuerySet with annotated field - present_objects_counter."""
return self.annotate_in_restaurant_section().annotate(
present_objects_counter=models.Count(
'guideelement',
filter=models.Q(guideelement__guide_element_type__name__in=['EstablishmentNode', 'WineNode']) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
return (
self.annotate_restaurant_counter()
.annotate_shop_counter()
.annotate_wine_counter()
.annotate(
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):

View File

@ -109,6 +109,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
restaurant_counter = serializers.IntegerField(read_only=True)
shop_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,
source='count_related_objects')
@ -131,6 +132,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
'restaurant_counter',
'shop_counter',
'wine_counter',
'present_objects_counter',
'count_objects_during_init',
]
extra_kwargs = {

View File

@ -1,14 +1,11 @@
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics
from rest_framework import mixins, permissions, viewsets
from rest_framework import status
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response
from collection import models, serializers
from collection import tasks
from collection import models, serializers, filters, tasks
from utils.methods import get_permission_classes
from utils.views import BindObjectMixin
@ -22,7 +19,7 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
def get_queryset(self):
"""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:
qs = qs.by_country_code(self.request.country_code)
return qs
@ -35,12 +32,7 @@ class GuideBaseView(generics.GenericAPIView):
def get_queryset(self):
"""Overridden get_queryset method."""
return models.Guide.objects.with_base_related() \
.annotate_restaurant_counter() \
.annotate_shop_counter() \
.annotate_wine_counter() \
.annotate_present_objects_counter() \
.distinct()
return models.Guide.objects.with_extended_related().annotate_counters()
class GuideFilterBaseView(generics.GenericAPIView):
@ -73,17 +65,14 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
BindObjectMixin,
CollectionViewSet):
"""ViewSet for Collection model for BackOffice users."""
"""ViewSet for Collections list for BackOffice users and Collection create."""
queryset = models.Collection.objects.all()
filter_backends = [DjangoFilterBackend, OrderingFilter]
queryset = models.Collection.objects.with_base_related().order_by('-start')
filter_class = filters.CollectionFilterSet
serializer_class = serializers.CollectionBackOfficeSerializer
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
permission_classes = get_permission_classes()
ordering_fields = ('rank', 'start')
ordering = ('-start', )
def perform_binding(self, serializer):
data = serializer.validated_data
collection = data.pop('collection')
@ -107,7 +96,8 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
class GuideListCreateView(GuideBaseView,
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,

View File

@ -48,6 +48,9 @@ class CommentQuerySet(ContentTypeQuerySetMixin):
qs = self.filter(id__in=tuple(waiting_ids))
return qs
def with_base_related(self):
return self.prefetch_related("content_object").select_related("user", "content_type")
class Comment(ProjectBaseMixin):
"""Comment model."""

View File

@ -1,9 +1,7 @@
"""Common serializers for app comment."""
from rest_framework import serializers
import establishment.serializers.common as establishment_serializers
from comment.models import Comment
from establishment.models import EstablishmentType
class CommentBaseSerializer(serializers.ModelSerializer):
@ -22,6 +20,8 @@ class CommentBaseSerializer(serializers.ModelSerializer):
user_email = serializers.CharField(read_only=True, source='user.email')
slug = serializers.CharField(read_only=True, source='content_object.slug')
class Meta:
"""Serializer for model Comment"""
model = Comment
@ -39,13 +39,20 @@ class CommentBaseSerializer(serializers.ModelSerializer):
'status_display',
'last_ip',
'content_type',
'content_name'
'content_name',
'slug',
]
extra_kwargs = {
# 'status': {'read_only': True},
}
def get_content_type(self, instance: Comment):
import establishment.serializers.common as establishment_serializers
from establishment.models import EstablishmentType, Establishment
from product.models import Product
from product.serializers import ProductTypeBaseSerializer
if isinstance(instance.content_object, Establishment):
if instance.content_object.establishment_type == EstablishmentType.PRODUCER:
return establishment_serializers.EstablishmentSubTypeBaseSerializer(
instance.content_object.establishment_subtypes, many=True
@ -54,3 +61,7 @@ class CommentBaseSerializer(serializers.ModelSerializer):
return establishment_serializers.EstablishmentTypeBaseSerializer(
instance.content_object.establishment_type
).data
if isinstance(instance.content_object, Product):
return ProductTypeBaseSerializer(
instance.content_object.product_type
).data

View File

@ -8,4 +8,6 @@ app_name = 'comment'
urlpatterns = [
path('', views.CommentLstView.as_view(), name='comment-list-create'),
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'),
]

View File

@ -8,6 +8,26 @@ from utils.permissions import IsModerator
class CommentLstView(generics.ListCreateAPIView):
"""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
queryset = models.Comment.objects.all()
permission_classes = get_permission_classes(IsModerator)

View File

@ -1,8 +1,7 @@
"""Establishment app filters."""
from django.core.validators import EMPTY_VALUES
from django.utils.translation import ugettext_lazy as _
from django_filters import rest_framework as filters, Filter
from django_filters.fields import Lookup
from django_filters import rest_framework as filters
from rest_framework.serializers import ValidationError
from establishment import models

View 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')},
},
),
]

View 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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View File

@ -33,7 +33,7 @@ from utils.methods import transform_into_readable_str
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
TranslatedFieldsMixin, BaseAttributes, GalleryMixin,
IntermediateGalleryModelMixin, HasTagsMixin,
FavoritesMixin, TypeDefaultImageMixin)
FavoritesMixin, TypeDefaultImageMixin, FileMixin)
# todo: establishment type&subtypes check
@ -541,6 +541,9 @@ class EstablishmentQuerySet(models.QuerySet):
self.filter(id__in=administrator_establishment_ids)
)
def with_contacts(self):
return self.prefetch_related('emails', 'phones')
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
@ -552,6 +555,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
name = models.CharField(_('name'), max_length=255, default='')
transliterated_name = models.CharField(default='', max_length=255,
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='')
description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'),
@ -583,6 +588,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
verbose_name=_('Facebook URL'))
twitter = models.URLField(blank=True, null=True, default=None, max_length=255,
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,
verbose_name=_('Lafourchette URL'))
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)
current_week = now_at_est_tz.weekday()
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
time_at_est_tz = now_at_est_tz.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
def tags_indexing(self):
return [{'id': tag.metadata.id,
'label': tag.metadata.label} for tag in self.tags.all()]
return [{
'id': tag.metadata.id,
'label': tag.metadata.label
} for tag in self.tags.all()]
@property
def last_published_review(self):
@ -837,6 +847,11 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
if self.phones:
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
def establishment_subtype_labels(self):
if self.establishment_subtypes:
@ -1249,6 +1264,24 @@ class Plate(TranslatedFieldsMixin, models.Model):
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):
def with_schedule_plates_establishment(self):
return self.select_related(
@ -1258,6 +1291,11 @@ class MenuQuerySet(models.QuerySet):
'plates',
)
def with_gallery(self):
return self.prefetch_related(
'menu_gallery'
)
def dishes(self):
return self.filter(
Q(category__icontains='starter') |
@ -1269,25 +1307,38 @@ class MenuQuerySet(models.QuerySet):
"""Search by category."""
return self.filter(category__icontains=value)
def with_dishes(self):
return self.filter(~Q(dishes=None))
class Menu(TranslatedFieldsMixin, BaseAttributes):
"""Menu model."""
STR_FIELD_NAME = 'category'
category = TJSONField(
blank=True, null=True, default=None, verbose_name=_('category'),
help_text='{"en-GB":"some text"}')
name = models.CharField(_('name'), max_length=255, default='')
establishment = models.ForeignKey(
'establishment.Establishment', verbose_name=_('establishment'),
on_delete=models.CASCADE)
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(
to='timetable.Timetable',
blank=True,
verbose_name=_('Establishment schedule'),
related_name='menus',
verbose_name=_('Menu schedule'),
)
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)
objects = MenuQuerySet.as_manager()
@ -1298,15 +1349,16 @@ class Menu(TranslatedFieldsMixin, BaseAttributes):
ordering = ('-created',)
class MenuUploads(BaseAttributes):
class MenuFiles(FileMixin, BaseAttributes):
"""Menu files"""
menu = models.ForeignKey(Menu, verbose_name=_('Menu'), on_delete=models.CASCADE, related_name='uploads')
file = models.FileField(
_('File'),
validators=[FileExtensionValidator(allowed_extensions=('jpg', 'jpeg', 'png', 'doc', 'docx', 'pdf')), ],
TYPES = (
('image', 'Image'),
('file', 'File')
)
name = models.CharField(_('name'), max_length=255, default='')
type = models.CharField(_('type'), choices=TYPES, max_length=65, default='')
class Meta:
verbose_name = _('menu upload')
verbose_name_plural = _('menu uploads')

View File

@ -1,20 +1,22 @@
from functools import lru_cache
from pprint import pprint
from django.db.models import F
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from account.serializers.common import UserShortSerializer
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 location.models import Address
from location.serializers import AddressDetailSerializer, TranslatedField
from main.models import Currency
from main.serializers import AwardSerializer
from timetable.serialziers import ScheduleRUDSerializer
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):
@ -27,6 +29,16 @@ def phones_handler(phones_list, establishment):
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):
"""Establishment create serializer"""
@ -40,14 +52,14 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
queryset=models.Address.objects.all(),
write_only=True
)
emails = model_serializers.ContactEmailsSerializer(read_only=True,
many=True, )
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
many=True, )
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type',
read_only=True)
address_id = serializers.PrimaryKeyRelatedField(write_only=True, source='address',
queryset=Address.objects.all())
socials = model_serializers.SocialNetworkRelatedSerializers(
read_only=True,
many=True,
)
type = model_serializers.EstablishmentTypeBaseSerializer(
source='establishment_type',
read_only=True,
)
tz = TimeZoneChoiceField()
phones = serializers.ListField(
source='contact_phones',
@ -56,6 +68,13 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
child=serializers.CharField(max_length=128),
required=False,
)
emails = serializers.ListField(
source='contact_emails',
allow_null=True,
allow_empty=True,
child=serializers.CharField(max_length=128),
required=False,
)
class Meta:
model = models.Establishment
@ -81,6 +100,7 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
'tags',
'tz',
'address_id',
'western_name',
]
def create(self, validated_data):
@ -88,11 +108,29 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
if 'contact_phones' in validated_data:
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)
phones_handler(phones_list, instance)
emails_handler(emails_list, 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):
"""Establishment create serializer"""
@ -100,9 +138,14 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
source='establishment_type',
queryset=models.EstablishmentType.objects.all(), write_only=True
)
address = AddressDetailSerializer()
emails = model_serializers.ContactEmailsSerializer(read_only=False,
many=True, )
address = AddressDetailSerializer(read_only=True)
emails = serializers.ListField(
source='contact_emails',
allow_null=True,
allow_empty=True,
child=serializers.CharField(max_length=128),
required=False,
)
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
many=True, )
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
@ -119,6 +162,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
fields = [
'id',
'slug',
'western_name',
'name',
'website',
'phones',
@ -132,6 +176,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
# TODO: check in admin filters
'is_publish',
'address',
'transportation',
'tags',
]
@ -140,8 +185,13 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
if 'contact_phones' in validated_data:
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)
phones_handler(phones_list, instance)
emails_handler(emails_list, instance)
return instance
@ -516,52 +566,79 @@ class EstablishmentAdminListSerializer(UserShortSerializer):
]
class _PlateSerializer(ProjectModelSerializer):
name_translated = TranslatedField()
class EstablishmentEmployeePositionsSerializer(serializers.ModelSerializer):
"""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:
model = models.Plate
model = models.EstablishmentEmployee
fields = [
'name_translated',
'price',
'restaurant_name',
'position',
'state',
'start',
'end',
]
class MenuDishesSerializer(ProjectModelSerializer):
"""for dessert, main_course and starter category"""
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
category_translated = serializers.CharField(read_only=True)
last_update = serializers.DateTimeField(source='created')
establishment_id = serializers.PrimaryKeyRelatedField(read_only=True)
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
dishes = MenuDishSerializer(many=True, read_only=True)
class Meta:
model = models.Menu
fields = [
'id',
'category',
'category_translated',
'establishment',
'is_drinks_included',
'schedule',
'plates',
'last_update',
'establishment_id',
'establishment_slug',
'dishes',
]
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):
"""for dessert, main_course and starter category"""
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
schedule = ScheduleRUDSerializer(read_only=True, many=True, allow_null=True)
establishment_id = serializers.PrimaryKeyRelatedField(queryset=models.Establishment.objects.all())
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
dishes = MenuDishSerializer(many=True, read_only=True)
class Meta:
model = models.Menu
fields = [
'id',
'category',
'plates',
'establishment',
'is_drinks_included',
'schedule',
'establishment_id',
'establishment_slug',
'dishes',
]

View File

@ -2,6 +2,7 @@
import logging
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
from rest_framework import serializers
@ -9,18 +10,19 @@ from rest_framework import serializers
from comment import models as comment_models
from comment.serializers import common as comment_serializers
from establishment import models
from location.serializers import AddressBaseSerializer, CityBaseSerializer, AddressDetailSerializer, \
CityShortSerializer
from location.serializers import EstablishmentWineRegionBaseSerializer, \
EstablishmentWineOriginBaseSerializer
from location.serializers import (
AddressBaseSerializer, AddressDetailSerializer, CityBaseSerializer,
CityShortSerializer, EstablishmentWineOriginBaseSerializer, EstablishmentWineRegionBaseSerializer,
)
from main.serializers import AwardSerializer, CurrencySerializer
from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer
from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
from utils.serializers import (ProjectModelSerializer, TranslatedField,
FavoritesCreateSerializer)
from utils.serializers import (
CarouselCreateSerializer, FavoritesCreateSerializer, ImageBaseSerializer,
ProjectModelSerializer, TranslatedField,
)
logger = logging.getLogger(__name__)
@ -70,31 +72,87 @@ class PlateSerializer(ProjectModelSerializer):
]
class MenuSerializers(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category_translated = serializers.CharField(read_only=True)
class MenuDishSerializer(ProjectModelSerializer):
class Meta:
model = models.Menu
model = models.MenuDish
fields = [
'id',
'name',
'category',
'category_translated',
'plates',
'establishment'
'price',
'signature'
]
class MenuRUDSerializers(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
class MenuFilesSerializers(ProjectModelSerializer):
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:
model = models.Menu
fields = [
'id',
'category',
'plates',
'establishment'
'name',
'establishment_id',
'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',
'facebook',
'twitter',
'instagram',
'lafourchette',
'booking',
'phones',
@ -527,7 +586,7 @@ class EstablishmentCommentBaseSerializer(comment_serializers.CommentBaseSerializ
'created',
'text',
'mark',
'nickname',
# 'nickname',
'profile_pic',
'status',
'status_display',
@ -567,7 +626,7 @@ class EstablishmentCommentRUDSerializer(comment_serializers.CommentBaseSerialize
'created',
'text',
'mark',
'nickname',
# 'nickname',
'profile_pic',
]

View File

@ -30,10 +30,15 @@ urlpatterns = [
name='note-rud'),
path('slug/<slug:slug>/admin/', views.EstablishmentAdminView.as_view(),
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/slug/<slug:slug>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
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/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
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/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
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(),
name='employee-establishments-list')
name='employee-establishments-list'),
path('employee_establishment_positions/<int:pk>/', views.EmployeeEstablishmentPositionsView.as_view(),
name='employee-establishment-positions')
]

View File

@ -1,7 +1,9 @@
"""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_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 account.models import User
@ -16,6 +18,20 @@ from utils.permissions import (
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:
"""Establishment mixin."""
@ -34,12 +50,27 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
filter_class = filters.EstablishmentFilter
serializer_class = serializers.EstablishmentListCreateSerializer
permission_classes = get_permission_classes(
IsReviewManager,
IsEstablishmentManager,
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):
"""Establishment by employee list view."""
serializer_class = serializers.EstablishmentListCreateSerializer
@ -54,6 +85,22 @@ class EmployeeEstablishmentsListView(generics.ListAPIView):
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):
lookup_field = 'slug'
serializer_class = serializers.EstablishmentRUDSerializer
@ -120,13 +167,17 @@ class MenuListCreateView(generics.ListCreateAPIView):
IsEstablishmentAdministrator,
)
filterset_fields = (
'establishment',
'establishment__id',
'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."""
lookup_field = None
serializer_class = serializers.MenuRUDSerializers
queryset = models.Menu.objects.all()
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):
"""SocialChoice list create view."""
serializer_class = serializers.SocialChoiceSerializers
@ -523,10 +594,10 @@ class EstablishmentAdminView(generics.ListAPIView):
return User.objects.establishment_admin(establishment).distinct()
class MenuDishesListCreateView(generics.ListCreateAPIView):
class MenuDishesListView(generics.ListAPIView):
"""Menu (dessert, main_course, starter) list create view."""
serializer_class = serializers.MenuDishesSerializer
queryset = models.Menu.objects.with_schedule_plates_establishment().dishes().distinct()
queryset = models.Menu.objects.with_dishes()
filter_class = filters.MenuDishesBackFilter
permission_classes = get_permission_classes(
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."""
lookup_field = None
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(
IsEstablishmentManager,
IsEstablishmentAdministrator

View 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',
)
]

View 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'),
),
]

View File

@ -1,20 +1,20 @@
"""Location app models."""
from functools import reduce
from json import dumps
from typing import List
from django.conf import settings
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.transaction import on_commit
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from functools import reduce
from typing import List
from django.contrib.postgres.fields.jsonb import KeyTextTransform
from django.contrib.postgres.fields import ArrayField
from translation.models import Language
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
TranslatedFieldsMixin, get_current_locale,
IntermediateGalleryModelMixin, GalleryMixin)
IntermediateGalleryModelMixin)
class CountryQuerySet(models.QuerySet):
@ -139,13 +139,14 @@ class CityQuerySet(models.QuerySet):
"""Return establishments by country 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."""
name = models.CharField(_('name'), max_length=250)
name_translated = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Translated name'),
help_text='{"en-GB":"some text"}')
name = TJSONField(default=None, null=True, help_text='{"en-GB":"some city name"}',
verbose_name=_('City name json'))
code = models.CharField(_('code'), max_length=250)
region = models.ForeignKey(Region, on_delete=models.CASCADE,
blank=True, null=True,
@ -177,7 +178,12 @@ class City(models.Model):
verbose_name = _('city')
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
def image_object(self):
@ -217,6 +223,8 @@ class Address(models.Model):
default='', help_text=_('Ex.: 350018'))
coordinates = models.PointField(
_('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)
class Meta:

View File

@ -1,6 +1,8 @@
from location import models
from location.serializers import common
from utils.serializers import TranslatedField
class AddressCreateSerializer(common.AddressDetailSerializer):
"""Address create serializer."""
@ -9,6 +11,8 @@ class AddressCreateSerializer(common.AddressDetailSerializer):
class CountryBackSerializer(common.CountrySerializer):
"""Country back-office serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Country
fields = [
@ -16,5 +20,6 @@ class CountryBackSerializer(common.CountrySerializer):
'code',
'svg_image',
'name',
'name_translated',
'country_id'
]

View File

@ -34,6 +34,27 @@ class CountrySimpleSerializer(serializers.ModelSerializer):
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):
"""Region serializer"""
@ -43,6 +64,7 @@ class RegionSerializer(serializers.ModelSerializer):
queryset=models.Country.objects.all(),
write_only=True
)
parent_region = ParentRegionSerializer(read_only=True)
class Meta:
model = models.Region
@ -59,13 +81,14 @@ class RegionSerializer(serializers.ModelSerializer):
class CityShortSerializer(serializers.ModelSerializer):
"""Short city serializer"""
country = CountrySerializer(read_only=True)
name_translated = TranslatedField()
class Meta:
"""Meta class"""
model = models.City
fields = (
'id',
'name',
'name_translated',
'code',
'country',
)
@ -91,12 +114,14 @@ class CityBaseSerializer(serializers.ModelSerializer):
required=False,
)
country = CountrySerializer(read_only=True)
name_translated = TranslatedField()
class Meta:
model = models.City
fields = [
'id',
'name',
'name_translated',
'region',
'region_id',
'country_id',
@ -146,6 +171,7 @@ class AddressBaseSerializer(serializers.ModelSerializer):
'postal_code',
'latitude',
'longitude',
'district_name',
# todo: remove this fields (backward compatibility)
'geo_lon',

View File

@ -50,8 +50,8 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
def get_queryset(self):
"""Overridden method 'get_queryset'."""
qs = models.City.objects.all().annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\
.order_by('locale_name')
qs = models.City.objects.all().annotate(locale_name=KeyTextTransform(get_current_locale(), 'name'))\
.order_by('locale_name').with_base_related()
if self.request.country_code:
qs = qs.by_country_code(self.request.country_code)
return qs
@ -61,7 +61,7 @@ class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City."""
serializer_class = serializers.CityBaseSerializer
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')
filter_class = filters.CityBackFilter
pagination_class = None

View File

@ -24,7 +24,7 @@ class RegionViewMixin(generics.GenericAPIView):
class CityViewMixin(generics.GenericAPIView):
"""View Mixin for model City"""
model = models.City
queryset = models.City.objects.all()
queryset = models.City.objects.with_base_related()
class AddressViewMixin(generics.GenericAPIView):
@ -101,7 +101,7 @@ class CityListView(CityViewMixin, generics.ListAPIView):
def get_queryset(self):
qs = super().get_queryset()
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

View File

@ -24,6 +24,8 @@ class NewsListFilterSet(filters.FilterSet):
state = filters.NumberFilter()
state__in = filters.CharFilter(method='by_states_list')
SORT_BY_CREATED_CHOICE = "created"
SORT_BY_START_CHOICE = "start"
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
def by_states_list(self, queryset, name, value):
states = value.split('__')
return queryset.filter(state__in=states)
def in_tags(self, queryset, name, value):
tags = value.split('__')
return queryset.filter(tags__value__in=tags)

View 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'),
),
]

View File

@ -101,6 +101,10 @@ class NewsQuerySet(TranslationQuerysetMixin):
"""Return qs with related objects."""
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):
"""Filter News by 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)). \
filter(models.Q(models.Q(end__gte=now) |
models.Q(end__isnull=True)),
state__in=self.model.PUBLISHED_STATES, publication_date__lte=date_now,
publication_time__lte=time_now)
state__in=self.model.PUBLISHED_STATES)\
.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 country?
def should_read(self, news, user):
return self.model.objects.exclude(pk=news.pk).published(). \
annotate_in_favorites(user). \
filter(country=news.country). \
with_base_related().by_type(news.news_type).distinct().order_by('?')
def same_theme(self, news, user):
@ -252,18 +263,18 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
)
# STATE CHOICES
WAITING = 0
REMOVE = 0
HIDDEN = 1
PUBLISHED = 2
PUBLISHED_EXCLUSIVE = 3
UNPUBLISHED = 3
PUBLISHED_STATES = [PUBLISHED, PUBLISHED_EXCLUSIVE]
PUBLISHED_STATES = [PUBLISHED]
STATE_CHOICES = (
(WAITING, _('Waiting')),
(HIDDEN, _('Hidden')),
(PUBLISHED, _('Published')),
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
(REMOVE, _('remove')), # simply stored in DB news. not shown anywhere
(HIDDEN, _('hidden')), # not shown in api/web or api/mobile
(PUBLISHED, _('published')), # shown everywhere
(UNPUBLISHED, _('not published')), # newly created news
)
INTERNATIONAL_TAG_VALUE = 'international'
@ -295,7 +306,7 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
slugs = HStoreField(null=True, blank=True, default=dict,
verbose_name=_('Slugs for current news obj'),
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'))
is_highlighted = models.BooleanField(default=False,
verbose_name=_('Is highlighted'))
@ -340,7 +351,7 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
def create_duplicate(self, new_country, view_count_model):
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.country = new_country
self.views_count = view_count_model

View File

@ -155,6 +155,9 @@ class NewsDetailSerializer(NewsBaseSerializer):
'gallery',
)
def update(self, instance, validated_data):
return super().update(instance, validated_data)
class NewsDetailWebSerializer(NewsDetailSerializer):
"""News detail serializer for web users.."""
@ -207,7 +210,8 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer."""
is_published = serializers.BooleanField(source='is_publish', read_only=True)
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):
"""Meta class."""
@ -226,7 +230,9 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
'created',
'modified',
'descriptions',
'agenda'
'agenda',
'state',
'state_display',
)
extra_kwargs = {
'created': {'read_only': True},
@ -234,6 +240,8 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
'duplication_date': {'read_only': True},
'locale_to_description_is_active': {'allow_null': False},
'must_of_the_week': {'read_only': True},
# 'state': {'read_only': True},
'state_display': {'read_only': True},
}
def validate(self, attrs):
@ -255,7 +263,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
instance = models.News.objects.get(pk=self.context['request'].data['id'])
for key in ['slugs', 'title', 'locale_to_description_is_active', 'description']:
for locale in locales:
if not attrs[key].get(locale):
if locale not in attrs[key]:
attrs[key][locale] = getattr(instance, key).get(locale)
return attrs
@ -299,7 +307,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
slugs_set = set(slugs_list)
if models.News.objects.filter(
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')})
agenda_data = validated_data.get('agenda')
@ -357,8 +365,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
template_display = serializers.CharField(source='get_template_display',
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."""
fields = NewsBackOfficeBaseSerializer.Meta.fields + \
@ -373,6 +382,13 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
'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):
"""Serializer class for model NewsGallery."""
@ -503,3 +519,8 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer,
view_count_model = rating_models.ViewCount.objects.create(count=0)
instance.create_duplicate(new_country, view_count_model)
return get_object_or_404(models.News, pk=kwargs['pk'])
class NewsStatesSerializer(serializers.Serializer):
value = serializers.IntegerField()
state_translated = serializers.CharField()

View File

@ -8,6 +8,7 @@ app_name = 'news'
urlpatterns = [
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>/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),

View File

@ -26,6 +26,7 @@ class NewsMixinView:
"""Override get_queryset method."""
qs = models.News.objects.published() \
.with_base_related() \
.visible() \
.annotate_in_favorites(self.request.user) \
.order_by('-is_highlighted', '-publication_date', '-publication_time')
@ -43,7 +44,7 @@ class NewsMixinView:
return qs
def get_object(self):
instance = self.get_queryset().filter(
instance = self.get_queryset().visible().with_base_related().filter(
slugs__values__contains=[self.kwargs['slug']]
).first()
@ -53,6 +54,23 @@ class NewsMixinView:
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):
"""News list view."""
@ -137,7 +155,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
def get_queryset(self):
"""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:
self.request.GET._mutable = True
if '-publication_datetime' in self.request.query_params['ordering']:

View 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'),
),
]

View File

@ -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',
),
]

View File

@ -2,6 +2,7 @@
from django.conf import settings
from django.db import models
from django.db.models import F
from django.utils.timezone import now
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 utils.methods import generate_string_code
from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin
from notification.tasks import send_unsubscribe_email
class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin):
@ -131,14 +133,23 @@ class Subscriber(ProjectBaseMixin):
self.update_code = generate_string_code()
return super(Subscriber, self).save(*args, **kwargs)
def unsubscribe(self, query: dict):
def unsubscribe(self):
"""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:
send_unsubscribe_email.delay(self.email)
send_unsubscribe_email.delay(self.pk)
else:
send_unsubscribe_email(self.email)
send_unsubscribe_email(self.pk)
@property
def send_to(self):
@ -159,6 +170,10 @@ class Subscriber(ProjectBaseMixin):
def active_subscriptions(self):
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):
@ -166,18 +181,26 @@ class SubscribeQuerySet(models.QuerySet):
"""Fetches active subscriptions."""
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):
"""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)
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE)
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE)
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, null=True)
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()
@property
def subscribe_date(self):
return self.created
class Meta:
"""Meta class."""

View File

@ -40,6 +40,7 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
model = models.Subscriber
fields = (
'id',
'email',
'subscription_types',
'link_to_unsubscribe',
@ -54,7 +55,7 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
user = request.user
# validate email
email = attrs.get('send_to')
email = attrs.pop('send_to')
if attrs.get('email'):
email = attrs.get('email')
@ -89,12 +90,20 @@ class CreateAndUpdateSubscribeSerializer(serializers.ModelSerializer):
subscriber = models.Subscriber.objects.make_subscriber(**validated_data)
if settings.USE_CELERY:
send_subscribes_update_email.delay(subscriber.email)
send_subscribes_update_email.delay(subscriber.pk)
else:
send_subscribes_update_email(subscriber.email)
send_subscribes_update_email(subscriber.pk)
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):
"""Update with code Subscribe serializer."""
@ -141,14 +150,27 @@ class UpdateSubscribeSerializer(serializers.ModelSerializer):
class SubscribeObjectSerializer(serializers.ModelSerializer):
"""Subscribe serializer."""
"""Subscription type serializer."""
subscription_type = serializers.SerializerMethodField()
class Meta:
"""Meta class."""
model = models.Subscriber
fields = ('subscriber',)
read_only_fields = ('subscribe_date', 'unsubscribe_date',)
model = models.Subscribe
fields = (
'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):
@ -156,6 +178,7 @@ class SubscribeSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=False, source='send_to')
subscription_types = SubscriptionTypeSerializer(source='active_subscriptions', read_only=True, many=True)
history = SubscribeObjectSerializer(source='subscription_history', many=True)
class Meta:
"""Meta class."""
@ -165,4 +188,16 @@ class SubscribeSerializer(serializers.ModelSerializer):
'email',
'subscription_types',
'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

View File

@ -3,16 +3,16 @@ from datetime import datetime
from celery import shared_task
from django.conf import settings
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 main.models import SiteSettings
from notification import models
from django.utils.translation import gettext_lazy as _
@shared_task
def send_subscribes_update_email(email):
subscriber = models.Subscriber.objects.filter(email=email).first()
def send_subscribes_update_email(subscriber_id):
subscriber = models.Subscriber.objects.get(pk=subscriber_id)
if subscriber is None:
return
@ -53,8 +53,8 @@ def send_subscribes_update_email(email):
@shared_task
def send_unsubscribe_email(email):
subscriber = models.Subscriber.objects.filter(email=email).first()
def send_unsubscribe_email(subscriber_id):
subscriber = models.Subscriber.objects.get(pk=subscriber_id)
if subscriber is None:
return

View File

@ -1,6 +1,6 @@
"""Notification app common views."""
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 notification import models
@ -15,6 +15,18 @@ class CreateSubscribeView(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
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):
"""Subscribe info view."""
@ -41,20 +53,7 @@ class SubscribeInfoAuthUserView(generics.RetrieveAPIView):
lookup_field = None
def get_object(self):
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
return get_object_or_404(models.Subscriber, user=self.request.user)
class UnsubscribeView(generics.UpdateAPIView):
@ -65,9 +64,9 @@ class UnsubscribeView(generics.UpdateAPIView):
queryset = models.Subscriber.objects.all()
serializer_class = serializers.SubscribeSerializer
def patch(self, request, *args, **kw):
def put(self, request, *args, **kw):
obj = self.get_object()
obj.unsubscribe(request.query_params)
obj.unsubscribe()
serializer = self.get_serializer(instance=obj)
return Response(data=serializer.data)
@ -81,7 +80,7 @@ class UnsubscribeAuthUserView(generics.GenericAPIView):
def patch(self, request, *args, **kw):
user = request.user
obj = models.Subscriber.objects.filter(user=user).first()
obj.unsubscribe(request.query_params)
obj.unsubscribe()
serializer = self.get_serializer(instance=obj)
return Response(data=serializer.data)

34
apps/partner/filters.py Normal file
View 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

View 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),
]

View 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)
]

View 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'),
),
]

View File

@ -1,10 +1,25 @@
from django.db import models
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 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):
"""Partner model."""
@ -17,24 +32,36 @@ class Partner(ProjectBaseMixin):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
name = models.CharField(_('name'), max_length=255, blank=True, null=True)
url = models.URLField(verbose_name=_('Partner URL'))
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
establishment = models.ForeignKey(
Establishment,
verbose_name=_('Establishment'),
related_name='partners',
on_delete=models.CASCADE,
blank=True,
null=True,
images = ArrayField(
models.URLField(verbose_name=_('Partner image URL')), blank=True, null=True, default=None,
)
establishment = models.ManyToManyField('establishment.Establishment', related_name='partners',
through='PartnerToEstablishment',
verbose_name=_('Establishments'))
type = models.PositiveSmallIntegerField(choices=MODEL_TYPES, default=PARTNER)
starting_date = models.DateField(_('starting date'), blank=True, null=True)
expiry_date = models.DateField(_('expiry date'), blank=True, null=True)
price_per_month = models.DecimalField(_('price per month'), max_digits=10, decimal_places=2, blank=True, null=True)
country = models.ForeignKey('location.Country', null=True, default=None, on_delete=models.SET_NULL)
objects = PartnerQueryset.as_manager()
class Meta:
verbose_name = _('partner')
verbose_name_plural = _('partners')
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)

View File

@ -1,21 +1,85 @@
"""Back account 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):
# 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:
model = Partner
fields = (
'id',
'old_id',
'name',
'url',
'image',
'establishment',
'establishment_id',
'images',
'type',
'type_display',
'starting_date',
'expiry_date',
'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',
)

View File

@ -11,6 +11,6 @@ class PartnerSerializer(serializers.ModelSerializer):
model = models.Partner
fields = (
'id',
'image',
# 'image',
'url'
)

View File

@ -1,7 +1,7 @@
from pprint import pprint
from establishment.models import Establishment
from partner.models import Partner
from partner.models import Partner, PartnerToEstablishment
from transfer.models import EstablishmentBacklinks
from transfer.serializers.partner import PartnerSerializer

View File

@ -8,4 +8,11 @@ app_name = 'partner'
urlpatterns = [
path('', views.PartnerLstView.as_view(), name='partner-list-create'),
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'),
]

View File

@ -1,34 +1,81 @@
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
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 utils.methods import get_permission_classes
from utils.permissions import IsEstablishmentManager, IsEstablishmentAdministrator
class PartnerLstView(generics.ListCreateAPIView):
"""Partner list create view."""
queryset = Partner.objects.all()
"""Partner list/create view.
Allows to get partners for current country, or create a new one.
"""
queryset = Partner.objects.with_base_related()
serializer_class = serializers.BackPartnerSerializer
pagination_class = None
filter_backends = (DjangoFilterBackend,)
permission_classes = get_permission_classes(
IsEstablishmentManager,
IsEstablishmentAdministrator
)
filterset_fields = (
'establishment',
'type',
filter_class = filters.PartnerFilterSet
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):
"""Partner RUD view."""
queryset = Partner.objects.all()
queryset = Partner.objects.with_base_related()
serializer_class = serializers.BackPartnerSerializer
lookup_field = 'id'
permission_classes = get_permission_classes(
IsEstablishmentManager,
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'])

View File

@ -175,7 +175,8 @@ class EstablishmentDocument(Document):
'city': fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'name': fields.KeywordField(attr='name_dumped'),
'name_translated': fields.KeywordField(),
'is_island': fields.BooleanField(),
'country': fields.ObjectField(
properties={

View File

@ -44,7 +44,8 @@ class ProductDocument(Document):
attr='address.city',
properties={
'id': fields.IntegerField(),
'name': fields.KeywordField(),
'name': fields.KeywordField(attr='name_dumped'),
'name_translated': fields.KeywordField(),
'code': fields.KeywordField(),
'country': fields.ObjectField(
properties={

View File

@ -124,7 +124,13 @@ class CityDocumentShortSerializer(serializers.Serializer):
id = serializers.IntegerField()
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):

View File

@ -13,7 +13,7 @@ def update_document(sender, **kwargs):
app_label_model_name_to_filter = {
('location', 'country'): 'address__city__country',
('location', 'city'): 'address__city',
# ('location', 'city'): 'address__city',
('location', 'address'): 'address',
# todo: remove after migration
('establishment', 'establishmenttype'): 'establishment_type',

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from establishment.models import Establishment
from partner.models import Partner
from partner.models import Partner, PartnerToEstablishment
class PartnerSerializer(serializers.Serializer):
@ -43,10 +43,32 @@ class PartnerSerializer(serializers.Serializer):
return establishment
def create(self, validated_data):
obj, _ = Partner.objects.update_or_create(
old_id=validated_data['old_id'],
defaults=validated_data,
establishment = validated_data.pop('establishment')
url = validated_data.pop('url')
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

View File

@ -6,7 +6,7 @@ import string
from collections import namedtuple
from functools import reduce
from io import BytesIO
import pathlib
import requests
from PIL import Image
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):
"""Determine avatar path method."""
filename = '%s.jpeg' % generate_code()

View File

@ -8,6 +8,7 @@ from django.contrib.gis.db import models
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTextTransform
from django.core.validators import FileExtensionValidator
from django.utils import timezone
from django.utils.html import mark_safe
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 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
logger = logging.getLogger(__name__)
@ -86,6 +87,7 @@ def translate_field(self, field_name, toggle_field_name=None):
return None
return value
return None
return translate
@ -159,6 +161,33 @@ class BaseAttributes(ProjectBaseMixin):
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):
"""Avatar model."""

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff