Merge branch 'develop' into 'feature/subscriptions'
# Conflicts: # apps/account/serializers/common.py
This commit is contained in:
commit
27f47cfae6
|
|
@ -7,12 +7,14 @@ from account import models
|
||||||
|
|
||||||
@admin.register(models.Role)
|
@admin.register(models.Role)
|
||||||
class RoleAdmin(admin.ModelAdmin):
|
class RoleAdmin(admin.ModelAdmin):
|
||||||
list_display = ['role', 'country']
|
list_display = ['id', 'role', 'country']
|
||||||
|
raw_id_fields = ['country', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.UserRole)
|
@admin.register(models.UserRole)
|
||||||
class UserRoleAdmin(admin.ModelAdmin):
|
class UserRoleAdmin(admin.ModelAdmin):
|
||||||
list_display = ['user', 'role', 'establishment']
|
list_display = ['user', 'role', 'establishment', ]
|
||||||
|
raw_id_fields = ['user', 'role', 'establishment', 'requester', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.User)
|
@admin.register(models.User)
|
||||||
|
|
|
||||||
29
apps/account/filters.py
Normal file
29
apps/account/filters.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""Account app filters."""
|
||||||
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from account import models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBackOfficeFilter(filters.FilterSet):
|
||||||
|
"""Account filter set."""
|
||||||
|
|
||||||
|
role = filters.MultipleChoiceFilter(choices=models.Role.ROLE_CHOICES,
|
||||||
|
method='filter_by_roles')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
model = models.User
|
||||||
|
fields = (
|
||||||
|
'role',
|
||||||
|
'email_confirmed',
|
||||||
|
'is_staff',
|
||||||
|
'is_active',
|
||||||
|
'is_superuser',
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_by_roles(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.by_roles(value)
|
||||||
|
return queryset
|
||||||
|
|
@ -70,10 +70,12 @@ class Command(BaseCommand):
|
||||||
role_choice = getattr(Role, old_role.new_role)
|
role_choice = getattr(Role, old_role.new_role)
|
||||||
sites = SiteSettings.objects.filter(old_id=s.site_id)
|
sites = SiteSettings.objects.filter(old_id=s.site_id)
|
||||||
for site in sites:
|
for site in sites:
|
||||||
role = Role.objects.filter(site=site, role=role_choice)
|
data = {'site': site, 'role': role_choice}
|
||||||
|
|
||||||
|
role = Role.objects.filter(**data)
|
||||||
if not role.exists():
|
if not role.exists():
|
||||||
objects.append(
|
objects.append(
|
||||||
Role(site=site, role=role_choice)
|
Role(**data)
|
||||||
)
|
)
|
||||||
|
|
||||||
Role.objects.bulk_create(objects)
|
Role.objects.bulk_create(objects)
|
||||||
|
|
@ -81,7 +83,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def update_site_role(self):
|
def update_site_role(self):
|
||||||
roles = Role.objects.filter(country__isnull=True).select_related('site')\
|
roles = Role.objects.filter(country__isnull=True).select_related('site')\
|
||||||
.filter(site__id__isnull=False).select_for_update()
|
.filter(site__id__isnull=False).select_for_update()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for role in tqdm(roles, desc='Update role country'):
|
for role in tqdm(roles, desc='Update role country'):
|
||||||
role.country = role.site.country
|
role.country = role.site.country
|
||||||
|
|
@ -114,8 +116,7 @@ class Command(BaseCommand):
|
||||||
users = User.objects.filter(old_id=s.account_id)
|
users = User.objects.filter(old_id=s.account_id)
|
||||||
for user in users:
|
for user in users:
|
||||||
for role in roles:
|
for role in roles:
|
||||||
user_role = UserRole.objects.get_or_create(user=user,
|
UserRole.objects.get_or_create(user=user, role=role, state=UserRole.VALIDATED)
|
||||||
role=role)
|
|
||||||
self.stdout.write(self.style.WARNING(f'Added users roles.'))
|
self.stdout.write(self.style.WARNING(f'Added users roles.'))
|
||||||
|
|
||||||
def superuser_role_sql(self):
|
def superuser_role_sql(self):
|
||||||
|
|
|
||||||
26
apps/account/migrations/0032_auto_20200114_1311.py
Normal file
26
apps/account/migrations/0032_auto_20200114_1311.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-14 13:11
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0046_auto_20200114_1218'),
|
||||||
|
('account', '0031_user_last_country'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='role',
|
||||||
|
name='navigation_bar_permission',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='navigation bar item permission', null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.NavigationBarPermission', verbose_name='navigation bar permission'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userrole',
|
||||||
|
name='requester',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, help_text='A user (REQUESTER) who requests a role change for a USER', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='roles_requested', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
"""Account models"""
|
"""Account models"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from tabnanny import verbose
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
||||||
from django.contrib.auth.tokens import default_token_generator as password_token_generator
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.loader import render_to_string, get_template
|
from django.template.loader import render_to_string, get_template
|
||||||
|
|
@ -24,6 +22,19 @@ from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
||||||
from utils.tokens import GMRefreshToken
|
from utils.tokens import GMRefreshToken
|
||||||
|
|
||||||
|
|
||||||
|
class RoleQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
def annotate_role_name(self):
|
||||||
|
return self.annotate(role_name=models.Case(*self.model.role_condition_expressions(),
|
||||||
|
output_field=models.CharField()))
|
||||||
|
|
||||||
|
def annotate_role_counter(self):
|
||||||
|
return self.annotate(
|
||||||
|
role_counter=models.Count('userrole',
|
||||||
|
distinct=True,
|
||||||
|
filter=models.Q(userrole__state=UserRole.VALIDATED)))
|
||||||
|
|
||||||
|
|
||||||
class Role(ProjectBaseMixin):
|
class Role(ProjectBaseMixin):
|
||||||
"""Base Role model."""
|
"""Base Role model."""
|
||||||
STANDARD_USER = 1
|
STANDARD_USER = 1
|
||||||
|
|
@ -46,13 +57,14 @@ class Role(ProjectBaseMixin):
|
||||||
(CONTENT_PAGE_MANAGER, _('Content page manager')),
|
(CONTENT_PAGE_MANAGER, _('Content page manager')),
|
||||||
(ESTABLISHMENT_MANAGER, _('Establishment manager')),
|
(ESTABLISHMENT_MANAGER, _('Establishment manager')),
|
||||||
(REVIEWER_MANGER, _('Reviewer manager')),
|
(REVIEWER_MANGER, _('Reviewer manager')),
|
||||||
(RESTAURANT_REVIEWER, 'Restaurant reviewer'),
|
(RESTAURANT_REVIEWER, _('Restaurant reviewer')),
|
||||||
(SALES_MAN, 'Sales man'),
|
(SALES_MAN, _('Sales man')),
|
||||||
(WINERY_REVIEWER, 'Winery reviewer'),
|
(WINERY_REVIEWER, _('Winery reviewer')),
|
||||||
(SELLER, 'Seller'),
|
(SELLER, _('Seller')),
|
||||||
(LIQUOR_REVIEWER, 'Liquor reviewer'),
|
(LIQUOR_REVIEWER, _('Liquor reviewer')),
|
||||||
(PRODUCT_REVIEWER, 'Product reviewer'),
|
(PRODUCT_REVIEWER, _('Product reviewer')),
|
||||||
)
|
)
|
||||||
|
|
||||||
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
||||||
null=False, blank=False)
|
null=False, blank=False)
|
||||||
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||||
|
|
@ -62,6 +74,27 @@ class Role(ProjectBaseMixin):
|
||||||
establishment_subtype = models.ForeignKey(EstablishmentSubType,
|
establishment_subtype = models.ForeignKey(EstablishmentSubType,
|
||||||
verbose_name=_('Establishment subtype'),
|
verbose_name=_('Establishment subtype'),
|
||||||
null=True, blank=True, on_delete=models.SET_NULL)
|
null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
navigation_bar_permission = models.ForeignKey('main.NavigationBarPermission',
|
||||||
|
blank=True, null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
help_text='navigation bar item permission',
|
||||||
|
verbose_name=_('navigation bar permission'))
|
||||||
|
|
||||||
|
objects = RoleQuerySet.as_manager()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def role_names(cls):
|
||||||
|
return [role_name._proxy____args[0]
|
||||||
|
for role_name in dict(cls.ROLE_CHOICES).values()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def role_condition_expressions(cls) -> list:
|
||||||
|
role_choices = {role_id: role_name._proxy____args[0]
|
||||||
|
for role_id, role_name in dict(cls.ROLE_CHOICES).items()}
|
||||||
|
|
||||||
|
whens = [models.When(role=role_id, then=models.Value(role_name))
|
||||||
|
for role_id, role_name in role_choices.items()]
|
||||||
|
return whens
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
|
|
@ -238,7 +271,7 @@ class User(AbstractUser):
|
||||||
@property
|
@property
|
||||||
def reset_password_token(self):
|
def reset_password_token(self):
|
||||||
"""Make a token for finish signup."""
|
"""Make a token for finish signup."""
|
||||||
return password_token_generator.make_token(self)
|
return GMTokenGenerator(purpose=GMTokenGenerator.RESET_PASSWORD).make_token(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_user_uidb64(self):
|
def get_user_uidb64(self):
|
||||||
|
|
@ -334,6 +367,22 @@ class User(AbstractUser):
|
||||||
model='product',
|
model='product',
|
||||||
).values_list('object_id', flat=True)
|
).values_list('object_id', flat=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscription_types(self):
|
||||||
|
result = []
|
||||||
|
for subscription in self.subscriber.all():
|
||||||
|
for item in subscription.active_subscriptions:
|
||||||
|
result.append(item.id)
|
||||||
|
return set(result)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleQueryset(models.QuerySet):
|
||||||
|
"""QuerySet for model UserRole."""
|
||||||
|
|
||||||
|
def country_admin_role(self):
|
||||||
|
return self.filter(role__role=self.model.role.field.target_field.model.COUNTRY_ADMIN,
|
||||||
|
state=self.model.VALIDATED)
|
||||||
|
|
||||||
|
|
||||||
class UserRole(ProjectBaseMixin):
|
class UserRole(ProjectBaseMixin):
|
||||||
"""UserRole model."""
|
"""UserRole model."""
|
||||||
|
|
@ -348,6 +397,7 @@ class UserRole(ProjectBaseMixin):
|
||||||
(CANCELLED, _('cancelled')),
|
(CANCELLED, _('cancelled')),
|
||||||
(REJECTED, _('rejected'))
|
(REJECTED, _('rejected'))
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
'account.User', verbose_name=_('User'), on_delete=models.CASCADE)
|
'account.User', verbose_name=_('User'), on_delete=models.CASCADE)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
|
|
@ -358,9 +408,13 @@ class UserRole(ProjectBaseMixin):
|
||||||
|
|
||||||
state = models.CharField(
|
state = models.CharField(
|
||||||
_('state'), choices=STATE_CHOICES, max_length=10, default=PENDING)
|
_('state'), choices=STATE_CHOICES, max_length=10, default=PENDING)
|
||||||
requester = models.ForeignKey(
|
requester = models.ForeignKey('account.User', on_delete=models.SET_NULL,
|
||||||
'account.User', blank=True, null=True, default=None, related_name='roles_requested',
|
blank=True, null=True, default=None,
|
||||||
on_delete=models.SET_NULL)
|
related_name='roles_requested',
|
||||||
|
help_text='A user (REQUESTER) who requests a '
|
||||||
|
'role change for a USER')
|
||||||
|
|
||||||
|
objects = UserRoleQueryset.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ['user', 'role', 'establishment', 'state']
|
unique_together = ['user', 'role', 'establishment', 'state']
|
||||||
|
|
@ -371,4 +425,4 @@ class OldRole(models.Model):
|
||||||
old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True)
|
old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('new_role', 'old_role')
|
unique_together = ('new_role', 'old_role')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from account.serializers.common import *
|
||||||
|
from account.serializers.web import *
|
||||||
|
from account.serializers.back import *
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
"""Back account serializers"""
|
"""Back account serializers"""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
from account.serializers import RoleBaseSerializer, subscriptions_handler
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.Role
|
|
||||||
fields = [
|
|
||||||
'role',
|
|
||||||
'country'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class _SiteSettingsSerializer(serializers.ModelSerializer):
|
class _SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SiteSettings
|
model = SiteSettings
|
||||||
|
|
@ -26,6 +19,15 @@ class _SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class BackUserSerializer(serializers.ModelSerializer):
|
class BackUserSerializer(serializers.ModelSerializer):
|
||||||
last_country = _SiteSettingsSerializer(read_only=True)
|
last_country = _SiteSettingsSerializer(read_only=True)
|
||||||
|
roles = RoleBaseSerializer(many=True, read_only=True)
|
||||||
|
subscriptions = serializers.ListField(
|
||||||
|
source='subscription_types',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.IntegerField(min_value=1),
|
||||||
|
required=False,
|
||||||
|
help_text='list of subscription_types id',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
@ -45,37 +47,98 @@ class BackUserSerializer(serializers.ModelSerializer):
|
||||||
'unconfirmed_email',
|
'unconfirmed_email',
|
||||||
'email_confirmed',
|
'email_confirmed',
|
||||||
'newsletter',
|
'newsletter',
|
||||||
'roles',
|
|
||||||
'password',
|
'password',
|
||||||
'city',
|
'city',
|
||||||
'locale',
|
'locale',
|
||||||
'last_ip',
|
'last_ip',
|
||||||
'last_country',
|
'last_country',
|
||||||
|
'roles',
|
||||||
|
'subscriptions',
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'password': {'write_only': True},
|
'password': {'write_only': True},
|
||||||
}
|
}
|
||||||
read_only_fields = ('old_password', 'last_login', 'date_joined', 'city', 'locale', 'last_ip', 'last_country')
|
read_only_fields = (
|
||||||
|
'old_password',
|
||||||
|
'last_login',
|
||||||
|
'date_joined',
|
||||||
|
'city',
|
||||||
|
'locale',
|
||||||
|
'last_ip',
|
||||||
|
'last_country',
|
||||||
|
)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
subscriptions_list = []
|
||||||
|
if 'subscription_types' in validated_data:
|
||||||
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
user = super().create(validated_data)
|
user = super().create(validated_data)
|
||||||
user.set_password(validated_data['password'])
|
user.set_password(validated_data['password'])
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
subscriptions_handler(subscriptions_list, user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class BackDetailUserSerializer(BackUserSerializer):
|
class BackDetailUserSerializer(BackUserSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
exclude = ('password',)
|
fields = (
|
||||||
read_only_fields = ('old_password', 'last_login', 'date_joined')
|
'id',
|
||||||
|
'last_country',
|
||||||
|
'roles',
|
||||||
|
'last_login',
|
||||||
|
'is_superuser',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'is_staff',
|
||||||
|
'is_active',
|
||||||
|
'date_joined',
|
||||||
|
'username',
|
||||||
|
'image_url',
|
||||||
|
'cropped_image_url',
|
||||||
|
'email',
|
||||||
|
'unconfirmed_email',
|
||||||
|
'email_confirmed',
|
||||||
|
'newsletter',
|
||||||
|
'old_id',
|
||||||
|
'locale',
|
||||||
|
'city',
|
||||||
|
'last_ip',
|
||||||
|
'groups',
|
||||||
|
'user_permissions',
|
||||||
|
'subscriptions',
|
||||||
|
)
|
||||||
|
read_only_fields = (
|
||||||
|
'old_password',
|
||||||
|
'last_login',
|
||||||
|
'date_joined',
|
||||||
|
'last_ip',
|
||||||
|
'last_country',
|
||||||
|
)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
subscriptions_list = []
|
||||||
|
if 'subscription_types' in validated_data:
|
||||||
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
user = super().create(validated_data)
|
user = super().create(validated_data)
|
||||||
user.set_password(validated_data['password'])
|
user.set_password(validated_data['password'])
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
subscriptions_handler(subscriptions_list, user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
subscriptions_list = []
|
||||||
|
if 'subscription_types' in validated_data:
|
||||||
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
subscriptions_handler(subscriptions_list, instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class UserRoleSerializer(serializers.ModelSerializer):
|
class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -85,3 +148,9 @@ class UserRoleSerializer(serializers.ModelSerializer):
|
||||||
'user',
|
'user',
|
||||||
'establishment'
|
'establishment'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RoleTabRetrieveSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for BackOffice role tab."""
|
||||||
|
role_name = serializers.CharField()
|
||||||
|
role_counter = serializers.IntegerField()
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,59 @@
|
||||||
"""Common account serializers"""
|
"""Common account serializers"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.contrib.auth import password_validation as password_validators
|
from django.contrib.auth import password_validation as password_validators
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from fcm_django.models import FCMDevice
|
from fcm_django.models import FCMDevice
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework import validators as rest_validators
|
from rest_framework import validators as rest_validators
|
||||||
|
|
||||||
from account import models, tasks
|
from account import models, tasks
|
||||||
|
from main.serializers.common import NavigationBarPermissionBaseSerializer
|
||||||
|
from notification.models import Subscribe, Subscriber
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils import methods as utils_methods
|
from utils import methods as utils_methods
|
||||||
|
from utils.methods import generate_string_code
|
||||||
|
|
||||||
|
|
||||||
|
def subscriptions_handler(subscriptions_list, user):
|
||||||
|
"""
|
||||||
|
create or update subscriptions for user
|
||||||
|
"""
|
||||||
|
Subscribe.objects.filter(subscriber__user=user).delete()
|
||||||
|
subscriber, _ = Subscriber.objects.get_or_create(
|
||||||
|
email=user.email,
|
||||||
|
defaults={
|
||||||
|
'user': user,
|
||||||
|
'email': user.email,
|
||||||
|
'ip_address': user.last_ip,
|
||||||
|
'country_code': user.last_country.country.code if user.last_country else None,
|
||||||
|
'locale': user.locale,
|
||||||
|
'update_code': generate_string_code(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for subscription in subscriptions_list:
|
||||||
|
Subscribe.objects.create(
|
||||||
|
subscriber=subscriber,
|
||||||
|
subscription_type_id=subscription,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RoleBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model Role."""
|
||||||
|
role_display = serializers.CharField(source='get_role_display', read_only=True)
|
||||||
|
navigation_bar_permission = NavigationBarPermissionBaseSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.Role
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'role_display',
|
||||||
|
'navigation_bar_permission',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# User serializers
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
"""User serializer."""
|
"""User serializer."""
|
||||||
# RESPONSE
|
# RESPONSE
|
||||||
|
|
@ -25,6 +66,15 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
email = serializers.EmailField(
|
email = serializers.EmailField(
|
||||||
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
|
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
|
||||||
required=False)
|
required=False)
|
||||||
|
roles = RoleBaseSerializer(many=True, read_only=True)
|
||||||
|
subscriptions = serializers.ListField(
|
||||||
|
source='subscription_types',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.IntegerField(min_value=1),
|
||||||
|
required=False,
|
||||||
|
help_text='list of subscription_types id',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
|
|
@ -38,6 +88,8 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
'email',
|
'email',
|
||||||
'email_confirmed',
|
'email_confirmed',
|
||||||
'newsletter',
|
'newsletter',
|
||||||
|
'roles',
|
||||||
|
'subscriptions',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'first_name': {'required': False, 'write_only': True, },
|
'first_name': {'required': False, 'write_only': True, },
|
||||||
|
|
@ -49,8 +101,14 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
subscriptions_list = []
|
||||||
|
if 'subscription_types' in validated_data:
|
||||||
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
user = super(UserSerializer, self).create(validated_data)
|
user = super(UserSerializer, self).create(validated_data)
|
||||||
validated_data['user'] = user
|
validated_data['user'] = user
|
||||||
|
Subscriber.objects.make_subscriber(**validated_data)
|
||||||
|
subscriptions_handler(subscriptions_list, user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def validate_email(self, value):
|
def validate_email(self, value):
|
||||||
|
|
@ -68,6 +126,10 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
"""Override update method"""
|
"""Override update method"""
|
||||||
|
subscriptions_list = []
|
||||||
|
if 'subscription_types' in validated_data:
|
||||||
|
subscriptions_list = validated_data.pop('subscription_types')
|
||||||
|
|
||||||
old_email = instance.email
|
old_email = instance.email
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
if 'email' in validated_data:
|
if 'email' in validated_data:
|
||||||
|
|
@ -80,12 +142,14 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
tasks.change_email_address.delay(
|
tasks.change_email_address.delay(
|
||||||
user_id=instance.id,
|
user_id=instance.id,
|
||||||
country_code=self.context.get('request').country_code,
|
country_code=self.context.get('request').country_code,
|
||||||
emails=[validated_data['email'],])
|
emails=[validated_data['email'], ])
|
||||||
else:
|
else:
|
||||||
tasks.change_email_address(
|
tasks.change_email_address(
|
||||||
user_id=instance.id,
|
user_id=instance.id,
|
||||||
country_code=self.context.get('request').country_code,
|
country_code=self.context.get('request').country_code,
|
||||||
emails=[validated_data['email'],])
|
emails=[validated_data['email'], ])
|
||||||
|
|
||||||
|
subscriptions_handler(subscriptions_list, instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -212,6 +276,7 @@ class ChangeEmailSerializer(serializers.ModelSerializer):
|
||||||
# Firebase Cloud Messaging serializers
|
# Firebase Cloud Messaging serializers
|
||||||
class FCMDeviceSerializer(serializers.ModelSerializer):
|
class FCMDeviceSerializer(serializers.ModelSerializer):
|
||||||
"""FCM Device model serializer"""
|
"""FCM Device model serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FCMDevice
|
model = FCMDevice
|
||||||
fields = ('id', 'name', 'registration_id', 'device_id',
|
fields = ('id', 'name', 'registration_id', 'device_id',
|
||||||
|
|
@ -231,8 +296,8 @@ class FCMDeviceSerializer(serializers.ModelSerializer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(FCMDeviceSerializer, self).__init__(*args, **kwargs)
|
super(FCMDeviceSerializer, self).__init__(*args, **kwargs)
|
||||||
self.fields['type'].help_text = (
|
self.fields['type'].help_text = (
|
||||||
'Should be one of ["%s"]' %
|
'Should be one of ["%s"]' %
|
||||||
'", "'.join([i for i in self.fields['type'].choices]))
|
'", "'.join([i for i in self.fields['type'].choices]))
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ from account.views import back as views
|
||||||
app_name = 'account'
|
app_name = 'account'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('role/', views.RoleLstView.as_view(), name='role-list-create'),
|
path('role/', views.RoleListView.as_view(), name='role-list-create'),
|
||||||
path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'),
|
path('role-tab/', views.RoleTabRetrieveView.as_view(), name='role-tab'),
|
||||||
path('user/', views.UserLstView.as_view(), name='user-create-list'),
|
path('user-role/', views.UserRoleListView.as_view(), name='user-role-list-create'),
|
||||||
|
path('user/', views.UserListView.as_view(), name='user-create-list'),
|
||||||
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
path('user/<int:id>/', views.UserRUDView.as_view(), name='user-rud'),
|
||||||
path('user/<int:id>/csv/', views.get_user_csv, name='user-csv'),
|
path('user/<int:id>/csv/', views.get_user_csv, name='user-csv'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,71 @@
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, status
|
||||||
|
from rest_framework.response import Response
|
||||||
from rest_framework.filters import OrderingFilter
|
from rest_framework.filters import OrderingFilter
|
||||||
import csv
|
import csv
|
||||||
from django.http import HttpResponse, HttpResponseNotFound
|
from django.http import HttpResponse, HttpResponseNotFound
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from account import models
|
from account import models, filters
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from account.serializers import back as serializers
|
from account.serializers import back as serializers
|
||||||
|
from account.serializers.common import RoleBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class RoleLstView(generics.ListCreateAPIView):
|
class RoleListView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = RoleBaseSerializer
|
||||||
queryset = models.Role.objects.all()
|
queryset = models.Role.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class UserRoleLstView(generics.ListCreateAPIView):
|
class RoleTabRetrieveView(generics.GenericAPIView):
|
||||||
|
permission_classes = [permissions.IsAdminUser]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Overridden get_queryset method."""
|
||||||
|
additional_filters = {}
|
||||||
|
|
||||||
|
if (self.request.user.userrole_set.country_admin_role().exists() and
|
||||||
|
hasattr(self.request, 'country_code')):
|
||||||
|
additional_filters.update({'country__code': self.request.country_code})
|
||||||
|
|
||||||
|
return models.Role.objects.filter(**additional_filters)\
|
||||||
|
.annotate_role_name()\
|
||||||
|
.values('role_name')\
|
||||||
|
.annotate_role_counter()\
|
||||||
|
.values('role_name', 'role_counter')
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Implement GET-method"""
|
||||||
|
data = list(self.get_queryset())
|
||||||
|
|
||||||
|
# todo: Need refactoring. Extend data list with non-existed role.
|
||||||
|
for role in models.Role.role_names():
|
||||||
|
if role not in [role.get('role_name') for role in data]:
|
||||||
|
data.append({'role_name': role, 'role_number': 0})
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoleListView(generics.ListCreateAPIView):
|
||||||
serializer_class = serializers.UserRoleSerializer
|
serializer_class = serializers.UserRoleSerializer
|
||||||
queryset = models.UserRole.objects.all()
|
queryset = models.UserRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class UserLstView(generics.ListCreateAPIView):
|
class UserListView(generics.ListCreateAPIView):
|
||||||
"""User list create view."""
|
"""User list create view."""
|
||||||
queryset = User.objects.prefetch_related('roles')
|
queryset = User.objects.prefetch_related('roles')
|
||||||
serializer_class = serializers.BackUserSerializer
|
serializer_class = serializers.BackUserSerializer
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
filter_class = filters.AccountBackOfficeFilter
|
||||||
filterset_fields = (
|
filter_backends = (OrderingFilter, DjangoFilterBackend)
|
||||||
'email_confirmed',
|
|
||||||
'is_staff',
|
|
||||||
'is_active',
|
|
||||||
'is_superuser',
|
|
||||||
'roles',
|
|
||||||
)
|
|
||||||
ordering_fields = (
|
ordering_fields = (
|
||||||
'email_confirmed',
|
'email_confirmed',
|
||||||
'is_staff',
|
'is_staff',
|
||||||
'is_active',
|
'is_active',
|
||||||
'is_superuser',
|
'is_superuser',
|
||||||
'roles',
|
'last_login',
|
||||||
'last_login'
|
'date_joined',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"""Web account views"""
|
"""Web account views"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.tokens import default_token_generator as password_token_generator
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_decode
|
||||||
|
|
@ -10,6 +9,7 @@ from rest_framework.response import Response
|
||||||
from account import tasks, models
|
from account import tasks, models
|
||||||
from account.serializers import web as serializers
|
from account.serializers import web as serializers
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
|
from utils.models import GMTokenGenerator
|
||||||
from utils.views import JWTGenericViewMixin
|
from utils.views import JWTGenericViewMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,22 +40,23 @@ class PasswordResetConfirmView(JWTGenericViewMixin, generics.GenericAPIView):
|
||||||
queryset = models.User.objects.active()
|
queryset = models.User.objects.active()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""Override get_object method"""
|
"""Overridden get_object method"""
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
uidb64 = self.kwargs.get('uidb64')
|
uidb64 = self.kwargs.get('uidb64')
|
||||||
|
|
||||||
user_id = force_text(urlsafe_base64_decode(uidb64))
|
user_id = force_text(urlsafe_base64_decode(uidb64))
|
||||||
token = self.kwargs.get('token')
|
token = self.kwargs.get('token')
|
||||||
|
|
||||||
obj = get_object_or_404(queryset, id=user_id)
|
user = get_object_or_404(queryset, id=user_id)
|
||||||
|
|
||||||
if not password_token_generator.check_token(user=obj, token=token):
|
if not GMTokenGenerator(GMTokenGenerator.RESET_PASSWORD).check_token(
|
||||||
|
user, token):
|
||||||
raise utils_exceptions.NotValidTokenError()
|
raise utils_exceptions.NotValidTokenError()
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, obj)
|
self.check_object_permissions(self.request, user)
|
||||||
|
|
||||||
return obj
|
return user
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
def patch(self, request, *args, **kwargs):
|
||||||
"""Implement PATCH method"""
|
"""Implement PATCH method"""
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from utils.tokens import GMRefreshToken
|
||||||
# Serializers
|
# Serializers
|
||||||
class SignupSerializer(serializers.ModelSerializer):
|
class SignupSerializer(serializers.ModelSerializer):
|
||||||
"""Signup serializer serializer mixin"""
|
"""Signup serializer serializer mixin"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = account_models.User
|
model = account_models.User
|
||||||
fields = (
|
fields = (
|
||||||
|
|
@ -37,11 +38,13 @@ class SignupSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def validate_username(self, value):
|
def validate_username(self, value):
|
||||||
"""Custom username validation"""
|
"""Custom username validation"""
|
||||||
valid = utils_methods.username_validator(username=value)
|
if value:
|
||||||
if not valid:
|
valid = utils_methods.username_validator(username=value)
|
||||||
raise utils_exceptions.NotValidUsernameError()
|
if not valid:
|
||||||
if account_models.User.objects.filter(username__iexact=value).exists():
|
raise utils_exceptions.NotValidUsernameError()
|
||||||
raise serializers.ValidationError()
|
if account_models.User.objects.filter(username__iexact=value).exists():
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_email(self, value):
|
def validate_email(self, value):
|
||||||
|
|
@ -63,6 +66,13 @@ class SignupSerializer(serializers.ModelSerializer):
|
||||||
request = self.context.get('request')
|
request = self.context.get('request')
|
||||||
|
|
||||||
"""Overridden create method"""
|
"""Overridden create method"""
|
||||||
|
|
||||||
|
username = validated_data.get('username')
|
||||||
|
if not username:
|
||||||
|
username = utils_methods.username_random()
|
||||||
|
while account_models.User.objects.filter(username__iexact=username).exists():
|
||||||
|
username = utils_methods.username_random()
|
||||||
|
|
||||||
obj = account_models.User.objects.make(
|
obj = account_models.User.objects.make(
|
||||||
username=validated_data.get('username'),
|
username=validated_data.get('username'),
|
||||||
password=validated_data.get('password'),
|
password=validated_data.get('password'),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
from sorl.thumbnail import get_thumbnail
|
||||||
|
|
||||||
|
from collection.models import Collection
|
||||||
|
from utils.methods import image_url_valid, get_image_meta_by_url
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
SORL_THUMBNAIL_ALIAS = 'collection_image'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
max_size = 1048576
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for collection in Collection.objects.all():
|
||||||
|
if not image_url_valid(collection.image_url):
|
||||||
|
continue
|
||||||
|
|
||||||
|
size, width, height = get_image_meta_by_url(collection.image_url)
|
||||||
|
|
||||||
|
if size < max_size:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'No need to compress images size is {size / (2 ** 20)}Mb\n'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
percents = round(max_size / (size * 0.01))
|
||||||
|
width = round(width * percents / 100)
|
||||||
|
height = round(height * percents / 100)
|
||||||
|
collection.image_url = get_thumbnail(
|
||||||
|
file_=collection.image_url,
|
||||||
|
geometry_string=f'{width}x{height}',
|
||||||
|
upscale=False
|
||||||
|
).url
|
||||||
|
|
||||||
|
collection.save()
|
||||||
|
|
@ -165,7 +165,7 @@ class GuideQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Return QuerySet with related."""
|
"""Return QuerySet with related."""
|
||||||
return self.select_related('guide_type', 'site')
|
return self.select_related('site', )
|
||||||
|
|
||||||
def by_country_id(self, country_id):
|
def by_country_id(self, country_id):
|
||||||
"""Return QuerySet filtered by country code."""
|
"""Return QuerySet filtered by country code."""
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ class CollectionEstablishmentListView(CollectionListView):
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
self.check_object_permissions(self.request, collection)
|
self.check_object_permissions(self.request, collection)
|
||||||
|
|
||||||
return collection.establishments.published().annotate_in_favorites(self.request.user)
|
return collection.establishments.published().annotate_in_favorites(self.request.user) \
|
||||||
|
.with_base_related().with_extended_related()
|
||||||
|
|
||||||
|
|
||||||
# Guide
|
# Guide
|
||||||
|
|
|
||||||
18
apps/comment/management/commands/add_status_to_comments.py
Normal file
18
apps/comment/management/commands/add_status_to_comments.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from comment.models import Comment
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add status to comments from is_publish_ flag.'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
to_update = []
|
||||||
|
|
||||||
|
for comment in tqdm(Comment.objects.all()):
|
||||||
|
if hasattr(comment, 'is_publish') and hasattr(comment, 'status'):
|
||||||
|
comment.status = Comment.PUBLISHED if comment.is_publish else Comment.WAITING
|
||||||
|
to_update.append(comment)
|
||||||
|
|
||||||
|
Comment.objects.bulk_update(to_update, ('status', ))
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated {len(to_update)} objects.'))
|
||||||
18
apps/comment/migrations/0008_comment_status.py
Normal file
18
apps/comment/migrations/0008_comment_status.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 13:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comment', '0007_comment_site'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comment',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(0, 'Waiting'), (1, 'Published'), (2, 'Rejected'), (3, 'Deleted')], default=0, verbose_name='status'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -16,6 +16,10 @@ class CommentQuerySet(ContentTypeQuerySetMixin):
|
||||||
"""Return comments by author"""
|
"""Return comments by author"""
|
||||||
return self.filter(user=user)
|
return self.filter(user=user)
|
||||||
|
|
||||||
|
def published(self):
|
||||||
|
"""Return only published comments."""
|
||||||
|
return self.filter(status=self.model.PUBLISHED)
|
||||||
|
|
||||||
def annotate_is_mine_status(self, user):
|
def annotate_is_mine_status(self, user):
|
||||||
"""Annotate belonging status"""
|
"""Annotate belonging status"""
|
||||||
return self.annotate(is_mine=models.Case(
|
return self.annotate(is_mine=models.Case(
|
||||||
|
|
@ -27,16 +31,48 @@ class CommentQuerySet(ContentTypeQuerySetMixin):
|
||||||
output_field=models.BooleanField()
|
output_field=models.BooleanField()
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def public(self, user):
|
||||||
|
"""
|
||||||
|
Return QuerySet by rules:
|
||||||
|
1 With status PUBLISHED
|
||||||
|
2 With status WAITING if comment author is <user>
|
||||||
|
"""
|
||||||
|
qs = self.published()
|
||||||
|
if isinstance(user, User):
|
||||||
|
waiting_ids = list(
|
||||||
|
self.filter(status=self.model.WAITING, user=user)
|
||||||
|
.values_list('id', flat=True))
|
||||||
|
published_ids = list(qs.values_list('id', flat=True))
|
||||||
|
waiting_ids.extend(published_ids)
|
||||||
|
qs = self.filter(id__in=tuple(waiting_ids))
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class Comment(ProjectBaseMixin):
|
class Comment(ProjectBaseMixin):
|
||||||
"""Comment model."""
|
"""Comment model."""
|
||||||
|
|
||||||
|
WAITING = 0
|
||||||
|
PUBLISHED = 1
|
||||||
|
REJECTED = 2
|
||||||
|
DELETED = 3
|
||||||
|
|
||||||
|
STATUSES = (
|
||||||
|
(WAITING, _('Waiting')),
|
||||||
|
(PUBLISHED, _('Published')),
|
||||||
|
(REJECTED, _('Rejected')),
|
||||||
|
(DELETED, _('Deleted')),
|
||||||
|
)
|
||||||
|
|
||||||
text = models.TextField(verbose_name=_('Comment text'))
|
text = models.TextField(verbose_name=_('Comment text'))
|
||||||
mark = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Mark'))
|
mark = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Mark'))
|
||||||
user = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('User'))
|
user = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('User'))
|
||||||
old_id = models.IntegerField(null=True, blank=True, default=None)
|
old_id = models.IntegerField(null=True, blank=True, default=None)
|
||||||
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
||||||
|
status = models.PositiveSmallIntegerField(choices=STATUSES,
|
||||||
|
default=WAITING,
|
||||||
|
verbose_name=_('status'))
|
||||||
site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
|
site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
|
||||||
on_delete=models.SET_NULL, verbose_name=_('site'))
|
on_delete=models.SET_NULL, verbose_name=_('site'))
|
||||||
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(generic.ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
|
from .common import *
|
||||||
from .mobile import *
|
from .mobile import *
|
||||||
from .back import *
|
from .back import *
|
||||||
from .web import *
|
from .web import *
|
||||||
from .common import *
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1 @@
|
||||||
"""Comment app common serializers."""
|
"""Comment app common serializers."""
|
||||||
from comment import models
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
|
|
||||||
class CommentBaseSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.Comment
|
|
||||||
fields = ('id', 'text', 'mark', 'user', 'object_id', 'content_type')
|
|
||||||
|
|
@ -4,13 +4,16 @@ from rest_framework import serializers
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
|
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
class CommentBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Comment serializer"""
|
"""Comment serializer"""
|
||||||
nickname = serializers.CharField(read_only=True,
|
nickname = serializers.CharField(read_only=True,
|
||||||
source='user.username')
|
source='user.username')
|
||||||
is_mine = serializers.BooleanField(read_only=True)
|
is_mine = serializers.BooleanField(read_only=True)
|
||||||
profile_pic = serializers.URLField(read_only=True,
|
profile_pic = serializers.URLField(read_only=True,
|
||||||
source='user.cropped_image_url')
|
source='user.cropped_image_url')
|
||||||
|
status_display = serializers.CharField(read_only=True,
|
||||||
|
source='get_status_display')
|
||||||
|
last_ip = serializers.IPAddressField(read_only=True, source='user.last_ip')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Serializer for model Comment"""
|
"""Serializer for model Comment"""
|
||||||
|
|
@ -23,19 +26,11 @@ class CommentSerializer(serializers.ModelSerializer):
|
||||||
'text',
|
'text',
|
||||||
'mark',
|
'mark',
|
||||||
'nickname',
|
'nickname',
|
||||||
'profile_pic'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CommentRUDSerializer(CommentSerializer):
|
|
||||||
"""Retrieve/Update/Destroy comment serializer."""
|
|
||||||
class Meta(CommentSerializer.Meta):
|
|
||||||
"""Meta class."""
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'created',
|
|
||||||
'text',
|
|
||||||
'mark',
|
|
||||||
'nickname',
|
|
||||||
'profile_pic',
|
'profile_pic',
|
||||||
|
'status',
|
||||||
|
'status_display',
|
||||||
|
'last_ip',
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'status': {'read_only': True},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ def transfer_comments():
|
||||||
'mark',
|
'mark',
|
||||||
'establishment_id',
|
'establishment_id',
|
||||||
'account_id',
|
'account_id',
|
||||||
|
'state',
|
||||||
)
|
)
|
||||||
|
|
||||||
serialized_data = CommentSerializer(data=list(queryset.values()), many=True)
|
serialized_data = CommentSerializer(data=list(queryset.values()), many=True)
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
from comment.serializers import back as serializers
|
from comment.serializers import CommentBaseSerializer
|
||||||
from comment import models
|
from comment import models
|
||||||
from utils.permissions import IsCommentModerator, IsCountryAdmin
|
from utils.permissions import IsCommentModerator, IsCountryAdmin
|
||||||
|
|
||||||
|
|
||||||
class CommentLstView(generics.ListCreateAPIView):
|
class CommentLstView(generics.ListCreateAPIView):
|
||||||
"""Comment list create view."""
|
"""Comment list create view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
# permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
# permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Comment RUD view."""
|
"""Comment RUD view."""
|
||||||
serializer_class = serializers.CommentBaseSerializer
|
serializer_class = CommentBaseSerializer
|
||||||
queryset = models.Comment.objects.all()
|
queryset = models.Comment.objects.all()
|
||||||
permission_classes = [IsCommentModerator]
|
permission_classes = [IsCommentModerator]
|
||||||
# permission_classes = [IsCountryAdmin | IsCommentModerator]
|
# permission_classes = [IsCountryAdmin | IsCommentModerator]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
"""Establishment app filters."""
|
"""Establishment app filters."""
|
||||||
from django.core.validators import EMPTY_VALUES
|
from django.core.validators import EMPTY_VALUES
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
|
|
||||||
|
|
@ -8,8 +10,8 @@ from establishment import models
|
||||||
class EstablishmentFilter(filters.FilterSet):
|
class EstablishmentFilter(filters.FilterSet):
|
||||||
"""Establishment filter set."""
|
"""Establishment filter set."""
|
||||||
|
|
||||||
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')
|
search = filters.CharFilter(method='search_text')
|
||||||
type = filters.CharFilter(method='by_type')
|
type = filters.CharFilter(method='by_type')
|
||||||
subtype = filters.CharFilter(method='by_subtype')
|
subtype = filters.CharFilter(method='by_subtype')
|
||||||
|
|
@ -65,6 +67,10 @@ class EmployeeBackFilter(filters.FilterSet):
|
||||||
"""Employee filter set."""
|
"""Employee filter set."""
|
||||||
|
|
||||||
search = filters.CharFilter(method='search_by_name_or_last_name')
|
search = filters.CharFilter(method='search_by_name_or_last_name')
|
||||||
|
position_id = filters.NumberFilter(method='search_by_actual_position_id')
|
||||||
|
public_mark = filters.NumberFilter(method='search_by_public_mark')
|
||||||
|
toque_number = filters.NumberFilter(method='search_by_toque_number')
|
||||||
|
username = filters.CharFilter(method='search_by_username_or_name')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -72,10 +78,47 @@ class EmployeeBackFilter(filters.FilterSet):
|
||||||
model = models.Employee
|
model = models.Employee
|
||||||
fields = (
|
fields = (
|
||||||
'search',
|
'search',
|
||||||
|
'position_id',
|
||||||
|
'public_mark',
|
||||||
|
'toque_number',
|
||||||
|
'username',
|
||||||
)
|
)
|
||||||
|
|
||||||
def search_by_name_or_last_name(self, queryset, name, value):
|
def search_by_name_or_last_name(self, queryset, name, value):
|
||||||
"""Search by name or last name."""
|
"""Search by name or last name."""
|
||||||
if value not in EMPTY_VALUES:
|
if value not in EMPTY_VALUES:
|
||||||
return queryset.search_by_name_or_last_name(value)
|
return queryset.trigram_search(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def search_by_actual_position_id(self, queryset, name, value):
|
||||||
|
"""Search by actual position_id."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_position_id(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def search_by_public_mark(self, queryset, name, value):
|
||||||
|
"""Search by establishment public_mark."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_public_mark(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def search_by_toque_number(self, queryset, name, value):
|
||||||
|
"""Search by establishment toque_number."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_toque_number(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def search_by_username_or_name(self, queryset, name, value):
|
||||||
|
"""Search by username or name."""
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
return queryset.search_by_username_or_name(value)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeBackSearchFilter(EmployeeBackFilter):
|
||||||
|
def search_by_name_or_last_name(self, queryset, name, value):
|
||||||
|
if value not in EMPTY_VALUES:
|
||||||
|
if len(value) < 3:
|
||||||
|
raise ValidationError({'detail': _('Type at least 3 characters to search please.')})
|
||||||
|
return queryset.trigram_search(value)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
from sorl.thumbnail import get_thumbnail
|
||||||
|
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from utils.methods import image_url_valid, get_image_meta_by_url
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
SORL_THUMBNAIL_ALIAS = 'establishment_collection_image'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
with transaction.atomic():
|
||||||
|
for establishment in Establishment.objects.all():
|
||||||
|
if establishment.preview_image_url is None \
|
||||||
|
or not image_url_valid(establishment.preview_image_url):
|
||||||
|
continue
|
||||||
|
_, width, height = get_image_meta_by_url(establishment.preview_image_url)
|
||||||
|
sorl_settings = settings.SORL_THUMBNAIL_ALIASES[self.SORL_THUMBNAIL_ALIAS]
|
||||||
|
sorl_width_height = sorl_settings['geometry_string'].split('x')
|
||||||
|
|
||||||
|
if int(sorl_width_height[0]) > width or int(sorl_width_height[1]) > height:
|
||||||
|
_, file_ext = os.path.splitext(establishment.preview_image_url)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'Optimize: {establishment.preview_image_url}, extension: {file_ext}'))
|
||||||
|
|
||||||
|
establishment.preview_image_url = get_thumbnail(
|
||||||
|
file_=establishment.preview_image_url,
|
||||||
|
**sorl_settings,
|
||||||
|
format='JPEG' if file_ext[1:] == 'jpg' else 'PNG'
|
||||||
|
).url
|
||||||
|
establishment.save()
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import math
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from celery import group
|
||||||
|
from establishment.models import Establishment
|
||||||
|
from establishment.tasks import update_establishment_image_urls
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """
|
||||||
|
Updating image links for establishments.
|
||||||
|
Run command ./manage.py update_establishment_image_urls
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--bucket_size',
|
||||||
|
type=int,
|
||||||
|
default=128,
|
||||||
|
help='Size of one basket to update'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
bucket_size = kwargs.get('bucket_size', 128)
|
||||||
|
|
||||||
|
objects = Establishment.objects.all()
|
||||||
|
objects_size = objects.count()
|
||||||
|
summary_tasks = math.ceil(objects_size / bucket_size)
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
for index in range(0, objects_size, bucket_size):
|
||||||
|
bucket = objects[index: index + bucket_size]
|
||||||
|
|
||||||
|
task = update_establishment_image_urls.s(
|
||||||
|
(index + bucket_size) / bucket_size, summary_tasks,
|
||||||
|
list(bucket.values_list('id', flat=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
tasks.append(task)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(f'Created all celery update tasks.\n'))
|
||||||
|
|
||||||
|
job = group(*tasks)
|
||||||
|
job.delay()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'Done all celery update tasks.\n'))
|
||||||
16
apps/establishment/migrations/0072_auto_20200115_1702.py
Normal file
16
apps/establishment/migrations/0072_auto_20200115_1702.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 17:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.contrib.postgres.operations import TrigramExtension, BtreeGinExtension
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0071_auto_20200110_1055'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
TrigramExtension(),
|
||||||
|
BtreeGinExtension(),
|
||||||
|
]
|
||||||
22
apps/establishment/migrations/0073_auto_20200115_1710.py
Normal file
22
apps/establishment/migrations/0073_auto_20200115_1710.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 17:10
|
||||||
|
|
||||||
|
import django.contrib.postgres.indexes
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0072_auto_20200115_1702'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='employee',
|
||||||
|
index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='establishme_name_39fda6_gin'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='employee',
|
||||||
|
index=django.contrib.postgres.indexes.GinIndex(fields=['last_name'], name='establishme_last_na_3c53de_gin'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-17 08:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0073_auto_20200115_1710'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='available_for_events',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Available for events'),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
apps/establishment/migrations/0075_employee_photo.py
Normal file
20
apps/establishment/migrations/0075_employee_photo.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-17 10:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0008_merge_20191212_0752'),
|
||||||
|
('establishment', '0074_employee_available_for_events'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='photo',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_photo', to='gallery.Image', verbose_name='image instance of model Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -11,10 +11,12 @@ from django.contrib.gis.db.models.functions import Distance
|
||||||
from django.contrib.gis.geos import Point
|
from django.contrib.gis.geos import Point
|
||||||
from django.contrib.gis.measure import Distance as DistanceMeasure
|
from django.contrib.gis.measure import Distance as DistanceMeasure
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.contrib.postgres.search import TrigramDistance, TrigramSimilarity
|
||||||
|
from django.contrib.postgres.indexes import GinIndex
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch
|
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch, Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
@ -58,8 +60,6 @@ class EstablishmentType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBas
|
||||||
blank=True, null=True, default=None,
|
blank=True, null=True, default=None,
|
||||||
verbose_name='default image')
|
verbose_name='default image')
|
||||||
|
|
||||||
chosen_tags = generic.GenericRelation(to='tag.ChosenTag')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
return self.select_related('address', 'establishment_type'). \
|
return self.select_related('address', 'establishment_type'). \
|
||||||
prefetch_related('tags', 'tags__translation')
|
prefetch_related('tags', 'tags__translation').with_main_image()
|
||||||
|
|
||||||
def with_schedule(self):
|
def with_schedule(self):
|
||||||
"""Return qs with related schedule."""
|
"""Return qs with related schedule."""
|
||||||
|
|
@ -266,16 +266,16 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
2 With ordering by distance.
|
2 With ordering by distance.
|
||||||
"""
|
"""
|
||||||
qs = self.similar_base(establishment) \
|
qs = self.similar_base(establishment) \
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
if establishment.address and establishment.address.coordinates:
|
if establishment.address and establishment.address.coordinates:
|
||||||
return Subquery(
|
return Subquery(
|
||||||
qs.annotate_distance(point=establishment.location)
|
qs.annotate_distance(point=establishment.location)
|
||||||
.order_by('distance')
|
.order_by('distance')
|
||||||
.distinct()
|
.distinct()
|
||||||
.values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS]
|
.values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS]
|
||||||
)
|
)
|
||||||
return Subquery(
|
return Subquery(
|
||||||
qs.values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS]
|
qs.values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS]
|
||||||
)
|
)
|
||||||
|
|
||||||
def similar_restaurants(self, restaurant):
|
def similar_restaurants(self, restaurant):
|
||||||
|
|
@ -293,12 +293,12 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return self.filter(id__in=ids_by_subquery.queryset) \
|
return self.filter(id__in=ids_by_subquery.queryset) \
|
||||||
.annotate_intermediate_public_mark() \
|
.annotate_intermediate_public_mark() \
|
||||||
.annotate_mark_similarity(mark=restaurant.public_mark) \
|
.annotate_mark_similarity(mark=restaurant.public_mark) \
|
||||||
.order_by('mark_similarity') \
|
.order_by('mark_similarity') \
|
||||||
.distinct('mark_similarity', 'id')
|
.distinct('mark_similarity', 'id')
|
||||||
|
|
||||||
def same_subtype(self, establishment):
|
def annotate_same_subtype(self, establishment):
|
||||||
"""Annotate flag same subtype."""
|
"""Annotate flag same subtype."""
|
||||||
return self.annotate(same_subtype=Case(
|
return self.annotate(same_subtype=Case(
|
||||||
models.When(
|
models.When(
|
||||||
|
|
@ -314,7 +314,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
Return QuerySet with objects that similar to Artisan/Producer(s).
|
Return QuerySet with objects that similar to Artisan/Producer(s).
|
||||||
:param establishment: Establishment instance
|
:param establishment: Establishment instance
|
||||||
"""
|
"""
|
||||||
base_qs = self.similar_base(establishment).same_subtype(establishment)
|
base_qs = self.similar_base(establishment).annotate_same_subtype(establishment)
|
||||||
similarity_rules = {
|
similarity_rules = {
|
||||||
'ordering': [F('same_subtype').desc(), ],
|
'ordering': [F('same_subtype').desc(), ],
|
||||||
'distinctions': ['same_subtype', ]
|
'distinctions': ['same_subtype', ]
|
||||||
|
|
@ -325,8 +325,8 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
similarity_rules['distinctions'].append('distance')
|
similarity_rules['distinctions'].append('distance')
|
||||||
|
|
||||||
return base_qs.has_published_reviews() \
|
return base_qs.has_published_reviews() \
|
||||||
.order_by(*similarity_rules['ordering']) \
|
.order_by(*similarity_rules['ordering']) \
|
||||||
.distinct(*similarity_rules['distinctions'], 'id')
|
.distinct(*similarity_rules['distinctions'], 'id')
|
||||||
|
|
||||||
def by_wine_region(self, wine_region):
|
def by_wine_region(self, wine_region):
|
||||||
"""
|
"""
|
||||||
|
|
@ -360,17 +360,19 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
similarity_rules['distinctions'].append('distance')
|
similarity_rules['distinctions'].append('distance')
|
||||||
|
|
||||||
return base_qs.order_by(*similarity_rules['ordering']) \
|
return base_qs.order_by(*similarity_rules['ordering']) \
|
||||||
.distinct(*similarity_rules['distinctions'], 'id')
|
.distinct(*similarity_rules['distinctions'], 'id')
|
||||||
|
|
||||||
def similar_distilleries(self, distillery):
|
def similar_distilleries(self, distillery):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with objects that similar to Distillery.
|
Return QuerySet with objects that similar to Distillery.
|
||||||
:param distillery: Establishment instance
|
:param distillery: Establishment instance
|
||||||
"""
|
"""
|
||||||
base_qs = self.similar_base(distillery).same_subtype(distillery)
|
base_qs = self.similar_base(distillery).annotate_same_subtype(distillery).filter(
|
||||||
|
same_subtype=True
|
||||||
|
)
|
||||||
similarity_rules = {
|
similarity_rules = {
|
||||||
'ordering': [F('same_subtype').desc(), ],
|
'ordering': [],
|
||||||
'distinctions': ['same_subtype', ]
|
'distinctions': []
|
||||||
}
|
}
|
||||||
if distillery.address and distillery.address.coordinates:
|
if distillery.address and distillery.address.coordinates:
|
||||||
base_qs = base_qs.annotate_distance(point=distillery.location)
|
base_qs = base_qs.annotate_distance(point=distillery.location)
|
||||||
|
|
@ -378,27 +380,29 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
similarity_rules['distinctions'].append('distance')
|
similarity_rules['distinctions'].append('distance')
|
||||||
|
|
||||||
return base_qs.published() \
|
return base_qs.published() \
|
||||||
.has_published_reviews() \
|
.has_published_reviews() \
|
||||||
.order_by(*similarity_rules['ordering']) \
|
.order_by(*similarity_rules['ordering']) \
|
||||||
.distinct(*similarity_rules['distinctions'], 'id')
|
.distinct(*similarity_rules['distinctions'], 'id')
|
||||||
|
|
||||||
def similar_food_producers(self, distillery):
|
def similar_food_producers(self, food_producer):
|
||||||
"""
|
"""
|
||||||
Return QuerySet with objects that similar to Food Producer.
|
Return QuerySet with objects that similar to Food Producer.
|
||||||
:param distillery: Establishment instance
|
:param food_producer: Establishment instance
|
||||||
"""
|
"""
|
||||||
base_qs = self.similar_base(distillery).same_subtype(distillery)
|
base_qs = self.similar_base(food_producer).annotate_same_subtype(food_producer).filter(
|
||||||
|
same_subtype=True
|
||||||
|
)
|
||||||
similarity_rules = {
|
similarity_rules = {
|
||||||
'ordering': [F('same_subtype').desc(), ],
|
'ordering': [],
|
||||||
'distinctions': ['same_subtype', ]
|
'distinctions': []
|
||||||
}
|
}
|
||||||
if distillery.address and distillery.address.coordinates:
|
if food_producer.address and food_producer.address.coordinates:
|
||||||
base_qs = base_qs.annotate_distance(point=distillery.location)
|
base_qs = base_qs.annotate_distance(point=food_producer.location)
|
||||||
similarity_rules['ordering'].append(F('distance').asc())
|
similarity_rules['ordering'].append(F('distance').asc())
|
||||||
similarity_rules['distinctions'].append('distance')
|
similarity_rules['distinctions'].append('distance')
|
||||||
|
|
||||||
return base_qs.order_by(*similarity_rules['ordering']) \
|
return base_qs.order_by(*similarity_rules['ordering']) \
|
||||||
.distinct(*similarity_rules['distinctions'], 'id')
|
.distinct(*similarity_rules['distinctions'], 'id')
|
||||||
|
|
||||||
def last_reviewed(self, point: Point):
|
def last_reviewed(self, point: Point):
|
||||||
"""
|
"""
|
||||||
|
|
@ -502,6 +506,13 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
to_attr=attr_name)
|
to_attr=attr_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def with_main_image(self):
|
||||||
|
return self.prefetch_related(
|
||||||
|
models.Prefetch('establishment_gallery',
|
||||||
|
queryset=EstablishmentGallery.objects.main_image(),
|
||||||
|
to_attr='main_image')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):
|
||||||
|
|
@ -649,7 +660,6 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
country = self.address.city.country
|
country = self.address.city.country
|
||||||
return country.low_price, country.high_price
|
return country.low_price, country.high_price
|
||||||
|
|
||||||
|
|
||||||
def set_establishment_type(self, establishment_type):
|
def set_establishment_type(self, establishment_type):
|
||||||
self.establishment_type = establishment_type
|
self.establishment_type = establishment_type
|
||||||
self.establishment_subtypes.exclude(
|
self.establishment_subtypes.exclude(
|
||||||
|
|
@ -769,10 +779,12 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
return self.products.wines()
|
return self.products.wines()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def main_image(self):
|
def _main_image(self):
|
||||||
|
"""Please consider using prefetched query_set instead due to API performance issues"""
|
||||||
qs = self.establishment_gallery.main_image()
|
qs = self.establishment_gallery.main_image()
|
||||||
if qs.exists():
|
image_model = qs.first()
|
||||||
return qs.first().image
|
if image_model is not None:
|
||||||
|
return image_model.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def restaurant_category_indexing(self):
|
def restaurant_category_indexing(self):
|
||||||
|
|
@ -786,6 +798,10 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def artisan_category_indexing(self):
|
def artisan_category_indexing(self):
|
||||||
return self.tags.filter(category__index_name='shop_category')
|
return self.tags.filter(category__index_name='shop_category')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def distillery_type_indexing(self):
|
||||||
|
return self.tags.filter(category__index_name='distillery_type')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_comment(self):
|
def last_comment(self):
|
||||||
if hasattr(self, 'comments_prefetched') and len(self.comments_prefetched):
|
if hasattr(self, 'comments_prefetched') and len(self.comments_prefetched):
|
||||||
|
|
@ -856,11 +872,6 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
metadata.append(category_tags)
|
metadata.append(category_tags)
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
@property
|
|
||||||
def distillery_types(self):
|
|
||||||
"""Tags from tag category - distillery_type."""
|
|
||||||
return self.tags.filter(category__index_name='distillery_type')
|
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentNoteQuerySet(models.QuerySet):
|
class EstablishmentNoteQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model EstablishmentNote."""
|
"""QuerySet for model EstablishmentNote."""
|
||||||
|
|
@ -976,16 +987,97 @@ class EmployeeQuerySet(models.QuerySet):
|
||||||
]
|
]
|
||||||
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))
|
||||||
|
|
||||||
|
def trigram_search(self, search_value: str):
|
||||||
|
"""Search with mistakes by name or last name."""
|
||||||
|
return self.annotate(
|
||||||
|
search_exact_match=models.Case(
|
||||||
|
models.When(Q(name__iexact=search_value) | Q(last_name__iexact=search_value),
|
||||||
|
then=100),
|
||||||
|
default=0,
|
||||||
|
output_field=models.FloatField()
|
||||||
|
),
|
||||||
|
search_contains_match=models.Case(
|
||||||
|
models.When(Q(name__icontains=search_value) | Q(last_name__icontains=search_value),
|
||||||
|
then=50),
|
||||||
|
default=0,
|
||||||
|
output_field=models.FloatField()
|
||||||
|
),
|
||||||
|
search_name_similarity=models.Case(
|
||||||
|
models.When(
|
||||||
|
Q(name__isnull=False),
|
||||||
|
then=TrigramSimilarity('name', search_value.lower())
|
||||||
|
),
|
||||||
|
default=0,
|
||||||
|
output_field=models.FloatField()
|
||||||
|
),
|
||||||
|
search_last_name_similarity=models.Case(
|
||||||
|
models.When(
|
||||||
|
Q(last_name__isnull=False),
|
||||||
|
then=TrigramSimilarity('last_name', search_value.lower())
|
||||||
|
),
|
||||||
|
default=0,
|
||||||
|
output_field=models.FloatField()
|
||||||
|
),
|
||||||
|
relevance=(F('search_name_similarity') + F('search_exact_match')
|
||||||
|
+ F('search_contains_match') + F('search_last_name_similarity'))
|
||||||
|
).filter(relevance__gte=0.3).order_by('-relevance')
|
||||||
|
|
||||||
def search_by_name_or_last_name(self, value):
|
def search_by_name_or_last_name(self, value):
|
||||||
"""Search by name or last_name."""
|
"""Search by name or last_name."""
|
||||||
return self._generic_search(value, ['name', 'last_name'])
|
return self._generic_search(value, ['name', 'last_name'])
|
||||||
|
|
||||||
|
def search_by_actual_employee(self):
|
||||||
|
"""Search by actual employee."""
|
||||||
|
return self.filter(
|
||||||
|
Q(establishmentemployee__from_date__lte=datetime.now()),
|
||||||
|
Q(establishmentemployee__to_date__gte=datetime.now()) |
|
||||||
|
Q(establishmentemployee__to_date__isnull=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_position_id(self, value):
|
||||||
|
"""Search by position_id."""
|
||||||
|
return self.filter(
|
||||||
|
Q(establishmentemployee__position_id=value),
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_public_mark(self, value):
|
||||||
|
"""Search by establishment public_mark."""
|
||||||
|
return self.filter(
|
||||||
|
Q(establishmentemployee__establishment__public_mark=value),
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_toque_number(self, value):
|
||||||
|
"""Search by establishment toque_number."""
|
||||||
|
return self.filter(
|
||||||
|
Q(establishmentemployee__establishment__toque_number=value),
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_username_or_name(self, value):
|
||||||
|
"""Search by username or name."""
|
||||||
|
return self.search_by_actual_employee().filter(
|
||||||
|
Q(user__username__icontains=value) |
|
||||||
|
Q(user__first_name__icontains=value) |
|
||||||
|
Q(user__last_name__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
def actual_establishment(self):
|
def actual_establishment(self):
|
||||||
e = EstablishmentEmployee.objects.actual().filter(employee=self)
|
e = EstablishmentEmployee.objects.actual().filter(employee=self)
|
||||||
return self.prefetch_related(models.Prefetch('establishmentemployee_set',
|
return self.prefetch_related(models.Prefetch('establishmentemployee_set',
|
||||||
queryset=EstablishmentEmployee.objects.actual()
|
queryset=EstablishmentEmployee.objects.actual()
|
||||||
)).all().distinct()
|
)).all().distinct()
|
||||||
|
|
||||||
|
def with_extended_related(self):
|
||||||
|
return self.prefetch_related('establishments')
|
||||||
|
|
||||||
|
def with_back_office_related(self):
|
||||||
|
return self.prefetch_related(
|
||||||
|
Prefetch('establishmentemployee_set',
|
||||||
|
queryset=EstablishmentEmployee.objects.actual()
|
||||||
|
.prefetch_related('establishment', 'position').order_by('-from_date'),
|
||||||
|
to_attr='prefetched_establishment_employee'),
|
||||||
|
'awards'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Employee(BaseAttributes):
|
class Employee(BaseAttributes):
|
||||||
"""Employee model."""
|
"""Employee model."""
|
||||||
|
|
@ -1021,6 +1113,11 @@ class Employee(BaseAttributes):
|
||||||
verbose_name=_('Tags'))
|
verbose_name=_('Tags'))
|
||||||
# old_id = profile_id
|
# old_id = profile_id
|
||||||
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
||||||
|
available_for_events = models.BooleanField(_('Available for events'), default=False)
|
||||||
|
photo = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
related_name='employee_photo',
|
||||||
|
verbose_name=_('image instance of model Image'))
|
||||||
|
|
||||||
objects = EmployeeQuerySet.as_manager()
|
objects = EmployeeQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -1029,6 +1126,34 @@ class Employee(BaseAttributes):
|
||||||
|
|
||||||
verbose_name = _('Employee')
|
verbose_name = _('Employee')
|
||||||
verbose_name_plural = _('Employees')
|
verbose_name_plural = _('Employees')
|
||||||
|
indexes = [
|
||||||
|
GinIndex(fields=('name',)),
|
||||||
|
GinIndex(fields=('last_name',))
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_object(self):
|
||||||
|
"""Return image object."""
|
||||||
|
return self.photo.image if self.photo else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def crop_image(self):
|
||||||
|
if hasattr(self, 'photo') and hasattr(self, '_meta'):
|
||||||
|
if self.photo:
|
||||||
|
image_property = {
|
||||||
|
'id': self.photo.id,
|
||||||
|
'title': self.photo.title,
|
||||||
|
'original_url': self.photo.image.url,
|
||||||
|
'orientation_display': self.photo.get_orientation_display(),
|
||||||
|
'auto_crop_images': {},
|
||||||
|
}
|
||||||
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
||||||
|
if p.startswith(self._meta.model_name.lower())]
|
||||||
|
for crop in crop_parameters:
|
||||||
|
image_property['auto_crop_images'].update(
|
||||||
|
{crop: self.photo.get_image_url(crop)}
|
||||||
|
)
|
||||||
|
return image_property
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentScheduleQuerySet(models.QuerySet):
|
class EstablishmentScheduleQuerySet(models.QuerySet):
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from django.db.models import F
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from account.serializers.common import UserShortSerializer
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from establishment import serializers as model_serializers
|
from establishment import serializers as model_serializers
|
||||||
|
from establishment.models import ContactPhone, EstablishmentEmployee
|
||||||
|
from gallery.models import Image
|
||||||
|
from location.models import Address
|
||||||
from location.serializers import AddressDetailSerializer, TranslatedField
|
from location.serializers import AddressDetailSerializer, TranslatedField
|
||||||
from main.models import Currency
|
from main.models import Currency
|
||||||
from location.models import Address
|
|
||||||
from main.serializers import AwardSerializer
|
from main.serializers import AwardSerializer
|
||||||
from utils.decorators import with_base_attributes
|
from utils.decorators import with_base_attributes
|
||||||
from utils.serializers import TimeZoneChoiceField
|
from utils.serializers import TimeZoneChoiceField, ImageBaseSerializer
|
||||||
from gallery.models import Image
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from account.serializers.common import UserShortSerializer
|
def phones_handler(phones_list, establishment):
|
||||||
|
"""
|
||||||
|
create or update phones for establishment
|
||||||
|
"""
|
||||||
|
ContactPhone.objects.filter(establishment=establishment).delete()
|
||||||
|
|
||||||
|
for new_phone in phones_list:
|
||||||
|
ContactPhone.objects.create(establishment=establishment, phone=new_phone)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
|
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
|
|
@ -26,8 +40,6 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
queryset=models.Address.objects.all(),
|
queryset=models.Address.objects.all(),
|
||||||
write_only=True
|
write_only=True
|
||||||
)
|
)
|
||||||
phones = model_serializers.ContactPhonesSerializer(read_only=True,
|
|
||||||
many=True, )
|
|
||||||
emails = model_serializers.ContactEmailsSerializer(read_only=True,
|
emails = model_serializers.ContactEmailsSerializer(read_only=True,
|
||||||
many=True, )
|
many=True, )
|
||||||
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
|
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
|
||||||
|
|
@ -37,6 +49,13 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
address_id = serializers.PrimaryKeyRelatedField(write_only=True, source='address',
|
address_id = serializers.PrimaryKeyRelatedField(write_only=True, source='address',
|
||||||
queryset=Address.objects.all())
|
queryset=Address.objects.all())
|
||||||
tz = TimeZoneChoiceField()
|
tz = TimeZoneChoiceField()
|
||||||
|
phones = serializers.ListField(
|
||||||
|
source='contact_phones',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
|
|
@ -64,6 +83,15 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria
|
||||||
'address_id',
|
'address_id',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
phones_list = []
|
||||||
|
if 'contact_phones' in validated_data:
|
||||||
|
phones_list = validated_data.pop('contact_phones')
|
||||||
|
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
phones_handler(phones_list, instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
"""Establishment create serializer"""
|
"""Establishment create serializer"""
|
||||||
|
|
@ -73,18 +101,24 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
queryset=models.EstablishmentType.objects.all(), write_only=True
|
queryset=models.EstablishmentType.objects.all(), write_only=True
|
||||||
)
|
)
|
||||||
address = AddressDetailSerializer()
|
address = AddressDetailSerializer()
|
||||||
phones = model_serializers.ContactPhonesSerializer(read_only=False,
|
|
||||||
many=True, )
|
|
||||||
emails = model_serializers.ContactEmailsSerializer(read_only=False,
|
emails = model_serializers.ContactEmailsSerializer(read_only=False,
|
||||||
many=True, )
|
many=True, )
|
||||||
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
|
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=False,
|
||||||
many=True, )
|
many=True, )
|
||||||
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
|
type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type')
|
||||||
|
phones = serializers.ListField(
|
||||||
|
source='contact_phones',
|
||||||
|
allow_null=True,
|
||||||
|
allow_empty=True,
|
||||||
|
child=serializers.CharField(max_length=128),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Establishment
|
model = models.Establishment
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
|
'slug',
|
||||||
'name',
|
'name',
|
||||||
'website',
|
'website',
|
||||||
'phones',
|
'phones',
|
||||||
|
|
@ -101,6 +135,15 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
phones_list = []
|
||||||
|
if 'contact_phones' in validated_data:
|
||||||
|
phones_list = validated_data.pop('contact_phones')
|
||||||
|
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
phones_handler(phones_list, instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class SocialChoiceSerializers(serializers.ModelSerializer):
|
class SocialChoiceSerializers(serializers.ModelSerializer):
|
||||||
"""SocialChoice serializers."""
|
"""SocialChoice serializers."""
|
||||||
|
|
@ -166,7 +209,6 @@ class ContactEmailBackSerializers(model_serializers.PlateSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PositionBackSerializer(serializers.ModelSerializer):
|
class PositionBackSerializer(serializers.ModelSerializer):
|
||||||
"""Position Back serializer."""
|
"""Position Back serializer."""
|
||||||
|
|
||||||
|
|
@ -181,6 +223,7 @@ class PositionBackSerializer(serializers.ModelSerializer):
|
||||||
'index_name',
|
'index_name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# TODO: test decorator
|
# TODO: test decorator
|
||||||
@with_base_attributes
|
@with_base_attributes
|
||||||
class EmployeeBackSerializers(serializers.ModelSerializer):
|
class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
|
|
@ -189,25 +232,52 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
positions = serializers.SerializerMethodField()
|
positions = serializers.SerializerMethodField()
|
||||||
establishment = serializers.SerializerMethodField()
|
establishment = serializers.SerializerMethodField()
|
||||||
awards = AwardSerializer(many=True, read_only=True)
|
awards = AwardSerializer(many=True, read_only=True)
|
||||||
|
toque_number = serializers.SerializerMethodField()
|
||||||
|
photo = ImageBaseSerializer(source='crop_image', read_only=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@lru_cache(maxsize=32)
|
||||||
|
def get_qs(obj):
|
||||||
|
return obj.establishmentemployee_set.actual().annotate(
|
||||||
|
public_mark=F('establishment__public_mark'),
|
||||||
|
est_id=F('establishment__id'),
|
||||||
|
est_slug=F('establishment__slug'),
|
||||||
|
toque_number=F('establishment__toque_number'),
|
||||||
|
).order_by('-from_date').first()
|
||||||
|
|
||||||
def get_public_mark(self, obj):
|
def get_public_mark(self, obj):
|
||||||
"""Get last list actual public_mark"""
|
"""Get last list actual public_mark"""
|
||||||
qs = obj.establishmentemployee_set.actual().order_by('-from_date')\
|
if hasattr(obj, 'prefetched_establishment_employee'):
|
||||||
.values('establishment__public_mark').first()
|
return obj.prefetched_establishment_employee[0].establishment.public_mark if len(
|
||||||
return qs['establishment__public_mark'] if qs else None
|
obj.prefetched_establishment_employee) else None
|
||||||
|
qs = self.get_qs(obj)
|
||||||
|
if qs:
|
||||||
|
return qs.public_mark
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_toque_number(self, obj):
|
||||||
|
if hasattr(obj, 'prefetched_establishment_employee'):
|
||||||
|
return obj.prefetched_establishment_employee[0].establishment.toque_number if len(
|
||||||
|
obj.prefetched_establishment_employee) else None
|
||||||
|
qs = self.get_qs(obj)
|
||||||
|
if qs:
|
||||||
|
return qs.toque_number
|
||||||
|
return None
|
||||||
|
|
||||||
def get_positions(self, obj):
|
def get_positions(self, obj):
|
||||||
"""Get last list actual positions"""
|
"""Get last list actual positions"""
|
||||||
est_id = obj.establishmentemployee_set.actual().\
|
if hasattr(obj, 'prefetched_establishment_employee'):
|
||||||
order_by('-from_date').first()
|
if not len(obj.prefetched_establishment_employee):
|
||||||
|
return []
|
||||||
|
return [PositionBackSerializer(ee.position).data for ee in obj.prefetched_establishment_employee]
|
||||||
|
|
||||||
|
est_id = self.get_qs(obj)
|
||||||
|
|
||||||
if not est_id:
|
if not est_id:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
qs = obj.establishmentemployee_set.actual()\
|
qs = obj.establishmentemployee_set.actual() \
|
||||||
.filter(establishment_id=est_id.establishment_id)\
|
.filter(establishment_id=est_id.establishment_id) \
|
||||||
.prefetch_related('position').values('position')
|
.prefetch_related('position').values('position')
|
||||||
|
|
||||||
positions = models.Position.objects.filter(id__in=[q['position'] for q in qs])
|
positions = models.Position.objects.filter(id__in=[q['position'] for q in qs])
|
||||||
|
|
@ -216,15 +286,19 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
|
|
||||||
def get_establishment(self, obj):
|
def get_establishment(self, obj):
|
||||||
"""Get last actual establishment"""
|
"""Get last actual establishment"""
|
||||||
est = obj.establishmentemployee_set.actual().order_by('-from_date')\
|
if hasattr(obj, 'prefetched_establishment_employee'):
|
||||||
.first()
|
return {
|
||||||
|
'id': obj.prefetched_establishment_employee[0].establishment.pk,
|
||||||
|
'slug': obj.prefetched_establishment_employee[0].establishment.slug,
|
||||||
|
} if len(obj.prefetched_establishment_employee) else None
|
||||||
|
est = self.get_qs(obj)
|
||||||
|
|
||||||
if not est:
|
if not est:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": est.establishment.id,
|
"id": est.est_id,
|
||||||
"slug": est.establishment.slug
|
"slug": est.est_slug
|
||||||
}
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -242,7 +316,9 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
|
||||||
'birth_date',
|
'birth_date',
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'toque_number'
|
'toque_number',
|
||||||
|
'available_for_events',
|
||||||
|
'photo',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -264,6 +340,53 @@ class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstEmployeeBackSerializer(EmployeeBackSerializers):
|
||||||
|
@property
|
||||||
|
def request_kwargs(self):
|
||||||
|
"""Get url kwargs from request."""
|
||||||
|
return self.context.get('request').parser_context.get('kwargs')
|
||||||
|
|
||||||
|
def get_positions(self, obj):
|
||||||
|
establishment_id = self.request_kwargs.get('establishment_id')
|
||||||
|
es_emp = EstablishmentEmployee.objects.filter(
|
||||||
|
employee=obj,
|
||||||
|
establishment_id=establishment_id,
|
||||||
|
).distinct().order_by('position_id')
|
||||||
|
result = []
|
||||||
|
for item in es_emp:
|
||||||
|
result.append({
|
||||||
|
'id': item.id,
|
||||||
|
'from_date': item.from_date,
|
||||||
|
'to_date': item.to_date,
|
||||||
|
'status': item.status,
|
||||||
|
'position_id': item.position_id,
|
||||||
|
'position_priority': item.position.priority,
|
||||||
|
'position_index_name': item.position.index_name,
|
||||||
|
'position_name_translated': item.position.name_translated,
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Employee
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'last_name',
|
||||||
|
'user',
|
||||||
|
'public_mark',
|
||||||
|
'positions',
|
||||||
|
'awards',
|
||||||
|
'sex',
|
||||||
|
'birth_date',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'toque_number',
|
||||||
|
'available_for_events',
|
||||||
|
'photo',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer):
|
||||||
"""Serializer class for model EstablishmentGallery."""
|
"""Serializer class for model EstablishmentGallery."""
|
||||||
|
|
||||||
|
|
@ -380,6 +503,7 @@ class EstablishmentNoteListCreateSerializer(EstablishmentNoteBaseSerializer):
|
||||||
|
|
||||||
class EstablishmentAdminListSerializer(UserShortSerializer):
|
class EstablishmentAdminListSerializer(UserShortSerializer):
|
||||||
"""Establishment admin serializer."""
|
"""Establishment admin serializer."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserShortSerializer.Meta.model
|
model = UserShortSerializer.Meta.model
|
||||||
fields = [
|
fields = [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
"""Establishment serializers."""
|
"""Establishment serializers."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
|
from phonenumber_field.phonenumber import to_python as str_to_phonenumber
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
@ -6,8 +9,10 @@ from rest_framework import serializers
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from comment.serializers import common as comment_serializers
|
from comment.serializers import common as comment_serializers
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer, \
|
from location.serializers import AddressBaseSerializer, CityBaseSerializer, AddressDetailSerializer, \
|
||||||
CityShortSerializer
|
CityShortSerializer
|
||||||
|
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
||||||
|
EstablishmentWineOriginBaseSerializer
|
||||||
from main.serializers import AwardSerializer, CurrencySerializer
|
from main.serializers import AwardSerializer, CurrencySerializer
|
||||||
from review.serializers import ReviewShortSerializer
|
from review.serializers import ReviewShortSerializer
|
||||||
from tag.serializers import TagBaseSerializer
|
from tag.serializers import TagBaseSerializer
|
||||||
|
|
@ -16,9 +21,8 @@ from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
|
||||||
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
from utils.serializers import (ProjectModelSerializer, TranslatedField,
|
||||||
FavoritesCreateSerializer)
|
FavoritesCreateSerializer)
|
||||||
from location.serializers import EstablishmentWineRegionBaseSerializer, \
|
|
||||||
EstablishmentWineOriginBaseSerializer
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||||
"""Contact phone serializer"""
|
"""Contact phone serializer"""
|
||||||
|
|
@ -198,7 +202,7 @@ class EstablishmentEmployeeCreateSerializer(serializers.ModelSerializer):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
model = models.EstablishmentEmployee
|
model = models.EstablishmentEmployee
|
||||||
fields = ('id',)
|
fields = ('id', 'from_date', 'to_date')
|
||||||
|
|
||||||
def _validate_entity(self, entity_id_param: str, entity_class):
|
def _validate_entity(self, entity_id_param: str, entity_class):
|
||||||
entity_id = self.context.get('request').parser_context.get('kwargs').get(entity_id_param)
|
entity_id = self.context.get('request').parser_context.get('kwargs').get(entity_id_param)
|
||||||
|
|
@ -231,7 +235,7 @@ class EstablishmentEmployeeCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class EstablishmentShortSerializer(serializers.ModelSerializer):
|
class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
"""Short serializer for establishment."""
|
"""Short serializer for establishment."""
|
||||||
city = CitySerializer(source='address.city', allow_null=True)
|
city = CityBaseSerializer(source='address.city', allow_null=True)
|
||||||
establishment_type = EstablishmentTypeGeoSerializer()
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
||||||
currency = CurrencySerializer(read_only=True)
|
currency = CurrencySerializer(read_only=True)
|
||||||
|
|
@ -253,10 +257,9 @@ class EstablishmentShortSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class _EstablishmentAddressShortSerializer(serializers.ModelSerializer):
|
class _EstablishmentAddressShortSerializer(serializers.ModelSerializer):
|
||||||
"""Short serializer for establishment."""
|
"""Short serializer for establishment."""
|
||||||
city = CitySerializer(source='address.city', allow_null=True)
|
city = CityBaseSerializer(source='address.city', allow_null=True)
|
||||||
establishment_type = EstablishmentTypeGeoSerializer()
|
establishment_type = EstablishmentTypeGeoSerializer()
|
||||||
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
establishment_subtypes = EstablishmentSubTypeBaseSerializer(many=True)
|
||||||
currency = CurrencySerializer(read_only=True)
|
|
||||||
address = AddressBaseSerializer(read_only=True)
|
address = AddressBaseSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -320,15 +323,45 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
currency = CurrencySerializer()
|
currency = CurrencySerializer()
|
||||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||||
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||||
image = serializers.URLField(source='image_url', read_only=True)
|
image = serializers.SerializerMethodField(read_only=True)
|
||||||
wine_regions = EstablishmentWineRegionBaseSerializer(many=True, source='wine_origins_unique',
|
wine_regions = EstablishmentWineRegionBaseSerializer(many=True, source='wine_origins_unique',
|
||||||
read_only=True, allow_null=True)
|
read_only=True, allow_null=True)
|
||||||
preview_image = serializers.URLField(source='preview_image_url',
|
preview_image = serializers.URLField(source='preview_image_url',
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
tz = serializers.CharField(read_only=True, source='timezone_as_str')
|
tz = serializers.CharField(read_only=True, source='timezone_as_str')
|
||||||
new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True)
|
new_image = serializers.SerializerMethodField(allow_null=True, read_only=True)
|
||||||
distillery_types = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
distillery_type = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
|
||||||
|
def get_image(self, obj):
|
||||||
|
if obj.main_image:
|
||||||
|
return obj.main_image[0].image.image.url if len(obj.main_image) else None
|
||||||
|
logging.info('Possibly not optimal image reading')
|
||||||
|
return obj._main_image # backwards compatibility
|
||||||
|
|
||||||
|
def get_new_image(self, obj):
|
||||||
|
if hasattr(self, 'main_image') and hasattr(self, '_meta'):
|
||||||
|
if obj.main_image and len(obj.main_image):
|
||||||
|
main_image = obj.main_image[0].image
|
||||||
|
else:
|
||||||
|
logging.info('Possibly not optimal image reading')
|
||||||
|
main_image = obj._main_image
|
||||||
|
if main_image:
|
||||||
|
image = main_image
|
||||||
|
image_property = {
|
||||||
|
'id': image.id,
|
||||||
|
'title': image.title,
|
||||||
|
'original_url': image.image.url,
|
||||||
|
'orientation_display': image.get_orientation_display(),
|
||||||
|
'auto_crop_images': {},
|
||||||
|
}
|
||||||
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
||||||
|
if p.startswith(self._meta.model_name.lower())]
|
||||||
|
for crop in crop_parameters:
|
||||||
|
image_property['auto_crop_images'].update(
|
||||||
|
{crop: image.get_image_url(crop)}
|
||||||
|
)
|
||||||
|
return image_property
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -354,7 +387,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||||
'new_image',
|
'new_image',
|
||||||
'tz',
|
'tz',
|
||||||
'wine_regions',
|
'wine_regions',
|
||||||
'distillery_types',
|
'distillery_type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -366,6 +399,7 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
|
||||||
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
distillery_type = TagBaseSerializer(read_only=True, many=True, allow_null=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -375,6 +409,7 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer):
|
||||||
'restaurant_category',
|
'restaurant_category',
|
||||||
'restaurant_cuisine',
|
'restaurant_cuisine',
|
||||||
'artisan_category',
|
'artisan_category',
|
||||||
|
'distillery_type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -449,7 +484,7 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
||||||
class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer):
|
class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer):
|
||||||
"""Serializer for Establishment model for mobiles."""
|
"""Serializer for Establishment model for mobiles."""
|
||||||
|
|
||||||
last_comment = comment_serializers.CommentRUDSerializer(allow_null=True)
|
last_comment = comment_serializers.CommentBaseSerializer(allow_null=True)
|
||||||
|
|
||||||
class Meta(EstablishmentDetailSerializer.Meta):
|
class Meta(EstablishmentDetailSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -468,6 +503,7 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
artisan_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
restaurant_category = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
restaurant_cuisine = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
distillery_type = TagBaseSerializer(many=True, allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta(EstablishmentBaseSerializer.Meta):
|
class Meta(EstablishmentBaseSerializer.Meta):
|
||||||
fields = EstablishmentBaseSerializer.Meta.fields + [
|
fields = EstablishmentBaseSerializer.Meta.fields + [
|
||||||
|
|
@ -476,16 +512,15 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer):
|
||||||
'artisan_category',
|
'artisan_category',
|
||||||
'restaurant_category',
|
'restaurant_category',
|
||||||
'restaurant_cuisine',
|
'restaurant_cuisine',
|
||||||
|
'distillery_type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentBaseSerializer(comment_serializers.CommentBaseSerializer):
|
||||||
"""Create comment serializer"""
|
"""Create comment serializer"""
|
||||||
mark = serializers.IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta(comment_serializers.CommentBaseSerializer.Meta):
|
||||||
"""Serializer for model Comment"""
|
"""Serializer for model Comment"""
|
||||||
model = comment_models.Comment
|
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'created',
|
'created',
|
||||||
|
|
@ -493,8 +528,14 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
|
||||||
'mark',
|
'mark',
|
||||||
'nickname',
|
'nickname',
|
||||||
'profile_pic',
|
'profile_pic',
|
||||||
|
'status',
|
||||||
|
'status_display',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstablishmentCommentCreateSerializer(EstablishmentCommentBaseSerializer):
|
||||||
|
"""Extended EstablishmentCommentBaseSerializer."""
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
# Check establishment object
|
# Check establishment object
|
||||||
|
|
@ -514,7 +555,7 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer):
|
class EstablishmentCommentRUDSerializer(comment_serializers.CommentBaseSerializer):
|
||||||
"""Retrieve/Update/Destroy comment serializer."""
|
"""Retrieve/Update/Destroy comment serializer."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
"""Establishment app tasks."""
|
"""Establishment app tasks."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
from celery.task import periodic_task
|
from celery.task import periodic_task
|
||||||
|
|
||||||
from django.core import management
|
|
||||||
from django_elasticsearch_dsl.management.commands import search_index
|
|
||||||
|
|
||||||
from django_elasticsearch_dsl.registries import registry
|
from django_elasticsearch_dsl.registries import registry
|
||||||
|
|
||||||
from establishment import models
|
from establishment import models
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from search_indexes.documents.establishment import EstablishmentDocument
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -28,6 +25,7 @@ def recalculate_price_levels_by_country(country_id):
|
||||||
establishment.recalculate_price_level(low_price=country.low_price,
|
establishment.recalculate_price_level(low_price=country.low_price,
|
||||||
high_price=country.high_price)
|
high_price=country.high_price)
|
||||||
|
|
||||||
|
|
||||||
# @periodic_task(run_every=crontab(minute=59))
|
# @periodic_task(run_every=crontab(minute=59))
|
||||||
# def rebuild_establishment_indices():
|
# def rebuild_establishment_indices():
|
||||||
# management.call_command(search_index.Command(), action='populate', models=[models.Establishment.__name__],
|
# management.call_command(search_index.Command(), action='populate', models=[models.Establishment.__name__],
|
||||||
|
|
@ -50,3 +48,47 @@ def recalculation_public_mark(establishment_id):
|
||||||
establishment = models.Establishment.objects.get(id=establishment_id)
|
establishment = models.Establishment.objects.get(id=establishment_id)
|
||||||
establishment.recalculate_public_mark()
|
establishment.recalculate_public_mark()
|
||||||
establishment.recalculate_toque_number()
|
establishment.recalculate_toque_number()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def update_establishment_image_urls(part_number: int, summary_tasks: int, bucket_ids: list):
|
||||||
|
queryset = models.Establishment.objects.filter(id__in=bucket_ids)
|
||||||
|
|
||||||
|
for establishment in queryset:
|
||||||
|
live_link = None
|
||||||
|
|
||||||
|
image_urls = [
|
||||||
|
('image_url', establishment.image_url),
|
||||||
|
('preview_image_url', establishment.preview_image_url)
|
||||||
|
]
|
||||||
|
|
||||||
|
for data in image_urls:
|
||||||
|
attr, url = data
|
||||||
|
|
||||||
|
if establishment.image_url is not None:
|
||||||
|
try:
|
||||||
|
response = requests.get(url, allow_redirects=True)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
setattr(establishment, attr, None)
|
||||||
|
|
||||||
|
else:
|
||||||
|
live_link = url
|
||||||
|
|
||||||
|
except (
|
||||||
|
requests.exceptions.ConnectionError,
|
||||||
|
requests.exceptions.ConnectTimeout
|
||||||
|
):
|
||||||
|
setattr(establishment, attr, None)
|
||||||
|
|
||||||
|
if live_link is not None:
|
||||||
|
if establishment.image_url is None:
|
||||||
|
establishment.image_url = live_link
|
||||||
|
|
||||||
|
elif establishment.preview_image_url is None:
|
||||||
|
establishment.preview_image_url = live_link
|
||||||
|
|
||||||
|
establishment.save()
|
||||||
|
|
||||||
|
logger.info(f'The {part_number}th part of the image update '
|
||||||
|
f'from {summary_tasks} parts was completed')
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ urlpatterns = [
|
||||||
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
|
path('<int:establishment_id>/employees/', views.EstablishmentEmployeeListView.as_view(),
|
||||||
name='establishment-employees'),
|
name='establishment-employees'),
|
||||||
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
||||||
|
path('employees/search/', views.EmployeesListSearchViews.as_view(), name='employees-search'),
|
||||||
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
||||||
path('<int:establishment_id>/employee/<int:employee_id>/position/<int:position_id>',
|
path('<int:establishment_id>/employee/<int:employee_id>/position/<int:position_id>',
|
||||||
views.EstablishmentEmployeeCreateView.as_view(),
|
views.EstablishmentEmployeeCreateView.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, permissions, status
|
from rest_framework import generics, permissions, status, filters as rest_filters
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
|
|
@ -34,7 +34,10 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
||||||
|
|
||||||
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all().prefetch_related(
|
||||||
|
'establishmentemployee_set',
|
||||||
|
'establishmentemployee_set__establishment',
|
||||||
|
)
|
||||||
serializer_class = serializers.EstablishmentRUDSerializer
|
serializer_class = serializers.EstablishmentRUDSerializer
|
||||||
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager]
|
||||||
|
|
||||||
|
|
@ -171,49 +174,61 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
filter_class = filters.EmployeeBackFilter
|
filter_class = filters.EmployeeBackFilter
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
queryset = models.Employee.objects.all()
|
queryset = models.Employee.objects.all().with_back_office_related()
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeesListSearchViews(generics.ListAPIView):
|
||||||
|
"""Employee search view"""
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
queryset = models.Employee.objects.all().with_back_office_related().select_related('photo')
|
||||||
|
filter_class = filters.EmployeeBackSearchFilter
|
||||||
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentEmployeeListView(generics.ListCreateAPIView):
|
class EstablishmentEmployeeListView(generics.ListCreateAPIView):
|
||||||
"""Establishment emplyoees list view."""
|
"""Establishment emplyoees list view."""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.EstablishmentEmployeeBackSerializer
|
serializer_class = serializers.EstEmployeeBackSerializer
|
||||||
|
pagination_class = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
establishment_id = self.kwargs['establishment_id']
|
establishment_id = self.kwargs['establishment_id']
|
||||||
return models.EstablishmentEmployee.objects.filter(establishment__id=establishment_id)
|
return models.Employee.objects.filter(
|
||||||
|
establishmentemployee__establishment_id=establishment_id,
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Employee RUD view."""
|
"""Employee RUD view."""
|
||||||
serializer_class = serializers.EmployeeBackSerializers
|
serializer_class = serializers.EmployeeBackSerializers
|
||||||
queryset = models.Employee.objects.all()
|
queryset = models.Employee.objects.all().with_back_office_related()
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeListCreateView(generics.ListCreateAPIView):
|
class EstablishmentTypeListCreateView(generics.ListCreateAPIView):
|
||||||
"""Establishment type list/create view."""
|
"""Establishment type list/create view."""
|
||||||
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||||
queryset = models.EstablishmentType.objects.all()
|
queryset = models.EstablishmentType.objects.all().select_related('default_image')
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment type retrieve/update/destroy view."""
|
"""Establishment type retrieve/update/destroy view."""
|
||||||
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||||
queryset = models.EstablishmentType.objects.all()
|
queryset = models.EstablishmentType.objects.all().select_related('default_image')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView):
|
class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView):
|
||||||
"""Establishment subtype list/create view."""
|
"""Establishment subtype list/create view."""
|
||||||
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||||
queryset = models.EstablishmentSubType.objects.all()
|
queryset = models.EstablishmentSubType.objects.all().select_related('default_image')
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Establishment subtype retrieve/update/destroy view."""
|
"""Establishment subtype retrieve/update/destroy view."""
|
||||||
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||||
queryset = models.EstablishmentSubType.objects.all()
|
queryset = models.EstablishmentSubType.objects.all().select_related('default_image')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews,
|
||||||
|
|
@ -385,7 +400,7 @@ class EstablishmentPositionListView(generics.ListAPIView):
|
||||||
class EstablishmentAdminView(generics.ListAPIView):
|
class EstablishmentAdminView(generics.ListAPIView):
|
||||||
"""Establishment admin list view."""
|
"""Establishment admin list view."""
|
||||||
serializer_class = serializers.EstablishmentAdminListSerializer
|
serializer_class = serializers.EstablishmentAdminListSerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
establishment = get_object_or_404(
|
establishment = get_object_or_404(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from comment import models as comment_models
|
from comment import models as comment_models
|
||||||
from comment.serializers import CommentRUDSerializer
|
|
||||||
from establishment import filters, models, serializers
|
from establishment import filters, models, serializers
|
||||||
from main import methods
|
from main import methods
|
||||||
from utils.pagination import PortionPagination
|
from utils.pagination import PortionPagination
|
||||||
|
|
@ -38,7 +37,8 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||||
.with_extended_address_related().with_currency_related() \
|
.with_extended_address_related().with_currency_related() \
|
||||||
.with_certain_tag_category_related('category', 'restaurant_category') \
|
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||||
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
.with_certain_tag_category_related('shop_category', 'artisan_category') \
|
||||||
|
.with_certain_tag_category_related('distillery_type', 'distillery_type')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSimilarView(EstablishmentListView):
|
class EstablishmentSimilarView(EstablishmentListView):
|
||||||
|
|
@ -67,7 +67,8 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView
|
||||||
serializer_class = serializers.EstablishmentDetailSerializer
|
serializer_class = serializers.EstablishmentDetailSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().with_extended_related()
|
return super().get_queryset().with_extended_related() \
|
||||||
|
.with_certain_tag_category_related('distillery_type', 'distillery_type')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
|
class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
|
||||||
|
|
@ -186,18 +187,17 @@ class EstablishmentCommentListView(generics.ListAPIView):
|
||||||
"""View for return list of establishment comments."""
|
"""View for return list of establishment comments."""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.EstablishmentCommentCreateSerializer
|
serializer_class = serializers.EstablishmentCommentBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
|
|
||||||
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
|
||||||
return establishment.comments.order_by('-created')
|
return establishment.comments.public(self.request.user).order_by('-created')
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""View for retrieve/update/destroy establishment comment."""
|
"""View for retrieve/update/destroy establishment comment."""
|
||||||
serializer_class = CommentRUDSerializer
|
serializer_class = serializers.EstablishmentCommentBaseSerializer
|
||||||
queryset = models.Establishment.objects.all()
|
queryset = models.Establishment.objects.all()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ class FavoritesEstablishmentListView(generics.ListAPIView):
|
||||||
.order_by('-favorites').with_base_related() \
|
.order_by('-favorites').with_base_related() \
|
||||||
.with_certain_tag_category_related('category', 'restaurant_category') \
|
.with_certain_tag_category_related('category', 'restaurant_category') \
|
||||||
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
.with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
|
||||||
.with_certain_tag_category_related('shop_category', 'artisan_category')
|
.with_certain_tag_category_related('shop_category', 'artisan_category') \
|
||||||
|
.with_certain_tag_category_related('distillery_type', 'distillery_type')
|
||||||
|
|
||||||
|
|
||||||
class FavoritesProductListView(generics.ListAPIView):
|
class FavoritesProductListView(generics.ListAPIView):
|
||||||
|
|
|
||||||
20
apps/location/migrations/0034_city_image.py
Normal file
20
apps/location/migrations/0034_city_image.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 09:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0008_merge_20191212_0752'),
|
||||||
|
('location', '0033_merge_20191224_0920'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='city',
|
||||||
|
name='image',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='city_image', to='gallery.Image', verbose_name='image instance of model Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
apps/location/migrations/0035_auto_20200115_1117.py
Normal file
20
apps/location/migrations/0035_auto_20200115_1117.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 11:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('location', '0034_city_image'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='city',
|
||||||
|
name='gallery',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='CityGallery',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -75,7 +75,6 @@ class Country(TranslatedFieldsMixin,
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RegionQuerySet(models.QuerySet):
|
class RegionQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model Region."""
|
"""QuerySet for model Region."""
|
||||||
|
|
||||||
|
|
@ -144,7 +143,7 @@ class CityQuerySet(models.QuerySet):
|
||||||
return self.filter(country__code=code)
|
return self.filter(country__code=code)
|
||||||
|
|
||||||
|
|
||||||
class City(GalleryMixin, models.Model):
|
class City(models.Model):
|
||||||
"""Region model."""
|
"""Region model."""
|
||||||
name = models.CharField(_('name'), max_length=250)
|
name = models.CharField(_('name'), max_length=250)
|
||||||
name_translated = TJSONField(blank=True, null=True, default=None,
|
name_translated = TJSONField(blank=True, null=True, default=None,
|
||||||
|
|
@ -167,8 +166,10 @@ class City(GalleryMixin, models.Model):
|
||||||
map2 = models.CharField(max_length=255, blank=True, null=True)
|
map2 = models.CharField(max_length=255, blank=True, null=True)
|
||||||
map_ref = models.CharField(max_length=255, blank=True, null=True)
|
map_ref = models.CharField(max_length=255, blank=True, null=True)
|
||||||
situation = models.CharField(max_length=255, blank=True, null=True)
|
situation = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
gallery = models.ManyToManyField('gallery.Image', through='location.CityGallery', blank=True)
|
blank=True, null=True, default=None,
|
||||||
|
related_name='city_image',
|
||||||
|
verbose_name=_('image instance of model Image'))
|
||||||
|
|
||||||
mysql_id = models.IntegerField(blank=True, null=True, default=None)
|
mysql_id = models.IntegerField(blank=True, null=True, default=None)
|
||||||
|
|
||||||
|
|
@ -181,23 +182,29 @@ class City(GalleryMixin, models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image_object(self):
|
||||||
|
"""Return image object."""
|
||||||
|
return self.image.image if self.image else None
|
||||||
|
|
||||||
class CityGallery(IntermediateGalleryModelMixin):
|
@property
|
||||||
"""Gallery for model City."""
|
def crop_image(self):
|
||||||
city = models.ForeignKey(City, null=True,
|
if hasattr(self, 'image') and hasattr(self, '_meta'):
|
||||||
related_name='city_gallery',
|
if self.image:
|
||||||
on_delete=models.CASCADE,
|
image_property = {
|
||||||
verbose_name=_('city'))
|
'id': self.image.id,
|
||||||
image = models.ForeignKey('gallery.Image', null=True,
|
'title': self.image.title,
|
||||||
related_name='city_gallery',
|
'original_url': self.image.image.url,
|
||||||
on_delete=models.CASCADE,
|
'orientation_display': self.image.get_orientation_display(),
|
||||||
verbose_name=_('image'))
|
'auto_crop_images': {},
|
||||||
|
}
|
||||||
class Meta:
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
||||||
"""CityGallery meta class."""
|
if p.startswith(self._meta.model_name.lower())]
|
||||||
verbose_name = _('city gallery')
|
for crop in crop_parameters:
|
||||||
verbose_name_plural = _('city galleries')
|
image_property['auto_crop_images'].update(
|
||||||
unique_together = (('city', 'is_main'), ('city', 'image'))
|
{crop: self.image.get_image_url(crop)}
|
||||||
|
)
|
||||||
|
return image_property
|
||||||
|
|
||||||
|
|
||||||
class Address(models.Model):
|
class Address(models.Model):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
from location import models
|
from location import models
|
||||||
from location.serializers import common
|
from location.serializers import common
|
||||||
from rest_framework import serializers
|
|
||||||
from gallery.models import Image
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class AddressCreateSerializer(common.AddressDetailSerializer):
|
class AddressCreateSerializer(common.AddressDetailSerializer):
|
||||||
|
|
@ -21,46 +18,3 @@ class CountryBackSerializer(common.CountrySerializer):
|
||||||
'name',
|
'name',
|
||||||
'country_id'
|
'country_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CityGallerySerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer class for model CityGallery."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class"""
|
|
||||||
|
|
||||||
model = models.CityGallery
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'is_main',
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def request_kwargs(self):
|
|
||||||
"""Get url kwargs from request."""
|
|
||||||
return self.context.get('request').parser_context.get('kwargs')
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method."""
|
|
||||||
city_pk = self.request_kwargs.get('pk')
|
|
||||||
image_id = self.request_kwargs.get('image_id')
|
|
||||||
|
|
||||||
city_qs = models.City.objects.filter(pk=city_pk)
|
|
||||||
image_qs = Image.objects.filter(id=image_id)
|
|
||||||
|
|
||||||
if not city_qs.exists():
|
|
||||||
raise serializers.ValidationError({'detail': _('City not found')})
|
|
||||||
|
|
||||||
if not image_qs.exists():
|
|
||||||
raise serializers.ValidationError({'detail': _('Image not found')})
|
|
||||||
|
|
||||||
city = city_qs.first()
|
|
||||||
image = image_qs.first()
|
|
||||||
|
|
||||||
if image in city.gallery.all():
|
|
||||||
raise serializers.ValidationError({'detail': _('Image is already added.')})
|
|
||||||
|
|
||||||
attrs['city'] = city
|
|
||||||
attrs['image'] = image
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ from django.contrib.gis.geos import Point
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from location import models
|
from location import models
|
||||||
from utils.serializers import TranslatedField
|
from gallery import models as gallery_models
|
||||||
|
from utils.serializers import TranslatedField, ImageBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class CountrySerializer(serializers.ModelSerializer):
|
class CountrySerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -70,7 +71,7 @@ class CityShortSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CitySerializer(serializers.ModelSerializer):
|
class CityBaseSerializer(serializers.ModelSerializer):
|
||||||
"""City serializer."""
|
"""City serializer."""
|
||||||
region = RegionSerializer(read_only=True)
|
region = RegionSerializer(read_only=True)
|
||||||
region_id = serializers.PrimaryKeyRelatedField(
|
region_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
|
@ -83,6 +84,11 @@ class CitySerializer(serializers.ModelSerializer):
|
||||||
queryset=models.Country.objects.all(),
|
queryset=models.Country.objects.all(),
|
||||||
write_only=True
|
write_only=True
|
||||||
)
|
)
|
||||||
|
image_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
source='image',
|
||||||
|
queryset=gallery_models.Image.objects.all(),
|
||||||
|
write_only=True
|
||||||
|
)
|
||||||
country = CountrySerializer(read_only=True)
|
country = CountrySerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -96,6 +102,22 @@ class CitySerializer(serializers.ModelSerializer):
|
||||||
'country',
|
'country',
|
||||||
'postal_code',
|
'postal_code',
|
||||||
'is_island',
|
'is_island',
|
||||||
|
'image',
|
||||||
|
'image_id',
|
||||||
|
]
|
||||||
|
extra_fields = {
|
||||||
|
'image': {'read_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CityDetailSerializer(CityBaseSerializer):
|
||||||
|
"""Serializer for detail view."""
|
||||||
|
image = ImageBaseSerializer(source='crop_image', read_only=True)
|
||||||
|
|
||||||
|
class Meta(CityBaseSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
fields = CityBaseSerializer.Meta.fields + [
|
||||||
|
'image',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -154,7 +176,7 @@ class AddressDetailSerializer(AddressBaseSerializer):
|
||||||
city_id = serializers.PrimaryKeyRelatedField(
|
city_id = serializers.PrimaryKeyRelatedField(
|
||||||
source='city', write_only=True,
|
source='city', write_only=True,
|
||||||
queryset=models.City.objects.all())
|
queryset=models.City.objects.all())
|
||||||
city = CitySerializer(read_only=True)
|
city = CityBaseSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta(AddressBaseSerializer.Meta):
|
class Meta(AddressBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from tqdm import tqdm
|
||||||
from account.models import Role
|
from account.models import Role
|
||||||
from collection.models import Collection
|
from collection.models import Collection
|
||||||
from gallery.models import Image
|
from gallery.models import Image
|
||||||
from location.models import Country, Region, City, Address, CityGallery
|
from location.models import Country, Region, City, Address
|
||||||
from main.models import AwardType
|
from main.models import AwardType
|
||||||
from news.models import News
|
from news.models import News
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
|
|
@ -218,29 +218,6 @@ def migrate_city_map_situation(get_exists_cities=False):
|
||||||
pprint(f"City info serializer errors: {serialized_data.errors}")
|
pprint(f"City info serializer errors: {serialized_data.errors}")
|
||||||
|
|
||||||
|
|
||||||
def migrate_city_photos():
|
|
||||||
queryset = transfer_models.CityPhotos.objects.raw("""SELECT city_photos.id, city_photos.city_id, city_photos.attachment_file_name
|
|
||||||
FROM city_photos WHERE
|
|
||||||
city_photos.attachment_file_name IS NOT NULL AND
|
|
||||||
city_id IN(
|
|
||||||
SELECT cities.id
|
|
||||||
FROM cities WHERE
|
|
||||||
region_code IS NOT NULL AND
|
|
||||||
region_code != "" AND
|
|
||||||
country_code_2 IS NOT NULL AND
|
|
||||||
country_code_2 != ""
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
queryset = [vars(query) for query in queryset]
|
|
||||||
|
|
||||||
serialized_data = location_serializers.CityGallerySerializer(data=queryset, many=True)
|
|
||||||
if serialized_data.is_valid():
|
|
||||||
serialized_data.save()
|
|
||||||
else:
|
|
||||||
pprint(f"Address serializer errors: {serialized_data.errors}")
|
|
||||||
|
|
||||||
|
|
||||||
# Update location models with ruby library
|
# Update location models with ruby library
|
||||||
# Utils functions defined before transfer functions
|
# Utils functions defined before transfer functions
|
||||||
def get_ruby_socket(params):
|
def get_ruby_socket(params):
|
||||||
|
|
@ -554,10 +531,10 @@ def remove_old_records():
|
||||||
clean_old_region_records(Region, {"mysql_ids__isnull": True})
|
clean_old_region_records(Region, {"mysql_ids__isnull": True})
|
||||||
|
|
||||||
|
|
||||||
def transfer_city_gallery():
|
def transfer_city_photos():
|
||||||
created_counter = 0
|
created_counter = 0
|
||||||
cities_not_exists = {}
|
cities_not_exists = {}
|
||||||
gallery_obj_exists_counter = 0
|
cities_has_same_image = 0
|
||||||
|
|
||||||
city_gallery = transfer_models.CityPhotos.objects.exclude(city__isnull=True) \
|
city_gallery = transfer_models.CityPhotos.objects.exclude(city__isnull=True) \
|
||||||
.exclude(city__country_code_2__isnull=True) \
|
.exclude(city__country_code_2__isnull=True) \
|
||||||
|
|
@ -565,7 +542,7 @@ def transfer_city_gallery():
|
||||||
.exclude(city__region_code__isnull=True) \
|
.exclude(city__region_code__isnull=True) \
|
||||||
.exclude(city__region_code__iexact='') \
|
.exclude(city__region_code__iexact='') \
|
||||||
.values_list('city_id', 'attachment_suffix_url')
|
.values_list('city_id', 'attachment_suffix_url')
|
||||||
for old_city_id, image_suffix_url in city_gallery:
|
for old_city_id, image_suffix_url in tqdm(city_gallery):
|
||||||
city = City.objects.filter(old_id=old_city_id)
|
city = City.objects.filter(old_id=old_city_id)
|
||||||
if city.exists():
|
if city.exists():
|
||||||
city = city.first()
|
city = city.first()
|
||||||
|
|
@ -575,19 +552,18 @@ def transfer_city_gallery():
|
||||||
'orientation': Image.HORIZONTAL,
|
'orientation': Image.HORIZONTAL,
|
||||||
'title': f'{city.name} - {image_suffix_url}',
|
'title': f'{city.name} - {image_suffix_url}',
|
||||||
})
|
})
|
||||||
city_gallery, created = CityGallery.objects.get_or_create(image=image,
|
if city.image != image:
|
||||||
city=city,
|
city.image = image
|
||||||
is_main=True)
|
city.save()
|
||||||
if created:
|
|
||||||
created_counter += 1
|
created_counter += 1
|
||||||
else:
|
else:
|
||||||
gallery_obj_exists_counter += 1
|
cities_has_same_image += 1
|
||||||
else:
|
else:
|
||||||
cities_not_exists.update({'city_old_id': old_city_id})
|
cities_not_exists.update({'city_old_id': old_city_id})
|
||||||
|
|
||||||
print(f'Created: {created_counter}\n'
|
print(f'Created: {created_counter}\n'
|
||||||
f'City not exists: {cities_not_exists}\n'
|
f'City not exists: {cities_not_exists}\n'
|
||||||
f'Already added: {gallery_obj_exists_counter}')
|
f'City has same image: {cities_has_same_image}')
|
||||||
|
|
||||||
|
|
||||||
@atomic
|
@atomic
|
||||||
|
|
@ -758,8 +734,8 @@ def setup_clean_db():
|
||||||
print('update_flags')
|
print('update_flags')
|
||||||
update_flags()
|
update_flags()
|
||||||
|
|
||||||
print('transfer_city_gallery')
|
print('transfer_city_photos')
|
||||||
transfer_city_gallery()
|
transfer_city_photos()
|
||||||
|
|
||||||
|
|
||||||
def set_unused_regions():
|
def set_unused_regions():
|
||||||
|
|
@ -796,7 +772,6 @@ def set_unused_regions():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
# transfer_countries,
|
# transfer_countries,
|
||||||
|
|
@ -813,9 +788,6 @@ data_types = {
|
||||||
"update_city_info": [
|
"update_city_info": [
|
||||||
migrate_city_map_situation
|
migrate_city_map_situation
|
||||||
],
|
],
|
||||||
"migrate_city_gallery": [
|
|
||||||
migrate_city_photos
|
|
||||||
],
|
|
||||||
"fix_location": [
|
"fix_location": [
|
||||||
add_fake_country,
|
add_fake_country,
|
||||||
fix_location_models,
|
fix_location_models,
|
||||||
|
|
@ -823,13 +795,12 @@ data_types = {
|
||||||
"remove_old_locations": [
|
"remove_old_locations": [
|
||||||
remove_old_records
|
remove_old_records
|
||||||
],
|
],
|
||||||
"fill_city_gallery": [
|
"migrate_city_photos": [
|
||||||
transfer_city_gallery
|
transfer_city_photos,
|
||||||
],
|
],
|
||||||
"add_fake_country": [
|
"add_fake_country": [
|
||||||
add_fake_country,
|
add_fake_country,
|
||||||
],
|
],
|
||||||
|
|
||||||
"setup_clean_db": [setup_clean_db],
|
"setup_clean_db": [setup_clean_db],
|
||||||
"set_unused_regions": [set_unused_regions],
|
"set_unused_regions": [set_unused_regions],
|
||||||
"update_fake_country_flag": [update_fake_country_flag]
|
"update_fake_country_flag": [update_fake_country_flag]
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,6 @@ urlpatterns = [
|
||||||
path('cities/', views.CityListCreateView.as_view(), name='city-list-create'),
|
path('cities/', views.CityListCreateView.as_view(), name='city-list-create'),
|
||||||
path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'),
|
path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'),
|
||||||
path('cities/<int:pk>/', views.CityRUDView.as_view(), name='city-retrieve'),
|
path('cities/<int:pk>/', views.CityRUDView.as_view(), name='city-retrieve'),
|
||||||
path('cities/<int:pk>/gallery/', views.CityGalleryListView.as_view(),
|
|
||||||
name='gallery-list'),
|
|
||||||
path('cities/<int:pk>/gallery/<int:image_id>/',
|
|
||||||
views.CityGalleryCreateDestroyView.as_view(),
|
|
||||||
name='gallery-create-destroy'),
|
|
||||||
|
|
||||||
path('countries/', views.CountryListCreateView.as_view(), name='country-list-create'),
|
path('countries/', views.CountryListCreateView.as_view(), name='country-list-create'),
|
||||||
path('countries/<int:pk>/', views.CountryRUDView.as_view(), name='country-retrieve'),
|
path('countries/<int:pk>/', views.CountryRUDView.as_view(), name='country-retrieve'),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIV
|
||||||
# City
|
# City
|
||||||
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
queryset = models.City.objects.all()
|
queryset = models.City.objects.all()
|
||||||
filter_class = filters.CityBackFilter
|
filter_class = filters.CityBackFilter
|
||||||
|
|
@ -52,7 +52,7 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
|
|
||||||
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model City."""
|
"""Create view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
queryset = models.City.objects.all()\
|
queryset = models.City.objects.all()\
|
||||||
.annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\
|
.annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\
|
||||||
|
|
@ -63,61 +63,10 @@ class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||||
|
|
||||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""RUD view for model City."""
|
"""RUD view for model City."""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityDetailSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
||||||
|
|
||||||
|
|
||||||
class CityGalleryCreateDestroyView(common.CityViewMixin,
|
|
||||||
CreateDestroyGalleryViewMixin):
|
|
||||||
"""Resource for a create gallery for product for back-office users."""
|
|
||||||
serializer_class = serializers.CityGallerySerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
"""
|
|
||||||
Returns the object the view is displaying.
|
|
||||||
"""
|
|
||||||
city_qs = self.filter_queryset(self.get_queryset())
|
|
||||||
|
|
||||||
city = get_object_or_404(city_qs, pk=self.kwargs.get('pk'))
|
|
||||||
gallery = get_object_or_404(city.city_gallery, image_id=self.kwargs.get('image_id'))
|
|
||||||
|
|
||||||
# May raise a permission denied
|
|
||||||
self.check_object_permissions(self.request, gallery)
|
|
||||||
|
|
||||||
return gallery
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
return super(CityGalleryCreateDestroyView, self).create(request, *args, **kwargs)
|
|
||||||
except IntegrityError as e:
|
|
||||||
if not 'unique constraint' in e.args[0]:
|
|
||||||
raise e
|
|
||||||
models.CityGallery.objects.filter(city=kwargs['pk'], is_main=request.data['is_main']).delete()
|
|
||||||
return super(CityGalleryCreateDestroyView, self).create(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class CityGalleryListView(common.CityViewMixin,
|
|
||||||
generics.ListAPIView):
|
|
||||||
"""Resource for returning gallery for product for back-office users."""
|
|
||||||
serializer_class = ImageBaseSerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin]
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
"""Override get_object method."""
|
|
||||||
qs = super(CityGalleryListView, self).get_queryset()
|
|
||||||
city = get_object_or_404(qs, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
# May raise a permission denied
|
|
||||||
self.check_object_permissions(self.request, city)
|
|
||||||
|
|
||||||
return city
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""Override get_queryset method."""
|
|
||||||
return self.get_object().crop_gallery
|
|
||||||
|
|
||||||
|
|
||||||
# Region
|
# Region
|
||||||
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
||||||
"""Create view for model Region"""
|
"""Create view for model Region"""
|
||||||
|
|
|
||||||
|
|
@ -85,18 +85,18 @@ class RegionUpdateView(RegionViewMixin, generics.UpdateAPIView):
|
||||||
# City
|
# City
|
||||||
class CityCreateView(CityViewMixin, generics.CreateAPIView):
|
class CityCreateView(CityViewMixin, generics.CreateAPIView):
|
||||||
"""Create view for model City"""
|
"""Create view for model City"""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class CityRetrieveView(CityViewMixin, generics.RetrieveAPIView):
|
class CityRetrieveView(CityViewMixin, generics.RetrieveAPIView):
|
||||||
"""Retrieve view for model City"""
|
"""Retrieve view for model City"""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityDetailSerializer
|
||||||
|
|
||||||
|
|
||||||
class CityListView(CityViewMixin, generics.ListAPIView):
|
class CityListView(CityViewMixin, generics.ListAPIView):
|
||||||
"""List view for model City"""
|
"""List view for model City"""
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
|
|
@ -107,12 +107,12 @@ class CityListView(CityViewMixin, generics.ListAPIView):
|
||||||
|
|
||||||
class CityDestroyView(CityViewMixin, generics.DestroyAPIView):
|
class CityDestroyView(CityViewMixin, generics.DestroyAPIView):
|
||||||
"""Destroy view for model City"""
|
"""Destroy view for model City"""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class CityUpdateView(CityViewMixin, generics.UpdateAPIView):
|
class CityUpdateView(CityViewMixin, generics.UpdateAPIView):
|
||||||
"""Update view for model City"""
|
"""Update view for model City"""
|
||||||
serializer_class = serializers.CitySerializer
|
serializer_class = serializers.CityBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,18 @@ class SiteSettingsAdmin(admin.ModelAdmin):
|
||||||
inlines = [SiteSettingsInline, ]
|
inlines = [SiteSettingsInline, ]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.SiteFeature)
|
||||||
|
class SiteFeatureAdmin(admin.ModelAdmin):
|
||||||
|
"""Site feature admin conf."""
|
||||||
|
list_display = ['id', 'site_settings', 'feature',
|
||||||
|
'published', 'main', 'backoffice', ]
|
||||||
|
raw_id_fields = ['site_settings', 'feature', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Feature)
|
@admin.register(models.Feature)
|
||||||
class FeatureAdmin(admin.ModelAdmin):
|
class FeatureAdmin(admin.ModelAdmin):
|
||||||
"""Feature admin conf."""
|
"""Feature admin conf."""
|
||||||
|
list_display = ['id', '__str__', 'priority', 'route', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.AwardType)
|
@admin.register(models.AwardType)
|
||||||
|
|
@ -46,6 +55,7 @@ class CarouselAdmin(admin.ModelAdmin):
|
||||||
@admin.register(models.PageType)
|
@admin.register(models.PageType)
|
||||||
class PageTypeAdmin(admin.ModelAdmin):
|
class PageTypeAdmin(admin.ModelAdmin):
|
||||||
"""PageType admin."""
|
"""PageType admin."""
|
||||||
|
list_display = ['id', '__str__', ]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Page)
|
@admin.register(models.Page)
|
||||||
|
|
@ -80,3 +90,13 @@ class PanelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'name', 'user', 'created',)
|
list_display = ('id', 'name', 'user', 'created',)
|
||||||
raw_id_fields = ('user',)
|
raw_id_fields = ('user',)
|
||||||
list_display_links = ('id', 'name',)
|
list_display_links = ('id', 'name',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.NavigationBarPermission)
|
||||||
|
class NavigationBarPermissionAdmin(admin.ModelAdmin):
|
||||||
|
"""NavigationBarPermission admin."""
|
||||||
|
list_display = ('id', 'permission_mode_display', )
|
||||||
|
|
||||||
|
def permission_mode_display(self, obj):
|
||||||
|
"""Get permission mode display."""
|
||||||
|
return obj.get_permission_mode_display()
|
||||||
|
|
|
||||||
44
apps/main/migrations/0046_auto_20200114_1218.py
Normal file
44
apps/main/migrations/0046_auto_20200114_1218.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-14 12:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0045_carousel_is_international'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitefeature',
|
||||||
|
name='backoffice',
|
||||||
|
field=models.BooleanField(default=False, help_text='shows on backoffice page', verbose_name='backoffice'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sitefeature',
|
||||||
|
name='main',
|
||||||
|
field=models.BooleanField(default=False, help_text='shows on main page', verbose_name='Main'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sitefeature',
|
||||||
|
name='nested',
|
||||||
|
field=models.ManyToManyField(to='main.SiteFeature', blank=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NavigationBarPermission',
|
||||||
|
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')),
|
||||||
|
('permission_mode', models.PositiveSmallIntegerField(choices=[(0, 'read'), (1, 'write')], default=0, help_text='READ - allows only retrieve data,WRITE - allows to perform any operations over the object', verbose_name='permission mode')),
|
||||||
|
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.SiteFeature', verbose_name='section')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Navigation bar item permission',
|
||||||
|
'verbose_name_plural': 'Navigation bar item permissions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/main/migrations/0047_auto_20200115_1013.py
Normal file
18
apps/main/migrations/0047_auto_20200115_1013.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 10:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0046_auto_20200114_1218'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='feature',
|
||||||
|
name='priority',
|
||||||
|
field=models.IntegerField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
apps/main/migrations/0048_auto_20200115_1944.py
Normal file
24
apps/main/migrations/0048_auto_20200115_1944.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 19:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0047_auto_20200115_1013'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='navigationbarpermission',
|
||||||
|
name='sections',
|
||||||
|
field=models.ManyToManyField(to='main.SiteFeature', verbose_name='sections'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='navigationbarpermission',
|
||||||
|
name='section',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='old_sections', to='main.SiteFeature', verbose_name='section'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-15 20:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0048_auto_20200115_1944'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='navigationbarpermission',
|
||||||
|
name='section',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -6,16 +6,18 @@ from django.contrib.contenttypes import fields as generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.core.validators import EMPTY_VALUES
|
from django.core.validators import EMPTY_VALUES
|
||||||
from django.db import connections, connection
|
from django.db import connections
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
|
||||||
from configuration.models import TranslationSettings
|
from configuration.models import TranslationSettings
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from main import methods
|
from main import methods
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
|
from tag.models import Tag
|
||||||
from utils.exceptions import UnprocessableEntityError
|
from utils.exceptions import UnprocessableEntityError
|
||||||
from utils.methods import dictfetchall
|
from utils.methods import dictfetchall
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
|
|
@ -112,11 +114,13 @@ class Feature(ProjectBaseMixin, PlatformMixin):
|
||||||
"""Feature model."""
|
"""Feature model."""
|
||||||
|
|
||||||
slug = models.SlugField(max_length=255, unique=True)
|
slug = models.SlugField(max_length=255, unique=True)
|
||||||
priority = models.IntegerField(unique=True, null=True, default=None)
|
priority = models.IntegerField(blank=True, null=True, default=None)
|
||||||
route = models.ForeignKey('PageType', on_delete=models.PROTECT, null=True, default=None)
|
route = models.ForeignKey('PageType', on_delete=models.PROTECT, null=True, default=None)
|
||||||
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
|
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
|
||||||
old_id = models.IntegerField(null=True, blank=True)
|
old_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
chosen_tags = generic.GenericRelation(to='tag.ChosenTag')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
verbose_name = _('Feature')
|
verbose_name = _('Feature')
|
||||||
|
|
@ -125,6 +129,10 @@ class Feature(ProjectBaseMixin, PlatformMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.slug}'
|
return f'{self.slug}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_chosen_tags(self):
|
||||||
|
return Tag.objects.filter(chosen_tags__in=self.chosen_tags.all()).distinct()
|
||||||
|
|
||||||
|
|
||||||
class SiteFeatureQuerySet(models.QuerySet):
|
class SiteFeatureQuerySet(models.QuerySet):
|
||||||
"""Extended queryset for SiteFeature model."""
|
"""Extended queryset for SiteFeature model."""
|
||||||
|
|
@ -144,9 +152,15 @@ class SiteFeature(ProjectBaseMixin):
|
||||||
|
|
||||||
site_settings = models.ForeignKey(SiteSettings, on_delete=models.CASCADE)
|
site_settings = models.ForeignKey(SiteSettings, on_delete=models.CASCADE)
|
||||||
feature = models.ForeignKey(Feature, on_delete=models.PROTECT)
|
feature = models.ForeignKey(Feature, on_delete=models.PROTECT)
|
||||||
|
|
||||||
published = models.BooleanField(default=False, verbose_name=_('Published'))
|
published = models.BooleanField(default=False, verbose_name=_('Published'))
|
||||||
main = models.BooleanField(default=False, verbose_name=_('Main'))
|
main = models.BooleanField(default=False,
|
||||||
nested = models.ManyToManyField('self', symmetrical=False)
|
help_text='shows on main page',
|
||||||
|
verbose_name=_('Main'),)
|
||||||
|
backoffice = models.BooleanField(default=False,
|
||||||
|
help_text='shows on backoffice page',
|
||||||
|
verbose_name=_('backoffice'),)
|
||||||
|
nested = models.ManyToManyField('self', blank=True, symmetrical=False)
|
||||||
old_id = models.IntegerField(null=True, blank=True)
|
old_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
objects = SiteFeatureQuerySet.as_manager()
|
objects = SiteFeatureQuerySet.as_manager()
|
||||||
|
|
@ -216,6 +230,8 @@ class CarouselQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def by_country_code(self, code):
|
def by_country_code(self, code):
|
||||||
"""Filter collection by country code."""
|
"""Filter collection by country code."""
|
||||||
|
if code in settings.INTERNATIONAL_COUNTRY_CODES:
|
||||||
|
return self.filter(is_international=True)
|
||||||
return self.filter(country__code=code)
|
return self.filter(country__code=code)
|
||||||
|
|
||||||
def get_international(self):
|
def get_international(self):
|
||||||
|
|
@ -520,3 +536,28 @@ class Panel(ProjectBaseMixin):
|
||||||
params = params + new_params
|
params = params + new_params
|
||||||
query = self.query + limit_offset
|
query = self.query + limit_offset
|
||||||
return query, params
|
return query, params
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationBarPermission(ProjectBaseMixin):
|
||||||
|
"""Model for navigation bar item permissions."""
|
||||||
|
READ = 0
|
||||||
|
WRITE = 1
|
||||||
|
|
||||||
|
PERMISSION_MODES = (
|
||||||
|
(READ, _('read')),
|
||||||
|
(WRITE, _('write')),
|
||||||
|
)
|
||||||
|
|
||||||
|
sections = models.ManyToManyField('main.SiteFeature',
|
||||||
|
verbose_name=_('sections'))
|
||||||
|
permission_mode = models.PositiveSmallIntegerField(choices=PERMISSION_MODES,
|
||||||
|
default=READ,
|
||||||
|
help_text='READ - allows only retrieve data,'
|
||||||
|
'WRITE - allows to perform any '
|
||||||
|
'operations over the object',
|
||||||
|
verbose_name=_('permission mode'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
verbose_name = _('Navigation bar item permission')
|
||||||
|
verbose_name_plural = _('Navigation bar item permissions')
|
||||||
|
|
|
||||||
1
apps/main/serializers/__init__.py
Normal file
1
apps/main/serializers/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from main.serializers.common import *
|
||||||
29
apps/main/serializers/back.py
Normal file
29
apps/main/serializers/back.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from account.serializers import BackUserSerializer
|
||||||
|
from main import models
|
||||||
|
|
||||||
|
|
||||||
|
class PanelSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for Custom panel."""
|
||||||
|
user_id = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
source='user',
|
||||||
|
write_only=True
|
||||||
|
)
|
||||||
|
user = BackUserSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Panel
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'display',
|
||||||
|
'description',
|
||||||
|
'query',
|
||||||
|
'created',
|
||||||
|
'modified',
|
||||||
|
'user',
|
||||||
|
'user_id'
|
||||||
|
]
|
||||||
|
|
@ -4,9 +4,8 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from location.serializers import CountrySerializer
|
from location.serializers import CountrySerializer
|
||||||
from main import models
|
from main import models
|
||||||
|
from tag.serializers import TagBackOfficeSerializer
|
||||||
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
|
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
|
||||||
from account.serializers.back import BackUserSerializer
|
|
||||||
from account.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class FeatureSerializer(serializers.ModelSerializer):
|
class FeatureSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -84,24 +83,46 @@ class FooterBackSerializer(FooterSerializer):
|
||||||
|
|
||||||
|
|
||||||
class SiteFeatureSerializer(serializers.ModelSerializer):
|
class SiteFeatureSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(source='feature.id')
|
id = serializers.IntegerField(source='feature.id', allow_null=True)
|
||||||
slug = serializers.CharField(source='feature.slug')
|
slug = serializers.CharField(source='feature.slug', allow_null=True)
|
||||||
priority = serializers.IntegerField(source='feature.priority')
|
priority = serializers.IntegerField(source='feature.priority', allow_null=True)
|
||||||
route = serializers.CharField(source='feature.route.name')
|
route = serializers.CharField(source='feature.route.name', allow_null=True)
|
||||||
source = serializers.IntegerField(source='feature.source')
|
source = serializers.IntegerField(source='feature.source', allow_null=True)
|
||||||
nested = RecursiveFieldSerializer(many=True, allow_null=True)
|
nested = RecursiveFieldSerializer(many=True, read_only=True, allow_null=True)
|
||||||
|
chosen_tags = TagBackOfficeSerializer(
|
||||||
|
source='feature.get_chosen_tags', many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
model = models.SiteFeature
|
model = models.SiteFeature
|
||||||
fields = ('main',
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'slug',
|
'main',
|
||||||
'priority',
|
'slug',
|
||||||
'route',
|
'priority',
|
||||||
'source',
|
'route',
|
||||||
'nested',
|
'source',
|
||||||
)
|
'nested',
|
||||||
|
'chosen_tags',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationBarSectionBaseSerializer(SiteFeatureSerializer):
|
||||||
|
"""Serializer for navigation bar."""
|
||||||
|
source_display = serializers.CharField(source='feature.get_source_display',
|
||||||
|
read_only=True)
|
||||||
|
|
||||||
|
class Meta(SiteFeatureSerializer.Meta):
|
||||||
|
model = models.SiteFeature
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'slug',
|
||||||
|
'route',
|
||||||
|
'source',
|
||||||
|
'source_display',
|
||||||
|
'priority',
|
||||||
|
'nested',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteSettingsSerializer(serializers.ModelSerializer):
|
class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -291,30 +312,6 @@ class ContentTypeBackSerializer(serializers.ModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class PanelSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for Custom panel."""
|
|
||||||
user_id = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
source='user',
|
|
||||||
write_only=True
|
|
||||||
)
|
|
||||||
user = BackUserSerializer(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Panel
|
|
||||||
fields = [
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'display',
|
|
||||||
'description',
|
|
||||||
'query',
|
|
||||||
'created',
|
|
||||||
'modified',
|
|
||||||
'user',
|
|
||||||
'user_id'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class PanelExecuteSerializer(serializers.ModelSerializer):
|
class PanelExecuteSerializer(serializers.ModelSerializer):
|
||||||
"""Panel execute serializer."""
|
"""Panel execute serializer."""
|
||||||
|
|
||||||
|
|
@ -331,3 +328,20 @@ class PanelExecuteSerializer(serializers.ModelSerializer):
|
||||||
'user',
|
'user',
|
||||||
'user_id'
|
'user_id'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationBarPermissionBaseSerializer(serializers.ModelSerializer):
|
||||||
|
"""Navigation bar permission serializer."""
|
||||||
|
|
||||||
|
sections = NavigationBarSectionBaseSerializer(many=True, read_only=True)
|
||||||
|
permission_mode_display = serializers.CharField(source='get_permission_mode_display',
|
||||||
|
read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class."""
|
||||||
|
model = models.NavigationBarPermission
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'sections',
|
||||||
|
'permission_mode_display',
|
||||||
|
]
|
||||||
|
|
@ -6,6 +6,7 @@ from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from main import serializers
|
from main import serializers
|
||||||
|
from main.serializers.back import PanelSerializer
|
||||||
from main import tasks
|
from main import tasks
|
||||||
from main.filters import AwardFilter
|
from main.filters import AwardFilter
|
||||||
from main.models import Award, Footer, PageType, Panel, SiteFeature, Feature
|
from main.models import Award, Footer, PageType, Panel, SiteFeature, Feature
|
||||||
|
|
@ -108,7 +109,7 @@ class PanelsListCreateView(generics.ListCreateAPIView):
|
||||||
permission_classes = (
|
permission_classes = (
|
||||||
permissions.IsAdminUser,
|
permissions.IsAdminUser,
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PanelSerializer
|
serializer_class = PanelSerializer
|
||||||
queryset = Panel.objects.all()
|
queryset = Panel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -117,7 +118,7 @@ class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
permission_classes = (
|
permission_classes = (
|
||||||
permissions.IsAdminUser,
|
permissions.IsAdminUser,
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PanelSerializer
|
serializer_class = PanelSerializer
|
||||||
queryset = Panel.objects.all()
|
queryset = Panel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
69
apps/news/management/commands/news_optimize_images.py
Normal file
69
apps/news/management/commands/news_optimize_images.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# coding=utf-8
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from utils.methods import get_url_images_in_text, get_image_meta_by_url
|
||||||
|
from news.models import News
|
||||||
|
from sorl.thumbnail import get_thumbnail
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
IMAGE_MAX_SIZE_IN_BYTES = 1048576 # ~ 1mb
|
||||||
|
IMAGE_QUALITY_PERCENTS = 50
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'-s',
|
||||||
|
'--size',
|
||||||
|
default=self.IMAGE_MAX_SIZE_IN_BYTES,
|
||||||
|
help='Максимальный размер файла в байтах',
|
||||||
|
type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-q',
|
||||||
|
'--quality',
|
||||||
|
default=self.IMAGE_QUALITY_PERCENTS,
|
||||||
|
help='Качество изображения',
|
||||||
|
type=int
|
||||||
|
)
|
||||||
|
|
||||||
|
def optimize(self, text, max_size, max_quality):
|
||||||
|
"""optimize news images"""
|
||||||
|
if isinstance(text, str):
|
||||||
|
for image in get_url_images_in_text(text):
|
||||||
|
try:
|
||||||
|
size, width, height = get_image_meta_by_url(image)
|
||||||
|
except IOError as ie:
|
||||||
|
self.stdout.write(self.style.NOTICE(f'{ie}\n'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if size < max_size:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'No need to compress images size is {size / (2**20)}Mb\n'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
percents = round(max_size / (size * 0.01))
|
||||||
|
width = round(width * percents / 100)
|
||||||
|
height = round(height * percents / 100)
|
||||||
|
optimized_image = get_thumbnail(
|
||||||
|
file_=image,
|
||||||
|
geometry_string=f'{width}x{height}',
|
||||||
|
upscale=False,
|
||||||
|
quality=max_quality
|
||||||
|
).url
|
||||||
|
text = text.replace(image, optimized_image)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f'Optimized {image} -> {optimized_image}\n'
|
||||||
|
f'Quality [{percents}%]\n'))
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
size = options['size']
|
||||||
|
quality = options['quality']
|
||||||
|
|
||||||
|
for news in News.objects.all():
|
||||||
|
if not isinstance(news.description, dict):
|
||||||
|
continue
|
||||||
|
news.description = {
|
||||||
|
locale: self.optimize(text, size, quality)
|
||||||
|
for locale, text in news.description.items()
|
||||||
|
}
|
||||||
|
news.save()
|
||||||
|
|
@ -7,6 +7,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import HStoreField
|
from django.contrib.postgres.fields import HStoreField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Case, When
|
from django.db.models import Case, When
|
||||||
|
from django.urls.exceptions import NoReverseMatch
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
@ -54,7 +55,6 @@ class NewsType(models.Model):
|
||||||
name = models.CharField(_('name'), max_length=250)
|
name = models.CharField(_('name'), max_length=250)
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='news_types')
|
related_name='news_types')
|
||||||
chosen_tags = generic.GenericRelation(to='tag.ChosenTag')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -79,7 +79,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
|
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
return self.select_related('news_type', 'country').prefetch_related('tags', 'tags__translation')
|
return self.select_related('news_type', 'country').prefetch_related('tags', 'tags__translation', 'gallery')
|
||||||
|
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
|
|
@ -296,7 +296,10 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def web_url(self):
|
def web_url(self):
|
||||||
return reverse('web:news:rud', kwargs={'slug': next(iter(self.slugs.values()))})
|
try:
|
||||||
|
return reverse('web:news:rud', kwargs={'slug': next(iter(self.slugs.values()))})
|
||||||
|
except NoReverseMatch as e:
|
||||||
|
return None # no active links
|
||||||
|
|
||||||
def should_read(self, user):
|
def should_read(self, user):
|
||||||
return self.__class__.objects.should_read(self, user)[:3]
|
return self.__class__.objects.should_read(self, user)[:3]
|
||||||
|
|
@ -307,8 +310,9 @@ class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
|
||||||
@property
|
@property
|
||||||
def main_image(self):
|
def main_image(self):
|
||||||
qs = self.news_gallery.main_image()
|
qs = self.news_gallery.main_image()
|
||||||
if qs.exists():
|
image_model = qs.order_by('-id').first()
|
||||||
return qs.order_by('-id').first().image
|
if image_model is not None:
|
||||||
|
return image_model.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_url(self):
|
def image_url(self):
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ from utils.serializers import (
|
||||||
class AgendaSerializer(ProjectModelSerializer):
|
class AgendaSerializer(ProjectModelSerializer):
|
||||||
start_datetime = serializers.DateTimeField()
|
start_datetime = serializers.DateTimeField()
|
||||||
end_datetime = serializers.DateTimeField()
|
end_datetime = serializers.DateTimeField()
|
||||||
address = AddressBaseSerializer()
|
address = AddressBaseSerializer(read_only=True)
|
||||||
|
address_id = serializers.PrimaryKeyRelatedField(write_only=True, queryset=location_models.Address.objects.all(),
|
||||||
|
source='address')
|
||||||
event_name_translated = TranslatedField()
|
event_name_translated = TranslatedField()
|
||||||
content_translated = TranslatedField()
|
content_translated = TranslatedField()
|
||||||
|
|
||||||
|
|
@ -36,7 +38,8 @@ class AgendaSerializer(ProjectModelSerializer):
|
||||||
'end_datetime',
|
'end_datetime',
|
||||||
'address',
|
'address',
|
||||||
'content_translated',
|
'content_translated',
|
||||||
'event_name_translated'
|
'event_name_translated',
|
||||||
|
'address_id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -158,6 +161,7 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
should_read = SerializerMethodField()
|
should_read = SerializerMethodField()
|
||||||
agenda = AgendaSerializer()
|
agenda = AgendaSerializer()
|
||||||
banner = NewsBannerSerializer()
|
banner = NewsBannerSerializer()
|
||||||
|
in_favorites = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
class Meta(NewsDetailSerializer.Meta):
|
class Meta(NewsDetailSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -167,6 +171,7 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
'should_read',
|
'should_read',
|
||||||
'agenda',
|
'agenda',
|
||||||
'banner',
|
'banner',
|
||||||
|
'in_favorites',
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_same_theme(self, obj):
|
def get_same_theme(self, obj):
|
||||||
|
|
@ -176,10 +181,31 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
|
||||||
return NewsSimilarListSerializer(obj.should_read(self.context['request'].user), many=True, read_only=True).data
|
return NewsSimilarListSerializer(obj.should_read(self.context['request'].user), many=True, read_only=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class NewsPreviewWebSerializer(NewsDetailSerializer):
|
||||||
|
"""News preview serializer for web users.."""
|
||||||
|
|
||||||
|
same_theme = SerializerMethodField()
|
||||||
|
agenda = AgendaSerializer()
|
||||||
|
banner = NewsBannerSerializer()
|
||||||
|
|
||||||
|
class Meta(NewsDetailSerializer.Meta):
|
||||||
|
"""Meta class."""
|
||||||
|
|
||||||
|
fields = NewsDetailSerializer.Meta.fields + (
|
||||||
|
'same_theme',
|
||||||
|
'agenda',
|
||||||
|
'banner',
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_same_theme(self, obj):
|
||||||
|
return NewsSimilarListSerializer(obj.same_theme(self.context['request'].user), many=True, read_only=True).data
|
||||||
|
|
||||||
|
|
||||||
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
"""News back office base serializer."""
|
"""News back office base serializer."""
|
||||||
is_published = serializers.BooleanField(source='is_publish', read_only=True)
|
is_published = serializers.BooleanField(source='is_publish', read_only=True)
|
||||||
descriptions = serializers.ListField(required=False)
|
descriptions = serializers.ListField(required=False)
|
||||||
|
agenda = AgendaSerializer()
|
||||||
|
|
||||||
class Meta(NewsBaseSerializer.Meta):
|
class Meta(NewsBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -198,6 +224,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
'created',
|
'created',
|
||||||
'modified',
|
'modified',
|
||||||
'descriptions',
|
'descriptions',
|
||||||
|
'agenda'
|
||||||
)
|
)
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'created': {'read_only': True},
|
'created': {'read_only': True},
|
||||||
|
|
@ -228,6 +255,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
for locale in locales:
|
for locale in locales:
|
||||||
if not attrs[key].get(locale):
|
if not attrs[key].get(locale):
|
||||||
attrs[key][locale] = getattr(instance, key).get(locale)
|
attrs[key][locale] = getattr(instance, key).get(locale)
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|
@ -245,10 +273,25 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
user = request.user
|
user = request.user
|
||||||
validated_data['created_by'] = user
|
validated_data['created_by'] = user
|
||||||
|
|
||||||
return super().create(validated_data)
|
agenda_data = validated_data.get('agenda')
|
||||||
|
agenda = None
|
||||||
|
|
||||||
|
if agenda_data is not None:
|
||||||
|
agenda_data['address_id'] = agenda_data.pop('address').pk
|
||||||
|
agenda_serializer = AgendaSerializer(data=agenda_data)
|
||||||
|
agenda_serializer.is_valid(raise_exception=True)
|
||||||
|
agenda = agenda_serializer.save()
|
||||||
|
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
instance.agenda = agenda
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
slugs = validated_data.get('slugs')
|
slugs = validated_data.get('slugs')
|
||||||
|
slugs_list = list(map(lambda x: x.lower(), slugs.values() if slugs else ()))
|
||||||
|
slugs_set = set(slugs_list)
|
||||||
if slugs:
|
if slugs:
|
||||||
slugs_list = list(map(lambda x: x.lower(), slugs.values()))
|
slugs_list = list(map(lambda x: x.lower(), slugs.values()))
|
||||||
slugs_set = set(slugs_list)
|
slugs_set = set(slugs_list)
|
||||||
|
|
@ -256,6 +299,29 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
|
||||||
slugs__values__contains=list(slugs.values())
|
slugs__values__contains=list(slugs.values())
|
||||||
).exists() or len(slugs_list) != len(slugs_set):
|
).exists() or len(slugs_list) != len(slugs_set):
|
||||||
raise serializers.ValidationError({'slugs': _('Slug should be unique')})
|
raise serializers.ValidationError({'slugs': _('Slug should be unique')})
|
||||||
|
|
||||||
|
agenda_data = validated_data.get('agenda')
|
||||||
|
agenda = instance.agenda
|
||||||
|
|
||||||
|
if agenda is None and agenda_data is not None:
|
||||||
|
agenda_data['address_id'] = agenda_data.pop('address').pk
|
||||||
|
agenda_serializer = AgendaSerializer(data=agenda_data)
|
||||||
|
agenda_serializer.is_valid(raise_exception=True)
|
||||||
|
agenda_serializer.save()
|
||||||
|
|
||||||
|
elif agenda_data is not None:
|
||||||
|
agenda.start_datetime = agenda_data.pop(
|
||||||
|
'start_datetime') if 'start_datetime' in agenda_data else agenda.start_datetime
|
||||||
|
agenda.end_datetime = agenda_data.pop(
|
||||||
|
'end_datetime') if 'end_datetime' in agenda_data else agenda.end_datetime
|
||||||
|
agenda.address = agenda_data.pop(
|
||||||
|
'address') if 'address' in agenda_data else agenda.address
|
||||||
|
agenda.event_name = agenda_data.pop(
|
||||||
|
'event_name') if 'event_time' in agenda_data else agenda.event_name
|
||||||
|
agenda.content = agenda_data.pop(
|
||||||
|
'content') if 'content' in agenda_data else agenda.content
|
||||||
|
agenda.save()
|
||||||
|
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from tag.models import TagCategory, Tag
|
||||||
from translation.models import SiteInterfaceDictionary
|
from translation.models import SiteInterfaceDictionary
|
||||||
from transfer.models import PageTexts, PageCounters, PageMetadata
|
from transfer.models import PageTexts, PageCounters, PageMetadata
|
||||||
from transfer.serializers.news import NewsSerializer
|
from transfer.serializers.news import NewsSerializer
|
||||||
|
from utils.methods import transform_camelcase_to_underscore
|
||||||
|
|
||||||
|
|
||||||
def add_locale(locale, data):
|
def add_locale(locale, data):
|
||||||
|
|
@ -36,35 +37,38 @@ def clear_old_news():
|
||||||
|
|
||||||
images.delete()
|
images.delete()
|
||||||
news.delete()
|
news.delete()
|
||||||
# NewsType.objects.all().delete()
|
|
||||||
|
|
||||||
print(f'Deleted {img_num} images')
|
print(f'Deleted {img_num} images')
|
||||||
print(f'Deleted {news_num} news')
|
print(f'Deleted {news_num} news')
|
||||||
|
|
||||||
|
|
||||||
def transfer_news():
|
def transfer_news():
|
||||||
news_type, _ = NewsType.objects.get_or_create(name='news')
|
migrated_news_types = ('News', 'StaticPage', )
|
||||||
|
|
||||||
queryset = PageTexts.objects.filter(
|
for news_type in migrated_news_types:
|
||||||
page__type='News',
|
news_type_obj, _ = NewsType.objects.get_or_create(
|
||||||
).annotate(
|
name=transform_camelcase_to_underscore(news_type))
|
||||||
page__id=F('page__id'),
|
|
||||||
news_type_id=Value(news_type.id, output_field=IntegerField()),
|
|
||||||
page__created_at=F('page__created_at'),
|
|
||||||
page__account_id=F('page__account_id'),
|
|
||||||
page__state=F('page__state'),
|
|
||||||
page__template=F('page__template'),
|
|
||||||
page__site__country_code_2=F('page__site__country_code_2'),
|
|
||||||
page__root_title=F('page__root_title'),
|
|
||||||
page__attachment_suffix_url=F('page__attachment_suffix_url'),
|
|
||||||
page__published_at=F('page__published_at'),
|
|
||||||
)
|
|
||||||
|
|
||||||
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
queryset = PageTexts.objects.filter(
|
||||||
if serialized_data.is_valid():
|
page__type=news_type,
|
||||||
serialized_data.save()
|
).annotate(
|
||||||
else:
|
page__id=F('page__id'),
|
||||||
pprint(f'News serializer errors: {serialized_data.errors}')
|
news_type_id=Value(news_type_obj.id, output_field=IntegerField()),
|
||||||
|
page__created_at=F('page__created_at'),
|
||||||
|
page__account_id=F('page__account_id'),
|
||||||
|
page__state=F('page__state'),
|
||||||
|
page__template=F('page__template'),
|
||||||
|
page__site__country_code_2=F('page__site__country_code_2'),
|
||||||
|
page__root_title=F('page__root_title'),
|
||||||
|
page__attachment_suffix_url=F('page__attachment_suffix_url'),
|
||||||
|
page__published_at=F('page__published_at'),
|
||||||
|
)
|
||||||
|
|
||||||
|
serialized_data = NewsSerializer(data=list(queryset.values()), many=True)
|
||||||
|
if serialized_data.is_valid():
|
||||||
|
serialized_data.save()
|
||||||
|
else:
|
||||||
|
pprint(f'News serializer errors: {serialized_data.errors}')
|
||||||
|
|
||||||
|
|
||||||
def update_en_gb_locales():
|
def update_en_gb_locales():
|
||||||
|
|
@ -166,5 +170,5 @@ data_types = {
|
||||||
update_en_gb_locales,
|
update_en_gb_locales,
|
||||||
add_views_count,
|
add_views_count,
|
||||||
add_tags,
|
add_tags,
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ common_urlpatterns = [
|
||||||
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
|
path('slug/<slug:slug>/', views.NewsDetailView.as_view(), name='rud'),
|
||||||
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(),
|
path('slug/<slug:slug>/favorites/', views.NewsFavoritesCreateDestroyView.as_view(),
|
||||||
name='create-destroy-favorites'),
|
name='create-destroy-favorites'),
|
||||||
|
path('preview/slug/<slug:slug>/', views.NewsPreviewView.as_view(), name='preview'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,18 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class NewsPreviewView(NewsMixinView, generics.RetrieveAPIView):
|
||||||
|
"""News preview view."""
|
||||||
|
|
||||||
|
lookup_field = None
|
||||||
|
serializer_class = serializers.NewsPreviewWebSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Override get_queryset method."""
|
||||||
|
qs = models.News.objects.all().annotate_in_favorites(self.request.user)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class NewsTypeListView(generics.ListAPIView):
|
class NewsTypeListView(generics.ListAPIView):
|
||||||
"""NewsType list view."""
|
"""NewsType list view."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from notification import models
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
@admin.register(models.SubscriptionType)
|
||||||
|
class SubscriptionTypeAdmin(admin.ModelAdmin):
|
||||||
|
"""SubscriptionType admin."""
|
||||||
|
list_display = ['index_name', 'country']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Subscriber)
|
||||||
|
class SubscriberAdmin(admin.ModelAdmin):
|
||||||
|
"""Subscriber admin."""
|
||||||
|
raw_id_fields = ('user',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Subscribe)
|
||||||
|
class SubscribeAdmin(admin.ModelAdmin):
|
||||||
|
"""Subscribe admin."""
|
||||||
|
raw_id_fields = ('subscriber',)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,13 @@ class ProductType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin
|
||||||
(SOUVENIR, 'souvenir'),
|
(SOUVENIR, 'souvenir'),
|
||||||
(BOOK, 'book')
|
(BOOK, 'book')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
INDEX_PLURAL_ONE = {
|
||||||
|
'food': 'food',
|
||||||
|
'wines': 'wine',
|
||||||
|
'liquors': 'liquor',
|
||||||
|
}
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None,
|
name = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
||||||
|
|
@ -176,7 +183,7 @@ class ProductQuerySet(models.QuerySet):
|
||||||
"""Return objects with geo location."""
|
"""Return objects with geo location."""
|
||||||
return self.filter(establishment__address__coordinates__isnull=False)
|
return self.filter(establishment__address__coordinates__isnull=False)
|
||||||
|
|
||||||
def same_subtype(self, product):
|
def annotate_same_subtype(self, product):
|
||||||
"""Annotate flag same subtype."""
|
"""Annotate flag same subtype."""
|
||||||
return self.annotate(same_subtype=Case(
|
return self.annotate(same_subtype=Case(
|
||||||
models.When(
|
models.When(
|
||||||
|
|
@ -215,7 +222,7 @@ class ProductQuerySet(models.QuerySet):
|
||||||
similarity_rules['ordering'].append(F('distance').asc())
|
similarity_rules['ordering'].append(F('distance').asc())
|
||||||
similarity_rules['distinction'].append('distance')
|
similarity_rules['distinction'].append('distance')
|
||||||
return self.similar_base(product) \
|
return self.similar_base(product) \
|
||||||
.same_subtype(product) \
|
.annotate_same_subtype(product) \
|
||||||
.order_by(*similarity_rules['ordering']) \
|
.order_by(*similarity_rules['ordering']) \
|
||||||
.distinct(*similarity_rules['distinction'],
|
.distinct(*similarity_rules['distinction'],
|
||||||
'id')
|
'id')
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from comment.serializers import CommentSerializer
|
from comment.serializers import CommentBaseSerializer
|
||||||
from establishment.serializers import EstablishmentProductShortSerializer
|
from establishment.serializers import EstablishmentProductShortSerializer
|
||||||
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
from establishment.serializers.common import _EstablishmentAddressShortSerializer
|
||||||
from location.serializers import WineOriginRegionBaseSerializer,\
|
from location.serializers import WineOriginRegionBaseSerializer,\
|
||||||
|
|
@ -200,13 +200,11 @@ class ProductFavoritesCreateSerializer(FavoritesCreateSerializer):
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class ProductCommentCreateSerializer(CommentSerializer):
|
class ProductCommentBaseSerializer(CommentBaseSerializer):
|
||||||
"""Create comment serializer"""
|
"""Create comment serializer."""
|
||||||
mark = serializers.IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta(CommentBaseSerializer.Meta):
|
||||||
"""Serializer for model Comment"""
|
"""Serializer for model Comment"""
|
||||||
model = Comment
|
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'created',
|
'created',
|
||||||
|
|
@ -214,8 +212,14 @@ class ProductCommentCreateSerializer(CommentSerializer):
|
||||||
'mark',
|
'mark',
|
||||||
'nickname',
|
'nickname',
|
||||||
'profile_pic',
|
'profile_pic',
|
||||||
|
'status',
|
||||||
|
'status_display',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCommentCreateSerializer(ProductCommentBaseSerializer):
|
||||||
|
"""Serializer for creating comments for product."""
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
# Check product object
|
# Check product object
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,13 @@ urlpatterns = [
|
||||||
|
|
||||||
# similar products by type/subtype
|
# similar products by type/subtype
|
||||||
# temporary uses single mechanism, bec. description in process
|
# temporary uses single mechanism, bec. description in process
|
||||||
path('slug/<slug:slug>/similar/wines/', views.SimilarListView.as_view(),
|
# path('slug/<slug:slug>/similar/wines/', views.SimilarListView.as_view(),
|
||||||
name='similar-wine'),
|
# name='similar-wine'),
|
||||||
path('slug/<slug:slug>/similar/liquors/', views.SimilarListView.as_view(),
|
# path('slug/<slug:slug>/similar/liquors/', views.SimilarListView.as_view(),
|
||||||
name='similar-liquor'),
|
# name='similar-liquor'),
|
||||||
path('slug/<slug:slug>/similar/food/', views.SimilarListView.as_view(),
|
# path('slug/<slug:slug>/similar/food/', views.SimilarListView.as_view(),
|
||||||
name='similar-food'),
|
# name='similar-food'),
|
||||||
|
|
||||||
|
path('slug/<slug:slug>/similar/<str:type>/', views.SimilarListView.as_view(),
|
||||||
|
name='similar-products')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,10 @@ from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
from comment.models import Comment
|
from comment.models import Comment
|
||||||
from comment.serializers import CommentRUDSerializer
|
from comment.serializers import CommentBaseSerializer
|
||||||
from product import filters, serializers
|
from product import filters, serializers
|
||||||
from product.models import Product
|
from product.models import Product, ProductType
|
||||||
from utils.views import FavoritesCreateDestroyMixinView
|
from utils.views import FavoritesCreateDestroyMixinView
|
||||||
from utils.pagination import PortionPagination
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class ProductBaseView(generics.GenericAPIView):
|
class ProductBaseView(generics.GenericAPIView):
|
||||||
|
|
@ -44,8 +42,16 @@ class ProductSimilarView(ProductListView):
|
||||||
"""
|
"""
|
||||||
Return base product instance for a getting list of similar products.
|
Return base product instance for a getting list of similar products.
|
||||||
"""
|
"""
|
||||||
product = get_object_or_404(Product.objects.all(),
|
find_by = {
|
||||||
slug=self.kwargs.get('slug'))
|
'slug': self.kwargs.get('slug'),
|
||||||
|
}
|
||||||
|
|
||||||
|
if isinstance(self.kwargs.get('type'), str):
|
||||||
|
if not self.kwargs.get('type') in ProductType.INDEX_PLURAL_ONE:
|
||||||
|
return None
|
||||||
|
find_by['product_type'] = get_object_or_404(ProductType.objects.all(), index_name=ProductType.INDEX_PLURAL_ONE[self.kwargs.get('type')])
|
||||||
|
|
||||||
|
product = get_object_or_404(Product.objects.all(), **find_by)
|
||||||
return product
|
return product
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,17 +79,17 @@ class ProductCommentListView(generics.ListAPIView):
|
||||||
"""View for return list of product comments."""
|
"""View for return list of product comments."""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
serializer_class = serializers.ProductCommentCreateSerializer
|
serializer_class = serializers.ProductCommentBaseSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Override get_queryset method"""
|
"""Override get_queryset method"""
|
||||||
product = get_object_or_404(Product, slug=self.kwargs['slug'])
|
product = get_object_or_404(Product, slug=self.kwargs['slug'])
|
||||||
return product.comments.order_by('-created')
|
return product.comments.public(self.request.user).order_by('-created')
|
||||||
|
|
||||||
|
|
||||||
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""View for retrieve/update/destroy product comment."""
|
"""View for retrieve/update/destroy product comment."""
|
||||||
serializer_class = CommentRUDSerializer
|
serializer_class = serializers.ProductCommentBaseSerializer
|
||||||
queryset = Product.objects.all()
|
queryset = Product.objects.all()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
|
||||||
|
|
@ -77,15 +77,15 @@ class EstablishmentDocument(Document):
|
||||||
'value': fields.KeywordField(),
|
'value': fields.KeywordField(),
|
||||||
},
|
},
|
||||||
multi=True, attr='artisan_category_indexing')
|
multi=True, attr='artisan_category_indexing')
|
||||||
visible_tags = fields.ObjectField(
|
distillery_type = fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(attr='id'),
|
'id': fields.IntegerField(attr='id'),
|
||||||
'label': fields.ObjectField(attr='label_indexing',
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
properties=OBJECT_FIELD_PROPERTIES),
|
properties=OBJECT_FIELD_PROPERTIES),
|
||||||
'value': fields.KeywordField(),
|
'value': fields.KeywordField(),
|
||||||
},
|
},
|
||||||
multi=True)
|
multi=True, attr='distillery_type_indexing')
|
||||||
distillery_types = fields.ObjectField(
|
visible_tags = fields.ObjectField(
|
||||||
properties={
|
properties={
|
||||||
'id': fields.IntegerField(attr='id'),
|
'id': fields.IntegerField(attr='id'),
|
||||||
'label': fields.ObjectField(attr='label_indexing',
|
'label': fields.ObjectField(attr='label_indexing',
|
||||||
|
|
@ -153,6 +153,7 @@ class EstablishmentDocument(Document):
|
||||||
'id': fields.IntegerField(attr='id'),
|
'id': fields.IntegerField(attr='id'),
|
||||||
'weekday': fields.IntegerField(attr='weekday'),
|
'weekday': fields.IntegerField(attr='weekday'),
|
||||||
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
||||||
|
'weekday_display_short': fields.KeywordField(attr='weekday_display_short'),
|
||||||
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
||||||
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
||||||
'closed_at_indexing': fields.DateField(),
|
'closed_at_indexing': fields.DateField(),
|
||||||
|
|
|
||||||
|
|
@ -71,5 +71,5 @@ class NewsDocument(Document):
|
||||||
The related_models option should be used with caution because it can lead in the index
|
The related_models option should be used with caution because it can lead in the index
|
||||||
to the updating of a lot of items.
|
to the updating of a lot of items.
|
||||||
"""
|
"""
|
||||||
if isinstance(related_instance, models.NewsType):
|
if isinstance(related_instance, models.NewsType) and hasattr(related_instance, 'news_set'):
|
||||||
return related_instance.news_set.all()
|
return related_instance.news_set.all()
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend):
|
||||||
tag_facets = []
|
tag_facets = []
|
||||||
preserve_ids = []
|
preserve_ids = []
|
||||||
facet_name = '_filter_' + __field
|
facet_name = '_filter_' + __field
|
||||||
all_tag_categories = TagCategoryDocument.search() \
|
all_tag_categories = list(TagCategoryDocument.search() \
|
||||||
.filter('term', public=True) \
|
.filter('term', public=True) \
|
||||||
.filter(Q('term', value_type=TagCategory.LIST) | Q('match', index_name='wine-color'))
|
.filter(Q('term', value_type=TagCategory.LIST) | Q('match', index_name='wine-color'))[0:1000])
|
||||||
for category in all_tag_categories:
|
for category in all_tag_categories:
|
||||||
tags_to_remove = list(map(lambda t: str(t.id), category.tags))
|
tags_to_remove = list(map(lambda t: str(t.id), category.tags))
|
||||||
qs = queryset.__copy__()
|
qs = queryset.__copy__()
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
tags = TagsDocumentSerializer(many=True, source='visible_tags')
|
||||||
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
|
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
distillery_types = TagsDocumentSerializer(many=True, allow_null=True)
|
distillery_type = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
|
artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
|
||||||
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
|
||||||
wine_origins = WineOriginSerializer(many=True)
|
wine_origins = WineOriginSerializer(many=True)
|
||||||
|
|
@ -311,7 +311,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
|
||||||
# 'collections',
|
# 'collections',
|
||||||
'type',
|
'type',
|
||||||
'subtypes',
|
'subtypes',
|
||||||
'distillery_types',
|
'distillery_type',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||||
from tag import models
|
from tag import models
|
||||||
from product import models as product_models
|
from product import models as product_models
|
||||||
|
|
||||||
|
|
||||||
class TagsBaseFilterSet(filters.FilterSet):
|
class TagsBaseFilterSet(filters.FilterSet):
|
||||||
|
|
||||||
# Object type choices
|
# Object type choices
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from tag import models
|
||||||
from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound
|
from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound
|
||||||
from utils.serializers import TranslatedField
|
from utils.serializers import TranslatedField
|
||||||
from utils.models import get_default_locale, get_language, to_locale
|
from utils.models import get_default_locale, get_language, to_locale
|
||||||
|
from main.models import Feature
|
||||||
|
|
||||||
|
|
||||||
def translate_obj(obj):
|
def translate_obj(obj):
|
||||||
|
|
@ -309,48 +310,25 @@ class ChosenTagSerializer(serializers.ModelSerializer):
|
||||||
class ChosenTagBindObjectSerializer(serializers.Serializer):
|
class ChosenTagBindObjectSerializer(serializers.Serializer):
|
||||||
"""Serializer for binding chosen tag and objects"""
|
"""Serializer for binding chosen tag and objects"""
|
||||||
|
|
||||||
ESTABLISHMENT_TYPE = 'establishment_type'
|
feature_id = serializers.IntegerField()
|
||||||
NEWS_TYPE = 'news_type'
|
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
|
||||||
(ESTABLISHMENT_TYPE, 'Establishment type'),
|
|
||||||
(NEWS_TYPE, 'News type'),
|
|
||||||
)
|
|
||||||
|
|
||||||
type = serializers.ChoiceField(TYPE_CHOICES)
|
|
||||||
object_id = serializers.IntegerField()
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
view = self.context.get('view')
|
view = self.context.get('view')
|
||||||
request = self.context.get('request')
|
request = self.context.get('request')
|
||||||
|
|
||||||
obj_type = attrs.get('type')
|
obj_id = attrs.get('feature_id')
|
||||||
obj_id = attrs.get('object_id')
|
|
||||||
|
|
||||||
tag = view.get_object()
|
tag = view.get_object()
|
||||||
attrs['tag'] = tag
|
attrs['tag'] = tag
|
||||||
|
|
||||||
if obj_type == self.ESTABLISHMENT_TYPE:
|
feature = Feature.objects.filter(pk=obj_id). \
|
||||||
establishment_type = EstablishmentType.objects.filter(pk=obj_id). \
|
first()
|
||||||
first()
|
if not feature:
|
||||||
if not establishment_type:
|
raise BindingObjectNotFound()
|
||||||
raise BindingObjectNotFound()
|
if request.method == 'DELETE' and not feature. \
|
||||||
if request.method == 'DELETE' and not establishment_type. \
|
chosen_tags.filter(tag=tag). \
|
||||||
chosen_tags.filter(tag=tag). \
|
exists():
|
||||||
exists():
|
raise RemovedBindingObjectNotFound()
|
||||||
raise RemovedBindingObjectNotFound()
|
attrs['related_object'] = feature
|
||||||
attrs['related_object'] = establishment_type
|
|
||||||
|
|
||||||
elif obj_type == self.NEWS_TYPE:
|
|
||||||
news_type = NewsType.objects.filter(pk=obj_id).first()
|
|
||||||
if not news_type:
|
|
||||||
raise BindingObjectNotFound()
|
|
||||||
if request.method == 'POST' and news_type.chosen_tags. \
|
|
||||||
filter(tag=tag).exists():
|
|
||||||
raise ObjectAlreadyAdded()
|
|
||||||
if request.method == 'DELETE' and not news_type.chosen_tags. \
|
|
||||||
filter(tag=tag).exists():
|
|
||||||
raise RemovedBindingObjectNotFound()
|
|
||||||
attrs['related_object'] = news_type
|
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
params_type = query_params.get('product_type')
|
params_type = query_params.get('product_type')
|
||||||
|
|
||||||
week_days = tuple(map(_, ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")))
|
week_days = tuple(map(_, ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")))
|
||||||
|
short_week_days = tuple(map(_, ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))
|
||||||
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
|
flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday')
|
||||||
filter_flags = {flag_name: False for flag_name in flags}
|
filter_flags = {flag_name: False for flag_name in flags}
|
||||||
additional_flags = []
|
additional_flags = []
|
||||||
|
|
@ -155,7 +156,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
"label_translated": week_days[weekday]
|
"label_translated": short_week_days[weekday],
|
||||||
} for weekday in range(7)]
|
} for weekday in range(7)]
|
||||||
}
|
}
|
||||||
result_list.append(works_noon)
|
result_list.append(works_noon)
|
||||||
|
|
@ -170,7 +171,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
"label_translated": week_days[weekday]
|
"label_translated": short_week_days[weekday],
|
||||||
} for weekday in range(7)]
|
} for weekday in range(7)]
|
||||||
}
|
}
|
||||||
result_list.append(works_evening)
|
result_list.append(works_evening)
|
||||||
|
|
@ -193,7 +194,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet):
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"id": weekday,
|
"id": weekday,
|
||||||
"index_name": week_days[weekday].lower(),
|
"index_name": week_days[weekday].lower(),
|
||||||
"label_translated": week_days[weekday]
|
"label_translated": short_week_days[weekday],
|
||||||
} for weekday in range(7)]
|
} for weekday in range(7)]
|
||||||
}
|
}
|
||||||
result_list.append(works_at_weekday)
|
result_list.append(works_at_weekday)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
from datetime import datetime, time, date, timedelta
|
||||||
|
|
||||||
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 datetime import time, datetime
|
|
||||||
|
|
||||||
from utils.models import ProjectBaseMixin
|
from utils.models import ProjectBaseMixin
|
||||||
|
|
||||||
|
|
@ -24,7 +25,8 @@ class Timetable(ProjectBaseMixin):
|
||||||
(THURSDAY, _('Thursday')),
|
(THURSDAY, _('Thursday')),
|
||||||
(FRIDAY, _('Friday')),
|
(FRIDAY, _('Friday')),
|
||||||
(SATURDAY, _('Saturday')),
|
(SATURDAY, _('Saturday')),
|
||||||
(SUNDAY, _('Sunday')))
|
(SUNDAY, _('Sunday'))
|
||||||
|
)
|
||||||
|
|
||||||
weekday = models.PositiveSmallIntegerField(choices=WEEKDAYS_CHOICES, verbose_name=_('Week day'))
|
weekday = models.PositiveSmallIntegerField(choices=WEEKDAYS_CHOICES, verbose_name=_('Week day'))
|
||||||
|
|
||||||
|
|
@ -51,6 +53,13 @@ class Timetable(ProjectBaseMixin):
|
||||||
f'works_at_noon - {self.works_at_noon}, ' \
|
f'works_at_noon - {self.works_at_noon}, ' \
|
||||||
f'works_at_afternoon: {self.works_at_afternoon})'
|
f'works_at_afternoon: {self.works_at_afternoon})'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weekday_display_short(self):
|
||||||
|
"""Translated short day of the week"""
|
||||||
|
monday = date(2020, 1, 6)
|
||||||
|
with_weekday = monday + timedelta(days=self.weekday)
|
||||||
|
return _(with_weekday.strftime("%a"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def closed_at_str(self):
|
def closed_at_str(self):
|
||||||
return str(self.closed_at) if self.closed_at else None
|
return str(self.closed_at) if self.closed_at else None
|
||||||
|
|
@ -61,11 +70,13 @@ class Timetable(ProjectBaseMixin):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def closed_at_indexing(self):
|
def closed_at_indexing(self):
|
||||||
return datetime.combine(time=self.closed_at, date=datetime(1970, 1, 1 + self.weekday).date()) if self.closed_at else None
|
return datetime.combine(time=self.closed_at,
|
||||||
|
date=datetime(1970, 1, 1 + self.weekday).date()) if self.closed_at else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opening_at_indexing(self):
|
def opening_at_indexing(self):
|
||||||
return datetime.combine(time=self.opening_at, date=datetime(1970, 1, 1 + self.weekday).date()) if self.opening_at else None
|
return datetime.combine(time=self.opening_at,
|
||||||
|
date=datetime(1970, 1, 1 + self.weekday).date()) if self.opening_at else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opening_time(self):
|
def opening_time(self):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
"""Serializer for app timetable"""
|
"""Serializer for app timetable"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
@ -11,8 +14,8 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
NULLABLE_FIELDS = ['lunch_start', 'lunch_end', 'dinner_start',
|
NULLABLE_FIELDS = ['lunch_start', 'lunch_end', 'dinner_start',
|
||||||
'dinner_end', 'opening_at', 'closed_at']
|
'dinner_end', 'opening_at', 'closed_at']
|
||||||
|
|
||||||
weekday_display = serializers.CharField(source='get_weekday_display',
|
weekday_display = serializers.CharField(source='get_weekday_display', read_only=True)
|
||||||
read_only=True)
|
weekday_display_short = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
lunch_start = serializers.TimeField(required=False)
|
lunch_start = serializers.TimeField(required=False)
|
||||||
lunch_end = serializers.TimeField(required=False)
|
lunch_end = serializers.TimeField(required=False)
|
||||||
|
|
@ -29,6 +32,7 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'weekday_display',
|
'weekday_display',
|
||||||
|
'weekday_display_short',
|
||||||
'weekday',
|
'weekday',
|
||||||
'lunch_start',
|
'lunch_start',
|
||||||
'lunch_end',
|
'lunch_end',
|
||||||
|
|
@ -41,13 +45,13 @@ class ScheduleRUDSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""Override validate method"""
|
"""Override validate method"""
|
||||||
establishment_pk = self.context.get('request')\
|
establishment_pk = self.context.get('request') \
|
||||||
.parser_context.get('view')\
|
.parser_context.get('view') \
|
||||||
.kwargs.get('pk')
|
.kwargs.get('pk')
|
||||||
|
|
||||||
establishment_slug = self.context.get('request')\
|
establishment_slug = self.context.get('request') \
|
||||||
.parser_context.get('view')\
|
.parser_context.get('view') \
|
||||||
.kwargs.get('slug')
|
.kwargs.get('slug')
|
||||||
|
|
||||||
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
|
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
|
||||||
|
|
||||||
|
|
@ -91,13 +95,14 @@ class ScheduleCreateSerializer(ScheduleRUDSerializer):
|
||||||
|
|
||||||
class TimetableSerializer(serializers.ModelSerializer):
|
class TimetableSerializer(serializers.ModelSerializer):
|
||||||
"""Serailzier for Timetable model."""
|
"""Serailzier for Timetable model."""
|
||||||
weekday_display = serializers.CharField(source='get_weekday_display',
|
weekday_display = serializers.CharField(source='get_weekday_display', read_only=True)
|
||||||
read_only=True)
|
weekday_display_short = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Timetable
|
model = Timetable
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'weekday_display',
|
'weekday_display',
|
||||||
|
'weekday_display_short',
|
||||||
'works_at_noon',
|
'works_at_noon',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ class Command(BaseCommand):
|
||||||
'product_review',
|
'product_review',
|
||||||
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
|
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
|
||||||
'purchased_plaques', # №6 - перенос купленных тарелок
|
'purchased_plaques', # №6 - перенос купленных тарелок
|
||||||
'fill_city_gallery', # №3 - перенос галереи городов
|
|
||||||
'guides',
|
'guides',
|
||||||
'guide_filters',
|
'guide_filters',
|
||||||
'guide_element_sections',
|
'guide_element_sections',
|
||||||
|
|
@ -51,7 +50,6 @@ class Command(BaseCommand):
|
||||||
'guide_element_label_photo',
|
'guide_element_label_photo',
|
||||||
'guide_complete',
|
'guide_complete',
|
||||||
'update_city_info',
|
'update_city_info',
|
||||||
'migrate_city_gallery',
|
|
||||||
'fix_location',
|
'fix_location',
|
||||||
'remove_old_locations',
|
'remove_old_locations',
|
||||||
'add_fake_country',
|
'add_fake_country',
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class CommentSerializer(serializers.Serializer):
|
||||||
mark = serializers.DecimalField(max_digits=4, decimal_places=2, allow_null=True)
|
mark = serializers.DecimalField(max_digits=4, decimal_places=2, allow_null=True)
|
||||||
account_id = serializers.IntegerField()
|
account_id = serializers.IntegerField()
|
||||||
establishment_id = serializers.CharField()
|
establishment_id = serializers.CharField()
|
||||||
|
state = serializers.CharField()
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data.update({
|
data.update({
|
||||||
|
|
@ -18,14 +19,18 @@ class CommentSerializer(serializers.Serializer):
|
||||||
'mark': self.get_mark(data),
|
'mark': self.get_mark(data),
|
||||||
'content_object': self.get_content_object(data),
|
'content_object': self.get_content_object(data),
|
||||||
'user': self.get_account(data),
|
'user': self.get_account(data),
|
||||||
|
'status': self.get_status(data),
|
||||||
})
|
})
|
||||||
data.pop('establishment_id')
|
data.pop('establishment_id')
|
||||||
data.pop('account_id')
|
data.pop('account_id')
|
||||||
|
data.pop('state')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
try:
|
try:
|
||||||
return Comment.objects.create(**validated_data)
|
comment, _ = Comment.objects.get_or_create(old_id=validated_data.get('old_id'),
|
||||||
|
defaults=validated_data)
|
||||||
|
return comment
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error creating comment with {validated_data}: {e}")
|
raise ValueError(f"Error creating comment with {validated_data}: {e}")
|
||||||
|
|
||||||
|
|
@ -48,3 +53,12 @@ class CommentSerializer(serializers.Serializer):
|
||||||
if not data['mark']:
|
if not data['mark']:
|
||||||
return None
|
return None
|
||||||
return data['mark'] * -1 if data['mark'] < 0 else data['mark']
|
return data['mark'] * -1 if data['mark'] < 0 else data['mark']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_status(data):
|
||||||
|
if data.get('state'):
|
||||||
|
state = data.get('state')
|
||||||
|
if state == 'published':
|
||||||
|
return Comment.PUBLISHED
|
||||||
|
elif state == 'deleted':
|
||||||
|
return Comment.DELETED
|
||||||
|
|
|
||||||
|
|
@ -431,50 +431,6 @@ class CityMapCorrectSerializer(CityMapSerializer):
|
||||||
city.save()
|
city.save()
|
||||||
|
|
||||||
|
|
||||||
class CityGallerySerializer(serializers.ModelSerializer):
|
|
||||||
id = serializers.IntegerField()
|
|
||||||
city_id = serializers.IntegerField()
|
|
||||||
attachment_file_name = serializers.CharField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.CityGallery
|
|
||||||
fields = ("id", "city_id", "attachment_file_name")
|
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
data = self.set_old_id(data)
|
|
||||||
data = self.set_gallery(data)
|
|
||||||
data = self.set_city(data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
return models.CityGallery.objects.create(**validated_data)
|
|
||||||
|
|
||||||
def set_old_id(self, data):
|
|
||||||
data['old_id'] = data.pop('id')
|
|
||||||
return data
|
|
||||||
|
|
||||||
def set_gallery(self, data):
|
|
||||||
link_prefix = "city_photos/00baf486523f62cdf131fa1b19c5df2bf21fc9f8/"
|
|
||||||
try:
|
|
||||||
data['image'] = Image.objects.create(
|
|
||||||
image=f"{link_prefix}{data['attachment_file_name']}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Cannot create image with {data}: {e}")
|
|
||||||
del(data['attachment_file_name'])
|
|
||||||
return data
|
|
||||||
|
|
||||||
def set_city(self, data):
|
|
||||||
try:
|
|
||||||
data['city'] = models.City.objects.get(old_id=data.pop('city_id'))
|
|
||||||
except models.City.DoesNotExist as e:
|
|
||||||
raise ValueError(f"Cannot get city with {data}: {e}")
|
|
||||||
except MultipleObjectsReturned as e:
|
|
||||||
raise ValueError(f"Multiple cities find with {data}: {e}")
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class CepageWineRegionSerializer(TransferSerializerMixin):
|
class CepageWineRegionSerializer(TransferSerializerMixin):
|
||||||
|
|
||||||
CATEGORY_LABEL = 'Cepage'
|
CATEGORY_LABEL = 'Cepage'
|
||||||
|
|
|
||||||
18
apps/translation/migrations/0008_index_siteifdict_text.py
Normal file
18
apps/translation/migrations/0008_index_siteifdict_text.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.7 on 2020-01-17 22:01
|
||||||
|
|
||||||
|
import django.contrib.postgres.indexes
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0007_language_is_active'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='siteinterfacedictionary',
|
||||||
|
index=django.contrib.postgres.indexes.GinIndex(fields=['text'], name='translation_text_0b2bfa_gin'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Translation app models."""
|
"""Translation app models."""
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from django.contrib.postgres.indexes import GinIndex
|
||||||
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 django.apps import apps
|
from django.apps import apps
|
||||||
|
|
@ -105,6 +106,9 @@ class SiteInterfaceDictionary(ProjectBaseMixin):
|
||||||
|
|
||||||
verbose_name = _('Site interface dictionary')
|
verbose_name = _('Site interface dictionary')
|
||||||
verbose_name_plural = _('Site interface dictionary')
|
verbose_name_plural = _('Site interface dictionary')
|
||||||
|
indexes = [
|
||||||
|
GinIndex(fields=['text'])
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.page}: {self.keywords}'
|
return f'{self.page}: {self.keywords}'
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import random
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from functools import reduce
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -11,6 +13,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.gis.geos import Point
|
from django.contrib.gis.geos import Point
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from django.utils.timezone import datetime
|
from django.utils.timezone import datetime
|
||||||
|
from PIL import Image
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
|
|
@ -53,6 +56,19 @@ def username_validator(username: str) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def username_random():
|
||||||
|
"""Generate random username"""
|
||||||
|
username = list('{letters}{digits}'.format(
|
||||||
|
letters=''.join([random.choice(string.ascii_lowercase) for _ in range(4)]),
|
||||||
|
digits=random.randrange(100, 1000)
|
||||||
|
))
|
||||||
|
random.shuffle(username)
|
||||||
|
return '{first}{username}'.format(
|
||||||
|
first=random.choice(string.ascii_lowercase),
|
||||||
|
username=''.join(username)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def image_path(instance, filename):
|
def image_path(instance, filename):
|
||||||
"""Determine avatar path method."""
|
"""Determine avatar path method."""
|
||||||
filename = '%s.jpeg' % generate_code()
|
filename = '%s.jpeg' % generate_code()
|
||||||
|
|
@ -119,6 +135,7 @@ def absolute_url_decorator(func):
|
||||||
return f'{settings.MEDIA_URL}{url_path}/'
|
return f'{settings.MEDIA_URL}{url_path}/'
|
||||||
else:
|
else:
|
||||||
return url_path
|
return url_path
|
||||||
|
|
||||||
return get_absolute_image_url
|
return get_absolute_image_url
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -157,6 +174,23 @@ def transform_into_readable_str(raw_string: str, postfix: str = 'SectionNode'):
|
||||||
return f"{''.join([i.capitalize() for i in result])}"
|
return f"{''.join([i.capitalize() for i in result])}"
|
||||||
|
|
||||||
|
|
||||||
|
def transform_camelcase_to_underscore(raw_string: str):
|
||||||
|
"""
|
||||||
|
Transform str, i.e:
|
||||||
|
from
|
||||||
|
"ContentPage"
|
||||||
|
to
|
||||||
|
"content_page"
|
||||||
|
"""
|
||||||
|
|
||||||
|
re_exp = r'[A-Z][^A-Z]*'
|
||||||
|
result = [i.lower() for i in re.findall(re_exp, raw_string) if i]
|
||||||
|
if result:
|
||||||
|
return reduce(lambda x, y: f'{x}_{y}', result)
|
||||||
|
else:
|
||||||
|
return raw_string
|
||||||
|
|
||||||
|
|
||||||
def section_name_into_index_name(section_name: str):
|
def section_name_into_index_name(section_name: str):
|
||||||
"""
|
"""
|
||||||
Transform slug into section name, i.e:
|
Transform slug into section name, i.e:
|
||||||
|
|
@ -169,3 +203,16 @@ def section_name_into_index_name(section_name: str):
|
||||||
result = re.findall(re_exp, section_name)
|
result = re.findall(re_exp, section_name)
|
||||||
if result:
|
if result:
|
||||||
return f"{' '.join([word.capitalize() if i == 0 else word for i, word in enumerate(result[:-2])])}"
|
return f"{' '.join([word.capitalize() if i == 0 else word for i, word in enumerate(result[:-2])])}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_images_in_text(text):
|
||||||
|
"""Find images urls in text"""
|
||||||
|
return re.findall(r'\<img.+src="([^"]+)".+>', text)
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_meta_by_url(url) -> (int, int, int):
|
||||||
|
"""Returns image size (bytes, width, height)"""
|
||||||
|
image_raw = requests.get(url)
|
||||||
|
image = Image.open(BytesIO(image_raw.content))
|
||||||
|
width, height = image.size
|
||||||
|
return int(image_raw.headers.get('content-length')), width, height
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
"""Custom middlewares."""
|
"""Custom middlewares."""
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.utils import translation, timezone
|
from django.utils import translation, timezone
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from configuration.models import TranslationSettings
|
from configuration.models import TranslationSettings
|
||||||
|
|
@ -7,6 +10,8 @@ from main.methods import determine_user_city
|
||||||
from main.models import SiteSettings
|
from main.models import SiteSettings
|
||||||
from translation.models import Language
|
from translation.models import Language
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_locale(cookie_dict):
|
def get_locale(cookie_dict):
|
||||||
return cookie_dict.get('locale')
|
return cookie_dict.get('locale')
|
||||||
|
|
@ -92,3 +97,26 @@ def user_last_ip(get_response):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return middleware
|
return middleware
|
||||||
|
|
||||||
|
|
||||||
|
def log_db_queries_per_API_request(get_response):
|
||||||
|
"""Middleware-helper to optimize requests performance"""
|
||||||
|
|
||||||
|
def middleware(request):
|
||||||
|
total_time = 0
|
||||||
|
response = get_response(request)
|
||||||
|
for query in connection.queries:
|
||||||
|
query_time = query.get('time')
|
||||||
|
if query_time is None:
|
||||||
|
query_time = query.get('duration', 0) / 1000
|
||||||
|
total_time += float(query_time)
|
||||||
|
|
||||||
|
total_queries = len(connection.queries)
|
||||||
|
if total_queries > 10:
|
||||||
|
logger.error(
|
||||||
|
f'\t{len(connection.queries)} queries run, total {total_time} seconds \t'
|
||||||
|
f'URL: "{request.method} {request.get_full_path_info()}"'
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
return middleware
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ class SORLImageMixin(models.Model):
|
||||||
"""Get image thumbnail url."""
|
"""Get image thumbnail url."""
|
||||||
crop_image = self.get_image(thumbnail_key)
|
crop_image = self.get_image(thumbnail_key)
|
||||||
if hasattr(crop_image, 'url'):
|
if hasattr(crop_image, 'url'):
|
||||||
return self.get_image(thumbnail_key).url
|
return crop_image.url
|
||||||
|
|
||||||
def image_tag(self):
|
def image_tag(self):
|
||||||
"""Admin preview tag."""
|
"""Admin preview tag."""
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,13 @@ from sorl.thumbnail.engines.pil_engine import Engine as PILEngine
|
||||||
class GMEngine(PILEngine):
|
class GMEngine(PILEngine):
|
||||||
|
|
||||||
def create(self, image, geometry, options):
|
def create(self, image, geometry, options):
|
||||||
"""
|
|
||||||
Processing conductor, returns the thumbnail as an image engine instance
|
|
||||||
"""
|
|
||||||
image = self.cropbox(image, geometry, options)
|
image = self.cropbox(image, geometry, options)
|
||||||
image = self.orientation(image, geometry, options)
|
image = self.orientation(image, geometry, options)
|
||||||
image = self.colorspace(image, geometry, options)
|
image = self.colorspace(image, geometry, options)
|
||||||
image = self.remove_border(image, options)
|
image = self.remove_border(image, options)
|
||||||
|
image = self.scale(image, geometry, options)
|
||||||
image = self.crop(image, geometry, options)
|
image = self.crop(image, geometry, options)
|
||||||
|
image = self.scale(image, geometry, options)
|
||||||
image = self.rounded(image, geometry, options)
|
image = self.rounded(image, geometry, options)
|
||||||
image = self.blur(image, geometry, options)
|
image = self.blur(image, geometry, options)
|
||||||
image = self.padding(image, geometry, options)
|
image = self.padding(image, geometry, options)
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,14 @@
|
||||||
./manage.py transfer --overlook
|
./manage.py transfer --overlook
|
||||||
./manage.py transfer --inquiries
|
./manage.py transfer --inquiries
|
||||||
./manage.py transfer --product_review
|
./manage.py transfer --product_review
|
||||||
./manage.py transfer --transfer_text_review
|
./manage.py transfer --transfer_text_review
|
||||||
|
|
||||||
|
# оптимизация изображений
|
||||||
|
/manage.py news_optimize_images # сжимает картинки в описаниях новостей
|
||||||
|
/manage.py update_establishment_image_urls # удаляет неотображаемые картинки из модели заведения
|
||||||
|
|
||||||
|
# сотрудники с позициями для заведений
|
||||||
|
./manage.py add_employee
|
||||||
|
./manage.py add_position
|
||||||
|
./manage.py add_empl_position
|
||||||
|
./manage.py update_employee
|
||||||
|
|
@ -385,6 +385,7 @@ THUMBNAIL_QUALITY = 85
|
||||||
THUMBNAIL_DEBUG = False
|
THUMBNAIL_DEBUG = False
|
||||||
SORL_THUMBNAIL_ALIASES = {
|
SORL_THUMBNAIL_ALIASES = {
|
||||||
'news_preview': {'geometry_string': '300x260', 'crop': 'center'},
|
'news_preview': {'geometry_string': '300x260', 'crop': 'center'},
|
||||||
|
'news_description': {'geometry_string': '100x100'},
|
||||||
'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'},
|
'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'},
|
||||||
'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'},
|
'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'},
|
||||||
'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'},
|
'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'},
|
||||||
|
|
@ -411,6 +412,8 @@ SORL_THUMBNAIL_ALIASES = {
|
||||||
'city_detail': {'geometry_string': '1120x1120', 'crop': 'center'},
|
'city_detail': {'geometry_string': '1120x1120', 'crop': 'center'},
|
||||||
'city_original': {'geometry_string': '2048x1536', 'crop': 'center'},
|
'city_original': {'geometry_string': '2048x1536', 'crop': 'center'},
|
||||||
'type_preview': {'geometry_string': '300x260', 'crop': 'center'},
|
'type_preview': {'geometry_string': '300x260', 'crop': 'center'},
|
||||||
|
'collection_image': {'geometry_string': '940x620', 'upscale': False, 'quality': 100},
|
||||||
|
'establishment_collection_image': {'geometry_string': '940x620', 'upscale': False, 'quality': 100}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -533,3 +536,5 @@ COOKIE_DOMAIN = None
|
||||||
|
|
||||||
ELASTICSEARCH_DSL = {}
|
ELASTICSEARCH_DSL = {}
|
||||||
ELASTICSEARCH_INDEX_NAMES = {}
|
ELASTICSEARCH_INDEX_NAMES = {}
|
||||||
|
|
||||||
|
THUMBNAIL_FORCE_OVERWRITE = True
|
||||||
|
|
|
||||||
|
|
@ -80,4 +80,7 @@ EMAIL_USE_TLS = True
|
||||||
EMAIL_HOST = 'smtp.gmail.com'
|
EMAIL_HOST = 'smtp.gmail.com'
|
||||||
EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com'
|
EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com'
|
||||||
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
|
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
|
|
||||||
|
|
||||||
|
MIDDLEWARE.append('utils.middleware.log_db_queries_per_API_request')
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,15 @@ DATABASES = {
|
||||||
'options': '-c search_path=gm,public'
|
'options': '-c search_path=gm,public'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'legacy': {
|
'legacy': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
# 'HOST': '172.22.0.1',
|
# 'HOST': '172.22.0.1',
|
||||||
'HOST': 'mysql_db',
|
'HOST': 'mysql_db',
|
||||||
'PORT': 3306,
|
'PORT': 3306,
|
||||||
'NAME': 'dev',
|
'NAME': 'dev',
|
||||||
'USER': 'dev',
|
'USER': 'dev',
|
||||||
'PASSWORD': 'octosecret123'
|
'PASSWORD': 'octosecret123'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,11 +84,11 @@ LOGGING = {
|
||||||
'py.warnings': {
|
'py.warnings': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
},
|
},
|
||||||
'django.db.backends': {
|
# 'django.db.backends': {
|
||||||
'handlers': ['console', ],
|
# 'handlers': ['console', ],
|
||||||
'level': 'DEBUG',
|
# 'level': 'DEBUG',
|
||||||
'propagate': False,
|
# 'propagate': False,
|
||||||
},
|
# },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@
|
||||||
{% trans "Please confirm your email address to complete the registration:" %}
|
{% trans "Please confirm your email address to complete the registration:" %}
|
||||||
https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/
|
https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/
|
||||||
<br>
|
<br>
|
||||||
|
{% trans "If you use the mobile app, enter the following code in the form:" %}
|
||||||
|
{{ token }}
|
||||||
|
<br>
|
||||||
{% trans "Thanks for using our site!" %}
|
{% trans "Thanks for using our site!" %}
|
||||||
<br><br>
|
<br><br>
|
||||||
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ django-storages==1.7.2
|
||||||
|
|
||||||
sorl-thumbnail==12.5.0
|
sorl-thumbnail==12.5.0
|
||||||
|
|
||||||
|
|
||||||
PyYAML==5.1.2
|
PyYAML==5.1.2
|
||||||
|
|
||||||
# temp solution
|
# temp solution
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
-r base.txt
|
-r base.txt
|
||||||
ipdb
|
ipdb
|
||||||
ipython
|
ipython
|
||||||
mysqlclient==1.4.4
|
mysqlclient==1.4.4
|
||||||
|
|
||||||
|
pyparsing
|
||||||
|
graphviz
|
||||||
|
pydot
|
||||||
Loading…
Reference in New Issue
Block a user