Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	apps/establishment/urls/back.py
This commit is contained in:
Dmitriy Kuzmenko 2019-09-19 17:19:40 +03:00
commit 5a36613c97
49 changed files with 776 additions and 108 deletions

View File

@ -35,11 +35,8 @@ class PasswordResetSerializer(serializers.Serializer):
if filters:
filters.update({'is_active': True})
user_qs = models.User.objects.filter(**filters)
if not user_qs.exists():
raise utils_exceptions.UserNotFoundError()
user = user_qs.first()
if user_qs.exists():
user = user_qs.first()
attrs['user'] = user
return attrs

View File

@ -22,7 +22,7 @@ class PasswordResetView(generics.GenericAPIView):
"""Override create method"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if serializer.validated_data.get('user'):
if not serializer.validated_data.get('user').is_anonymous:
user = serializer.validated_data.pop('user')
if settings.USE_CELERY:
tasks.send_reset_password_email.delay(user_id=user.id,

View File

@ -0,0 +1,39 @@
# Generated by Django 2.2.4 on 2019-09-17 13:07
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('translation', '0003_auto_20190901_1032'),
('advertisement', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='advertisement',
name='source',
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web')], default=0, verbose_name='Source'),
),
migrations.AddField(
model_name='advertisement',
name='target_languages',
field=models.ManyToManyField(to='translation.Language'),
),
migrations.AddField(
model_name='advertisement',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.RemoveField(
model_name='advertisement',
name='block_level',
),
migrations.AddField(
model_name='advertisement',
name='block_level',
field=models.CharField(max_length=10, verbose_name='Block level')
)
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-19 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0002_auto_20190917_1307'),
]
operations = [
migrations.AlterField(
model_name='advertisement',
name='source',
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
),
]

View File

@ -1,28 +1,22 @@
"""Advertisement app models."""
import uuid
from django.db import models
from django.utils.translation import gettext_lazy as _
from utils.models import ProjectBaseMixin, ImageMixin
from translation.models import Language
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin
class Advertisement(ImageMixin, ProjectBaseMixin):
class Advertisement(ImageMixin, ProjectBaseMixin, PlatformMixin):
"""Advertisement model."""
LEVEL_1 = 1
LEVEL_2 = 2
LEVEL_3 = 3
BLOCK_LEVEL_CHOICES = (
(LEVEL_1, _('Ad block level 1')),
(LEVEL_2, _('Ad block level 2')),
(LEVEL_3, _('Ad block level 3'))
)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
url = models.URLField(verbose_name=_('Ad URL'))
width = models.PositiveIntegerField(verbose_name=_('Block width'))
height = models.PositiveIntegerField(verbose_name=_('Block height'))
block_level = models.PositiveSmallIntegerField(choices=BLOCK_LEVEL_CHOICES,
verbose_name=_('Block level'))
block_level = models.CharField(verbose_name=_('Block level'), max_length=10)
target_languages = models.ManyToManyField(Language)
class Meta:
verbose_name = _('Advertisement')

View File

@ -2,6 +2,7 @@
from rest_framework import serializers
from advertisement import models
from translation.serializers import LanguageSerializer
class AdvertisementSerializer(serializers.ModelSerializer):
@ -11,9 +12,11 @@ class AdvertisementSerializer(serializers.ModelSerializer):
model = models.Advertisement
fields = (
'id',
'uuid',
'url',
'image',
'width',
'height',
'block_level',
'source'
)

View File

@ -3,8 +3,8 @@ from django.urls import path
from advertisement.views import web as views
app_name = 'advertisement'
app_name = 'advertisements'
urlpatterns = [
path('', views.AdvertisementListView.as_view(), name='list')
path('<str:page>/', views.AdvertisementListView.as_view(), name='list')
]

View File

@ -10,6 +10,10 @@ class AdvertisementListView(generics.ListAPIView):
"""List view for model Advertisement"""
pagination_class = None
model = models.Advertisement
permission_classes = (permissions.AllowAny, )
queryset = models.Advertisement.objects.all()
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.AdvertisementSerializer
def get_queryset(self):
return models.Advertisement.objects\
.filter(page__page_name__contains=self.kwargs['page'])\
.filter(target_languages__locale=self.request.locale)

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-09-19 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authorization', '0007_jwtaccesstoken_refresh_token'),
]
operations = [
migrations.AlterField(
model_name='application',
name='source',
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
),
migrations.AlterField(
model_name='jwtrefreshtoken',
name='source',
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
),
]

View File

@ -48,8 +48,8 @@ class CollectionItemSerializer(serializers.ModelSerializer):
fields = [
'id',
'collection',
'item_type',
'item_ids'
'content_type',
'object_id',
]

View File

@ -61,11 +61,6 @@ class EstablishmentAdmin(admin.ModelAdmin):
ReviewInline, CommentInline]
@admin.register(models.EstablishmentSchedule)
class EstablishmentSchedule(admin.ModelAdmin):
"""Establishment schedule"""
@admin.register(models.Position)
class PositionAdmin(admin.ModelAdmin):
"""Position admin."""

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-13 13:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0018_socialnetwork'),
]
operations = [
migrations.AddField(
model_name='position',
name='priority',
field=models.IntegerField(default=None, null=True, unique=True),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 2.2.4 on 2019-09-18 12:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0020_merge_20190917_1415'),
]
operations = [
migrations.DeleteModel(
name='EstablishmentSchedule',
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-19 09:55
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0020_merge_20190917_1415'),
('establishment', '0019_position_priority'),
]
operations = [
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-09-18 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('timetable', '0001_initial'),
('establishment', '0021_delete_establishmentschedule'),
]
operations = [
migrations.AddField(
model_name='establishment',
name='schedule',
field=models.ManyToManyField(related_name='schedule', to='timetable.Timetable', verbose_name='Establishment schedule'),
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-19 11:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0021_merge_20190919_0955'),
('establishment', '0022_establishment_schedule'),
]
operations = [
]

View File

@ -262,6 +262,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
booking = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Booking URL'))
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
schedule = models.ManyToManyField(to='timetable.Timetable',
verbose_name=_('Establishment schedule'),
related_name='schedule')
# holidays_from = models.DateTimeField(verbose_name=_('Holidays from'),
# help_text=_('Holidays closing date from'))
# holidays_to = models.DateTimeField(verbose_name=_('Holidays to'),
# help_text=_('Holidays closing date to'))
awards = generic.GenericRelation(to='main.Award')
tags = generic.GenericRelation(to='main.MetaDataContent')
reviews = generic.GenericRelation(to='review.Review')
@ -335,6 +342,8 @@ class Position(BaseAttributes, TranslatedFieldsMixin):
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en":"some text"}')
priority = models.IntegerField(unique=True, null=True, default=None)
class Meta:
"""Meta class."""
@ -392,23 +401,6 @@ class EstablishmentScheduleQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentSchedule"""
class EstablishmentSchedule(BaseAttributes):
"""Establishment schedule model."""
establishment = models.OneToOneField(Establishment,
related_name='schedule',
on_delete=models.CASCADE,
verbose_name=_('Establishment'))
schedule = models.ManyToManyField(to='timetable.Timetable',
verbose_name=_('Establishment schedule'))
objects = EstablishmentScheduleQuerySet.as_manager()
class Meta:
"""Meta class"""
verbose_name = _('Establishment schedule')
verbose_name_plural = _('Establishment schedules')
class ContactPhone(models.Model):
"""Contact phone model."""
establishment = models.ForeignKey(

View File

@ -1,9 +1,12 @@
import json
from rest_framework import serializers
from establishment import models
from timetable.models import Timetable
from establishment.serializers import (
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
ContactPhonesSerializer, SocialNetworkRelatedSerializers)
ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentDetailSerializer
)
from main.models import Currency
@ -83,4 +86,15 @@ class ContactEmailBackSerializers(PlateSerializer):
'id',
'establishment',
'email'
]
class EmployeeBackSerializers(serializers.ModelSerializer):
"""Social network serializers."""
class Meta:
model = models.Employee
fields = [
'id',
'user',
'name'
]

