Merge remote-tracking branch 'origin/develop' into features/comments
# Conflicts: # apps/establishment/models.py # apps/establishment/serializers.py
This commit is contained in:
commit
21dd87e0ec
|
|
@ -4,6 +4,7 @@ from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from main.models import Award, MetaDataContent
|
from main.models import Award, MetaDataContent
|
||||||
|
from review import models as review_models
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.EstablishmentType)
|
@admin.register(models.EstablishmentType)
|
||||||
|
|
@ -26,10 +27,29 @@ class MetaDataContentInline(GenericTabularInline):
|
||||||
extra = 0
|
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)
|
@admin.register(models.Establishment)
|
||||||
class EstablishmentAdmin(admin.ModelAdmin):
|
class EstablishmentAdmin(admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
inlines = [AwardInline, MetaDataContentInline]
|
inlines = [
|
||||||
|
AwardInline, MetaDataContentInline,
|
||||||
|
ContactPhoneInline, ContactEmailInline,
|
||||||
|
ReviewInline]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.EstablishmentSchedule)
|
@admin.register(models.EstablishmentSchedule)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
from django_filters import FilterSet
|
"""Establishment app filters."""
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentFilter(FilterSet):
|
class EstablishmentFilter(filters.FilterSet):
|
||||||
|
"""Establishment filterset."""
|
||||||
|
|
||||||
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
||||||
award_id = filters.NumberFilter(field_name='awards__id',)
|
award_id = filters.NumberFilter(field_name='awards__id',)
|
||||||
|
search = filters.CharFilter(method='search_text')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
fields = (
|
fields = (
|
||||||
'tag_id',
|
'tag_id',
|
||||||
'award_id'
|
'award_id',
|
||||||
|
'search',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def search_text(self, queryset, name, value):
|
||||||
|
"""Search text."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search(value, locale=self.request.locale)
|
||||||
|
return queryset
|
||||||
|
|
|
||||||
77
apps/establishment/migrations/0005_auto_20190901_0831.py
Normal file
77
apps/establishment/migrations/0005_auto_20190901_0831.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
apps/establishment/migrations/0006_merge_20190901_0846.py
Normal file
14
apps/establishment/migrations/0006_merge_20190901_0846.py
Normal 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 = [
|
||||||
|
]
|
||||||
30
apps/establishment/migrations/0007_auto_20190901_1032.py
Normal file
30
apps/establishment/migrations/0007_auto_20190901_1032.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -3,10 +3,13 @@ from django.contrib.contenttypes import fields as generic
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField,
|
from utils.models import (
|
||||||
TraslatedFieldsMixin, BaseAttributes)
|
ProjectBaseMixin, ImageMixin, TJSONField,
|
||||||
|
TraslatedFieldsMixin, BaseAttributes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# todo: establishment type&subtypes check
|
# todo: establishment type&subtypes check
|
||||||
|
|
@ -14,7 +17,7 @@ class EstablishmentType(ProjectBaseMixin, TraslatedFieldsMixin):
|
||||||
"""Establishment type model."""
|
"""Establishment type model."""
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
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)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -38,7 +41,7 @@ class EstablishmentSubType(ProjectBaseMixin, TraslatedFieldsMixin):
|
||||||
"""Establishment type model."""
|
"""Establishment type model."""
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
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,
|
establishment_type = models.ForeignKey(EstablishmentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('Type'))
|
verbose_name=_('Type'))
|
||||||
|
|
@ -51,23 +54,34 @@ class EstablishmentSubType(ProjectBaseMixin, TraslatedFieldsMixin):
|
||||||
verbose_name = _('Establishment subtype')
|
verbose_name = _('Establishment subtype')
|
||||||
verbose_name_plural = _('Establishment subtypes')
|
verbose_name_plural = _('Establishment subtypes')
|
||||||
|
|
||||||
# def __str__(self):
|
|
||||||
# """__str__ method."""
|
|
||||||
# return self.name
|
|
||||||
|
|
||||||
def clean_fields(self, exclude=None):
|
def clean_fields(self, exclude=None):
|
||||||
if not self.establishment_type.use_subtypes:
|
if not self.establishment_type.use_subtypes:
|
||||||
raise ValidationError(_('Establishment type is not use subtypes.'))
|
raise ValidationError(_('Establishment type is not use subtypes.'))
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentQuerySet(models.QuerySet):
|
||||||
|
"""Extended queryset for Establishment model."""
|
||||||
|
|
||||||
|
def search(self, value, locale=None):
|
||||||
|
"""Search text in JSON fields."""
|
||||||
|
if locale is not None:
|
||||||
|
filters = [
|
||||||
|
{f'name__{locale}__icontains': value},
|
||||||
|
{f'description__{locale}__icontains': value}
|
||||||
|
]
|
||||||
|
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
||||||
|
else:
|
||||||
|
return self.none()
|
||||||
|
|
||||||
|
|
||||||
class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
|
class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
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,
|
description = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Description'),
|
verbose_name=_('Description'),
|
||||||
help_text='{"en":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
public_mark = models.PositiveIntegerField(blank=True, null=True,
|
public_mark = models.PositiveIntegerField(blank=True, null=True,
|
||||||
default=None,
|
default=None,
|
||||||
verbose_name=_('Public mark'),)
|
verbose_name=_('Public mark'),)
|
||||||
|
|
@ -89,6 +103,9 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
|
||||||
verbose_name=_('Price level'))
|
verbose_name=_('Price level'))
|
||||||
awards = generic.GenericRelation(to='main.Award')
|
awards = generic.GenericRelation(to='main.Award')
|
||||||
tags = generic.GenericRelation(to='main.MetaDataContent')
|
tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||||
|
reviews = generic.GenericRelation(to='review.Review')
|
||||||
|
|
||||||
|
objects = EstablishmentQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -149,6 +166,97 @@ class EstablishmentSchedule(BaseAttributes):
|
||||||
verbose_name_plural = _('Establishment schedules')
|
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."""
|
||||||
|
#
|
||||||
|
# STARTER = 0
|
||||||
|
# MAIN = 1
|
||||||
|
# COURSE = 2
|
||||||
|
# DESSERT = 3
|
||||||
|
#
|
||||||
|
# PLATE_TYPE_CHOICES = (
|
||||||
|
# (STARTER, _('starter')),
|
||||||
|
# (MAIN, _('main')),
|
||||||
|
# (COURSE, _('course')),
|
||||||
|
# (DESSERT, _('dessert')),
|
||||||
|
# )
|
||||||
|
# name = models.CharField(_('name'), max_length=255)
|
||||||
|
# plate_type = models.PositiveSmallIntegerField(_('plate_type'), choices=PLATE_TYPE_CHOICES)
|
||||||
|
# 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.ManyToManyField(to='Plate', verbose_name=_(''), through='establishment.Menu')
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
# verbose_name = _('plate')
|
||||||
|
# verbose_name_plural = _('plates')
|
||||||
|
#
|
||||||
|
# def __str__(self):
|
||||||
|
# return f'plate_id:{self.id}'
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Menu(TraslatedFieldsMixin, BaseAttributes):
|
||||||
|
# """Menu model."""
|
||||||
|
# establishment = models.ForeignKey(
|
||||||
|
# 'establishment.Establishment', verbose_name=_('establishment'),
|
||||||
|
# on_delete=models.CASCADE)
|
||||||
|
# plate = models.ForeignKey(Plate, verbose_name=_('menu'), on_delete=models.CASCADE)
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
# verbose_name = _('menu')
|
||||||
|
# verbose_name_plural = _('menu')
|
||||||
|
|
||||||
class CommentQuerySet(models.QuerySet):
|
class CommentQuerySet(models.QuerySet):
|
||||||
"""QuerySets for Comment model."""
|
"""QuerySets for Comment model."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,28 @@ from rest_framework import serializers
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.serializers import AddressSerializer
|
from location.serializers import AddressSerializer
|
||||||
from main.serializers import MetaDataContentSerializer, AwardSerializer
|
from main.serializers import MetaDataContentSerializer, AwardSerializer
|
||||||
|
from review import models as review_models
|
||||||
from timetable.models import Timetable
|
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 EstablishmentTypeSerializer(serializers.ModelSerializer):
|
class EstablishmentTypeSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for EstablishmentType model."""
|
"""Serializer for EstablishmentType model."""
|
||||||
|
|
||||||
|
|
@ -47,6 +66,16 @@ class EstablishmentScheduleSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model Review."""
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = review_models.Review
|
||||||
|
fields = (
|
||||||
|
'text',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
class CommentSerializer(serializers.ModelSerializer):
|
||||||
"""Comment serializer"""
|
"""Comment serializer"""
|
||||||
nickname = serializers.CharField(source='author.username')
|
nickname = serializers.CharField(source='author.username')
|
||||||
|
|
@ -76,6 +105,10 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
schedule = EstablishmentScheduleSerializer(source='schedule.schedule',
|
schedule = EstablishmentScheduleSerializer(source='schedule.schedule',
|
||||||
many=True,
|
many=True,
|
||||||
allow_null=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,
|
comments = CommentSerializer(many=True,
|
||||||
allow_null=True)
|
allow_null=True)
|
||||||
|
|
||||||
|
|
@ -98,5 +131,8 @@ class EstablishmentSerializer(serializers.ModelSerializer):
|
||||||
'tags',
|
'tags',
|
||||||
'awards',
|
'awards',
|
||||||
'schedule',
|
'schedule',
|
||||||
|
'phones',
|
||||||
|
'emails',
|
||||||
|
'reviews',
|
||||||
'comments',
|
'comments',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='country',
|
model_name='country',
|
||||||
name='name',
|
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'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
19
apps/location/migrations/0009_auto_20190901_0831.py
Normal file
19
apps/location/migrations/0009_auto_20190901_0831.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -17,7 +17,7 @@ class Country(SVGImageMixin, ProjectBaseMixin):
|
||||||
"""Country model."""
|
"""Country model."""
|
||||||
|
|
||||||
name = JSONField(null=True, blank=True, default=None,
|
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'))
|
code = models.CharField(max_length=255, unique=True, verbose_name=_('Code'))
|
||||||
low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
|
low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
|
||||||
high_price = models.IntegerField(default=50, verbose_name=_('High price'))
|
high_price = models.IntegerField(default=50, verbose_name=_('High price'))
|
||||||
|
|
|
||||||
35
apps/main/migrations/0013_auto_20190901_1032.py
Normal file
35
apps/main/migrations/0013_auto_20190901_1032.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -190,7 +190,7 @@ class Award(TraslatedFieldsMixin, models.Model):
|
||||||
award_type = models.ForeignKey('main.AwardType', on_delete=models.CASCADE)
|
award_type = models.ForeignKey('main.AwardType', on_delete=models.CASCADE)
|
||||||
title = TJSONField(
|
title = TJSONField(
|
||||||
_('title'), null=True, blank=True,
|
_('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='')
|
vintage_year = models.CharField(_('vintage year'), max_length=255, default='')
|
||||||
|
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
|
|
@ -228,7 +228,7 @@ class MetaData(TraslatedFieldsMixin, models.Model):
|
||||||
"""MetaData model."""
|
"""MetaData model."""
|
||||||
label = TJSONField(
|
label = TJSONField(
|
||||||
_('label'), null=True, blank=True,
|
_('label'), null=True, blank=True,
|
||||||
default=None, help_text='{"en":"some text"}')
|
default=None, help_text='{"en-GB":"some text"}')
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
MetaDataCategory, verbose_name=_('category'), on_delete=models.CASCADE)
|
MetaDataCategory, verbose_name=_('category'), on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
@ -250,3 +250,15 @@ class MetaDataContent(models.Model):
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||||
metadata = models.ForeignKey(MetaData, on_delete=models.CASCADE)
|
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}'
|
||||||
|
|
|
||||||
29
apps/news/migrations/0009_auto_20190901_1032.py
Normal file
29
apps/news/migrations/0009_auto_20190901_1032.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -43,14 +43,14 @@ class News(BaseAttributes, TraslatedFieldsMixin):
|
||||||
|
|
||||||
title = TJSONField(
|
title = TJSONField(
|
||||||
_('title'), null=True, blank=True,
|
_('title'), null=True, blank=True,
|
||||||
default=None, help_text='{"en":"some text"}')
|
default=None, help_text='{"en-GB":"some text"}')
|
||||||
subtitle = TJSONField(
|
subtitle = TJSONField(
|
||||||
_('subtitle'), null=True, blank=True,
|
_('subtitle'), null=True, blank=True,
|
||||||
default=None, help_text='{"en":"some text"}'
|
default=None, help_text='{"en-GB":"some text"}'
|
||||||
)
|
)
|
||||||
description = TJSONField(
|
description = TJSONField(
|
||||||
_('description'), null=True, blank=True,
|
_('description'), null=True, blank=True,
|
||||||
default=None, help_text='{"en":"some text"}'
|
default=None, help_text='{"en-GB":"some text"}'
|
||||||
)
|
)
|
||||||
start = models.DateTimeField(_('start'))
|
start = models.DateTimeField(_('start'))
|
||||||
end = models.DateTimeField(_('end'))
|
end = models.DateTimeField(_('end'))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
"""News app common serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from gallery import models as gallery_models
|
from gallery import models as gallery_models
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
from location.serializers import AddressSerializer
|
from location.serializers import AddressSerializer
|
||||||
|
|
@ -23,7 +23,7 @@ class NewsSerializer(serializers.ModelSerializer):
|
||||||
title_translated = serializers.CharField(read_only=True, allow_null=True)
|
title_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||||
subtitle_translated = serializers.CharField(read_only=True, allow_null=True)
|
subtitle_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||||
description_translated = serializers.CharField(read_only=True, allow_null=True)
|
description_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||||
image_url = serializers.ImageField(source='image.image')
|
image_url = serializers.ImageField(source='image.image', allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.News
|
model = models.News
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,37 @@
|
||||||
|
"""News app common app."""
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
from news import filters, models
|
||||||
from news import filters
|
|
||||||
from news.models import News, NewsType
|
|
||||||
from news.serializers import common as serializers
|
from news.serializers import common as serializers
|
||||||
from utils.views import (JWTGenericViewMixin,
|
from utils.views import JWTGenericViewMixin, JWTListAPIView
|
||||||
JWTListAPIView)
|
|
||||||
|
|
||||||
|
|
||||||
# Mixins
|
class NewsMixin:
|
||||||
class NewsViewMixin(JWTGenericViewMixin):
|
"""News mixin."""
|
||||||
"""View mixin for News model"""
|
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
|
||||||
"""Override get_queryset method"""
|
|
||||||
return News.objects.annotate_localized_fields(locale=self.request.locale)
|
|
||||||
|
|
||||||
|
|
||||||
class NewsListView(NewsViewMixin, JWTListAPIView):
|
|
||||||
"""News list view."""
|
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.NewsSerializer
|
serializer_class = serializers.NewsSerializer
|
||||||
filter_class = filters.NewsListFilterSet
|
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
def get_queryset(self, *args, **kwargs):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
return News.objects.published()\
|
return models.News.objects.published() \
|
||||||
.by_country_code(code=self.request.country_code)\
|
.by_country_code(code=self.request.country_code) \
|
||||||
.order_by('-is_highlighted', '-created')
|
.order_by('-is_highlighted', '-created')
|
||||||
|
|
||||||
|
|
||||||
# class NewsCreateView(generics.CreateAPIView):
|
class NewsListView(NewsMixin, JWTListAPIView):
|
||||||
# """News list view."""
|
"""News list view."""
|
||||||
# queryset = News.objects.all()
|
|
||||||
# permission_classes = (permissions.IsAuthenticated, )
|
filter_class = filters.NewsListFilterSet
|
||||||
# serializer_class = serializers.NewsCreateUpdateSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class NewsDetailView(NewsViewMixin, generics.RetrieveAPIView):
|
class NewsDetailView(NewsMixin, JWTGenericViewMixin, generics.RetrieveAPIView):
|
||||||
"""News detail view."""
|
"""News detail view."""
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
|
||||||
serializer_class = serializers.NewsSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class NewsTypeListView(generics.ListAPIView):
|
class NewsTypeListView(generics.ListAPIView):
|
||||||
"""NewsType list view."""
|
"""NewsType list view."""
|
||||||
|
|
||||||
serializer_class = serializers.NewsTypeSerializer
|
serializer_class = serializers.NewsTypeSerializer
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
queryset = NewsType.objects.all()
|
queryset = models.NewsType.objects.all()
|
||||||
|
|
|
||||||
0
apps/notification/__init__.py
Normal file
0
apps/notification/__init__.py
Normal file
3
apps/notification/admin.py
Normal file
3
apps/notification/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
7
apps/notification/apps.py
Normal file
7
apps/notification/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationConfig(AppConfig):
|
||||||
|
name = 'notification'
|
||||||
|
verbose_name = _('notification')
|
||||||
37
apps/notification/migrations/0001_initial.py
Normal file
37
apps/notification/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-08-30 11:22
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Subscriber',
|
||||||
|
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')),
|
||||||
|
('email', models.EmailField(blank=True, default=None, max_length=254, null=True, unique=True, verbose_name='Email')),
|
||||||
|
('ip_address', models.GenericIPAddressField(blank=True, default=None, null=True, verbose_name='IP address')),
|
||||||
|
('country_code', models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='Country code')),
|
||||||
|
('locale', models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='Locale identifier')),
|
||||||
|
('state', models.PositiveIntegerField(choices=[(0, 'Unusable'), (1, 'Usable')], default=1, verbose_name='State')),
|
||||||
|
('update_code', models.CharField(blank=True, db_index=True, default=None, max_length=254, null=True, verbose_name='Token')),
|
||||||
|
('user', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscriber', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Subscriber',
|
||||||
|
'verbose_name_plural': 'Subscribers',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
apps/notification/migrations/__init__.py
Normal file
0
apps/notification/migrations/__init__.py
Normal file
124
apps/notification/models.py
Normal file
124
apps/notification/models.py
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
"""Notification app models."""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from account.models import User
|
||||||
|
from utils.methods import generate_string_code
|
||||||
|
from utils.models import ProjectBaseMixin
|
||||||
|
|
||||||
|
|
||||||
|
# todo: associate user & subscriber after users registration
|
||||||
|
class SubscriberManager(models.Manager):
|
||||||
|
"""Extended manager for Subscriber model."""
|
||||||
|
|
||||||
|
def make_subscriber(self, email=None, user=None, ip_address=None, country_code=None,
|
||||||
|
locale=None, *args, **kwargs):
|
||||||
|
"""Make subscriber and update info."""
|
||||||
|
# search existing object
|
||||||
|
if not user:
|
||||||
|
user = User.objects.filter(email=email).first()
|
||||||
|
if user:
|
||||||
|
obj = self.model.objects.filter(models.Q(user=user) | models.Q(
|
||||||
|
email=user.email)).first()
|
||||||
|
else:
|
||||||
|
obj = self.model.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
# update or create
|
||||||
|
if obj:
|
||||||
|
if user:
|
||||||
|
obj.user = user
|
||||||
|
obj.email = None
|
||||||
|
else:
|
||||||
|
obj.email = email
|
||||||
|
obj.ip_address = ip_address
|
||||||
|
obj.country_code = country_code
|
||||||
|
obj.locale = locale
|
||||||
|
obj.state = self.model.USABLE
|
||||||
|
obj.update_code = generate_string_code()
|
||||||
|
obj.save()
|
||||||
|
else:
|
||||||
|
obj = self.model.objects.create(user=user, email=email, ip_address=ip_address,
|
||||||
|
country_code=country_code, locale=locale)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def associate_user(self, user):
|
||||||
|
"""Associate user."""
|
||||||
|
obj = self.model.objects.filter(user=user).first()
|
||||||
|
if obj is None:
|
||||||
|
obj = self.model.objects.filter(email=user.email_confirmed, user__isnull=True).first()
|
||||||
|
if obj:
|
||||||
|
obj.user = user
|
||||||
|
obj.email = None
|
||||||
|
obj.save()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriberQuerySet(models.QuerySet):
|
||||||
|
"""Extended queryset for Subscriber model."""
|
||||||
|
|
||||||
|
def by_usable(self, switcher=True):
|
||||||
|
if switcher:
|
||||||
|
return self.filter(state=self.model.USABLE)
|
||||||
|
else:
|
||||||
|
return self.filter(state=self.model.UNUSABLE)
|
||||||
|
|
||||||
|
|
||||||
|
class Subscriber(ProjectBaseMixin):
|
||||||
|
"""Subscriber model."""
|
||||||
|
|
||||||
|
UNUSABLE = 0
|
||||||
|
USABLE = 1
|
||||||
|
|
||||||
|
STATE_CHOICES = (
|
||||||
|
(UNUSABLE, _('Unusable')),
|
||||||
|
(USABLE, _('Usable')),
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(User, blank=True, null=True, default=None,
|
||||||
|
on_delete=models.SET_NULL, related_name='subscriber',
|
||||||
|
verbose_name=_('User'))
|
||||||
|
email = models.EmailField(blank=True, null=True, default=None, unique=True,
|
||||||
|
verbose_name=_('Email'))
|
||||||
|
ip_address = models.GenericIPAddressField(blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('IP address'))
|
||||||
|
country_code = models.CharField(max_length=10, blank=True, null=True, default=None,
|
||||||
|
verbose_name=_('Country code'))
|
||||||
|
locale = models.CharField(blank=True, null=True, default=None,
|
||||||
|
max_length=10, verbose_name=_('Locale identifier'))
|
||||||
|
state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE,
|
||||||
|
verbose_name=_('State'))
|
||||||
|
update_code = models.CharField(max_length=254, blank=True, null=True, default=None,
|
||||||
|
db_index=True, verbose_name=_('Token'))
|
||||||
|
|
||||||
|
objects = SubscriberManager.from_queryset(SubscriberQuerySet)()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
verbose_name = _('Subscriber')
|
||||||
|
verbose_name_plural = _('Subscribers')
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""Overrided save method."""
|
||||||
|
if self.update_code is None:
|
||||||
|
self.update_code = generate_string_code()
|
||||||
|
return super(Subscriber, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
"""Unsubscribe user."""
|
||||||
|
self.state = self.UNUSABLE
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def send_to(self):
|
||||||
|
"""Actual email."""
|
||||||
|
return self.user.email if self.user else self.email
|
||||||
|
|
||||||
|
@property
|
||||||
|
def link_to_unsubscribe(self):
|
||||||
|
"""Link to unsubscribe."""
|
||||||
|
schema = settings.SCHEMA_URI
|
||||||
|
site_domain = settings.SITE_DOMAIN_URI
|
||||||
|
url = settings.SITE_REDIRECT_URL_UNSUBSCRIBE
|
||||||
|
query = f'?code={self.update_code}'
|
||||||
|
return f'{schema}://{site_domain}{url}{query}'
|
||||||
0
apps/notification/serializers/__init__.py
Normal file
0
apps/notification/serializers/__init__.py
Normal file
47
apps/notification/serializers/common.py
Normal file
47
apps/notification/serializers/common.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
"""Notification app serializers."""
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
from notification import models
|
||||||
|
from utils.methods import get_user_ip
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Subscribe serializer."""
|
||||||
|
|
||||||
|
email = serializers.EmailField(required=False, source='send_to')
|
||||||
|
state_display = serializers.CharField(source='get_state_display', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.Subscriber
|
||||||
|
fields = ('email', 'state', 'state_display')
|
||||||
|
read_only_fields = ('state', 'state_display')
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Validate attrs."""
|
||||||
|
request = self.context.get('request')
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# validate email
|
||||||
|
email = attrs.get('send_to')
|
||||||
|
if user.is_authenticated:
|
||||||
|
if email is not None and email != user.email:
|
||||||
|
raise serializers.ValidationError(_('Does not match user email'))
|
||||||
|
else:
|
||||||
|
if email is None:
|
||||||
|
raise serializers.ValidationError({'email': _('This field is required.')})
|
||||||
|
|
||||||
|
# append info
|
||||||
|
attrs['email'] = email
|
||||||
|
attrs['country_code'] = request.country_code
|
||||||
|
attrs['locale'] = request.locale
|
||||||
|
attrs['ip_address'] = get_user_ip(request)
|
||||||
|
if user.is_authenticated:
|
||||||
|
attrs['user'] = user
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""Create obj."""
|
||||||
|
obj = models.Subscriber.objects.make_subscriber(**validated_data)
|
||||||
|
return obj
|
||||||
0
apps/notification/urls/__init__.py
Normal file
0
apps/notification/urls/__init__.py
Normal file
12
apps/notification/urls/common.py
Normal file
12
apps/notification/urls/common.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""Notification app common urlconf."""
|
||||||
|
from django.urls import path
|
||||||
|
from notification.views import common
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('subscribe/', common.SubscribeView.as_view(), name='subscribe'),
|
||||||
|
path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'),
|
||||||
|
path('subscribe-info/<code>/', common.SubscribeInfoView.as_view(), name='check-code'),
|
||||||
|
path('unsubscribe/', common.UnsubscribeAuthUserView.as_view(), name='unsubscribe-auth'),
|
||||||
|
path('unsubscribe/<code>/', common.UnsubscribeView.as_view(), name='unsubscribe'),
|
||||||
|
]
|
||||||
7
apps/notification/urls/web.py
Normal file
7
apps/notification/urls/web.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""Establishment app web urlconf."""
|
||||||
|
from notification.urls.common import urlpatterns as common_urlpatterns
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = []
|
||||||
|
|
||||||
|
urlpatterns.extend(common_urlpatterns)
|
||||||
0
apps/notification/views/__init__.py
Normal file
0
apps/notification/views/__init__.py
Normal file
78
apps/notification/views/common.py
Normal file
78
apps/notification/views/common.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
"""Notification app common views."""
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from notification import models
|
||||||
|
from notification.serializers import common as serializers
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeView(generics.GenericAPIView):
|
||||||
|
"""Subscribe View."""
|
||||||
|
|
||||||
|
queryset = models.Subscriber.objects.all()
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeInfoView(generics.RetrieveAPIView):
|
||||||
|
"""Subscribe info view."""
|
||||||
|
|
||||||
|
lookup_field = 'update_code'
|
||||||
|
lookup_url_kwarg = 'code'
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
queryset = models.Subscriber.objects.all()
|
||||||
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeInfoAuthUserView(generics.RetrieveAPIView):
|
||||||
|
"""Subscribe info auth user view."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
queryset = models.Subscriber.objects.all()
|
||||||
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
user = self.request.user
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
filter_kwargs = {'user': user}
|
||||||
|
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||||
|
self.check_object_permissions(self.request, obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class UnsubscribeView(generics.GenericAPIView):
|
||||||
|
"""Unsubscribe view."""
|
||||||
|
|
||||||
|
lookup_field = 'update_code'
|
||||||
|
lookup_url_kwarg = 'code'
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
queryset = models.Subscriber.objects.all()
|
||||||
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kw):
|
||||||
|
obj = self.get_object()
|
||||||
|
obj.unsubscribe()
|
||||||
|
serializer = self.get_serializer(instance=obj)
|
||||||
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsubscribeAuthUserView(generics.GenericAPIView):
|
||||||
|
"""Unsubscribe auth user view."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
queryset = models.Subscriber.objects.all()
|
||||||
|
serializer_class = serializers.SubscribeSerializer
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kw):
|
||||||
|
user = request.user
|
||||||
|
obj = get_object_or_404(models.Subscriber, user=user)
|
||||||
|
obj.unsubscribe()
|
||||||
|
serializer = self.get_serializer(instance=obj)
|
||||||
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
from django.contrib import admin
|
# @admin.register(models.Review)
|
||||||
from review import models
|
# class ReviewAdminModel(admin.ModelAdmin):
|
||||||
|
# """Admin model for model Review."""
|
||||||
|
|
||||||
@admin.register(models.Review)
|
|
||||||
class ReviewAdminModel(admin.ModelAdmin):
|
|
||||||
"""Admin model for model Review."""
|
|
||||||
|
|
|
||||||
43
apps/review/migrations/0001_initial.py
Normal file
43
apps/review/migrations/0001_initial.py
Normal 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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/translation/migrations/0003_auto_20190901_1032.py
Normal file
19
apps/translation/migrations/0003_auto_20190901_1032.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -49,7 +49,7 @@ class SiteInterfaceDictionary(ProjectBaseMixin):
|
||||||
verbose_name=_('Page'))
|
verbose_name=_('Page'))
|
||||||
keywords = models.CharField(max_length=255, verbose_name='Keywords')
|
keywords = models.CharField(max_length=255, verbose_name='Keywords')
|
||||||
text = JSONField(_('Text'), null=True, blank=True,
|
text = JSONField(_('Text'), null=True, blank=True,
|
||||||
default=None, help_text='{"en":"some text"}')
|
default=None, help_text='{"en-GB":"some text"}')
|
||||||
|
|
||||||
objects = SiteInterfaceDictionaryManager()
|
objects = SiteInterfaceDictionaryManager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Utils app method."""
|
"""Utils app method."""
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
|
|
@ -60,3 +61,20 @@ def svg_image_path(instance, filename):
|
||||||
instance._meta.model_name,
|
instance._meta.model_name,
|
||||||
datetime.now().strftime(settings.REST_DATE_FORMAT),
|
datetime.now().strftime(settings.REST_DATE_FORMAT),
|
||||||
filename)
|
filename)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_ip(request):
|
||||||
|
"""Get user ip."""
|
||||||
|
meta_dict = request.META
|
||||||
|
x_forwarded_for = meta_dict.get('HTTP_X_FORWARDED_FOR')
|
||||||
|
if x_forwarded_for:
|
||||||
|
ip = x_forwarded_for.split(',')[0]
|
||||||
|
else:
|
||||||
|
ip = meta_dict.get('REMOTE_ADDR')
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def generate_string_code(size=64,
|
||||||
|
chars=string.ascii_lowercase + string.ascii_uppercase + string.digits):
|
||||||
|
"""Generate string code."""
|
||||||
|
return ''.join([random.SystemRandom().choice(chars) for _ in range(size)])
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ PROJECT_APPS = [
|
||||||
'location.apps.LocationConfig',
|
'location.apps.LocationConfig',
|
||||||
'main.apps.MainConfig',
|
'main.apps.MainConfig',
|
||||||
'news.apps.NewsConfig',
|
'news.apps.NewsConfig',
|
||||||
|
'notification.apps.NotificationConfig',
|
||||||
'partner.apps.PartnerConfig',
|
'partner.apps.PartnerConfig',
|
||||||
'translation.apps.TranslationConfig',
|
'translation.apps.TranslationConfig',
|
||||||
'configuration.apps.ConfigurationConfig',
|
'configuration.apps.ConfigurationConfig',
|
||||||
|
|
@ -82,6 +83,7 @@ EXTERNAL_APPS = [
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'rest_framework_simplejwt.token_blacklist',
|
'rest_framework_simplejwt.token_blacklist',
|
||||||
'solo',
|
'solo',
|
||||||
|
'phonenumber_field',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -302,7 +304,6 @@ CELERY_ACCEPT_CONTENT = ['application/json']
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
CELERY_RESULT_SERIALIZER = 'json'
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
CELERY_TIMEZONE = TIME_ZONE
|
CELERY_TIMEZONE = TIME_ZONE
|
||||||
USE_CELERY = False
|
|
||||||
|
|
||||||
# Django FCM (Firebase push notificatoins)
|
# Django FCM (Firebase push notificatoins)
|
||||||
FCM_DJANGO_SETTINGS = {
|
FCM_DJANGO_SETTINGS = {
|
||||||
|
|
@ -388,5 +389,7 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
|
|
||||||
SOLO_CACHE_TIMEOUT = 300
|
SOLO_CACHE_TIMEOUT = 300
|
||||||
|
|
||||||
|
# REDIRECT URL
|
||||||
|
SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/'
|
||||||
|
|
||||||
SITE_NAME = 'Gault & Millau'
|
SITE_NAME = 'Gault & Millau'
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126']
|
||||||
|
|
||||||
SEND_SMS = False
|
SEND_SMS = False
|
||||||
SMS_CODE_SHOW = True
|
SMS_CODE_SHOW = True
|
||||||
|
USE_CELERY = False
|
||||||
|
|
||||||
SCHEMA_URI = 'http'
|
SCHEMA_URI = 'http'
|
||||||
DEFAULT_SUBDOMAIN = 'www'
|
DEFAULT_SUBDOMAIN = 'www'
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,6 @@ DEFAULT_SUBDOMAIN = 'www'
|
||||||
SITE_DOMAIN_URI = 'testserver.com:8000'
|
SITE_DOMAIN_URI = 'testserver.com:8000'
|
||||||
DOMAIN_URI = '0.0.0.0:8000'
|
DOMAIN_URI = '0.0.0.0:8000'
|
||||||
|
|
||||||
# OTHER SETTINGS
|
|
||||||
API_HOST = '0.0.0.0:8000'
|
|
||||||
API_HOST_URL = 'http://%s' % API_HOST
|
|
||||||
|
|
||||||
|
|
||||||
# CELERY
|
# CELERY
|
||||||
BROKER_URL = 'amqp://rabbitmq:5672'
|
BROKER_URL = 'amqp://rabbitmq:5672'
|
||||||
CELERY_RESULT_BACKEND = BROKER_URL
|
CELERY_RESULT_BACKEND = BROKER_URL
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ urlpatterns = [
|
||||||
path('collection/', include('collection.urls.web')),
|
path('collection/', include('collection.urls.web')),
|
||||||
path('establishments/', include('establishment.urls.web')),
|
path('establishments/', include('establishment.urls.web')),
|
||||||
path('news/', include('news.urls.web')),
|
path('news/', include('news.urls.web')),
|
||||||
|
path('notifications/', include('notification.urls.web')),
|
||||||
path('partner/', include('partner.urls.web')),
|
path('partner/', include('partner.urls.web')),
|
||||||
path('location/', include('location.urls')),
|
path('location/', include('location.urls')),
|
||||||
path('main/', include('main.urls')),
|
path('main/', include('main.urls')),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ djangorestframework-xml
|
||||||
celery
|
celery
|
||||||
amqp>=2.4.0
|
amqp>=2.4.0
|
||||||
geoip2==2.9.0
|
geoip2==2.9.0
|
||||||
|
django-phonenumber-field[phonenumbers]==2.1.0
|
||||||
|
|
||||||
# auth socials
|
# auth socials
|
||||||
djangorestframework-oauth
|
djangorestframework-oauth
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user