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

This commit is contained in:
Anatoly 2019-09-02 11:10:52 +03:00
commit 3c7fc7a436
52 changed files with 1129 additions and 30 deletions

View File

@ -18,7 +18,7 @@ class UserAdmin(BaseUserAdmin):
fieldsets = (
(None, {'fields': ('email', 'password',)}),
(_('Personal info'), {
'fields': ('username', 'first_name', 'last_name', )}),
'fields': ('username', 'first_name', 'last_name', 'image')}),
(_('Subscription'), {
'fields': (
'newsletter',

View File

@ -1,8 +1,10 @@
"""Establishment admin conf."""
from django.contrib import admin
from establishment import models
from django.contrib.contenttypes.admin import GenericTabularInline
from establishment import models
from main.models import Award, MetaDataContent
from review import models as review_models
from django.utils.translation import gettext_lazy as _
@admin.register(models.EstablishmentType)
@ -25,12 +27,63 @@ class MetaDataContentInline(GenericTabularInline):
extra = 0
class ContactPhoneInline(admin.TabularInline):
"""Contact phone inline admin."""
model = models.ContactPhone
extra = 0
class ContactEmailInline(admin.TabularInline):
"""Contact email inline admin."""
model = models.ContactEmail
extra = 0
class ReviewInline(GenericTabularInline):
model = review_models.Review
extra = 0
@admin.register(models.Establishment)
class EstablishmentAdmin(admin.ModelAdmin):
"""Establishment admin."""
inlines = [AwardInline, MetaDataContentInline]
inlines = [
AwardInline, MetaDataContentInline,
ContactPhoneInline, ContactEmailInline,
ReviewInline]
@admin.register(models.EstablishmentSchedule)
class EstablishmentSchedule(admin.ModelAdmin):
"""Establishment schedule"""
@admin.register(models.Comment)
class EstablishmentComment(admin.ModelAdmin):
"""Establishment comments."""
@admin.register(models.Position)
class PositionAdmin(admin.ModelAdmin):
"""Position admin."""
class PlateInline(admin.TabularInline):
"""Plate inline admin"""
model = models.Plate
extra = 0
@admin.register(models.Menu)
class MenuAdmin(admin.ModelAdmin):
"""Menu admin."""
list_display = ['id', 'category_translated']
inlines = [
PlateInline,
]
def category_translated(self, obj):
"""Get user's short name."""
return obj.category_translated
category_translated.short_description = _('category')

View File

@ -0,0 +1,77 @@
# Generated by Django 2.2.4 on 2019-09-01 08:31
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
import utils.models
class Migration(migrations.Migration):
dependencies = [
('location', '0009_auto_20190901_0831'),
('establishment', '0004_auto_20190828_1156'),
]
operations = [
migrations.CreateModel(
name='Contact',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Address')),
],
options={
'verbose_name': 'contact',
'verbose_name_plural': 'contacts',
},
),
migrations.AlterField(
model_name='establishment',
name='description',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='establishment',
name='name',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='establishmentsubtype',
name='name',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='establishmenttype',
name='name',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Description'),
),
migrations.CreateModel(
name='ContactPhone',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128)),
('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='phones', to='establishment.Contact')),
],
options={
'verbose_name': 'contact phone',
'verbose_name_plural': 'contact phones',
},
),
migrations.CreateModel(
name='ContactEmail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('contact', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emails', to='establishment.Contact')),
],
options={
'verbose_name': 'contact email',
'verbose_name_plural': 'contact emails',
},
),
migrations.AddField(
model_name='contact',
name='establishment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contacts', to='establishment.Establishment'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 2.2.4 on 2019-09-01 09:16
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('establishment', '0005_establishmentschedule'),
]
operations = [
migrations.CreateModel(
name='Comment',
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')),
('text', models.TextField(verbose_name='Comment text')),
('mark', models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='Mark')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL, verbose_name='Author')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='establishment.Establishment', verbose_name='Establishment')),
],
options={
'verbose_name': 'Comment',
'verbose_name_plural': 'Comments',
},
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-01 08:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0005_auto_20190901_0831'),
('establishment', '0005_establishmentschedule'),
]
operations = [
]