View File

@ -9,7 +9,7 @@ from location.serializers import AddressSerializer
from main.models import MetaDataContent
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
from review import models as review_models
from timetable.models import Timetable
from timetable.serialziers import ScheduleRUDSerializer
from utils import exceptions as utils_exceptions
@ -111,22 +111,6 @@ class EstablishmentSubTypeSerializer(serializers.ModelSerializer):
fields = ('id', 'name_translated')
class EstablishmentScheduleSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model."""
weekday = serializers.CharField(source='get_weekday_display')
class Meta:
"""Meta class."""
model = Timetable
fields = (
'weekday',
'lunch_start',
'lunch_end',
'dinner_start',
'dinner_end',
)
class ReviewSerializer(serializers.ModelSerializer):
"""Serializer for model Review."""
text_translated = serializers.CharField(read_only=True)
@ -146,12 +130,13 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='employee.name')
position_translated = serializers.CharField(source='position.name_translated')
awards = AwardSerializer(source='employee.awards', many=True)
priority = serializers.IntegerField(source='position.priority')
class Meta:
"""Meta class."""
model = models.Employee
fields = ('id', 'name', 'position_translated', 'awards')
fields = ('id', 'name', 'position_translated', 'awards', 'priority')
class EstablishmentBaseSerializer(serializers.ModelSerializer):
@ -160,7 +145,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
subtypes = EstablishmentSubTypeSerializer(many=True)
address = AddressSerializer()
tags = MetaDataContentSerializer(many=True)
preview_image = serializers.ImageField(source='image')
preview_image = serializers.SerializerMethodField()
class Meta:
"""Meta class."""
@ -203,9 +188,7 @@ class EstablishmentDetailSerializer(EstablishmentListSerializer):
"""Serializer for Establishment model."""
description_translated = serializers.CharField(allow_null=True)
awards = AwardSerializer(many=True)
schedule = EstablishmentScheduleSerializer(source='schedule.schedule',
many=True,
allow_null=True)
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True, )
emails = ContactEmailsSerializer(read_only=True, many=True, )
review = serializers.SerializerMethodField()
@ -355,6 +338,7 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
class EstablishmentTagListSerializer(serializers.ModelSerializer):
"""List establishment tag serializer."""
id = serializers.IntegerField(source='metadata.id')
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True)
@ -362,5 +346,6 @@ class EstablishmentTagListSerializer(serializers.ModelSerializer):
"""Meta class."""
model = MetaDataContent
fields = [
'id',
'label_translated',
]

View File

@ -10,6 +10,10 @@ app_name = 'establishment'
urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'),
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
name='schedule-rud'),
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
name='schedule-create'),
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
path('plates/', views.PlateListCreateView.as_view(), name='plates'),
@ -20,5 +24,6 @@ urlpatterns = [
path('phones/<int:pk>/', views.PhonesRUDView.as_view(), name='phones-rud'),
path('emails/', views.EmailListCreateView.as_view(), name='emails'),
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
]

View File

@ -2,7 +2,8 @@
from rest_framework import generics
from establishment import models, serializers
from establishment import models
from establishment import serializers
from establishment.views.common import EstablishmentMixin
@ -73,4 +74,16 @@ class EmailListCreateView(generics.ListCreateAPIView):
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view."""
serializer_class = serializers.ContactEmailBackSerializers
queryset = models.ContactEmail.objects.all()
queryset = models.ContactEmail.objects.all()
class EmployeeListCreateView(generics.ListCreateAPIView):
"""Emplyoee list create view."""
serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all()
pagination_class = None
class EmployeeRUDView(generics.RetrieveDestroyAPIView):
"""Social RUD view."""
serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all()

