diff --git a/apps/account/admin.py b/apps/account/admin.py index 7c914c2b..e9c853bb 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -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', diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index d40950b1..05ab36dd 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -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') \ No newline at end of file diff --git a/apps/establishment/migrations/0005_auto_20190901_0831.py b/apps/establishment/migrations/0005_auto_20190901_0831.py new file mode 100644 index 00000000..ef99bf4d --- /dev/null +++ b/apps/establishment/migrations/0005_auto_20190901_0831.py @@ -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'), + ), + ] diff --git a/apps/establishment/migrations/0006_comment.py b/apps/establishment/migrations/0006_comment.py new file mode 100644 index 00000000..4ae22034 --- /dev/null +++ b/apps/establishment/migrations/0006_comment.py @@ -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', + }, + ), + ] diff --git a/apps/establishment/migrations/0006_merge_20190901_0846.py b/apps/establishment/migrations/0006_merge_20190901_0846.py new file mode 100644 index 00000000..e629f2b9 --- /dev/null +++ b/apps/establishment/migrations/0006_merge_20190901_0846.py @@ -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 = [ + ] diff --git a/apps/establishment/migrations/0007_auto_20190901_1032.py b/apps/establishment/migrations/0007_auto_20190901_1032.py new file mode 100644 index 00000000..eb6c6a4a --- /dev/null +++ b/apps/establishment/migrations/0007_auto_20190901_1032.py @@ -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', + ), + ] diff --git a/apps/establishment/migrations/0008_contactemail_contactphone.py b/apps/establishment/migrations/0008_contactemail_contactphone.py new file mode 100644 index 00000000..bda629ed --- /dev/null +++ b/apps/establishment/migrations/0008_contactemail_contactphone.py @@ -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', + }, + ), + ] diff --git a/apps/establishment/migrations/0009_merge_20190901_1131.py b/apps/establishment/migrations/0009_merge_20190901_1131.py new file mode 100644 index 00000000..e5a59f4d --- /dev/null +++ b/apps/establishment/migrations/0009_merge_20190901_1131.py @@ -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 = [ + ] diff --git a/apps/establishment/migrations/0010_auto_20190901_1142.py b/apps/establishment/migrations/0010_auto_20190901_1142.py new file mode 100644 index 00000000..9a76424b --- /dev/null +++ b/apps/establishment/migrations/0010_auto_20190901_1142.py @@ -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'), + ), + ] diff --git a/apps/establishment/migrations/0011_menu_plate.py b/apps/establishment/migrations/0011_menu_plate.py new file mode 100644 index 00000000..e0d50925 --- /dev/null +++ b/apps/establishment/migrations/0011_menu_plate.py @@ -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), + ), + ] diff --git a/apps/establishment/migrations/0012_auto_20190901_1251.py b/apps/establishment/migrations/0012_auto_20190901_1251.py new file mode 100644 index 00000000..3828c997 --- /dev/null +++ b/apps/establishment/migrations/0012_auto_20190901_1251.py @@ -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'), + ), + ] diff --git a/apps/establishment/migrations/0013_auto_20190901_1428.py b/apps/establishment/migrations/0013_auto_20190901_1428.py new file mode 100644 index 00000000..9e91b715 --- /dev/null +++ b/apps/establishment/migrations/0013_auto_20190901_1428.py @@ -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'), + ), + ] diff --git a/apps/establishment/migrations/0014_establishment_website.py b/apps/establishment/migrations/0014_establishment_website.py new file mode 100644 index 00000000..e685f469 --- /dev/null +++ b/apps/establishment/migrations/0014_establishment_website.py @@ -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'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 68b8872e..41e2ae72 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -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) diff --git a/apps/establishment/serializers.py b/apps/establishment/serializers.py index 49788db3..e5b092c6 100644 --- a/apps/establishment/serializers.py +++ b/apps/establishment/serializers.py @@ -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') diff --git a/apps/establishment/views.py b/apps/establishment/views.py index 459aeeac..146c6f9b 100644 --- a/apps/establishment/views.py +++ b/apps/establishment/views.py @@ -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.""" diff --git a/apps/location/migrations/0003_auto_20190821_1407.py b/apps/location/migrations/0003_auto_20190821_1407.py index 548e2a70..c82ec597 100644 --- a/apps/location/migrations/0003_auto_20190821_1407.py +++ b/apps/location/migrations/0003_auto_20190821_1407.py @@ -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'), ), ] diff --git a/apps/location/migrations/0009_auto_20190901_0831.py b/apps/location/migrations/0009_auto_20190901_0831.py new file mode 100644 index 00000000..c4e21715 --- /dev/null +++ b/apps/location/migrations/0009_auto_20190901_0831.py @@ -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'), + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index 56bd89f3..613cc013 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -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')) diff --git a/apps/main/admin.py b/apps/main/admin.py index 9ff6606a..2d512868 100644 --- a/apps/main/admin.py +++ b/apps/main/admin.py @@ -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""" + + diff --git a/apps/main/migrations/0013_auto_20190901_1032.py b/apps/main/migrations/0013_auto_20190901_1032.py new file mode 100644 index 00000000..95c86883 --- /dev/null +++ b/apps/main/migrations/0013_auto_20190901_1032.py @@ -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'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index cb406d98..b58dc402 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -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}' diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 478093d0..0348026c 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -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: @@ -95,4 +96,14 @@ class MetaDataContentSerializer(serializers.ModelSerializer): fields = [ 'id', 'label_translated', + ] + + +class CurrencySerializer(serializers.ModelSerializer): + """Currency serializer""" + class Meta: + model = models.Currency + fields = [ + 'id', + 'name' ] \ No newline at end of file diff --git a/apps/news/filters.py b/apps/news/filters.py index 7af3452d..c4462189 100644 --- a/apps/news/filters.py +++ b/apps/news/filters.py @@ -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 diff --git a/apps/news/migrations/0009_auto_20190901_1032.py b/apps/news/migrations/0009_auto_20190901_1032.py new file mode 100644 index 00000000..5a083240 --- /dev/null +++ b/apps/news/migrations/0009_auto_20190901_1032.py @@ -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'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index e3eff57e..0abc7ab5 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -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')) diff --git a/apps/review/__init__.py b/apps/review/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/admin.py b/apps/review/admin.py new file mode 100644 index 00000000..cbd0bf94 --- /dev/null +++ b/apps/review/admin.py @@ -0,0 +1,3 @@ +# @admin.register(models.Review) +# class ReviewAdminModel(admin.ModelAdmin): +# """Admin model for model Review.""" diff --git a/apps/review/apps.py b/apps/review/apps.py new file mode 100644 index 00000000..6440958c --- /dev/null +++ b/apps/review/apps.py @@ -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') diff --git a/apps/review/migrations/0001_initial.py b/apps/review/migrations/0001_initial.py new file mode 100644 index 00000000..3deac75e --- /dev/null +++ b/apps/review/migrations/0001_initial.py @@ -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', + }, + ), + ] diff --git a/apps/review/migrations/0002_remove_review_text.py b/apps/review/migrations/0002_remove_review_text.py new file mode 100644 index 00000000..45e58d1f --- /dev/null +++ b/apps/review/migrations/0002_remove_review_text.py @@ -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', + ), + ] diff --git a/apps/review/migrations/0003_review_text.py b/apps/review/migrations/0003_review_text.py new file mode 100644 index 00000000..708fccd1 --- /dev/null +++ b/apps/review/migrations/0003_review_text.py @@ -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'), + ), + ] diff --git a/apps/review/migrations/__init__.py b/apps/review/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/models.py b/apps/review/models.py new file mode 100644 index 00000000..97b910dd --- /dev/null +++ b/apps/review/models.py @@ -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') diff --git a/apps/review/serializers/__init__.py b/apps/review/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/serializers/common.py b/apps/review/serializers/common.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/serializers/mobile.py b/apps/review/serializers/mobile.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/serializers/web.py b/apps/review/serializers/web.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/tests.py b/apps/review/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/review/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/review/urls/__init__.py b/apps/review/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/urls/common.py b/apps/review/urls/common.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/urls/mobile.py b/apps/review/urls/mobile.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/urls/web.py b/apps/review/urls/web.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/views/__init__.py b/apps/review/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/views/common.py b/apps/review/views/common.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/views/mobile.py b/apps/review/views/mobile.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/views/web.py b/apps/review/views/web.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/translation/migrations/0003_auto_20190901_1032.py b/apps/translation/migrations/0003_auto_20190901_1032.py new file mode 100644 index 00000000..85bf63eb --- /dev/null +++ b/apps/translation/migrations/0003_auto_20190901_1032.py @@ -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'), + ), + ] diff --git a/apps/translation/models.py b/apps/translation/models.py index f675b315..42530965 100644 --- a/apps/translation/models.py +++ b/apps/translation/models.py @@ -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() diff --git a/apps/utils/models.py b/apps/utils/models.py index b2d922dd..ab0c200e 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -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'), diff --git a/project/settings/base.py b/project/settings/base.py index 1030fc87..65311ac9 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -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}, } } diff --git a/requirements/base.txt b/requirements/base.txt index 20a87346..3d7987c0 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -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