View File

@ -0,0 +1,30 @@
# Generated by Django 2.2.4 on 2019-09-01 10:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0006_merge_20190901_0846'),
]
operations = [
migrations.RemoveField(
model_name='contactemail',
name='contact',
),
migrations.RemoveField(
model_name='contactphone',
name='contact',
),
migrations.DeleteModel(
name='Contact',
),
migrations.DeleteModel(
name='ContactEmail',
),
migrations.DeleteModel(
name='ContactPhone',
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 2.2.4 on 2019-09-01 10:36
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('establishment', '0007_auto_20190901_1032'),
]
operations = [
migrations.CreateModel(
name='ContactPhone',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128)),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='phones', to='establishment.Establishment')),
],
options={
'verbose_name': 'contact phone',
'verbose_name_plural': 'contact phones',
},
),
migrations.CreateModel(
name='ContactEmail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emails', to='establishment.Establishment')),
],
options={
'verbose_name': 'contact email',
'verbose_name_plural': 'contact emails',
},
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-01 11:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('establishment', '0006_comment'),
('establishment', '0008_contactemail_contactphone'),
]
operations = [
]

View File

@ -0,0 +1,81 @@
# Generated by Django 2.2.4 on 2019-09-01 11:42
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', '0009_merge_20190901_1131'),
]
operations = [
migrations.CreateModel(
name='Employee',
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(max_length=255, verbose_name='Last name')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
],
options={
'verbose_name': 'Employee',
'verbose_name_plural': 'Employees',
},
),
migrations.CreateModel(
name='Position',
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', utils.models.TJSONField(blank=True, default=None, help_text='{"en":"some text"}', null=True, verbose_name='Description')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='position_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='position_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
],
options={
'verbose_name': 'Position',
'verbose_name_plural': 'Positions',
},
bases=(models.Model, utils.models.TraslatedFieldsMixin),
),
migrations.CreateModel(
name='EstablishmentEmployee',
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')),
('from_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='From date')),
('to_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='To date')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentemployee_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('employee', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Employee', verbose_name='Employee')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Establishment', verbose_name='Establishment')),
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentemployee_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
('position', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Position', verbose_name='Position')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='employee',
name='establishments',
field=models.ManyToManyField(related_name='employees', through='establishment.EstablishmentEmployee', to='establishment.Establishment'),
),
migrations.AddField(
model_name='employee',
name='modified_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by'),
),
migrations.AddField(
model_name='employee',
name='user',
field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 2.2.4 on 2019-09-01 12:11
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),
('main', '0013_auto_20190901_1032'),
('establishment', '0010_auto_20190901_1142'),
]
operations = [
migrations.CreateModel(
name='Menu',
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')),
('category', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menu_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='establishment.Establishment', verbose_name='establishment')),
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='menu_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
],
options={
'verbose_name': 'menu',
'verbose_name_plural': 'menu',
},
bases=(utils.models.TraslatedFieldsMixin, models.Model),
),
migrations.CreateModel(
name='Plate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name')),
('description', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description')),
('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='price')),
('is_signature_plate', models.BooleanField(verbose_name='is signature plate')),
('currency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Currency', verbose_name='currency')),
('menu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='establishment.Menu', verbose_name='menu')),
],
options={
'verbose_name': 'plate',
'verbose_name_plural': 'plates',
},
bases=(utils.models.TraslatedFieldsMixin, models.Model),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-09-01 12:51
from django.db import migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0011_menu_plate'),
]
operations = [
migrations.AlterField(
model_name='menu',
name='category',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='category'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.4 on 2019-09-01 14:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0012_auto_20190901_1251'),
]
operations = [
migrations.AddField(
model_name='establishment',
name='booking',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Booking URL'),
),
migrations.AddField(
model_name='establishment',
name='facebook',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Facebook URL'),
),
migrations.AddField(
model_name='establishment',
name='lafourchette',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Lafourchette URL'),
),
migrations.AddField(
model_name='establishment',
name='twitter',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Twitter URL'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-01 14:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0013_auto_20190901_1428'),
]
operations = [
migrations.AddField(
model_name='establishment',
name='website',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Web site URL'),
),
]

View File

@ -1,12 +1,16 @@
"""Establishment models."""
from functools import reduce
from django.contrib.contenttypes import fields as generic
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from location.models import Address
from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField,
TraslatedFieldsMixin, BaseAttributes)
from django.contrib.contenttypes import fields as generic
# todo: establishment type&subtypes check
@ -14,7 +18,7 @@ class EstablishmentType(ProjectBaseMixin, TraslatedFieldsMixin):
"""Establishment type model."""
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en":"some text"}')
help_text='{"en-GB":"some text"}')
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
class Meta:
@ -38,7 +42,7 @@ class EstablishmentSubType(ProjectBaseMixin, TraslatedFieldsMixin):
"""Establishment type model."""
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en":"some text"}')
help_text='{"en-GB":"some text"}')
establishment_type = models.ForeignKey(EstablishmentType,
on_delete=models.CASCADE,
verbose_name=_('Type'))
@ -70,15 +74,27 @@ class EstablishmentQuerySet(models.QuerySet):
else:
return self.none()
def by_country_code(self, code):
"""Return establishments by country code"""
return self.filter(address__city__country__code=code)
def prefetch_actual_employees(self):
"""Prefetch actual employees."""
return self.prefetch_related(
models.Prefetch('establishmentemployee_set',
queryset=EstablishmentEmployee.objects.actual().select_related(
'position'),
to_attr='actual_establishment_employees'))
class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
"""Establishment model."""
name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en":"some text"}')
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Description'),
help_text='{"en":"some text"}')
help_text='{"en-GB":"some text"}')
public_mark = models.PositiveIntegerField(blank=True, null=True,
default=None,
verbose_name=_('Public mark'),)
@ -98,8 +114,19 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
price_level = models.PositiveIntegerField(blank=True, null=True,
default=None,
verbose_name=_('Price level'))
website = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Web site URL'))
facebook = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Facebook URL'))
twitter = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Twitter URL'))
lafourchette = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Lafourchette URL'))
booking = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Booking URL'))
awards = generic.GenericRelation(to='main.Award')
tags = generic.GenericRelation(to='main.MetaDataContent')
reviews = generic.GenericRelation(to='review.Review')
objects = EstablishmentQuerySet.as_manager()
@ -123,6 +150,7 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
country = self.address.city.country
return country.low_price, country.high_price
# todo: make via prefetch
@property
def subtypes(self):
return EstablishmentSubType.objects.filter(
@ -140,6 +168,73 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
raise ValidationError('Establishment type of subtype does not match')
self.establishment_subtypes.add(establishment_subtype)
@property
def best_price_menu(self):
return 150
@property
def best_price_carte(self):
return 200
class Position(BaseAttributes, TraslatedFieldsMixin):
"""Position model."""
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
help_text='{"en":"some text"}')
class Meta:
"""Meta class."""
verbose_name = _('Position')
verbose_name_plural = _('Positions')
class EstablishmentEmployeeQuerySet(models.QuerySet):
"""Extended queryset for EstablishmEntemployee model."""
def actual(self):
"""Actual objects.."""
now = timezone.now()
return self.filter(models.Q(from_date__lte=now),
(models.Q(to_date__gte=now) |
models.Q(to_date__isnull=True)))
class EstablishmentEmployee(BaseAttributes):
"""EstablishmentEmployee model."""
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
verbose_name=_('Establishment'))
employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT,
verbose_name=_('Employee'))
from_date = models.DateTimeField(default=timezone.now, verbose_name=_('From date'))
to_date = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('To date'))
position = models.ForeignKey(Position, on_delete=models.PROTECT,
verbose_name=_('Position'))
objects = EstablishmentEmployeeQuerySet.as_manager()
class Employee(BaseAttributes):
"""Employee model."""
user = models.OneToOneField('account.User', on_delete=models.PROTECT,
null=True, blank=True, default=None,
verbose_name=_('User'))
name = models.CharField(max_length=255, verbose_name=_('Last name'))
establishments = models.ManyToManyField(Establishment, related_name='employees',
through=EstablishmentEmployee)
awards = generic.GenericRelation(to='main.Award')
tags = generic.GenericRelation(to='main.MetaDataContent')
class Meta:
"""Meta class."""
verbose_name = _('Employee')
verbose_name_plural = _('Employees')
class EstablishmentScheduleQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentSchedule"""
@ -160,3 +255,124 @@ class EstablishmentSchedule(BaseAttributes):
"""Meta class"""
verbose_name = _('Establishment schedule')
verbose_name_plural = _('Establishment schedules')
class ContactPhone(models.Model):
"""Contact phone model."""
establishment = models.ForeignKey(
Establishment, related_name='phones', on_delete=models.CASCADE)
phone = PhoneNumberField()
class Meta:
verbose_name = _('contact phone')
verbose_name_plural = _('contact phones')
def __str__(self):
return f'{self.phone.as_e164}'
class ContactEmail(models.Model):
"""Contact email model."""
establishment = models.ForeignKey(
Establishment, related_name='emails', on_delete=models.CASCADE)
email = models.EmailField()
class Meta:
verbose_name = _('contact email')
verbose_name_plural = _('contact emails')
def __str__(self):
return f'{self.email}'
#
# class Wine(TraslatedFieldsMixin, models.Model):
# """Wine model."""
# establishment = models.ForeignKey(
# 'establishment.Establishment', verbose_name=_('establishment'),
# on_delete=models.CASCADE)
# bottles = models.IntegerField(_('bottles'))
# price_min = models.DecimalField(
# _('price min'), max_digits=14, decimal_places=2)
# price_max = models.DecimalField(
# _('price max'), max_digits=14, decimal_places=2)
# by_glass = models.BooleanField(_('by glass'))
# price_glass_min = models.DecimalField(
# _('price min'), max_digits=14, decimal_places=2)
# price_glass_max = models.DecimalField(
# _('price max'), max_digits=14, decimal_places=2)
#
class Plate(TraslatedFieldsMixin, models.Model):
"""Plate model."""
name = TJSONField(
blank=True, null=True, default=None, verbose_name=_('name'),
help_text='{"en-GB":"some text"}')
description = TJSONField(
blank=True, null=True, default=None, verbose_name=_('description'),
help_text='{"en-GB":"some text"}')
price = models.DecimalField(
_('price'), max_digits=14, decimal_places=2)
is_signature_plate = models.BooleanField(_('is signature plate'))
currency = models.ForeignKey(
'main.Currency', verbose_name=_('currency'), on_delete=models.CASCADE)
menu = models.ForeignKey(
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
class Meta:
verbose_name = _('plate')
verbose_name_plural = _('plates')
def __str__(self):
return f'plate_id:{self.id}'
class Menu(TraslatedFieldsMixin, BaseAttributes):
"""Menu model."""
category = TJSONField(
blank=True, null=True, default=None, verbose_name=_('category'),
help_text='{"en-GB":"some text"}')
establishment = models.ForeignKey(
'establishment.Establishment', verbose_name=_('establishment'),
on_delete=models.CASCADE)
class Meta:
verbose_name = _('menu')
verbose_name_plural = _('menu')
class CommentQuerySet(models.QuerySet):
"""QuerySets for Comment model."""
def by_author(self, author):
"""Return comments by author"""
return self.filter(author=author)
class Comment(ProjectBaseMixin):
"""Comment model."""
text = models.TextField(verbose_name=_('Comment text'))
mark = models.PositiveIntegerField(blank=True, null=True,
default=None,
verbose_name=_('Mark'))
author = models.ForeignKey('account.User',
related_name='comments',
on_delete=models.CASCADE,
verbose_name=_('Author'))
establishment = models.ForeignKey(Establishment,
related_name='comments',
on_delete=models.CASCADE,
verbose_name=_('Establishment'))
objects = CommentQuerySet.as_manager()
class Meta:
"""Meta class"""
verbose_name = _('Comment')
verbose_name_plural = _('Comments')
def __str__(self):
"""String representation"""
return str(self.author)

View File

@ -1,11 +1,57 @@
"""Establishment serializers."""
from rest_framework import serializers
from establishment import models
from location.serializers import AddressSerializer
from main.serializers import MetaDataContentSerializer, AwardSerializer
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
from review import models as review_models
from timetable.models import Timetable
class ContactPhonesSerializer(serializers.ModelSerializer):
"""Contact phone serializer"""
class Meta:
model = models.ContactPhone
fields = [
'phone'
]
class ContactEmailsSerializer(serializers.ModelSerializer):
"""Contact email serializer"""
class Meta:
model = models.ContactEmail
fields = [
'email'
]
class PlateSerializer(serializers.ModelSerializer):
currency = CurrencySerializer(read_only=True)
class Meta:
model = models.Plate
fields = [
'name_translated',
'currency',
'price',
'is_signature_plate',
]
class MenuSerializers(serializers.ModelSerializer):
plates = PlateSerializer(read_only=True, many=True, source='plate_set')
category_translated = serializers.CharField(read_only=True)
class Meta:
model = models.Menu
fields = [
'id',
'category_translated',
'plates'
]
class EstablishmentTypeSerializer(serializers.ModelSerializer):
"""Serializer for EstablishmentType model."""
@ -46,6 +92,50 @@ class EstablishmentScheduleSerializer(serializers.ModelSerializer):
)
class ReviewSerializer(serializers.ModelSerializer):
"""Serializer for model Review."""
text_translated = serializers.CharField(read_only=True)
class Meta:
"""Meta class."""
model = review_models.Review
fields = (
'text_translated',
)
class CommentSerializer(serializers.ModelSerializer):
"""Comment serializer"""
nickname = serializers.CharField(source='author.username')
profile_pic = serializers.ImageField(source='author.image')
class Meta:
"""Serializer for model Comment"""
model = models.Comment
fields = (
'created',
'text',
'mark',
'nickname',
'profile_pic'
)
class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
"""Serializer for actual employees."""
id = serializers.IntegerField(source='employee.id')
name = serializers.CharField(source='employee.name')
position_translated = serializers.CharField(source='position.name_translated')
awards = AwardSerializer(source='employee.awards', many=True)
class Meta:
"""Meta class."""
model = models.Employee
fields = ('id', 'name', 'position_translated', 'awards')
class EstablishmentSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model."""
@ -59,6 +149,17 @@ class EstablishmentSerializer(serializers.ModelSerializer):
schedule = EstablishmentScheduleSerializer(source='schedule.schedule',
many=True,
allow_null=True)
phones = ContactPhonesSerializer(read_only=True, many=True, )
emails = ContactEmailsSerializer(read_only=True, many=True, )
reviews = ReviewSerializer(source='reviews.last', allow_null=True)
comments = CommentSerializer(many=True, allow_null=True)
employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees',
many=True)
menu = MenuSerializers(source='menu_set', many=True, read_only=True)
preview_image = serializers.SerializerMethodField()
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
class Meta:
"""Meta class."""
@ -75,8 +176,27 @@ class EstablishmentSerializer(serializers.ModelSerializer):
'type',
'subtypes',
'image',
'preview_image',
'address',
'tags',
'awards',
'schedule',
'website',
'facebook',
'twitter',
'lafourchette',
'booking',
'phones',
'emails',
'reviews',
'comments',
'employees',
'menu',
'best_price_menu',
'best_price_carte'
)
def get_preview_image(self, obj):
"""Get preview image"""
return obj.get_full_image_url(request=self.context.get('request'),
thumbnail_key='establishment_preview')