View File

@ -6,9 +6,9 @@ from rest_framework import generics, permissions
from comment import models as comment_models
from establishment import filters
from establishment import models, serializers
from main.models import MetaDataContent
from utils.views import JWTGenericViewMixin
from establishment.views import EstablishmentMixin
from main.models import MetaDataContent
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
class EstablishmentListView(EstablishmentMixin, generics.ListAPIView):
@ -34,12 +34,12 @@ class EstablishmentSimilarListView(EstablishmentListView):
.order_by('-total_mark')[:13]
class EstablishmentRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.RetrieveAPIView):
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
serializer_class = serializers.EstablishmentDetailSerializer
class EstablishmentTypeListView(JWTGenericViewMixin, generics.ListAPIView):
class EstablishmentTypeListView(generics.ListAPIView):
"""Resource for getting a list of establishment types."""
permission_classes = (permissions.AllowAny,)
@ -122,7 +122,7 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D
return obj
class EstablishmentNearestRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.ListAPIView):
class EstablishmentNearestRetrieveView(EstablishmentMixin, generics.ListAPIView):
"""Resource for getting list of nearest establishments."""
serializer_class = serializers.EstablishmentListSerializer
filter_class = filters.EstablishmentFilter
@ -150,4 +150,42 @@ class EstablishmentTagListView(generics.ListAPIView):
def get_queryset(self):
"""Override get_queryset method"""
return MetaDataContent.objects.by_content_type(app_label='establishment',
model='establishment')
model='establishment')\
.distinct('metadata__label')
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Establishment schedule RUD view"""
serializer_class = ScheduleRUDSerializer
def get_object(self):
"""
Returns the object the view is displaying.
"""
lookup_url_kwargs = ('pk', 'schedule_id')
assert lookup_url_kwargs not in self.kwargs.keys(), (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwargs)
)
establishment_pk = self.kwargs['pk']
schedule_id = self.kwargs['schedule_id']
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
pk=establishment_pk)
schedule = get_object_or_404(klass=establishment.schedule,
id=schedule_id)
# May raise a permission denied
self.check_object_permissions(self.request, establishment)
self.check_object_permissions(self.request, schedule)
return schedule
class EstablishmentScheduleCreateView(generics.CreateAPIView):
"""Establishment schedule Create view"""
serializer_class = ScheduleCreateSerializer

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-12 13:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0013_auto_20190901_1032'),
]
operations = [
migrations.AddField(
model_name='feature',
name='priority',
field=models.IntegerField(default=None, null=True, unique=True),
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-18 13:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0014_feature_priority'),
('main', '0014_carousel'),
]
operations = [
]

View File

@ -0,0 +1,26 @@
# Generated by Django 2.2.4 on 2019-09-17 13:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('advertisement', '0002_auto_20190917_1307'),
('main', '0014_carousel'),
]
operations = [
migrations.CreateModel(
name='Page',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('page_name', models.CharField(max_length=255, unique=True)),
('advertisements', models.ManyToManyField(to='advertisement.Advertisement')),
],
options={
'verbose_name': 'Page',
'verbose_name_plural': 'Pages',
},
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-19 09:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0015_page'),
('main', '0015_merge_20190918_1341'),
]
operations = [
]

View File

@ -5,8 +5,11 @@ from django.contrib.postgres.fields import JSONField
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from advertisement.models import Advertisement
from location.models import Country
from main import methods
from review.models import Review
from utils.models import (ProjectBaseMixin, TJSONField,
TranslatedFieldsMixin, ImageMixin)
from utils.querysets import ContentTypeQuerySetMixin
@ -160,6 +163,7 @@ class Feature(ProjectBaseMixin):
"""Feature model."""
slug = models.CharField(max_length=255, unique=True)
priority = models.IntegerField(unique=True, null=True, default=None)
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
class Meta:
@ -311,6 +315,13 @@ class Carousel(models.Model):
if hasattr(self.content_object, 'awards'):
return self.content_object.awards
@property
def vintage_year(self):
if hasattr(self.content_object, 'reviews'):
review_qs = self.content_object.reviews.by_status(Review.READY)
if review_qs.exists():
return review_qs.last().vintage
@property
def toque_number(self):
if hasattr(self.content_object, 'toque_number'):
@ -323,12 +334,26 @@ class Carousel(models.Model):
@property
def image(self):
# Check if Generic obj has an image
if not hasattr(self.content_object.image, 'url'):
# Check if Generic obj has a FK to gallery
return self.content_object.image.image.url
return self.content_object.image.url
if hasattr(self.content_object.image, 'url'):
return self.content_object.image
if hasattr(self.content_object.image.image, 'url'):
return self.content_object.image.image
@property
def model_name(self):
return self.content_object.__class__.__name__
class Page(models.Model):
"""Page model."""
page_name = models.CharField(max_length=255, unique=True)
advertisements = models.ManyToManyField(Advertisement)
class Meta:
"""Meta class."""
verbose_name = _('Page')
verbose_name_plural = _('Pages')
def __str__(self):
return f'{self.page_name}'

View File

@ -1,6 +1,7 @@
"""Main app serializers."""
from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer
from main import models
@ -15,20 +16,23 @@ class FeatureSerializer(serializers.ModelSerializer):
fields = (
'id',
'slug',
'priority'
)
class SiteFeatureSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='feature.id')
slug = serializers.CharField(source='feature.slug')
priority = serializers.IntegerField(source='feature.priority')
class Meta:
"""Meta class."""
model = models.SiteFeature
fields = ('main',
'id',
'slug')
'slug',
'priority'
)
class SiteSettingsSerializer(serializers.ModelSerializer):
@ -36,7 +40,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
published_features = SiteFeatureSerializer(source='published_sitefeatures',
many=True, allow_null=True)
#todo: remove this
# todo: remove this
country_code = serializers.CharField(source='subdomain', read_only=True)
class Meta:
@ -58,7 +62,6 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
class SiteSerializer(serializers.ModelSerializer):
country = CountrySerializer()
class Meta:
@ -106,7 +109,7 @@ class AwardSerializer(AwardBaseSerializer):
class MetaDataContentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='metadata.id', read_only=True,)
id = serializers.IntegerField(source='metadata.id', read_only=True, )
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True)
@ -120,6 +123,7 @@ class MetaDataContentSerializer(serializers.ModelSerializer):
class CurrencySerializer(serializers.ModelSerializer):
"""Currency serializer"""
class Meta:
model = models.Currency
fields = [
@ -134,8 +138,9 @@ class CarouselListSerializer(serializers.ModelSerializer):
name = serializers.CharField()
toque_number = serializers.CharField()
public_mark = serializers.CharField()
image = serializers.URLField()
image = serializers.ImageField()
awards = AwardBaseSerializer(many=True)
vintage_year = serializers.IntegerField()
class Meta:
"""Meta class."""
@ -148,4 +153,19 @@ class CarouselListSerializer(serializers.ModelSerializer):
'toque_number',
'public_mark',
'image',
'vintage_year',
]
class PageSerializer(serializers.ModelSerializer):
page_name = serializers.CharField()
advertisements = AdvertisementSerializer(source='advertisements', many=True)
class Meta:
"""Meta class."""
model = models.Carousel
fields = [
'id',
'page_name',
'advertisements'
]

0
apps/recipe/__init__.py Normal file
View File

8
apps/recipe/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class RecipeConfig(AppConfig):
name = 'recipe'
verbose_name = _('RecipeConfig')

View File

@ -0,0 +1,44 @@
# Generated by Django 2.2.4 on 2019-09-19 11:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import easy_thumbnails.fields
import utils.methods
import utils.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Recipe',
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')),
('image', easy_thumbnails.fields.ThumbnailerImageField(blank=True, default=None, null=True, upload_to=utils.methods.image_path, verbose_name='Image')),
('title', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB": "some text"}', null=True, verbose_name='Title')),
('subtitle', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB": "some text"}', null=True, verbose_name='Subtitle')),
('description', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB": "some text"}', null=True, verbose_name='Description')),
('state', models.PositiveSmallIntegerField(choices=[(0, 'Waiting'), (1, 'Hidden'), (2, 'Published'), (3, 'Published exclusive')], default=0, verbose_name='State')),
('author', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author')),
('published_at', models.DateTimeField(blank=True, default=None, help_text='Published at', null=True, verbose_name='Published at')),
('published_scheduled_at', models.DateTimeField(blank=True, default=None, help_text='Published scheduled at', null=True, verbose_name='Published scheduled at')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recipe_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='recipe_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
],
options={
'verbose_name': 'Recipe',
'verbose_name_plural': 'Recipes',
},
bases=(utils.models.TranslatedFieldsMixin, models.Model),
),
]

View File

57
apps/recipe/models.py Normal file
View File

@ -0,0 +1,57 @@
"""Recipe app models."""
from django.db import models
from django.utils.translation import ugettext_lazy as _
from utils.models import (TranslatedFieldsMixin, ImageMixin, BaseAttributes,
TJSONField)
class RecipeQuerySet(models.QuerySet):
"""Extended queryset for Recipe model."""
# todo: what records are considered published?
def published(self):
return self.filter(state__in=[self.model.PUBLISHED,
self.model.PUBLISHED_EXCLUSIVE])
class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes):
"""Recipe model."""
WAITING = 0
HIDDEN = 1
PUBLISHED = 2
PUBLISHED_EXCLUSIVE = 3
STATE_CHOICES = (
(WAITING, _('Waiting')),
(HIDDEN, _('Hidden')),
(PUBLISHED, _('Published')),
(PUBLISHED_EXCLUSIVE, _('Published exclusive')),
)
STR_FIELD_NAME = 'title'
title = TJSONField(blank=True, null=True, default=None, verbose_name=_('Title'),
help_text='{"en-GB": "some text"}')
subtitle = TJSONField(blank=True, null=True, default=None, verbose_name=_('Subtitle'),
help_text='{"en-GB": "some text"}')
description = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en-GB": "some text"}')
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('State'))
author = models.CharField(max_length=255, blank=True, null=True, default=None,
verbose_name=_('Author'))
published_at = models.DateTimeField(verbose_name=_('Published at'),
blank=True, default=None, null=True,
help_text=_('Published at'))
published_scheduled_at = models.DateTimeField(verbose_name=_('Published scheduled at'),
blank=True, default=None, null=True,
help_text=_('Published scheduled at'))
objects = RecipeQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('Recipe')
verbose_name_plural = _('Recipes')

View File

View File

@ -0,0 +1,30 @@
"""Recipe app common serializers."""
from rest_framework import serializers
from recipe import models
class RecipeListSerializer(serializers.ModelSerializer):
"""Serializer for list of recipes."""
title_translated = serializers.CharField(allow_null=True, read_only=True)
subtitle_translated = serializers.CharField(allow_null=True, read_only=True)
in_favorites = serializers.BooleanField()
class Meta:
"""Meta class."""
model = models.Recipe
fields = ('id', 'title_translated', 'subtitle_translated', 'author',
'published_at', 'in_favorites')
read_only_fields = fields
class RecipeDetailSerializer(RecipeListSerializer):
"""Serializer for more information about the recipe."""
description_translated = serializers.CharField(allow_null=True, read_only=True)
class Meta(RecipeListSerializer.Meta):
"""Meta class."""
fields = RecipeListSerializer.Meta.fields + ('description_translated',)

View File

View File

@ -0,0 +1,10 @@
"""Recipe app common urlpatterns."""
from django.urls import path
from recipe.views import common as views
app_name = 'recipe'
urlpatterns = [
path('', views.RecipeListView.as_view(), name='list'),
path('<int:pk>/', views.RecipeDetailView.as_view(), name='detail'),
]

6
apps/recipe/urls/web.py Normal file
View File

@ -0,0 +1,6 @@
"""Recipe app web urlconf."""
from recipe.urls.common import urlpatterns as common_urlpatterns
urlpatterns = []
urlpatterns.extend(common_urlpatterns)

View File

View File

@ -0,0 +1,28 @@
"""Recipe app common views."""
from rest_framework import generics, permissions
from recipe import models
from recipe.serializers import common as serializers
class RecipeViewMixin(generics.GenericAPIView):
"""Recipe view mixin."""
pagination_class = None
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
user = self.request.user
qs = models.Recipe.objects.published().annotate_in_favorites(user)
return qs
class RecipeListView(RecipeViewMixin, generics.ListAPIView):
"""Resource for obtaining a list of recipes."""
serializer_class = serializers.RecipeListSerializer
class RecipeDetailView(RecipeViewMixin, generics.RetrieveAPIView):
"""Resource for detailed recipe information."""
serializer_class = serializers.RecipeDetailSerializer

View File

@ -16,10 +16,28 @@ class EstablishmentDocument(Document):
description = fields.ObjectField(attr='description_indexing',
properties=OBJECT_FIELD_PROPERTIES)
establishment_type = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing',
properties=OBJECT_FIELD_PROPERTIES)
})
establishment_subtypes = fields.ObjectField(
properties={
'id': fields.IntegerField(),
'name': fields.ObjectField(attr='name_indexing',
properties=OBJECT_FIELD_PROPERTIES)
},
multi=True)
tags = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label')
'id': fields.IntegerField(attr='metadata.id'),
'label': fields.ObjectField(attr='metadata.label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'category': fields.ObjectField(attr='metadata.category',
properties={
'id': fields.IntegerField(),
})
},
multi=True)
address = fields.ObjectField(
@ -50,6 +68,12 @@ class EstablishmentDocument(Document):
),
}
)
collections = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='collection.id'),
'collection_type': fields.IntegerField(attr='collection.collection_type'),
},
multi=True)
class Django:
@ -61,5 +85,5 @@ class EstablishmentDocument(Document):
'price_level',
)
def prepare_tags(self, instance):
return instance.tags_indexing
def get_queryset(self):
return super().get_queryset().published()

View File

@ -60,6 +60,9 @@ class EstablishmentDocumentSerializer(DocumentSerializer):
'description_translated',
'tags',
'address',
'collections',
'establishment_type',
'establishment_subtypes',
)
@staticmethod

View File

@ -1,7 +1,8 @@
"""Search indexes app views."""
from rest_framework import permissions
from django_elasticsearch_dsl_drf import constants
from django_elasticsearch_dsl_drf.filter_backends import FilteringFilterBackend
from django_elasticsearch_dsl_drf.filter_backends import (FilteringFilterBackend,
GeoSpatialFilteringFilterBackend)
from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination
from search_indexes import serializers, filters
@ -47,6 +48,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
filter_backends = [
FilteringFilterBackend,
filters.CustomSearchFilterBackend,
GeoSpatialFilteringFilterBackend,
]
search_fields = (
@ -85,5 +87,29 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
},
'country': {
'field': 'address.city.country.code'
},
'tags_id': {
'field': 'tags.id',
},
'tags_category_id': {
'field': 'tags.category.id',
},
'collection_type': {
'field': 'collections.collection_type'
},
'establishment_type': {
'field': 'establishment_type.id'
},
'establishment_subtypes': {
'field': 'establishment_subtypes.id'
},
}
geo_spatial_filter_fields = {
'location': {
'field': 'address.location',
'lookups': [
constants.LOOKUP_FILTER_GEO_BOUNDING_BOX,
]
}
}

View File

@ -0,0 +1,43 @@
# Generated by Django 2.2.4 on 2019-09-19 11:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('timetable', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='timetable',
name='closed_at',
field=models.TimeField(null=True, verbose_name='Closed time'),
),
migrations.AddField(
model_name='timetable',
name='opening_at',
field=models.TimeField(null=True, verbose_name='Opening time'),
),
migrations.AlterField(
model_name='timetable',
name='dinner_end',
field=models.TimeField(null=True, verbose_name='Dinner end time'),
),
migrations.AlterField(
model_name='timetable',
name='dinner_start',
field=models.TimeField(null=True, verbose_name='Dinner start time'),
),
migrations.AlterField(
model_name='timetable',
name='lunch_end',
field=models.TimeField(null=True, verbose_name='Lunch end time'),
),
migrations.AlterField(
model_name='timetable',
name='lunch_start',
field=models.TimeField(null=True, verbose_name='Lunch start time'),
),
]

View File

@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from utils.models import ProjectBaseMixin
@ -23,10 +24,13 @@ class Timetable(ProjectBaseMixin):
(SUNDAY, _('Sunday')))
weekday = models.PositiveSmallIntegerField(choices=WEEKDAYS_CHOICES, verbose_name=_('Week day'))
lunch_start = models.TimeField(verbose_name=_('Lunch start time'))
lunch_end = models.TimeField(verbose_name=_('Lunch end time'))
dinner_start = models.TimeField(verbose_name=_('Dinner start time'))
dinner_end = models.TimeField(verbose_name=_('Dinner end time'))
lunch_start = models.TimeField(verbose_name=_('Lunch start time'), null=True)
lunch_end = models.TimeField(verbose_name=_('Lunch end time'), null=True)
dinner_start = models.TimeField(verbose_name=_('Dinner start time'), null=True)
dinner_end = models.TimeField(verbose_name=_('Dinner end time'), null=True)
opening_at = models.TimeField(verbose_name=_('Opening time'), null=True)
closed_at = models.TimeField(verbose_name=_('Closed time'), null=True)
class Meta:
"""Meta class."""

View File

@ -1,16 +1,79 @@
"""Serializer for app timetable"""
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from timetable import models
from establishment.models import Establishment
from timetable.models import Timetable
class TimetableSerializer(serializers.ModelSerializer):
"""Serializer for model Timetable"""
class ScheduleRUDSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model."""
weekday_display = serializers.CharField(source='get_weekday_display',
read_only=True)
lunch_start = serializers.TimeField(required=False)
lunch_end = serializers.TimeField(required=False)
dinner_start = serializers.TimeField(required=False)
dinner_end = serializers.TimeField(required=False)
opening_at = serializers.TimeField(required=False)
closed_at = serializers.TimeField(required=False)
NULLABLE_FIELDS = ['lunch_start', 'lunch_end', 'dinner_start',
'dinner_end', 'opening_at', 'closed_at']
class Meta:
"""Meta class."""
model = models.Timetable
fields = (
model = Timetable
fields = [
'id',
'weekday_display',
'lunch_start',
'lunch_end',
'dinner_start',
'dinner_end',
'opening_at',
'closed_at',
]
def validate(self, attrs):
"""Override validate method"""
establishment_pk = self.context.get('request')\
.parser_context.get('view')\
.kwargs.get('pk')
# Check if establishment exists.
establishment_qs = Establishment.objects.filter(pk=establishment_pk)
if not establishment_qs.exists():
raise serializers.ValidationError({'detail': _('Establishment not found.')})
attrs['establishment'] = establishment_qs.first()
# If fields not in request data then put it in attrs with None value.
if not self.partial:
for field in self.NULLABLE_FIELDS:
if field not in attrs:
attrs.setdefault(field, None)
return attrs
class ScheduleCreateSerializer(ScheduleRUDSerializer):
"""Serializer for Establishment model."""
weekday = serializers.IntegerField(write_only=True)
class Meta:
"""Meta class."""
model = Timetable
fields = ScheduleRUDSerializer.Meta.fields + [
'weekday',
'start',
'end',
)
]
def create(self, validated_data):
"""Override create method"""
establishment = validated_data.pop('establishment')
weekday = validated_data.get('weekday')
instance = super().create(validated_data)
schedule_qs = establishment.schedule.filter(weekday=weekday)
if schedule_qs.exists():
schedule_qs.delete()
establishment.schedule.add(instance)
return instance

View File

@ -205,10 +205,12 @@ class PlatformMixin(models.Model):
MOBILE = 0
WEB = 1
ALL = 2
SOURCES = (
(MOBILE, _('Mobile')),
(WEB, _('Web')),
(ALL, _('All'))
)
source = models.PositiveSmallIntegerField(choices=SOURCES, default=MOBILE,
verbose_name=_('Source'))

View File

@ -63,6 +63,7 @@ PROJECT_APPS = [
'news.apps.NewsConfig',
'notification.apps.NotificationConfig',
'partner.apps.PartnerConfig',
'recipe.apps.RecipeConfig',
'search_indexes.apps.SearchIndexesConfig',
'translation.apps.TranslationConfig',
'configuration.apps.ConfigurationConfig',

View File

@ -27,6 +27,7 @@ urlpatterns = [
path('partner/', include('partner.urls.web')),
path('location/', include('location.urls.web')),
path('main/', include('main.urls')),
path('recipes/', include('recipe.urls.web')),
path('translation/', include('translation.urls')),
path('comments/', include('comment.urls.web')),
path('favorites/', include('favorites.urls')),