Merge branch 'feature/bo-menus' into 'develop'

Feature/bo menus

See merge request gm/gm-backend!247
This commit is contained in:
Олег Хаятов 2020-01-29 15:54:44 +00:00
commit 0e7fd39d37
10 changed files with 412 additions and 232 deletions

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

@ -6,34 +6,33 @@ from typing import List
import elasticsearch_dsl import elasticsearch_dsl
from django.conf import settings from django.conf import settings
from django.shortcuts import get_object_or_404
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.contrib.gis.db.models.functions import Distance from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance as DistanceMeasure from django.contrib.gis.measure import Distance as DistanceMeasure
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.search import TrigramDistance, TrigramSimilarity
from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator, FileExtensionValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch, Sum from django.db.models import Case, ExpressionWrapper, F, Prefetch, Q, Subquery, When
from django.shortcuts import get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from timezone_field import TimeZoneField from timezone_field import TimeZoneField
from location.models import Address from location.models import Address
from timetable.models import Timetable
from location.models import WineOriginAddressMixin
from main.models import Award, Currency from main.models import Award, Currency
from review.models import Review from review.models import Review
from tag.models import Tag from tag.models import Tag
from timetable.models import Timetable
from utils.methods import transform_into_readable_str from utils.methods import transform_into_readable_str
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, from utils.models import (
TranslatedFieldsMixin, BaseAttributes, GalleryMixin, BaseAttributes, FavoritesMixin, FileMixin, GalleryMixin, HasTagsMixin, IntermediateGalleryModelMixin,
IntermediateGalleryModelMixin, HasTagsMixin, ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, TypeDefaultImageMixin, URLImageMixin,
FavoritesMixin, TypeDefaultImageMixin) )
# todo: establishment type&subtypes check # todo: establishment type&subtypes check
@ -561,8 +560,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
verbose_name=_('Facebook URL')) verbose_name=_('Facebook URL'))
twitter = models.URLField(blank=True, null=True, default=None, max_length=255, twitter = models.URLField(blank=True, null=True, default=None, max_length=255,
verbose_name=_('Twitter URL')) verbose_name=_('Twitter URL'))
instagram =models.URLField(blank=True, null=True, default=None, max_length=255, instagram = models.URLField(blank=True, null=True, default=None, max_length=255,
verbose_name=_('Instagram URL')) verbose_name=_('Instagram URL'))
lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255, lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255,
verbose_name=_('Lafourchette URL')) verbose_name=_('Lafourchette URL'))
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'), guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
@ -734,7 +733,8 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
now_at_est_tz = datetime.now(tz=self.tz) now_at_est_tz = datetime.now(tz=self.tz)
current_week = now_at_est_tz.weekday() current_week = now_at_est_tz.weekday()
schedule_for_today = self.schedule.filter(weekday=current_week).first() schedule_for_today = self.schedule.filter(weekday=current_week).first()
if schedule_for_today is None or schedule_for_today.opening_time is None or schedule_for_today.ending_time is None: if schedule_for_today is None or schedule_for_today.opening_time is None or schedule_for_today.ending_time is \
None:
return False return False
time_at_est_tz = now_at_est_tz.time() time_at_est_tz = now_at_est_tz.time()
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
@ -746,8 +746,10 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
@property @property
def tags_indexing(self): def tags_indexing(self):
return [{'id': tag.metadata.id, return [{
'label': tag.metadata.label} for tag in self.tags.all()] 'id': tag.metadata.id,
'label': tag.metadata.label
} for tag in self.tags.all()]
@property @property
def last_published_review(self): def last_published_review(self):
@ -1241,6 +1243,24 @@ class Plate(TranslatedFieldsMixin, models.Model):
verbose_name_plural = _('plates') verbose_name_plural = _('plates')
class MenuDish(BaseAttributes):
"""Dish model."""
STR_FIELD_NAME = 'category'
name = models.CharField(_('name'), max_length=255, default='')
category = TJSONField(
blank=True, null=True, default=None, verbose_name=_('category'),
help_text='{"en-GB":"some text"}')
price = models.IntegerField(blank=True, null=True, default=None, verbose_name=_('price'))
signature = models.BooleanField(_('signature'), default=False)
class Meta:
verbose_name = _('dish')
verbose_name_plural = _('dishes')
ordering = ('-created',)
class MenuQuerySet(models.QuerySet): class MenuQuerySet(models.QuerySet):
def with_schedule_plates_establishment(self): def with_schedule_plates_establishment(self):
return self.select_related( return self.select_related(
@ -1266,25 +1286,38 @@ class MenuQuerySet(models.QuerySet):
"""Search by category.""" """Search by category."""
return self.filter(category__icontains=value) return self.filter(category__icontains=value)
def with_dishes(self):
return self.filter(~Q(dishes=None))
class Menu(GalleryMixin, TranslatedFieldsMixin, BaseAttributes):
class Menu(TranslatedFieldsMixin, BaseAttributes):
"""Menu model.""" """Menu model."""
STR_FIELD_NAME = 'category' name = models.CharField(_('name'), max_length=255, default='')
category = TJSONField(
blank=True, null=True, default=None, verbose_name=_('category'),
help_text='{"en-GB":"some text"}')
establishment = models.ForeignKey( establishment = models.ForeignKey(
'establishment.Establishment', verbose_name=_('establishment'), 'establishment.Establishment', verbose_name=_('establishment'),
on_delete=models.CASCADE) on_delete=models.CASCADE)
is_drinks_included = models.BooleanField(_('is drinks included'), default=False) is_drinks_included = models.BooleanField(_('is drinks included'), default=False)
price = models.IntegerField(blank=True, null=True, default=None, verbose_name=_('price'))
schedule = models.ManyToManyField( schedule = models.ManyToManyField(
to='timetable.Timetable', to='timetable.Timetable',
blank=True, blank=True,
verbose_name=_('Establishment schedule'), verbose_name=_('Menu schedule'),
related_name='menus',
) )
uploads = models.ManyToManyField(
to='MenuFiles',
blank=True,
verbose_name=_('Menu files'),
)
dishes = models.ManyToManyField(
to='MenuDish',
blank=True,
verbose_name=_('Menu dishes')
)
category = TJSONField(
blank=True, null=True, default=None, verbose_name=_('category'),
help_text='{"en-GB":"some text"}')
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
objects = MenuQuerySet.as_manager() objects = MenuQuerySet.as_manager()
@ -1295,43 +1328,15 @@ class Menu(GalleryMixin, TranslatedFieldsMixin, BaseAttributes):
ordering = ('-created',) ordering = ('-created',)
class MenuGallery(IntermediateGalleryModelMixin): class MenuFiles(FileMixin, BaseAttributes):
menu = models.ForeignKey(
Menu,
null=True,
related_name='menu_gallery',
on_delete=models.CASCADE,
verbose_name=_('menu'),
)
image = models.ForeignKey(
'gallery.Image',
null=True,
related_name='menu_gallery',
on_delete=models.CASCADE,
verbose_name=_('image'),
)
class Meta:
"""Meta class."""
verbose_name = _('menu gallery')
verbose_name_plural = _('menu galleries')
unique_together = (('menu', 'is_main'), ('menu', 'image'))
class MenuUploads(BaseAttributes):
"""Menu files""" """Menu files"""
TYPES = (
('image', 'Image'),
('file', 'File')
)
menu = models.ForeignKey( name = models.CharField(_('name'), max_length=255, default='')
Menu, type = models.CharField(_('type'), choices=TYPES, max_length=65, default='')
verbose_name=_('menu'),
on_delete=models.CASCADE,
related_name='menu_uploads',
)
title = models.CharField(_('title'), max_length=255, default='')
file = models.FileField(
_('File'),
validators=[FileExtensionValidator(allowed_extensions=('doc', 'docx', 'pdf')), ],
)
class Meta: class Meta:
verbose_name = _('menu upload') verbose_name = _('menu upload')

View File

@ -2,12 +2,14 @@ from functools import lru_cache
from pprint import pprint from pprint import pprint
from django.db.models import F from django.db.models import F
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from account.serializers.common import UserShortSerializer from account.serializers.common import UserShortSerializer
from establishment import models, serializers as model_serializers from establishment import models, serializers as model_serializers
from establishment.models import ContactPhone, EstablishmentEmployee, ContactEmail from establishment.models import ContactEmail, ContactPhone, EstablishmentEmployee
from establishment.serializers import MenuDishSerializer
from gallery.models import Image from gallery.models import Image
from location.models import Address from location.models import Address
from location.serializers import AddressDetailSerializer, TranslatedField from location.serializers import AddressDetailSerializer, TranslatedField
@ -584,109 +586,59 @@ class EstablishmentEmployeePositionsSerializer(serializers.ModelSerializer):
] ]
class _PlateSerializer(ProjectModelSerializer):
name_translated = TranslatedField()
class Meta:
model = models.Plate
fields = [
'name_translated',
'price',
]
class _MenuUploadsSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField()
original_url = serializers.URLField()
class MenuDishesSerializer(ProjectModelSerializer): class MenuDishesSerializer(ProjectModelSerializer):
"""for dessert, main_course and starter category""" """for dessert, main_course and starter category"""
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True) establishment_id = serializers.PrimaryKeyRelatedField(read_only=True)
category_translated = serializers.CharField(read_only=True) establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
last_update = serializers.DateTimeField(source='created') dishes = MenuDishSerializer(many=True, read_only=True)
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
class Meta: class Meta:
model = models.Menu model = models.Menu
fields = [ fields = [
'id', 'id',
'category', 'establishment_id',
'category_translated', 'establishment_slug',
'establishment', 'dishes',
'is_drinks_included',
'schedule',
'plates',
'last_update',
'gallery',
] ]
class MenuDishesCreateSerializer(ProjectModelSerializer):
"""Menu dishes create serializer"""
menu_id = serializers.IntegerField(write_only=True)
class Meta:
model = models.MenuDish
fields = [
'id',
'name',
'category',
'price',
'signature',
'menu_id'
]
def create(self, validated_data):
menu_id = validated_data.pop('menu_id')
menu = get_object_or_404(models.Menu, pk=menu_id)
instance = models.MenuDish.objects.create(**validated_data)
menu.dishes.add(instance)
return instance
class MenuDishesRUDSerializers(ProjectModelSerializer): class MenuDishesRUDSerializers(ProjectModelSerializer):
"""for dessert, main_course and starter category""" """for dessert, main_course and starter category"""
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True) establishment_id = serializers.PrimaryKeyRelatedField(queryset=models.Establishment.objects.all())
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True) establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
dishes = MenuDishSerializer(many=True, read_only=True)
class Meta: class Meta:
model = models.Menu model = models.Menu
fields = [ fields = [
'id', 'id',
'category', 'establishment_id',
'plates', 'establishment_slug',
'establishment', 'dishes',
'is_drinks_included',
'schedule',
'gallery',
] ]
class MenuGallerySerializer(serializers.ModelSerializer):
"""Serializer class for model MenuGallery."""
class Meta:
"""Meta class"""
model = models.MenuGallery
fields = [
'id',
'is_main',
]
@property
def request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def create(self, validated_data):
menu_pk = self.request_kwargs.get('pk')
image_id = self.request_kwargs.get('image_id')
qs = models.MenuGallery.objects.filter(image_id=image_id, menu_id=menu_pk)
instance = qs.first()
if instance:
qs.update(**validated_data)
return instance
return super().create(validated_data)
def validate(self, attrs):
"""Override validate method."""
menu_pk = self.request_kwargs.get('pk')
image_id = self.request_kwargs.get('image_id')
menu_qs = models.Menu.objects.filter(pk=menu_pk)
image_qs = Image.objects.filter(id=image_id)
if not menu_qs.exists():
raise serializers.ValidationError({'detail': _('Menu not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
menu = menu_qs.first()
image = image_qs.first()
attrs['menu'] = menu
attrs['image'] = image
return attrs

View File

@ -2,6 +2,7 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from phonenumber_field.phonenumber import to_python as str_to_phonenumber from phonenumber_field.phonenumber import to_python as str_to_phonenumber
from rest_framework import serializers from rest_framework import serializers
@ -9,18 +10,19 @@ from rest_framework import serializers
from comment import models as comment_models from comment import models as comment_models
from comment.serializers import common as comment_serializers from comment.serializers import common as comment_serializers
from establishment import models from establishment import models
from location.serializers import AddressBaseSerializer, CityBaseSerializer, AddressDetailSerializer, \ from location.serializers import (
CityShortSerializer AddressBaseSerializer, AddressDetailSerializer, CityBaseSerializer,
from location.serializers import EstablishmentWineRegionBaseSerializer, \ CityShortSerializer, EstablishmentWineOriginBaseSerializer, EstablishmentWineRegionBaseSerializer,
EstablishmentWineOriginBaseSerializer )
from main.serializers import AwardSerializer, CurrencySerializer from main.serializers import AwardSerializer, CurrencySerializer
from review.serializers import ReviewShortSerializer from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer
from timetable.serialziers import ScheduleRUDSerializer from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer from utils.serializers import (
from utils.serializers import (ProjectModelSerializer, TranslatedField, CarouselCreateSerializer, FavoritesCreateSerializer, ImageBaseSerializer,
FavoritesCreateSerializer) ProjectModelSerializer, TranslatedField,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -70,31 +72,87 @@ class PlateSerializer(ProjectModelSerializer):
] ]
class MenuSerializers(ProjectModelSerializer): class MenuDishSerializer(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category_translated = serializers.CharField(read_only=True)
class Meta: class Meta:
model = models.Menu model = models.MenuDish
fields = [ fields = [
'id', 'id',
'name',
'category', 'category',
'category_translated', 'price',
'plates', 'signature'
'establishment'
] ]
class MenuRUDSerializers(ProjectModelSerializer): class MenuFilesSerializers(ProjectModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set') menu_id = serializers.IntegerField(write_only=True)
class Meta:
model = models.MenuFiles
fields = [
'id',
'name',
'type',
'file',
'menu_id'
]
def create(self, validated_data):
menu_id = validated_data.pop('menu_id')
menu = get_object_or_404(models.Menu, pk=menu_id)
instance = models.MenuFiles.objects.create(**validated_data)
menu.uploads.add(instance)
return instance
class MenuSerializers(ProjectModelSerializer):
name = serializers.CharField()
establishment_id = serializers.PrimaryKeyRelatedField(queryset=models.Establishment.objects.all())
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
price = serializers.IntegerField(required=False)
drinks_included = serializers.BooleanField(source='is_drinks_included', required=False)
schedules = ScheduleRUDSerializer(many=True, allow_null=True, required=False)
uploads = MenuFilesSerializers(many=True, read_only=True)
class Meta: class Meta:
model = models.Menu model = models.Menu
fields = [ fields = [
'id', 'id',
'category', 'name',
'plates', 'establishment_id',
'establishment' 'establishment_slug',
'price',
'drinks_included',
'schedules',
'uploads',
]
def create(self, validated_data):
validated_data['establishment'] = validated_data.pop('establishment_id')
instance = models.Menu.objects.create(**validated_data)
return instance
class MenuRUDSerializers(ProjectModelSerializer):
name = serializers.CharField()
establishment_id = serializers.PrimaryKeyRelatedField(read_only=True)
establishment_slug = serializers.CharField(read_only=True, source='establishment.slug')
price = serializers.IntegerField(required=False)
drinks_included = serializers.BooleanField(source='is_drinks_included', required=False)
schedules = ScheduleRUDSerializer(many=True, allow_null=True, required=False)
uploads = MenuFilesSerializers(many=True)
class Meta:
model = models.Menu
fields = [
'id',
'name',
'establishment_id',
'establishment_slug',
'price',
'drinks_included',
'schedules',
'uploads',
] ]
@ -528,7 +586,7 @@ class EstablishmentCommentBaseSerializer(comment_serializers.CommentBaseSerializ
'created', 'created',
'text', 'text',
'mark', 'mark',
'nickname', # 'nickname',
'profile_pic', 'profile_pic',
'status', 'status',
'status_display', 'status_display',
@ -568,7 +626,7 @@ class EstablishmentCommentRUDSerializer(comment_serializers.CommentBaseSerialize
'created', 'created',
'text', 'text',
'mark', 'mark',
'nickname', # 'nickname',
'profile_pic', 'profile_pic',
] ]

View File

@ -30,13 +30,15 @@ urlpatterns = [
name='note-rud'), name='note-rud'),
path('slug/<slug:slug>/admin/', views.EstablishmentAdminView.as_view(), path('slug/<slug:slug>/admin/', views.EstablishmentAdminView.as_view(),
name='establishment-admin-list'), name='establishment-admin-list'),
path('menus/dishes/', views.MenuDishesListCreateView.as_view(), name='menu-dishes-list'), path('menus/dishes/', views.MenuDishesListView.as_view(), name='menu-dishes-list'),
path('menus/dishes/create/', views.MenuDishesCreateView.as_view(), name='menu-dishes-create'),
path('menus/dishes/<int:pk>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'), path('menus/dishes/<int:pk>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
path('menus/dishes/<int:pk>/gallery/', views.MenuGalleryListView.as_view(), name='menu-dishes-gallery-list'), path('menus/dishes/slug/<slug:slug>/', views.MenuDishesRUDView.as_view(), name='menu-dishes-rud'),
path('menus/dishes/<int:pk>/gallery/<int:image_id>/', views.MenuGalleryCreateDestroyView.as_view(),
name='menu-dishes-gallery-create-destroy'),
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'), path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
path('menus/slug/<slug:slug>/', views.MenuRUDView.as_view(), name='menu-rud'),
path('menus/uploads/', views.MenuFilesListCreateView.as_view(), name='menu-files-list'),
path('menus/uploads/<int:pk>/', views.MenuFilesRUDView.as_view(), name='menu-files-rud'),
path('plates/', views.PlateListCreateView.as_view(), name='plates'), path('plates/', views.PlateListCreateView.as_view(), name='plates'),
path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'), path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
path('social_choice/', views.SocialChoiceListCreateView.as_view(), name='socials_choice'), path('social_choice/', views.SocialChoiceListCreateView.as_view(), name='socials_choice'),

View File

@ -1,8 +1,9 @@
"""Establishment app views.""" """Establishment app views."""
from django.db.models.query_utils import Q
from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions, response from rest_framework import generics, permissions
from rest_framework.response import Response from rest_framework.response import Response
from account.models import User from account.models import User
@ -14,6 +15,20 @@ from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryRe
from utils.views import CreateDestroyGalleryViewMixin from utils.views import CreateDestroyGalleryViewMixin
class MenuRUDMixinViews:
"""Menu mixin"""
def get_object(self):
instance = self.get_queryset().filter(
Q(establishment__slug=self.kwargs.get('slug')) | Q(establishment__id=self.kwargs.get('pk'))
).first()
if instance is None:
raise Http404
return instance
class EstablishmentMixinViews: class EstablishmentMixinViews:
"""Establishment mixin.""" """Establishment mixin."""
@ -121,18 +136,37 @@ class MenuListCreateView(generics.ListCreateAPIView):
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filterset_fields = ( filterset_fields = (
'establishment', 'establishment__id',
'establishment__slug', 'establishment__slug',
) )
def get_queryset(self):
return super().get_queryset().prefetch_related('establishment')
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
class MenuRUDView(MenuRUDMixinViews, generics.RetrieveUpdateDestroyAPIView):
"""Menu RUD view.""" """Menu RUD view."""
lookup_field = None
serializer_class = serializers.MenuRUDSerializers serializer_class = serializers.MenuRUDSerializers
queryset = models.Menu.objects.all() queryset = models.Menu.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
class MenuFilesListCreateView(generics.ListCreateAPIView):
"""Menu files list create view."""
serializer_class = serializers.MenuFilesSerializers
queryset = models.MenuFiles.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
class MenuFilesRUDView(generics.RetrieveDestroyAPIView):
"""Menu files RUD view."""
serializer_class = serializers.MenuFilesSerializers
queryset = models.MenuFiles.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
class SocialChoiceListCreateView(generics.ListCreateAPIView): class SocialChoiceListCreateView(generics.ListCreateAPIView):
"""SocialChoice list create view.""" """SocialChoice list create view."""
serializer_class = serializers.SocialChoiceSerializers serializer_class = serializers.SocialChoiceSerializers
@ -449,69 +483,26 @@ class EstablishmentAdminView(generics.ListAPIView):
return User.objects.establishment_admin(establishment).distinct() return User.objects.establishment_admin(establishment).distinct()
class MenuDishesListCreateView(generics.ListCreateAPIView): class MenuDishesListView(generics.ListAPIView):
"""Menu (dessert, main_course, starter) list create view.""" """Menu (dessert, main_course, starter) list create view."""
serializer_class = serializers.MenuDishesSerializer serializer_class = serializers.MenuDishesSerializer
queryset = models.Menu.objects.with_schedule_plates_establishment().dishes().distinct() queryset = models.Menu.objects.with_dishes()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
filter_class = filters.MenuDishesBackFilter filter_class = filters.MenuDishesBackFilter
class MenuDishesRUDView(generics.RetrieveUpdateDestroyAPIView): class MenuDishesRUDView(MenuRUDMixinViews, generics.RetrieveUpdateDestroyAPIView):
"""Menu (dessert, main_course, starter) RUD view.""" """Menu (dessert, main_course, starter) RUD view."""
lookup_field = None
serializer_class = serializers.MenuDishesRUDSerializers serializer_class = serializers.MenuDishesRUDSerializers
queryset = models.Menu.objects.dishes().distinct() queryset = models.Menu.objects.with_dishes()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
class MenuGalleryListView(generics.ListAPIView): class MenuDishesCreateView(generics.CreateAPIView):
"""Resource for returning gallery for menu for back-office users.""" """Menu (dessert, main_course, starter) list create view."""
serializer_class = serializers.ImageBaseSerializer serializer_class = serializers.MenuDishesCreateSerializer
queryset = models.MenuDish.objects.all()
permission_classes = [IsWineryReviewer | IsEstablishmentManager] permission_classes = [IsWineryReviewer | IsEstablishmentManager]
queryset = models.Menu.objects.with_schedule_plates_establishment().with_gallery().dishes() filter_class = filters.MenuDishesBackFilter
def get_object(self):
"""Override get_object method."""
qs = super(MenuGalleryListView, self).get_queryset()
menu = get_object_or_404(qs, pk=self.kwargs.get('pk'))
# May raise a permission denied
# self.check_object_permissions(self.request, menu)
return menu
def get_queryset(self):
"""Override get_queryset method."""
return self.get_object().crop_gallery
class MenuGalleryCreateDestroyView(CreateDestroyGalleryViewMixin):
"""Resource for a create gallery for menu for back-office users."""
serializer_class = serializers.MenuGallerySerializer
permission_classes = [IsWineryReviewer | IsEstablishmentManager]
def get_queryset(self):
"""Override get_queryset method."""
qs = models.Menu.objects.with_schedule_plates_establishment().with_gallery().dishes()
return qs
def create(self, request, *args, **kwargs):
_ = super().create(request, *args, **kwargs)
news_qs = self.filter_queryset(self.get_queryset())
return response.Response(
data=serializers.MenuDishesRUDSerializers(get_object_or_404(news_qs, pk=kwargs.get('pk'))).data
)
def get_object(self):
"""
Returns the object the view is displaying.
"""
menu_qs = self.filter_queryset(self.get_queryset())
menu = get_object_or_404(menu_qs, pk=self.kwargs.get('pk'))
gallery = get_object_or_404(menu.menu_gallery, image_id=self.kwargs.get('image_id'))
# May raise a permission denied
self.check_object_permissions(self.request, gallery)
return gallery

View File

@ -6,7 +6,7 @@ import string
from collections import namedtuple from collections import namedtuple
from functools import reduce from functools import reduce
from io import BytesIO from io import BytesIO
import pathlib
import requests import requests
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -69,6 +69,15 @@ def username_random():
) )
def file_path(instance, filename):
"""Determine file path method."""
filename = '%s.%s' % (generate_code(), pathlib.Path(filename).suffix.lstrip('.'))
return 'files/%s/%s/%s' % (
instance._meta.model_name,
datetime.now().strftime(settings.REST_DATE_FORMAT),
filename)
def image_path(instance, filename): def image_path(instance, filename):
"""Determine avatar path method.""" """Determine avatar path method."""
filename = '%s.jpeg' % generate_code() filename = '%s.jpeg' % generate_code()

View File

@ -8,6 +8,7 @@ from django.contrib.gis.db import models
from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTextTransform from django.contrib.postgres.fields.jsonb import KeyTextTransform
from django.core.validators import FileExtensionValidator
from django.utils import timezone from django.utils import timezone
from django.utils.html import mark_safe from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _, get_language from django.utils.translation import ugettext_lazy as _, get_language
@ -16,7 +17,7 @@ from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.fields import ImageField as SORLImageField from sorl.thumbnail.fields import ImageField as SORLImageField
from configuration.models import TranslationSettings from configuration.models import TranslationSettings
from utils.methods import image_path, svg_image_path from utils.methods import image_path, svg_image_path, file_path
from utils.validators import svg_image_validator from utils.validators import svg_image_validator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -86,6 +87,7 @@ def translate_field(self, field_name, toggle_field_name=None):
return None return None
return value return value
return None return None
return translate return translate
@ -159,6 +161,33 @@ class BaseAttributes(ProjectBaseMixin):
abstract = True abstract = True
class FileMixin(models.Model):
"""File model."""
file = models.FileField(upload_to=file_path,
blank=True, null=True, default=None,
verbose_name=_('File'),
validators=[FileExtensionValidator(
allowed_extensions=('jpg', 'jpeg', 'png', 'doc', 'docx', 'pdf')
)])
class Meta:
"""Meta class."""
abstract = True
def get_file_url(self):
"""Get file url."""
return self.file.url if self.file else None
def get_full_file_url(self, request):
"""Get full file url"""
if self.file and exists(self.file.path):
return request.build_absolute_uri(self.file.url)
else:
return None
class ImageMixin(models.Model): class ImageMixin(models.Model):
"""Avatar model.""" """Avatar model."""
@ -230,7 +259,7 @@ class SORLImageMixin(models.Model):
else: else:
return None return None
def get_cropped_image(self, geometry: str, quality: int, cropbox:str) -> dict: def get_cropped_image(self, geometry: str, quality: int, cropbox: str) -> dict:
cropped_image = get_thumbnail(self.image, cropped_image = get_thumbnail(self.image,
geometry_string=geometry, geometry_string=geometry,
# crop=crop, # crop=crop,