View File

@ -5,22 +5,32 @@ from utils.views import JWTGenericViewMixin
from establishment import filters
class EstablishmentListView(JWTGenericViewMixin, generics.ListAPIView):
class EstablishmentMixin:
"""Establishment mixin."""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.EstablishmentSerializer
def get_queryset(self):
"""Overrided method 'get_queryset'."""
return models.Establishment.objects.all().prefetch_actual_employees()
class EstablishmentListView(EstablishmentMixin, JWTGenericViewMixin, generics.ListAPIView):
"""Resource for getting a list of establishments."""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.EstablishmentSerializer
queryset = models.Establishment.objects.all()
filter_class = filters.EstablishmentFilter
def get_queryset(self):
"""Overrided method 'get_queryset'."""
return models.Establishment.objects.all()\
.prefetch_actual_employees()\
.by_country_code(code=self.request.country_code)
class EstablishmentRetrieveView(JWTGenericViewMixin, generics.RetrieveAPIView):
class EstablishmentRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment."""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.EstablishmentSerializer
queryset = models.Establishment.objects.all()
class EstablishmentTypeListView(JWTGenericViewMixin, generics.ListAPIView):
"""Resource for getting a list of establishment types."""

View File

@ -30,6 +30,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='country',
name='name',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en":"some text"}', null=True, verbose_name='Text'),
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Text'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-09-01 08:31
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('location', '0008_auto_20190827_1302'),
]
operations = [
migrations.AlterField(
model_name='country',
name='name',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Name'),
),
]

View File

@ -17,7 +17,7 @@ class Country(SVGImageMixin, ProjectBaseMixin):
"""Country model."""
name = JSONField(null=True, blank=True, default=None,
verbose_name=_('Name'), help_text='{"en":"some text"}')
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
code = models.CharField(max_length=255, unique=True, verbose_name=_('Code'))
low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
high_price = models.IntegerField(default=50, verbose_name=_('High price'))

View File

@ -39,3 +39,10 @@ class MetaDataCategoryAdmin(admin.ModelAdmin):
@admin.register(models.MetaDataContent)
class MetaDataContentAdmin(admin.ModelAdmin):
"""MetaDataContent admin"""
@admin.register(models.Currency)
class CurrencContentAdmin(admin.ModelAdmin):
"""CurrencContent admin"""

View File

@ -0,0 +1,35 @@
# Generated by Django 2.2.4 on 2019-09-01 10:32
from django.db import migrations, models
import utils.models
class Migration(migrations.Migration):
dependencies = [
('main', '0012_auto_20190829_1155'),
]
operations = [
migrations.CreateModel(
name='Currency',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='name')),
],
options={
'verbose_name': 'currency',
'verbose_name_plural': 'currencies',
},
),
migrations.AlterField(
model_name='award',
name='title',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='title'),
),
migrations.AlterField(
model_name='metadata',
name='label',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='label'),
),
]

View File

@ -190,7 +190,7 @@ class Award(TraslatedFieldsMixin, models.Model):
award_type = models.ForeignKey('main.AwardType', on_delete=models.CASCADE)
title = TJSONField(
_('title'), null=True, blank=True,
default=None, help_text='{"en":"some text"}')
default=None, help_text='{"en-GB":"some text"}')
vintage_year = models.CharField(_('vintage year'), max_length=255, default='')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
@ -228,7 +228,7 @@ class MetaData(TraslatedFieldsMixin, models.Model):
"""MetaData model."""
label = TJSONField(
_('label'), null=True, blank=True,
default=None, help_text='{"en":"some text"}')
default=None, help_text='{"en-GB":"some text"}')
category = models.ForeignKey(
MetaDataCategory, verbose_name=_('category'), on_delete=models.CASCADE)
@ -250,3 +250,15 @@ class MetaDataContent(models.Model):
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
metadata = models.ForeignKey(MetaData, on_delete=models.CASCADE)
class Currency(models.Model):
"""Currency model."""
name = models.CharField(_('name'), max_length=50)
class Meta:
verbose_name = _('currency')
verbose_name_plural = _('currencies')
def __str__(self):
return f'{self.name}'

View File

@ -73,6 +73,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
class AwardSerializer(serializers.ModelSerializer):
"""Award serializer."""
title_translated = serializers.CharField(read_only=True, allow_null=True)
class Meta:
@ -96,3 +97,13 @@ class MetaDataContentSerializer(serializers.ModelSerializer):
'id',
'label_translated',
]
class CurrencySerializer(serializers.ModelSerializer):
"""Currency serializer"""
class Meta:
model = models.Currency
fields = [
'id',
'name'
]

View File

@ -22,7 +22,7 @@ class NewsListFilterSet(django_filters.FilterSet):
"""Crappy search by title according to locale"""
if value:
locale = self.request.locale
filters = {f'{name}__{locale}': value}
filters = {f'{name}__{locale}__icontains': value}
return queryset.filter(**filters)
else:
return queryset

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.4 on 2019-09-01 10:32
from django.db import migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('news', '0008_auto_20190828_1522'),
]
operations = [
migrations.AlterField(
model_name='news',
name='description',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='description'),
),
migrations.AlterField(
model_name='news',
name='subtitle',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='subtitle'),
),
migrations.AlterField(
model_name='news',
name='title',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='title'),
),
]

View File

@ -43,14 +43,14 @@ class News(BaseAttributes, TraslatedFieldsMixin):
title = TJSONField(
_('title'), null=True, blank=True,
default=None, help_text='{"en":"some text"}')
default=None, help_text='{"en-GB":"some text"}')
subtitle = TJSONField(
_('subtitle'), null=True, blank=True,
default=None, help_text='{"en":"some text"}'
default=None, help_text='{"en-GB":"some text"}'
)
description = TJSONField(
_('description'), null=True, blank=True,
default=None, help_text='{"en":"some text"}'
default=None, help_text='{"en-GB":"some text"}'
)
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))

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

3
apps/review/admin.py Normal file
View File

@ -0,0 +1,3 @@
# @admin.register(models.Review)
# class ReviewAdminModel(admin.ModelAdmin):
# """Admin model for model Review."""

7
apps/review/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ReviewConfig(AppConfig):
name = 'review'
verbose_name = _('reviews')

View File

@ -0,0 +1,43 @@
# Generated by Django 2.2.4 on 2019-09-01 09:32
import django.core.validators
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('translation', '0002_siteinterfacedictionary'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Review',
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')),
('object_id', models.PositiveIntegerField()),
('text', models.TextField(verbose_name='Text')),
('status', models.PositiveSmallIntegerField(choices=[(0, 'To investigate'), (1, 'To review'), (2, 'Ready')], default=0)),
('published_at', models.DateTimeField(blank=True, default=None, help_text='Review published datetime', null=True, verbose_name='Publish datetime')),
('vintage', models.IntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2100)], verbose_name='Year of review')),
('child', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='review.Review', verbose_name='Child review')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='review_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='translation.Language', verbose_name='Review language')),
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='review_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to=settings.AUTH_USER_MODEL, verbose_name='Reviewer')),
],
options={
'verbose_name': 'Review',
'verbose_name_plural': 'Reviews',
},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.4 on 2019-09-01 11:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('review', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='review',
name='text',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-09-01 11:53
from django.db import migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('review', '0002_remove_review_text'),
]
operations = [
migrations.AddField(
model_name='review',
name='text',
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"Text review"}', null=True, verbose_name='text'),
),
]

View File

64
apps/review/models.py Normal file
View File

@ -0,0 +1,64 @@
from django.contrib.contenttypes import fields as generic
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from utils.models import BaseAttributes, TraslatedFieldsMixin
from utils.models import TJSONField
class ReviewQuerySet(models.QuerySet):
"""QuerySets for model Review"""
def by_reviewer(self, user):
"""Return reviews by user"""
return self.filter(reviewer=user)
def by_vintage(self, year: int):
"""Return reviews by year"""
return self.filter(vintage=year)
class Review(BaseAttributes, TraslatedFieldsMixin):
"""Review model"""
TO_INVESTIGATE = 0
TO_REVIEW = 1
READY = 2
REVIEW_STATUSES = (
(TO_INVESTIGATE, _('To investigate')),
(TO_REVIEW, _('To review')),
(READY, _('Ready')),
)
reviewer = models.ForeignKey('account.User',
related_name='reviews',
on_delete=models.CASCADE,
verbose_name=_('Reviewer'))
text = TJSONField(
_('text'), null=True, blank=True,
default=None, help_text='{"en-GB":"Text review"}')
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
language = models.ForeignKey('translation.Language',
on_delete=models.CASCADE,
related_name='reviews',
verbose_name=_('Review language'))
status = models.PositiveSmallIntegerField(choices=REVIEW_STATUSES, default=TO_INVESTIGATE)
child = models.ForeignKey('self',
blank=True, default=None, null=True,
on_delete=models.CASCADE,
verbose_name=_('Child review'))
published_at = models.DateTimeField(verbose_name=_('Publish datetime'),
blank=True, default=None, null=True,
help_text=_('Review published datetime'))
vintage = models.IntegerField(verbose_name=_('Year of review'),
validators=[MinValueValidator(1900),
MaxValueValidator(2100)])
objects = ReviewQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('Review')
verbose_name_plural = _('Reviews')

View File

View File

View File

View File

3
apps/review/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

View File

View File

0
apps/review/urls/web.py Normal file
View File

View File

View File

View File

0
apps/review/views/web.py Normal file
View File

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-09-01 10:32
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('translation', '0002_siteinterfacedictionary'),
]
operations = [
migrations.AlterField(
model_name='siteinterfacedictionary',
name='text',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='Text'),
),
]

View File

@ -49,7 +49,7 @@ class SiteInterfaceDictionary(ProjectBaseMixin):
verbose_name=_('Page'))
keywords = models.CharField(max_length=255, verbose_name='Keywords')
text = JSONField(_('Text'), null=True, blank=True,
default=None, help_text='{"en":"some text"}')
default=None, help_text='{"en-GB":"some text"}')
objects = SiteInterfaceDictionaryManager()

View File

@ -74,9 +74,6 @@ class OAuthProjectMixin:
return NotImplemented
basemixin_fields = ['created', 'modified']
class BaseAttributes(ProjectBaseMixin):
created_by = models.ForeignKey(
'account.User', on_delete=models.SET_NULL, verbose_name=_('created by'),

View File

@ -66,6 +66,7 @@ PROJECT_APPS = [
'translation.apps.TranslationConfig',
'configuration.apps.ConfigurationConfig',
'timetable.apps.TimetableConfig',
'review.apps.ReviewConfig',
]
EXTERNAL_APPS = [
@ -82,6 +83,7 @@ EXTERNAL_APPS = [
'django_extensions',
'rest_framework_simplejwt.token_blacklist',
'solo',
'phonenumber_field',
]
@ -321,6 +323,7 @@ THUMBNAIL_ALIASES = {
'large': {'size': (1500, 0), },
'default': {'size': (300, 200), 'crop': True},
'gallery': {'size': (240, 160), 'crop': True},
'establishment_preview': {'size': (300, 280), 'crop': True},
}
}

View File

@ -17,6 +17,7 @@ djangorestframework-xml
celery
amqp>=2.4.0
geoip2==2.9.0
django-phonenumber-field[phonenumbers]==2.1.0
# auth socials
djangorestframework-oauth