Merge remote-tracking branch 'origin/develop' into develop
# Conflicts: # celerybeat-schedule
This commit is contained in:
commit
fe76fa64b5
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -21,4 +21,6 @@ logs/
|
|||
/geoip_db/
|
||||
|
||||
# dev
|
||||
./docker-compose.override.yml
|
||||
./docker-compose.override.yml
|
||||
|
||||
celerybeat-schedule
|
||||
|
|
|
|||
40
.gitlab-ci.yml
Normal file
40
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
image: docker:latest
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
- clean
|
||||
|
||||
|
||||
clean:
|
||||
stage: clean
|
||||
script:
|
||||
- docker-compose -f compose-ci.yml stop
|
||||
- docker-compose -f compose-ci.yml rm --force gm_app
|
||||
when: always
|
||||
|
||||
|
||||
buid:
|
||||
stage: build
|
||||
script:
|
||||
- docker-compose -f compose-ci.yml build gm_app
|
||||
when: always
|
||||
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput
|
||||
when: always
|
||||
|
||||
|
||||
|
||||
deploy-develop:
|
||||
stage: deploy
|
||||
only:
|
||||
- develop
|
||||
script:
|
||||
- fab --roles=develop deploy
|
||||
environment:
|
||||
name: Develop
|
||||
|
|
@ -12,7 +12,7 @@ class RoleAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(models.UserRole)
|
||||
class UserRoleAdmin(admin.ModelAdmin):
|
||||
list_display = ['user', 'role']
|
||||
list_display = ['user', 'role', 'establishment']
|
||||
|
||||
|
||||
@admin.register(models.User)
|
||||
|
|
|
|||
18
apps/account/migrations/0009_auto_20191002_0648.py
Normal file
18
apps/account/migrations/0009_auto_20191002_0648.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-02 06:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0008_auto_20190912_1325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(default=None, max_length=254, null=True, unique=True, verbose_name='email address'),
|
||||
),
|
||||
]
|
||||
14
apps/account/migrations/0011_merge_20191011_1336.py
Normal file
14
apps/account/migrations/0011_merge_20191011_1336.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-11 13:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0009_auto_20191002_0648'),
|
||||
('account', '0010_user_password_confirmed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
14
apps/account/migrations/0011_merge_20191014_0839.py
Normal file
14
apps/account/migrations/0011_merge_20191014_0839.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-14 08:39
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0009_auto_20191011_1123'),
|
||||
('account', '0010_user_password_confirmed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
14
apps/account/migrations/0012_merge_20191015_0708.py
Normal file
14
apps/account/migrations/0012_merge_20191015_0708.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-15 07:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0011_merge_20191014_0839'),
|
||||
('account', '0011_merge_20191014_1258'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
14
apps/account/migrations/0012_merge_20191015_0912.py
Normal file
14
apps/account/migrations/0012_merge_20191015_0912.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-15 09:12
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0011_merge_20191014_1258'),
|
||||
('account', '0011_merge_20191011_1336'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
30
apps/account/migrations/0013_auto_20191016_0810.py
Normal file
30
apps/account/migrations/0013_auto_20191016_0810.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-16 08:10
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'),
|
||||
('account', '0012_merge_20191015_0708'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userrole',
|
||||
name='establishment',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='country',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='role',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager')], verbose_name='Role'),
|
||||
),
|
||||
]
|
||||
14
apps/account/migrations/0014_merge_20191023_0959.py
Normal file
14
apps/account/migrations/0014_merge_20191023_0959.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 09:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0009_auto_20191002_0648'),
|
||||
('account', '0013_auto_20191016_0810'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
14
apps/account/migrations/0015_merge_20191023_1317.py
Normal file
14
apps/account/migrations/0015_merge_20191023_1317.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 13:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0014_merge_20191023_0959'),
|
||||
('account', '0012_merge_20191015_0912'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
24
apps/account/migrations/0016_auto_20191024_0830.py
Normal file
24
apps/account/migrations/0016_auto_20191024_0830.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 08:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0015_merge_20191023_1317'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='role',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer')], verbose_name='Role'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userrole',
|
||||
name='establishment',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'),
|
||||
),
|
||||
]
|
||||
24
apps/account/migrations/0016_auto_20191024_0833.py
Normal file
24
apps/account/migrations/0016_auto_20191024_0833.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 08:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0015_merge_20191023_1317'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='role',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer')], verbose_name='Role'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userrole',
|
||||
name='establishment',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'),
|
||||
),
|
||||
]
|
||||
14
apps/account/migrations/0017_merge_20191024_1233.py
Normal file
14
apps/account/migrations/0017_merge_20191024_1233.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 12:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0016_auto_20191024_0830'),
|
||||
('account', '0016_auto_20191024_0833'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
|
|
@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from authorization.models import Application
|
||||
from establishment.models import Establishment
|
||||
from location.models import Country
|
||||
from utils.models import GMTokenGenerator
|
||||
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
||||
|
|
@ -23,14 +24,25 @@ class Role(ProjectBaseMixin):
|
|||
"""Base Role model."""
|
||||
STANDARD_USER = 1
|
||||
COMMENTS_MODERATOR = 2
|
||||
COUNTRY_ADMIN = 3
|
||||
CONTENT_PAGE_MANAGER = 4
|
||||
ESTABLISHMENT_MANAGER = 5
|
||||
REVIEWER_MANGER = 6
|
||||
RESTAURANT_REVIEWER = 7
|
||||
|
||||
ROLE_CHOICES =(
|
||||
ROLE_CHOICES = (
|
||||
(STANDARD_USER, 'Standard user'),
|
||||
(COMMENTS_MODERATOR, 'Comments moderator'),
|
||||
(COUNTRY_ADMIN, 'Country admin'),
|
||||
(CONTENT_PAGE_MANAGER, 'Content page manager'),
|
||||
(ESTABLISHMENT_MANAGER, 'Establishment manager'),
|
||||
(REVIEWER_MANGER, 'Reviewer manager'),
|
||||
(RESTAURANT_REVIEWER, 'Restaurant reviewer')
|
||||
)
|
||||
role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
|
||||
null=False, blank=False)
|
||||
country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE)
|
||||
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||
null=True, blank=True, on_delete=models.SET_NULL)
|
||||
# is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False)
|
||||
# is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False)
|
||||
# is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False)
|
||||
|
|
@ -77,7 +89,7 @@ class User(AbstractUser):
|
|||
blank=True, null=True, default=None)
|
||||
cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'),
|
||||
blank=True, null=True, default=None)
|
||||
email = models.EmailField(_('email address'), blank=True,
|
||||
email = models.EmailField(_('email address'), unique=True,
|
||||
null=True, default=None)
|
||||
unconfirmed_email = models.EmailField(_('unconfirmed email'), blank=True, null=True, default=None)
|
||||
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||
|
|
@ -202,6 +214,15 @@ class User(AbstractUser):
|
|||
template_name=settings.RESETTING_TOKEN_TEMPLATE,
|
||||
context=context)
|
||||
|
||||
def notify_password_changed_template(self, country_code):
|
||||
"""Get notification email template"""
|
||||
context = {'contry_code': country_code}
|
||||
context.update(self.base_template)
|
||||
return render_to_string(
|
||||
template_name=settings.NOTIFICATION_PASSWORD_TEMPLATE,
|
||||
context=context,
|
||||
)
|
||||
|
||||
def confirm_email_template(self, country_code):
|
||||
"""Get confirm email template"""
|
||||
context = {'token': self.confirm_email_token,
|
||||
|
|
@ -224,4 +245,6 @@ class User(AbstractUser):
|
|||
class UserRole(ProjectBaseMixin):
|
||||
"""UserRole model."""
|
||||
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
|
||||
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
|
||||
role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True)
|
||||
establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'),
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
|
@ -127,6 +127,14 @@ class ChangePasswordSerializer(serializers.ModelSerializer):
|
|||
except serializers.ValidationError as e:
|
||||
raise serializers.ValidationError({'detail': e.detail})
|
||||
else:
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_password_changed_email(
|
||||
user_id=self.instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
else:
|
||||
tasks.send_password_changed_email(
|
||||
user_id=self.instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
return attrs
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""Serializers for account web"""
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import password_validation as password_validators
|
||||
from rest_framework import serializers
|
||||
|
||||
from account import models
|
||||
from account import models, tasks
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.methods import username_validator
|
||||
|
||||
|
|
@ -68,4 +69,12 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer):
|
|||
# Update user password from instance
|
||||
instance.set_password(validated_data.get('password'))
|
||||
instance.save()
|
||||
if settings.USE_CELERY:
|
||||
tasks.send_password_changed_email(
|
||||
user_id=instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
else:
|
||||
tasks.send_password_changed_email(
|
||||
user_id=instance.id,
|
||||
country_code=self.context.get('request').country_code)
|
||||
return instance
|
||||
|
|
|
|||
|
|
@ -1,47 +1,46 @@
|
|||
"""Account app celery tasks."""
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import models
|
||||
from account.models import User
|
||||
|
||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def send_email(user_id: int, subject: str, message_prop: str, country_code: str):
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
user.send_email(subject=_(subject),
|
||||
message=getattr(user, message_prop, lambda _: '')(country_code))
|
||||
except:
|
||||
cur_frame = inspect.currentframe()
|
||||
cal_frame = inspect.getouterframes(cur_frame, 2)
|
||||
logger.error(f'METHOD_NAME: {cal_frame[1][3]}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_reset_password_email(user_id, country_code):
|
||||
"""Send email to user for reset password."""
|
||||
try:
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Password resetting'),
|
||||
message=user.reset_password_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
|
||||
f'DETAIL: Exception occurred for reset password: '
|
||||
f'{user_id}')
|
||||
send_email(user_id, 'Password_resetting', 'reset_password_template', country_code)
|
||||
|
||||
|
||||
@shared_task
|
||||
def confirm_new_email_address(user_id, country_code):
|
||||
"""Send email to user new email."""
|
||||
try:
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Validate new email address'),
|
||||
message=user.confirm_email_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
send_email(user_id, 'Confirm new email address', 'confirm_email_template', country_code)
|
||||
|
||||
|
||||
@shared_task
|
||||
def change_email_address(user_id, country_code):
|
||||
"""Send email to user new email."""
|
||||
try:
|
||||
user = models.User.objects.get(id=user_id)
|
||||
user.send_email(subject=_('Validate new email address'),
|
||||
message=user.change_email_template(country_code))
|
||||
except:
|
||||
logger.error(f'METHOD_NAME: {change_email_address.__name__}\n'
|
||||
f'DETAIL: Exception occurred for user: {user_id}')
|
||||
send_email(user_id, 'Validate new email address', 'change_email_template', country_code)
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_password_changed_email(user_id, country_code):
|
||||
"""Send email which notifies user that his password had changed"""
|
||||
send_email(user_id, 'Notify password changed', 'notify_password_changed_template', country_code)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
from django.urls import reverse
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from account.models import Role, User
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
from location.models import Country
|
||||
from account.models import Role, User, UserRole
|
||||
|
||||
|
||||
class RoleTests(APITestCase):
|
||||
def setUp(self):
|
||||
|
|
@ -65,9 +68,11 @@ class UserRoleTests(APITestCase):
|
|||
)
|
||||
self.role.save()
|
||||
|
||||
self.user_test = User.objects.create_user(username='test',
|
||||
email='testemail@mail.com',
|
||||
password='passwordtest')
|
||||
self.user_test = User.objects.create_user(
|
||||
username='test',
|
||||
email='testemail@mail.com',
|
||||
password='passwordtest'
|
||||
)
|
||||
|
||||
def test_user_role_post(self):
|
||||
url = reverse('back:account:user-role-list-create')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
from http.cookies import SimpleCookie
|
||||
from account.models import User
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from account.models import User
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
|
||||
|
||||
class AccountUserTests(APITestCase):
|
||||
|
|
@ -62,7 +64,7 @@ class AccountChangePasswordTests(APITestCase):
|
|||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class AccountChangePasswordTests(APITestCase):
|
||||
class AccountConfirmEmail(APITestCase):
|
||||
def setUp(self):
|
||||
self.data = get_tokens_for_user()
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
|||
queryset = models.User.objects.active()
|
||||
|
||||
def get_object(self):
|
||||
"""Override get_object method
|
||||
"""
|
||||
"""Override get_object method"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
uidb64 = self.kwargs.get('uidb64')
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,6 @@ from utils.tokens import GMRefreshToken
|
|||
# Serializers
|
||||
class SignupSerializer(serializers.ModelSerializer):
|
||||
"""Signup serializer serializer mixin"""
|
||||
# REQUEST
|
||||
username = serializers.CharField(write_only=True)
|
||||
password = serializers.CharField(write_only=True)
|
||||
email = serializers.EmailField(write_only=True)
|
||||
newsletter = serializers.BooleanField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = account_models.User
|
||||
fields = (
|
||||
|
|
@ -32,6 +26,12 @@ class SignupSerializer(serializers.ModelSerializer):
|
|||
'email',
|
||||
'newsletter'
|
||||
)
|
||||
extra_kwargs = {
|
||||
'username': {'write_only': True},
|
||||
'password': {'write_only': True},
|
||||
'email': {'write_only': True},
|
||||
'newsletter': {'write_only': True}
|
||||
}
|
||||
|
||||
def validate_username(self, value):
|
||||
"""Custom username validation"""
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"""Authorization app celery tasks."""
|
||||
import logging
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from account import models as account_models
|
||||
from smtplib import SMTPException
|
||||
|
||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from account.models import User
|
||||
from django.urls import reverse
|
||||
# Create your tests here.
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from account.models import User
|
||||
|
||||
|
||||
def get_tokens_for_user(
|
||||
|
|
@ -28,7 +28,7 @@ class AuthorizationTests(APITestCase):
|
|||
self.password = data["password"]
|
||||
|
||||
def LoginTests(self):
|
||||
data ={
|
||||
data = {
|
||||
'username_or_email': self.username,
|
||||
'password': self.password,
|
||||
'remember': True
|
||||
|
|
|
|||
23
apps/collection/migrations/0014_auto_20191022_1242.py
Normal file
23
apps/collection/migrations/0014_auto_20191022_1242.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-22 12:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('collection', '0013_collection_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='collection',
|
||||
name='end',
|
||||
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='guide',
|
||||
name='end',
|
||||
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'),
|
||||
),
|
||||
]
|
||||
44
apps/collection/migrations/0015_auto_20191023_0715.py
Normal file
44
apps/collection/migrations/0015_auto_20191023_0715.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 07:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import utils.models
|
||||
|
||||
|
||||
def fill_title_json_from_title(apps, schema_editor):
|
||||
# We can't import the Person model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
Collection = apps.get_model('collection', 'Collection')
|
||||
for collection in Collection.objects.all():
|
||||
collection.name_json = {'en-GB': collection.name}
|
||||
collection.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('collection', '0014_auto_20191022_1242'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='name_json',
|
||||
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name'),
|
||||
),
|
||||
migrations.RunPython(fill_title_json_from_title, migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name='collection',
|
||||
name='name',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='collection',
|
||||
old_name='name_json',
|
||||
new_name='name',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='collection',
|
||||
name='name',
|
||||
field=utils.models.TJSONField(help_text='{"en-GB":"some text"}', verbose_name='name'),
|
||||
),
|
||||
]
|
||||
19
apps/collection/migrations/0016_auto_20191024_1334.py
Normal file
19
apps/collection/migrations/0016_auto_20191024_1334.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 13:34
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('collection', '0015_auto_20191023_0715'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='guide',
|
||||
name='collection',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='collection.Collection', verbose_name='collection'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
from django.contrib.postgres.fields import JSONField
|
||||
from django.contrib.contenttypes.fields import ContentType
|
||||
|
||||
from utils.models import TJSONField
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utils.models import ProjectBaseMixin, URLImageMixin
|
||||
from utils.models import TJSONField
|
||||
from utils.models import TranslatedFieldsMixin
|
||||
|
||||
from utils.querysets import RelatedObjectsCountMixin
|
||||
|
||||
|
||||
|
|
@ -24,7 +22,8 @@ class CollectionNameMixin(models.Model):
|
|||
class CollectionDateMixin(models.Model):
|
||||
"""CollectionDate mixin"""
|
||||
start = models.DateTimeField(_('start'))
|
||||
end = models.DateTimeField(_('end'))
|
||||
end = models.DateTimeField(blank=True, null=True, default=None,
|
||||
verbose_name=_('end'))
|
||||
|
||||
class Meta:
|
||||
"""Meta class"""
|
||||
|
|
@ -44,9 +43,11 @@ class CollectionQuerySet(RelatedObjectsCountMixin):
|
|||
return self.filter(is_publish=True)
|
||||
|
||||
|
||||
class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin,
|
||||
class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||
TranslatedFieldsMixin, URLImageMixin):
|
||||
"""Collection model."""
|
||||
STR_FIELD_NAME = 'name'
|
||||
|
||||
ORDINARY = 0 # Ordinary collection
|
||||
POP = 1 # POP collection
|
||||
|
||||
|
|
@ -55,6 +56,8 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin,
|
|||
(POP, _('Pop')),
|
||||
)
|
||||
|
||||
name = TJSONField(verbose_name=_('name'),
|
||||
help_text='{"en-GB":"some text"}')
|
||||
collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES,
|
||||
default=ORDINARY,
|
||||
verbose_name=_('Collection type'))
|
||||
|
|
@ -80,10 +83,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin,
|
|||
verbose_name = _('collection')
|
||||
verbose_name_plural = _('collections')
|
||||
|
||||
def __str__(self):
|
||||
"""String method."""
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class GuideQuerySet(models.QuerySet):
|
||||
"""QuerySet for Guide."""
|
||||
|
|
@ -102,8 +101,9 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
|||
advertorials = JSONField(
|
||||
_('advertorials'), null=True, blank=True,
|
||||
default=None, help_text='{"key":"value"}')
|
||||
collection = models.ForeignKey(
|
||||
Collection, verbose_name=_('collection'), on_delete=models.CASCADE)
|
||||
collection = models.ForeignKey(Collection, on_delete=models.CASCADE,
|
||||
null=True, blank=True, default=None,
|
||||
verbose_name=_('collection'))
|
||||
|
||||
objects = GuideQuerySet.as_manager()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ from rest_framework import serializers
|
|||
|
||||
from collection import models
|
||||
from location import models as location_models
|
||||
from utils.serializers import TranslatedField
|
||||
|
||||
|
||||
class CollectionBaseSerializer(serializers.ModelSerializer):
|
||||
"""Collection base serializer"""
|
||||
# RESPONSE
|
||||
description_translated = serializers.CharField(read_only=True, allow_null=True)
|
||||
name_translated = TranslatedField()
|
||||
description_translated = TranslatedField()
|
||||
|
||||
class Meta:
|
||||
model = models.Collection
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'name_translated',
|
||||
'description_translated',
|
||||
'image_url',
|
||||
'slug',
|
||||
|
|
@ -35,8 +36,7 @@ class CollectionSerializer(CollectionBaseSerializer):
|
|||
queryset=location_models.Country.objects.all(),
|
||||
write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Collection
|
||||
class Meta(CollectionBaseSerializer.Meta):
|
||||
fields = CollectionBaseSerializer.Meta.fields + [
|
||||
'start',
|
||||
'end',
|
||||
|
|
|
|||
|
|
@ -40,12 +40,13 @@ class CollectionDetailTests(BaseTestCase):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
country = Country.objects.first()
|
||||
if not country:
|
||||
country = Country.objects.create(
|
||||
name=json.dumps({"en-GB": "Test country"}),
|
||||
code="en"
|
||||
)
|
||||
# country = Country.objects.first()
|
||||
# if not country:
|
||||
country = Country.objects.create(
|
||||
name=json.dumps({"en-GB": "Test country"}),
|
||||
code="en"
|
||||
)
|
||||
country.save()
|
||||
|
||||
self.collection = Collection.objects.create(
|
||||
name='Test collection',
|
||||
|
|
@ -56,6 +57,8 @@ class CollectionDetailTests(BaseTestCase):
|
|||
slug='test-collection-slug',
|
||||
)
|
||||
|
||||
self.collection.save()
|
||||
|
||||
def test_collection_detail_Read(self):
|
||||
response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en',
|
||||
format='json')
|
||||
|
|
@ -66,7 +69,7 @@ class CollectionGuideTests(CollectionDetailTests):
|
|||
|
||||
def test_guide_list_Read(self):
|
||||
response = self.client.get('/api/web/collections/guides/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class CollectionGuideDetailTests(CollectionDetailTests):
|
||||
|
|
@ -78,6 +81,7 @@ class CollectionGuideDetailTests(CollectionDetailTests):
|
|||
start=datetime.now(pytz.utc),
|
||||
end=datetime.now(pytz.utc)
|
||||
)
|
||||
self.guide.save()
|
||||
|
||||
def test_guide_detail_Read(self):
|
||||
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
|
||||
|
|
|
|||
24
apps/comment/migrations/0003_auto_20191015_0704.py
Normal file
24
apps/comment/migrations/0003_auto_20191015_0704.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-15 07:04
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('location', '0012_data_migrate'),
|
||||
('comment', '0002_comment_language'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='comment',
|
||||
name='language',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='country',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'),
|
||||
),
|
||||
]
|
||||
|
|
@ -7,6 +7,8 @@ from account.models import User
|
|||
from utils.models import ProjectBaseMixin
|
||||
from utils.querysets import ContentTypeQuerySetMixin
|
||||
from translation.models import Language
|
||||
from location.models import Country
|
||||
|
||||
|
||||
class CommentQuerySet(ContentTypeQuerySetMixin):
|
||||
"""QuerySets for Comment model."""
|
||||
|
|
@ -41,7 +43,8 @@ class Comment(ProjectBaseMixin):
|
|||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
objects = CommentQuerySet.as_manager()
|
||||
language = models.ForeignKey(Language, verbose_name=_('Locale'), on_delete=models.SET_NULL, null=True)
|
||||
country = models.ForeignKey(Country, verbose_name=_('Country'),
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class"""
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
from rest_framework import permissions
|
||||
from account.models import UserRole, Role, User
|
||||
|
||||
|
||||
class IsCommentModerator(permissions.IsAuthenticatedOrReadOnly):
|
||||
"""
|
||||
Object-level permission to only allow owners of an object to edit it.
|
||||
Assumes the model instance has an `owner` attribute.
|
||||
"""
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
# Read permissions are allowed to any request,
|
||||
# so we'll always allow GET, HEAD or OPTIONS requests.
|
||||
if request.method in permissions.SAFE_METHODS or \
|
||||
obj.user == request.user or request.user.is_superuser:
|
||||
return True
|
||||
|
||||
# Must have role
|
||||
role = Role.objects.filter(role=Role.COMMENTS_MODERATOR,
|
||||
country__languages__id=obj.language_id)\
|
||||
.first() # 'Comments moderator'
|
||||
|
||||
is_access = UserRole.objects.filter(user=request.user, role=role).exists()
|
||||
if obj.user != request.user and is_access:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
|
@ -6,4 +6,4 @@ from rest_framework import serializers
|
|||
class CommentBaseSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Comment
|
||||
fields = ('id', 'text', 'mark', 'user')
|
||||
fields = ('id', 'text', 'mark', 'user', 'object_id', 'content_type')
|
||||
|
|
@ -1,32 +1,18 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
from django.urls import reverse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from http.cookies import SimpleCookie
|
||||
from location.models import Country
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from account.models import Role, User, UserRole
|
||||
from authorization.tests.tests_authorization import get_tokens_for_user
|
||||
from comment.models import Comment
|
||||
from translation.models import Language
|
||||
from utils.tests.tests_permissions import BasePermissionTests
|
||||
|
||||
|
||||
class CommentModeratorPermissionTests(APITestCase):
|
||||
class CommentModeratorPermissionTests(BasePermissionTests):
|
||||
def setUp(self):
|
||||
|
||||
self.lang = Language.objects.create(
|
||||
title='Russia',
|
||||
locale='ru-RU'
|
||||
)
|
||||
self.lang.save()
|
||||
|
||||
self.country_ru = Country.objects.create(
|
||||
name='{"ru-RU":"Russia"}',
|
||||
code='23',
|
||||
low_price=15,
|
||||
high_price=150000,
|
||||
)
|
||||
self.country_ru.languages.add(self.lang)
|
||||
self.country_ru.save()
|
||||
super().setUp()
|
||||
|
||||
self.role = Role.objects.create(
|
||||
role=2,
|
||||
|
|
@ -44,21 +30,47 @@ class CommentModeratorPermissionTests(APITestCase):
|
|||
)
|
||||
self.userRole.save()
|
||||
|
||||
content_type = ContentType.objects.get(app_label='location', model='country')
|
||||
self.content_type = ContentType.objects.get(app_label='location', model='country')
|
||||
|
||||
self.user_test = get_tokens_for_user()
|
||||
self.comment = Comment.objects.create(text='Test comment', mark=1,
|
||||
user=self.user_test["user"],
|
||||
object_id= self.country_ru.pk,
|
||||
content_type_id=content_type.id,
|
||||
language=self.lang
|
||||
object_id=self.country_ru.pk,
|
||||
content_type_id=self.content_type.id,
|
||||
country=self.country_ru
|
||||
)
|
||||
self.comment.save()
|
||||
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id})
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get(self.url, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
def test_post(self):
|
||||
self.url = reverse('back:comment:comment-list-create')
|
||||
|
||||
comment = {
|
||||
"text": "Test comment POST",
|
||||
"user": self.user_test["user"].id,
|
||||
"object_id": self.country_ru.pk,
|
||||
"content_type": self.content_type.id,
|
||||
"country_id": self.country_ru.id
|
||||
}
|
||||
|
||||
response = self.client.post(self.url, format='json', data=comment)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
comment = {
|
||||
"text": "Test comment POST moder",
|
||||
"user": self.moderator.id,
|
||||
"object_id": self.country_ru.id,
|
||||
"content_type": self.content_type.id,
|
||||
"country_id": self.country_ru.id
|
||||
}
|
||||
|
||||
tokens = User.create_jwt_tokens(self.moderator)
|
||||
self.client.cookies = SimpleCookie(
|
||||
{'access_token': tokens.get('access_token'),
|
||||
'refresh_token': tokens.get('access_token')})
|
||||
|
||||
response = self.client.post(self.url, format='json', data=comment)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_put_moderator(self):
|
||||
tokens = User.create_jwt_tokens(self.moderator)
|
||||
|
|
@ -70,12 +82,18 @@ class CommentModeratorPermissionTests(APITestCase):
|
|||
"id": self.comment.id,
|
||||
"text": "test text moderator",
|
||||
"mark": 1,
|
||||
"user": self.moderator.id
|
||||
"user": self.moderator.id,
|
||||
"object_id": self.comment.country_id,
|
||||
"content_type": self.content_type.id
|
||||
}
|
||||
|
||||
response = self.client.put(self.url, data=data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get(self.url, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_put_other_user(self):
|
||||
other_user = User.objects.create_user(username='test',
|
||||
email='test@mail.com',
|
||||
|
|
@ -113,11 +131,11 @@ class CommentModeratorPermissionTests(APITestCase):
|
|||
"id": self.comment.id,
|
||||
"text": "test text moderator",
|
||||
"mark": 1,
|
||||
"user": super_user.id
|
||||
"user": super_user.id,
|
||||
"object_id": self.country_ru.id,
|
||||
"content_type": self.content_type.id,
|
||||
}
|
||||
|
||||
response = self.client.put(self.url, data=data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
from rest_framework import generics, permissions
|
||||
from comment.serializers import back as serializers
|
||||
from comment import models
|
||||
from comment.permissions import IsCommentModerator
|
||||
from utils.permissions import IsCommentModerator, IsCountryAdmin
|
||||
|
||||
|
||||
class CommentLstView(generics.ListCreateAPIView):
|
||||
"""Comment list create view."""
|
||||
serializer_class = serializers.CommentBaseSerializer
|
||||
queryset = models.Comment.objects.all()
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin]
|
||||
|
||||
|
||||
class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Comment RUD view."""
|
||||
serializer_class = serializers.CommentBaseSerializer
|
||||
queryset = models.Comment.objects.all()
|
||||
permission_classes = [IsCommentModerator]
|
||||
|
||||
permission_classes = [IsCountryAdmin | IsCommentModerator]
|
||||
lookup_field = 'id'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from comment.models import Comment
|
||||
from establishment import models
|
||||
from main.models import Award, MetaDataContent
|
||||
from main.models import Award
|
||||
from review import models as review_models
|
||||
|
||||
|
||||
|
|
@ -24,11 +24,6 @@ class AwardInline(GenericTabularInline):
|
|||
extra = 0
|
||||
|
||||
|
||||
class MetaDataContentInline(GenericTabularInline):
|
||||
model = MetaDataContent
|
||||
extra = 0
|
||||
|
||||
|
||||
class ContactPhoneInline(admin.TabularInline):
|
||||
"""Contact phone inline admin."""
|
||||
model = models.ContactPhone
|
||||
|
|
@ -56,8 +51,7 @@ class EstablishmentAdmin(admin.ModelAdmin):
|
|||
"""Establishment admin."""
|
||||
list_display = ['id', '__str__', 'image_tag', ]
|
||||
inlines = [
|
||||
AwardInline, MetaDataContentInline,
|
||||
ContactPhoneInline, ContactEmailInline,
|
||||
AwardInline, ContactPhoneInline, ContactEmailInline,
|
||||
ReviewInline, CommentInline]
|
||||
|
||||
|
||||
|
|
@ -84,4 +78,4 @@ class MenuAdmin(admin.ModelAdmin):
|
|||
"""Get user's short name."""
|
||||
return obj.category_translated
|
||||
|
||||
category_translated.short_description = _('category')
|
||||
category_translated.short_description = _('category')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Establishment app filters."""
|
||||
from django.core.validators import EMPTY_VALUES
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from establishment import models
|
||||
|
||||
|
||||
|
|
@ -10,6 +11,10 @@ class EstablishmentFilter(filters.FilterSet):
|
|||
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
||||
award_id = filters.NumberFilter(field_name='awards__id',)
|
||||
search = filters.CharFilter(method='search_text')
|
||||
type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES,
|
||||
method='by_type')
|
||||
subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES,
|
||||
method='by_subtype')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -19,6 +24,8 @@ class EstablishmentFilter(filters.FilterSet):
|
|||
'tag_id',
|
||||
'award_id',
|
||||
'search',
|
||||
'type',
|
||||
'subtype',
|
||||
)
|
||||
|
||||
def search_text(self, queryset, name, value):
|
||||
|
|
@ -26,3 +33,27 @@ class EstablishmentFilter(filters.FilterSet):
|
|||
if value not in EMPTY_VALUES:
|
||||
return queryset.search(value, locale=self.request.locale)
|
||||
return queryset
|
||||
|
||||
def by_type(self, queryset, name, value):
|
||||
if value not in EMPTY_VALUES:
|
||||
return queryset.by_type(value)
|
||||
return queryset
|
||||
|
||||
def by_subtype(self, queryset, name, value):
|
||||
if value not in EMPTY_VALUES:
|
||||
return queryset.by_subtype(value)
|
||||
return queryset
|
||||
|
||||
|
||||
class EstablishmentTypeTagFilter(filters.FilterSet):
|
||||
"""Establishment tag filter set."""
|
||||
|
||||
type_id = filters.NumberFilter(field_name='id')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.EstablishmentType
|
||||
fields = (
|
||||
'type_id',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from pytz import timezone as py_tz
|
||||
from timezonefinder import TimezoneFinder
|
||||
from establishment.models import Establishment
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Attach correct timestamps according to coordinates to existing establishments'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.tf = TimezoneFinder(in_memory=True)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for establishment in Establishment.objects.prefetch_related('address').all():
|
||||
if establishment.address and establishment.address.latitude and establishment.address.longitude:
|
||||
establishment.tz = py_tz(self.tf.certain_timezone_at(lng=establishment.address.longitude,
|
||||
lat=establishment.address.latitude))
|
||||
establishment.save()
|
||||
self.stdout.write(self.style.SUCCESS(f'Attached timezone {establishment.tz} to {establishment}'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(f'Establishment {establishment} has no coordinates'
|
||||
f'passing...'))
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-09 07:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0031_establishment_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EstablishmentTag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'establishment tag',
|
||||
'verbose_name_plural': 'establishment tags',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EstablishmentTypeTagCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('establishment_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'establishment type tag categories',
|
||||
'verbose_name_plural': 'establishment type tag categories',
|
||||
},
|
||||
),
|
||||
]
|
||||
30
apps/establishment/migrations/0033_auto_20191009_0715.py
Normal file
30
apps/establishment/migrations/0033_auto_20191009_0715.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-09 07:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tag', '0001_initial'),
|
||||
('establishment', '0032_establishmenttag_establishmenttypetagcategory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='establishmenttypetagcategory',
|
||||
name='tag_category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='establishmenttag',
|
||||
name='establishment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='establishment.Establishment', verbose_name='establishment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='establishmenttag',
|
||||
name='tag',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.Tag', verbose_name='tag'),
|
||||
),
|
||||
]
|
||||
14
apps/establishment/migrations/0034_merge_20191009_1457.py
Normal file
14
apps/establishment/migrations/0034_merge_20191009_1457.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-09 14:57
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0033_auto_20191009_0715'),
|
||||
('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-11 10:47
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tag', '0002_auto_20191009_1408'),
|
||||
('establishment', '0034_merge_20191009_1457'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EstablishmentSubTypeTagCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('establishment_subtype', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentSubType', verbose_name='establishment subtype')),
|
||||
('tag_category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_subtype_tag_categories', to='tag.TagCategory', verbose_name='tag category')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'establishment subtype tag categories',
|
||||
'verbose_name_plural': 'establishment subtype tag categories',
|
||||
},
|
||||
),
|
||||
]
|
||||
18
apps/establishment/migrations/0036_auto_20191011_1356.py
Normal file
18
apps/establishment/migrations/0036_auto_20191011_1356.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-11 13:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0035_establishmentsubtypetagcategory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='establishment',
|
||||
name='establishment_subtypes',
|
||||
field=models.ManyToManyField(blank=True, related_name='subtype_establishment', to='establishment.EstablishmentSubType', verbose_name='subtype'),
|
||||
),
|
||||
]
|
||||
54
apps/establishment/migrations/0037_auto_20191015_1404.py
Normal file
54
apps/establishment/migrations/0037_auto_20191015_1404.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-15 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tag', '0002_auto_20191009_1408'),
|
||||
('establishment', '0036_auto_20191011_1356'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='establishmenttag',
|
||||
name='establishment',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='establishmenttag',
|
||||
name='tag',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='establishmenttypetagcategory',
|
||||
name='establishment_type',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='establishmenttypetagcategory',
|
||||
name='tag_category',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='establishment',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(related_name='establishments', to='tag.Tag', verbose_name='Tag'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='establishmentsubtype',
|
||||
name='tag_categories',
|
||||
field=models.ManyToManyField(related_name='establishment_subtypes', to='tag.TagCategory', verbose_name='Tag'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='establishmenttype',
|
||||
name='tag_categories',
|
||||
field=models.ManyToManyField(related_name='establishment_types', to='tag.TagCategory', verbose_name='Tag'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EstablishmentSubTypeTagCategory',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EstablishmentTag',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EstablishmentTypeTagCategory',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-16 11:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def fill_establishment_type(apps, schema_editor):
|
||||
# We can't import the Person model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
EstablishmentType = apps.get_model('establishment', 'EstablishmentType')
|
||||
for n, et in enumerate(EstablishmentType.objects.all()):
|
||||
et.index_name = f'Type {n}'
|
||||
et.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0037_auto_20191015_1404'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='establishmenttype',
|
||||
name='index_name',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||
),
|
||||
migrations.RunPython(fill_establishment_type, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='establishmenttype',
|
||||
name='index_name',
|
||||
field=models.CharField(choices=[('restaurant', 'Restaurant'), ('artisan', 'Artisan'),
|
||||
('producer', 'Producer')], db_index=True, max_length=50,
|
||||
unique=True, verbose_name='Index name'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-18 13:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def fill_establishment_subtype(apps, schema_editor):
|
||||
# We can't import the Person model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType')
|
||||
for n, et in enumerate(EstablishmentSubType.objects.all()):
|
||||
et.index_name = 'Type %s' % n
|
||||
et.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0038_establishmenttype_index_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='establishmentsubtype',
|
||||
name='index_name',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||
),
|
||||
migrations.RunPython(fill_establishment_subtype, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='establishmentsubtype',
|
||||
name='index_name',
|
||||
field=models.CharField(choices=[('winery', 'Winery'), ], db_index=True, max_length=50,
|
||||
unique=True, verbose_name='Index name'),
|
||||
),
|
||||
|
||||
]
|
||||
19
apps/establishment/migrations/0040_employee_tags.py
Normal file
19
apps/establishment/migrations/0040_employee_tags.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-22 13:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tag', '0004_tag_priority'),
|
||||
('establishment', '0039_establishmentsubtype_index_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='employee',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(related_name='employees', to='tag.Tag', verbose_name='Tags'),
|
||||
),
|
||||
]
|
||||
18
apps/establishment/migrations/0041_auto_20191023_0920.py
Normal file
18
apps/establishment/migrations/0041_auto_20191023_0920.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 09:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0040_employee_tags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='establishment',
|
||||
name='slug',
|
||||
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Establishment slug'),
|
||||
),
|
||||
]
|
||||
21
apps/establishment/migrations/0042_establishment_tz.py
Normal file
21
apps/establishment/migrations/0042_establishment_tz.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-21 17:33
|
||||
|
||||
import timezone_field.fields
|
||||
from django.db import migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0041_auto_20191023_0920'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='establishment',
|
||||
name='tz',
|
||||
field=timezone_field.fields.TimeZoneField(default=settings.TIME_ZONE),
|
||||
),
|
||||
]
|
||||
20
apps/establishment/migrations/0043_establishment_currency.py
Normal file
20
apps/establishment/migrations/0043_establishment_currency.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 13:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0022_auto_20191023_1113'),
|
||||
('establishment', '0042_establishment_tz'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='establishment',
|
||||
name='currency',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Currency', verbose_name='currency'),
|
||||
),
|
||||
]
|
||||
18
apps/establishment/migrations/0044_position_index_name.py
Normal file
18
apps/establishment/migrations/0044_position_index_name.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-24 14:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('establishment', '0043_establishment_currency'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='position',
|
||||
name='index_name',
|
||||
field=models.CharField(db_index=True, max_length=255, null=True, unique=True, verbose_name='Index name'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
"""Establishment models."""
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
|
||||
import elasticsearch_dsl
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes import fields as generic
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
|
|
@ -15,10 +17,11 @@ from phonenumber_field.modelfields import PhoneNumberField
|
|||
|
||||
from collection.models import Collection
|
||||
from location.models import Address
|
||||
from main.models import Award, MetaDataContent
|
||||
from main.models import Award, Currency
|
||||
from review.models import Review
|
||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||
TranslatedFieldsMixin, BaseAttributes)
|
||||
from timezone_field import TimeZoneField
|
||||
|
||||
|
||||
# todo: establishment type&subtypes check
|
||||
|
|
@ -27,9 +30,26 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
|
|||
|
||||
STR_FIELD_NAME = 'name'
|
||||
|
||||
# INDEX NAME CHOICES
|
||||
RESTAURANT = 'restaurant'
|
||||
ARTISAN = 'artisan'
|
||||
PRODUCER = 'producer'
|
||||
|
||||
INDEX_NAME_TYPES = (
|
||||
(RESTAURANT, _('Restaurant')),
|
||||
(ARTISAN, _('Artisan')),
|
||||
(PRODUCER, _('Producer')),
|
||||
)
|
||||
|
||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||
help_text='{"en-GB":"some text"}')
|
||||
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||
unique=True, db_index=True,
|
||||
verbose_name=_('Index name'))
|
||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||
related_name='establishment_types',
|
||||
verbose_name=_('Tag'))
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -51,11 +71,24 @@ class EstablishmentSubTypeManager(models.Manager):
|
|||
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||
"""Establishment type model."""
|
||||
|
||||
# INDEX NAME CHOICES
|
||||
WINERY = 'winery'
|
||||
|
||||
INDEX_NAME_TYPES = (
|
||||
(WINERY, _('Winery')),
|
||||
)
|
||||
|
||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||
help_text='{"en-GB":"some text"}')
|
||||
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||
unique=True, db_index=True,
|
||||
verbose_name=_('Index name'))
|
||||
establishment_type = models.ForeignKey(EstablishmentType,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_('Type'))
|
||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||
related_name='establishment_subtypes',
|
||||
verbose_name=_('Tag'))
|
||||
|
||||
objects = EstablishmentSubTypeManager()
|
||||
|
||||
|
|
@ -75,11 +108,8 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
|
||||
def with_base_related(self):
|
||||
"""Return qs with related objects."""
|
||||
return self.select_related('address').prefetch_related(
|
||||
models.Prefetch('tags',
|
||||
MetaDataContent.objects.select_related(
|
||||
'metadata__category'))
|
||||
)
|
||||
return self.select_related('address', 'establishment_type').\
|
||||
prefetch_related('tags')
|
||||
|
||||
def with_extended_related(self):
|
||||
return self.select_related('establishment_type').\
|
||||
|
|
@ -87,6 +117,14 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
'phones').\
|
||||
prefetch_actual_employees()
|
||||
|
||||
def with_type_related(self):
|
||||
return self.prefetch_related('establishment_subtypes')
|
||||
|
||||
def with_es_related(self):
|
||||
"""Return qs with related for ES indexing objects."""
|
||||
return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country').\
|
||||
prefetch_related('tags', 'schedule')
|
||||
|
||||
def search(self, value, locale=None):
|
||||
"""Search text in JSON fields."""
|
||||
if locale is not None:
|
||||
|
|
@ -98,15 +136,15 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
else:
|
||||
return self.none()
|
||||
|
||||
# def es_search(self, value, locale=None):
|
||||
# """Search text via ElasticSearch."""
|
||||
# from search_indexes.documents import EstablishmentDocument
|
||||
# search = EstablishmentDocument.search().filter(
|
||||
# Elastic_Q('match', name=value) |
|
||||
# Elastic_Q('match', **{f'description.{locale}': value})
|
||||
# ).execute()
|
||||
# ids = [result.meta.id for result in search]
|
||||
# return self.filter(id__in=ids)
|
||||
def es_search(self, value, locale=None):
|
||||
"""Search text via ElasticSearch."""
|
||||
from search_indexes.documents import EstablishmentDocument
|
||||
search = EstablishmentDocument.search().filter(
|
||||
elasticsearch_dsl.Q('match', name=value) |
|
||||
elasticsearch_dsl.Q('match', **{f'description.{locale}': value})
|
||||
).execute()
|
||||
ids = [result.meta.id for result in search]
|
||||
return self.filter(id__in=ids)
|
||||
|
||||
def by_country_code(self, code):
|
||||
"""Return establishments by country code"""
|
||||
|
|
@ -174,7 +212,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
.filter(image_url__isnull=False, public_mark__gte=10)
|
||||
.has_published_reviews()
|
||||
.annotate_distance(point=establishment.location)
|
||||
.order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
|
||||
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
||||
.values('id')
|
||||
)
|
||||
return self.filter(id__in=subquery_filter_by_distance) \
|
||||
|
|
@ -194,7 +232,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
self.filter(image_url__isnull=False, public_mark__gte=10)
|
||||
.has_published_reviews()
|
||||
.annotate_distance(point=point)
|
||||
.order_by('distance')[:settings.LIMITING_QUERY_NUMBER]
|
||||
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
||||
.values('id')
|
||||
)
|
||||
return self.filter(id__in=subquery_filter_by_distance) \
|
||||
|
|
@ -234,6 +272,31 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
kwargs = {unit: radius}
|
||||
return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
|
||||
|
||||
def artisans(self):
|
||||
"""Return artisans."""
|
||||
return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN)
|
||||
|
||||
def producers(self):
|
||||
"""Return producers."""
|
||||
return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER)
|
||||
|
||||
def restaurants(self):
|
||||
"""Return restaurants."""
|
||||
return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT)
|
||||
|
||||
def wineries(self):
|
||||
"""Return wineries."""
|
||||
return self.producers().filter(
|
||||
establishment_subtypes__index_name=EstablishmentSubType.WINERY)
|
||||
|
||||
def by_type(self, value):
|
||||
"""Return QuerySet with type by value."""
|
||||
return self.filter(establishment_type__index_name=value)
|
||||
|
||||
def by_subtype(self, value):
|
||||
"""Return QuerySet with subtype by value."""
|
||||
return self.filter(establishment_subtypes__index_name=value)
|
||||
|
||||
|
||||
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||
"""Establishment model."""
|
||||
|
|
@ -255,6 +318,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|||
on_delete=models.PROTECT,
|
||||
verbose_name=_('type'))
|
||||
establishment_subtypes = models.ManyToManyField(EstablishmentSubType,
|
||||
blank=True,
|
||||
related_name='subtype_establishment',
|
||||
verbose_name=_('subtype'))
|
||||
address = models.ForeignKey(Address, blank=True, null=True, default=None,
|
||||
|
|
@ -293,14 +357,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|||
verbose_name=_('Collections'))
|
||||
preview_image_url = models.URLField(verbose_name=_('Preview image URL path'),
|
||||
blank=True, null=True, default=None)
|
||||
slug = models.SlugField(unique=True, max_length=50, null=True,
|
||||
verbose_name=_('Establishment slug'), editable=True)
|
||||
slug = models.SlugField(unique=True, max_length=255, null=True,
|
||||
verbose_name=_('Establishment slug'))
|
||||
tz = TimeZoneField(default=settings.TIME_ZONE)
|
||||
|
||||
awards = generic.GenericRelation(to='main.Award', related_query_name='establishment')
|
||||
tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||
tags = models.ManyToManyField('tag.Tag', related_name='establishments',
|
||||
verbose_name=_('Tag'))
|
||||
reviews = generic.GenericRelation(to='review.Review')
|
||||
comments = generic.GenericRelation(to='comment.Comment')
|
||||
favorites = generic.GenericRelation(to='favorites.Favorites')
|
||||
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_('currency'))
|
||||
|
||||
objects = EstablishmentQuerySet.as_manager()
|
||||
|
||||
|
|
@ -359,6 +428,27 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|||
def best_price_carte(self):
|
||||
return 200
|
||||
|
||||
@property
|
||||
def works_noon(self):
|
||||
""" Used for indexing working by day """
|
||||
return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon]
|
||||
|
||||
@property
|
||||
def works_evening(self):
|
||||
""" Used for indexing working by day """
|
||||
return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon]
|
||||
|
||||
@property
|
||||
def works_now(self):
|
||||
""" Is establishment working now """
|
||||
now_at_est_tz = datetime.now(tz=self.tz)
|
||||
current_week = now_at_est_tz.weekday()
|
||||
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
||||
if schedule_for_today is None or schedule_for_today.closed_at is None or schedule_for_today.opening_at is None:
|
||||
return False
|
||||
time_at_est_tz = now_at_est_tz.time()
|
||||
return schedule_for_today.closed_at > time_at_est_tz > schedule_for_today.opening_at
|
||||
|
||||
@property
|
||||
def tags_indexing(self):
|
||||
return [{'id': tag.metadata.id,
|
||||
|
|
@ -379,8 +469,22 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|||
|
||||
@property
|
||||
def the_most_recent_award(self):
|
||||
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest(
|
||||
field_name='vintage_year')
|
||||
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \
|
||||
.latest(field_name='vintage_year')
|
||||
|
||||
@property
|
||||
def country_id(self):
|
||||
"""
|
||||
Return Country id of establishment location
|
||||
"""
|
||||
return self.address.country_id
|
||||
|
||||
@property
|
||||
def establishment_id(self):
|
||||
"""
|
||||
Return establishment id of establishment location
|
||||
"""
|
||||
return self.id
|
||||
|
||||
|
||||
class Position(BaseAttributes, TranslatedFieldsMixin):
|
||||
|
|
@ -393,6 +497,9 @@ class Position(BaseAttributes, TranslatedFieldsMixin):
|
|||
|
||||
priority = models.IntegerField(unique=True, null=True, default=None)
|
||||
|
||||
index_name = models.CharField(max_length=255, db_index=True, unique=True,
|
||||
null=True, verbose_name=_('Index name'))
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
|
|
@ -437,7 +544,8 @@ class Employee(BaseAttributes):
|
|||
establishments = models.ManyToManyField(Establishment, related_name='employees',
|
||||
through=EstablishmentEmployee,)
|
||||
awards = generic.GenericRelation(to='main.Award', related_query_name='employees')
|
||||
tags = generic.GenericRelation(to='main.MetaDataContent')
|
||||
tags = models.ManyToManyField('tag.Tag', related_name='employees',
|
||||
verbose_name=_('Tags'))
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -477,6 +585,7 @@ class ContactEmail(models.Model):
|
|||
def __str__(self):
|
||||
return f'{self.email}'
|
||||
|
||||
|
||||
#
|
||||
# class Wine(TranslatedFieldsMixin, models.Model):
|
||||
# """Wine model."""
|
||||
|
|
@ -515,6 +624,10 @@ class Plate(TranslatedFieldsMixin, models.Model):
|
|||
menu = models.ForeignKey(
|
||||
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
|
||||
|
||||
@property
|
||||
def establishment_id(self):
|
||||
return self.menu.establishment.id
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('plate')
|
||||
verbose_name_plural = _('plates')
|
||||
|
|
@ -550,3 +663,4 @@ class SocialNetwork(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from establishment import models
|
||||
from establishment.serializers import (
|
||||
EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer,
|
||||
ContactPhonesSerializer, SocialNetworkRelatedSerializers,
|
||||
EstablishmentTypeSerializer)
|
||||
|
||||
from utils.decorators import with_base_attributes
|
||||
|
||||
EstablishmentTypeBaseSerializer)
|
||||
from main.models import Currency
|
||||
from utils.decorators import with_base_attributes
|
||||
from utils.serializers import TimeZoneChoiceField
|
||||
|
||||
|
||||
class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
||||
|
|
@ -20,8 +20,8 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
|||
phones = ContactPhonesSerializer(read_only=True, many=True, )
|
||||
emails = ContactEmailsSerializer(read_only=True, many=True, )
|
||||
socials = SocialNetworkRelatedSerializers(read_only=True, many=True, )
|
||||
slug = serializers.SlugField(required=True, allow_blank=False, max_length=50)
|
||||
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
|
||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||
tz = TimeZoneChoiceField()
|
||||
|
||||
class Meta:
|
||||
model = models.Establishment
|
||||
|
|
@ -42,6 +42,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
|
|||
'is_publish',
|
||||
'guestonline_id',
|
||||
'lastable_id',
|
||||
'tz',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
|
|||
phones = ContactPhonesSerializer(read_only=False, many=True, )
|
||||
emails = ContactEmailsSerializer(read_only=False, many=True, )
|
||||
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
|
||||
type = EstablishmentTypeSerializer(source='establishment_type')
|
||||
type = EstablishmentTypeBaseSerializer(source='establishment_type')
|
||||
|
||||
class Meta:
|
||||
model = models.Establishment
|
||||
|
|
@ -141,4 +142,3 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
|
|||
'user',
|
||||
'name'
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
"""Establishment serializers."""
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from comment import models as comment_models
|
||||
from comment.serializers import common as comment_serializers
|
||||
from establishment import models
|
||||
from favorites.models import Favorites
|
||||
from location.serializers import AddressBaseSerializer
|
||||
from main.models import MetaDataContent
|
||||
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
|
||||
from main.serializers import AwardSerializer, CurrencySerializer
|
||||
from review import models as review_models
|
||||
from tag.serializers import TagBaseSerializer
|
||||
from timetable.serialziers import ScheduleRUDSerializer
|
||||
from utils import exceptions as utils_exceptions
|
||||
from utils.serializers import TranslatedField, ProjectModelSerializer
|
||||
from utils.serializers import ProjectModelSerializer
|
||||
from utils.serializers import TranslatedField
|
||||
|
||||
|
||||
class ContactPhonesSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -86,30 +88,6 @@ class MenuRUDSerializers(ProjectModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class EstablishmentTypeSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentType model."""
|
||||
|
||||
name_translated = serializers.CharField(allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.EstablishmentType
|
||||
fields = ('id', 'name_translated')
|
||||
|
||||
|
||||
class EstablishmentSubTypeSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentSubType models."""
|
||||
|
||||
name_translated = serializers.CharField(allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.EstablishmentSubType
|
||||
fields = ('id', 'name_translated')
|
||||
|
||||
|
||||
class ReviewSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for model Review."""
|
||||
text_translated = serializers.CharField(read_only=True)
|
||||
|
|
@ -122,6 +100,45 @@ class ReviewSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
|
||||
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentType model."""
|
||||
name_translated = TranslatedField()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
model = models.EstablishmentType
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'name_translated',
|
||||
'use_subtypes'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'write_only': True},
|
||||
'use_subtypes': {'write_only': True},
|
||||
}
|
||||
|
||||
|
||||
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentSubType models."""
|
||||
|
||||
name_translated = TranslatedField()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
model = models.EstablishmentSubType
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'name_translated',
|
||||
'establishment_type'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'write_only': True},
|
||||
'establishment_type': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for actual employees."""
|
||||
|
||||
|
|
@ -130,22 +147,23 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer):
|
|||
position_translated = serializers.CharField(source='position.name_translated')
|
||||
awards = AwardSerializer(source='employee.awards', many=True)
|
||||
priority = serializers.IntegerField(source='position.priority')
|
||||
position_index_name = serializers.CharField(source='position.index_name')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.Employee
|
||||
fields = ('id', 'name', 'position_translated', 'awards', 'priority')
|
||||
fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name')
|
||||
|
||||
|
||||
class EstablishmentBaseSerializer(ProjectModelSerializer):
|
||||
"""Base serializer for Establishment model."""
|
||||
|
||||
preview_image = serializers.URLField(source='preview_image_url')
|
||||
slug = serializers.SlugField(allow_blank=False, required=True, max_length=50)
|
||||
address = AddressBaseSerializer()
|
||||
tags = MetaDataContentSerializer(many=True)
|
||||
in_favorites = serializers.BooleanField(allow_null=True)
|
||||
tags = TagBaseSerializer(read_only=True, many=True)
|
||||
currency = CurrencySerializer()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -163,6 +181,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer):
|
|||
'in_favorites',
|
||||
'address',
|
||||
'tags',
|
||||
'currency'
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -171,8 +190,8 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
|
|||
|
||||
description_translated = TranslatedField()
|
||||
image = serializers.URLField(source='image_url')
|
||||
type = EstablishmentTypeSerializer(source='establishment_type', read_only=True)
|
||||
subtypes = EstablishmentSubTypeSerializer(many=True, source='establishment_subtypes')
|
||||
type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True)
|
||||
subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes')
|
||||
awards = AwardSerializer(many=True)
|
||||
schedule = ScheduleRUDSerializer(many=True, allow_null=True)
|
||||
phones = ContactPhonesSerializer(read_only=True, many=True)
|
||||
|
|
@ -306,17 +325,3 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
|
|||
})
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class EstablishmentTagListSerializer(serializers.ModelSerializer):
|
||||
"""List establishment tag serializer."""
|
||||
id = serializers.IntegerField(source='metadata.id')
|
||||
label_translated = serializers.CharField(
|
||||
source='metadata.label_translated', read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
model = MetaDataContent
|
||||
fields = [
|
||||
'id',
|
||||
'label_translated',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
"""Establishment app tasks."""
|
||||
import logging
|
||||
|
||||
from celery import shared_task
|
||||
from celery.schedules import crontab
|
||||
from celery.task import periodic_task
|
||||
from django.core import management
|
||||
from django_elasticsearch_dsl.management.commands import search_index
|
||||
|
||||
from establishment import models
|
||||
from location.models import Country
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -12,10 +17,15 @@ logger = logging.getLogger(__name__)
|
|||
def recalculate_price_levels_by_country(country_id):
|
||||
try:
|
||||
country = Country.objects.get(pk=country_id)
|
||||
except Country.DoesNotExist as ex:
|
||||
except Country.DoesNotExist as _:
|
||||
logger.error(f'ESTABLISHMENT. Country does not exist. ID {country_id}')
|
||||
else:
|
||||
qs = models.Establishment.objects.filter(address__city__country=country)
|
||||
for establishment in qs:
|
||||
establishment.recalculate_price_level(low_price=country.low_price,
|
||||
high_price=country.high_price)
|
||||
|
||||
@periodic_task(run_every=crontab(minute=59))
|
||||
def rebuild_establishment_indices():
|
||||
management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__],
|
||||
force=True)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ from main.models import Currency
|
|||
from establishment.models import Establishment, EstablishmentType, Menu
|
||||
# Create your tests here.
|
||||
from translation.models import Language
|
||||
from account.models import Role, UserRole
|
||||
from location.models import Country, Address, City, Region
|
||||
from pytz import timezone as py_tz
|
||||
|
||||
|
||||
class BaseTestCase(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = 'sedragurda'
|
||||
self.password = 'sedragurdaredips19'
|
||||
|
|
@ -18,20 +20,58 @@ class BaseTestCase(APITestCase):
|
|||
self.newsletter = True
|
||||
self.user = User.objects.create_user(
|
||||
username=self.username, email=self.email, password=self.password)
|
||||
#get tokkens
|
||||
tokkens = User.create_jwt_tokens(self.user)
|
||||
# get tokens
|
||||
tokens = User.create_jwt_tokens(self.user)
|
||||
self.client.cookies = SimpleCookie(
|
||||
{'access_token': tokkens.get('access_token'),
|
||||
'refresh_token': tokkens.get('refresh_token')})
|
||||
{'access_token': tokens.get('access_token'),
|
||||
'refresh_token': tokens.get('refresh_token')})
|
||||
|
||||
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
|
||||
self.establishment_type = EstablishmentType.objects.create(
|
||||
name="Test establishment type")
|
||||
|
||||
# Create lang object
|
||||
Language.objects.create(
|
||||
title='English',
|
||||
locale='en-GB'
|
||||
self.lang = Language.objects.get(
|
||||
title='Russia',
|
||||
locale='ru-RU'
|
||||
)
|
||||
|
||||
self.country_ru = Country.objects.get(
|
||||
name={"en-GB": "Russian"}
|
||||
)
|
||||
|
||||
self.region = Region.objects.create(name='Moscow area', code='01',
|
||||
country=self.country_ru)
|
||||
self.region.save()
|
||||
|
||||
self.city = City.objects.create(
|
||||
name='Mosocow', code='01',
|
||||
region=self.region,
|
||||
country=self.country_ru)
|
||||
self.city.save()
|
||||
|
||||
self.address = Address.objects.create(
|
||||
city=self.city, street_name_1='Krasnaya',
|
||||
number=2, postal_code='010100')
|
||||
self.address.save()
|
||||
|
||||
self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER)
|
||||
self.role.save()
|
||||
|
||||
self.establishment = Establishment.objects.create(
|
||||
name="Test establishment",
|
||||
establishment_type_id=self.establishment_type.id,
|
||||
is_publish=True,
|
||||
slug="test",
|
||||
address=self.address
|
||||
)
|
||||
|
||||
self.establishment.save()
|
||||
|
||||
self.user_role = UserRole.objects.create(
|
||||
user=self.user, role=self.role,
|
||||
establishment=self.establishment)
|
||||
self.user_role.save()
|
||||
|
||||
|
||||
class EstablishmentBTests(BaseTestCase):
|
||||
def test_establishment_CRUD(self):
|
||||
|
|
@ -44,24 +84,25 @@ class EstablishmentBTests(BaseTestCase):
|
|||
'type_id': self.establishment_type.id,
|
||||
'is_publish': True,
|
||||
'slug': 'test-establishment-slug',
|
||||
'tz': py_tz('Europe/Moscow').zone
|
||||
}
|
||||
|
||||
response = self.client.post('/api/back/establishments/', data=data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
establishment = response.json()
|
||||
|
||||
response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json')
|
||||
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
update_data = {
|
||||
'name': 'Test new establishment'
|
||||
}
|
||||
|
||||
response = self.client.patch(f'/api/back/establishments/{establishment["id"]}/', data=update_data)
|
||||
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/',
|
||||
data=update_data)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json')
|
||||
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/',
|
||||
format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
|
@ -92,43 +133,48 @@ class EmployeeTests(BaseTestCase):
|
|||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# Class to test childs
|
||||
class ChildTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.establishment = Establishment.objects.create(
|
||||
name="Test establishment",
|
||||
establishment_type_id=self.establishment_type.id,
|
||||
is_publish=True,
|
||||
slug="test"
|
||||
)
|
||||
|
||||
|
||||
# Test childs
|
||||
class EmailTests(ChildTestCase):
|
||||
def test_email_CRUD(self):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get('/api/back/establishments/emails/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_post(self):
|
||||
data = {
|
||||
'email': "test@test.com",
|
||||
'establishment': self.establishment.id
|
||||
}
|
||||
|
||||
response = self.client.post('/api/back/establishments/emails/', data=data)
|
||||
self.id_email = response.json()['id']
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
response = self.client.get('/api/back/establishments/emails/1/', format='json')
|
||||
def test_get_by_pk(self):
|
||||
self.test_post()
|
||||
response = self.client.get(f'/api/back/establishments/emails/{self.id_email}/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_patch(self):
|
||||
self.test_post()
|
||||
|
||||
update_data = {
|
||||
'email': 'testnew@test.com'
|
||||
}
|
||||
|
||||
response = self.client.patch('/api/back/establishments/emails/1/', data=update_data)
|
||||
response = self.client.patch(f'/api/back/establishments/emails/{self.id_email}/',
|
||||
data=update_data)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
response = self.client.delete('/api/back/establishments/emails/1/')
|
||||
def test_email_CRUD(self):
|
||||
self.test_post()
|
||||
response = self.client.delete(f'/api/back/establishments/emails/{self.id_email}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
|
@ -285,7 +331,7 @@ class EstablishmentWebTagTests(BaseTestCase):
|
|||
|
||||
def test_tag_Read(self):
|
||||
response = self.client.get('/api/web/establishments/tags/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class EstablishmentWebSlugTests(ChildTestCase):
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ urlpatterns = [
|
|||
path('emails/<int:pk>/', views.EmailRUDView.as_view(), name='emails-rud'),
|
||||
path('employees/', views.EmployeeListCreateView.as_view(), name='employees'),
|
||||
path('employees/<int:pk>/', views.EmployeeRUDView.as_view(), name='employees-rud'),
|
||||
]
|
||||
path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'),
|
||||
path('types/<int:pk>/', views.EstablishmentTypeRUDView.as_view(), name='type-rud'),
|
||||
path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'),
|
||||
path('subtypes/<int:pk>/', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ app_name = 'establishment'
|
|||
|
||||
urlpatterns = [
|
||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||
path('tags/', views.EstablishmentTagListView.as_view(), name='tags'),
|
||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||
name='recent-reviews'),
|
||||
# path('wineries/', views.WineriesListView.as_view(), name='wineries-list'),
|
||||
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ from establishment.urls.common import urlpatterns as common_urlpatterns
|
|||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
"""Establishment app views."""
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics
|
||||
|
||||
from establishment import models
|
||||
from establishment import serializers
|
||||
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
|
||||
from establishment import models, serializers
|
||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||
|
||||
|
||||
class EstablishmentMixinViews:
|
||||
|
|
@ -18,23 +19,55 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
|
|||
"""Establishment list/create view."""
|
||||
queryset = models.Establishment.objects.all()
|
||||
serializer_class = serializers.EstablishmentListCreateSerializer
|
||||
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
|
||||
|
||||
|
||||
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = models.Establishment.objects.all()
|
||||
serializer_class = serializers.EstablishmentRUDSerializer
|
||||
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
|
||||
|
||||
|
||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment schedule RUD view"""
|
||||
serializer_class = ScheduleRUDSerializer
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
Returns the object the view is displaying.
|
||||
"""
|
||||
establishment_pk = self.kwargs['pk']
|
||||
schedule_id = self.kwargs['schedule_id']
|
||||
|
||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||
pk=establishment_pk)
|
||||
schedule = get_object_or_404(klass=establishment.schedule,
|
||||
id=schedule_id)
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
self.check_object_permissions(self.request, schedule)
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||
"""Establishment schedule Create view"""
|
||||
serializer_class = ScheduleCreateSerializer
|
||||
|
||||
|
||||
class MenuListCreateView(generics.ListCreateAPIView):
|
||||
"""Menu list create view."""
|
||||
serializer_class = serializers.MenuSerializers
|
||||
queryset = models.Menu.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Menu RUD view."""
|
||||
serializer_class = serializers.MenuRUDSerializers
|
||||
queryset = models.Menu.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class SocialListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -42,12 +75,14 @@ class SocialListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.SocialNetworkSerializers
|
||||
queryset = models.SocialNetwork.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class SocialRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Social RUD view."""
|
||||
serializer_class = serializers.SocialNetworkSerializers
|
||||
queryset = models.SocialNetwork.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class PlateListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -55,12 +90,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.PlatesSerializers
|
||||
queryset = models.Plate.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Social RUD view."""
|
||||
serializer_class = serializers.PlatesSerializers
|
||||
queryset = models.Plate.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class PhonesListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -68,12 +105,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.ContactPhoneBackSerializers
|
||||
queryset = models.ContactPhone.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Social RUD view."""
|
||||
serializer_class = serializers.ContactPhoneBackSerializers
|
||||
queryset = models.ContactPhone.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmailListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -81,12 +120,14 @@ class EmailListCreateView(generics.ListCreateAPIView):
|
|||
serializer_class = serializers.ContactEmailBackSerializers
|
||||
queryset = models.ContactEmail.objects.all()
|
||||
pagination_class = None
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Social RUD view."""
|
||||
serializer_class = serializers.ContactEmailBackSerializers
|
||||
queryset = models.ContactEmail.objects.all()
|
||||
permission_classes = [IsEstablishmentManager]
|
||||
|
||||
|
||||
class EmployeeListCreateView(generics.ListCreateAPIView):
|
||||
|
|
@ -100,3 +141,29 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
|||
"""Social RUD view."""
|
||||
serializer_class = serializers.EmployeeBackSerializers
|
||||
queryset = models.Employee.objects.all()
|
||||
|
||||
|
||||
class EstablishmentTypeListCreateView(generics.ListCreateAPIView):
|
||||
"""Establishment type list/create view."""
|
||||
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||
queryset = models.EstablishmentType.objects.all()
|
||||
pagination_class = None
|
||||
|
||||
|
||||
class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment type retrieve/update/destroy view."""
|
||||
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||
queryset = models.EstablishmentType.objects.all()
|
||||
|
||||
|
||||
class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView):
|
||||
"""Establishment subtype list/create view."""
|
||||
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||
queryset = models.EstablishmentSubType.objects.all()
|
||||
pagination_class = None
|
||||
|
||||
|
||||
class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment subtype retrieve/update/destroy view."""
|
||||
serializer_class = serializers.EstablishmentSubTypeBaseSerializer
|
||||
queryset = models.EstablishmentSubType.objects.all()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ from comment import models as comment_models
|
|||
from establishment import filters
|
||||
from establishment import models, serializers
|
||||
from main import methods
|
||||
from main.models import MetaDataContent
|
||||
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
|
||||
from utils.pagination import EstablishmentPortionPagination
|
||||
from utils.permissions import IsCountryAdmin
|
||||
|
||||
|
||||
class EstablishmentMixinView:
|
||||
|
|
@ -19,9 +18,13 @@ class EstablishmentMixinView:
|
|||
permission_classes = (permissions.AllowAny,)
|
||||
|
||||
def get_queryset(self):
|
||||
"""Overrided method 'get_queryset'."""
|
||||
return models.Establishment.objects.published().with_base_related().\
|
||||
annotate_in_favorites(self.request.user)
|
||||
"""Overridden method 'get_queryset'."""
|
||||
qs = models.Establishment.objects.published() \
|
||||
.with_base_related() \
|
||||
.annotate_in_favorites(self.request.user)
|
||||
if self.request.country_code:
|
||||
qs = qs.by_country_code(self.request.country_code)
|
||||
return qs
|
||||
|
||||
|
||||
class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
||||
|
|
@ -30,13 +33,6 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
|
|||
filter_class = filters.EstablishmentFilter
|
||||
serializer_class = serializers.EstablishmentBaseSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Overridden method 'get_queryset'."""
|
||||
qs = super(EstablishmentListView, self).get_queryset()
|
||||
if self.request.country_code:
|
||||
qs = qs.by_country_code(self.request.country_code)
|
||||
return qs
|
||||
|
||||
|
||||
class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
|
||||
"""Resource for getting a establishment."""
|
||||
|
|
@ -84,7 +80,7 @@ class EstablishmentTypeListView(generics.ListAPIView):
|
|||
"""Resource for getting a list of establishment types."""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.EstablishmentTypeSerializer
|
||||
serializer_class = serializers.EstablishmentTypeBaseSerializer
|
||||
queryset = models.EstablishmentType.objects.all()
|
||||
|
||||
|
||||
|
|
@ -176,42 +172,12 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi
|
|||
return qs
|
||||
|
||||
|
||||
class EstablishmentTagListView(generics.ListAPIView):
|
||||
"""List view for establishment tags."""
|
||||
serializer_class = serializers.EstablishmentTagListSerializer
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
pagination_class = None
|
||||
|
||||
def get_queryset(self):
|
||||
"""Override get_queryset method"""
|
||||
return MetaDataContent.objects.by_content_type(app_label='establishment',
|
||||
model='establishment')\
|
||||
.distinct('metadata__label')
|
||||
|
||||
|
||||
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Establishment schedule RUD view"""
|
||||
serializer_class = ScheduleRUDSerializer
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
Returns the object the view is displaying.
|
||||
"""
|
||||
establishment_pk = self.kwargs['pk']
|
||||
schedule_id = self.kwargs['schedule_id']
|
||||
|
||||
establishment = get_object_or_404(klass=models.Establishment.objects.all(),
|
||||
pk=establishment_pk)
|
||||
schedule = get_object_or_404(klass=establishment.schedule,
|
||||
id=schedule_id)
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, establishment)
|
||||
self.check_object_permissions(self.request, schedule)
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
class EstablishmentScheduleCreateView(generics.CreateAPIView):
|
||||
"""Establishment schedule Create view"""
|
||||
serializer_class = ScheduleCreateSerializer
|
||||
# Wineries
|
||||
# todo: find out about difference between subtypes data
|
||||
# class WineriesListView(EstablishmentListView):
|
||||
# """Return list establishments with type Wineries"""
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# """Overridden get_queryset method."""
|
||||
# qs = super(WineriesListView, self).get_queryset()
|
||||
# return qs.with_type_related().wineries()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
# Create your tests here.
|
||||
from http.cookies import SimpleCookie
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from account.models import User
|
||||
from establishment.models import Establishment, EstablishmentType
|
||||
from favorites.models import Favorites
|
||||
from establishment.models import Establishment, EstablishmentType, EstablishmentSubType
|
||||
from news.models import NewsType, News
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class BaseTestCase(APITestCase):
|
||||
|
|
@ -17,38 +19,54 @@ class BaseTestCase(APITestCase):
|
|||
self.username = 'sedragurda'
|
||||
self.password = 'sedragurdaredips19'
|
||||
self.email = 'sedragurda@desoz.com'
|
||||
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
|
||||
tokkens = User.create_jwt_tokens(self.user)
|
||||
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
|
||||
'refresh_token': tokkens.get('refresh_token')})
|
||||
self.user = User.objects.create_user(
|
||||
username=self.username,
|
||||
email=self.email,
|
||||
password=self.password
|
||||
)
|
||||
|
||||
self.test_news_type = NewsType.objects.create(name="Test news type")
|
||||
self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"},
|
||||
news_type=self.test_news_type,
|
||||
description={"en-GB": "Description test news"},
|
||||
playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00",
|
||||
state=News.PUBLISHED, slug='test-news')
|
||||
tokens = User.create_jwt_tokens(self.user)
|
||||
self.client.cookies = SimpleCookie(
|
||||
{'access_token': tokens.get('access_token'),
|
||||
'refresh_token': tokens.get('refresh_token')})
|
||||
|
||||
self.test_content_type = ContentType.objects.get(app_label="news", model="news")
|
||||
self.test_news_type = NewsType.objects.create(
|
||||
name="Test news type",
|
||||
)
|
||||
self.test_news = News.objects.create(
|
||||
created_by=self.user,
|
||||
modified_by=self.user,
|
||||
title={"en-GB": "Test news"},
|
||||
news_type=self.test_news_type,
|
||||
description={"en-GB": "Description test news"},
|
||||
start=datetime.fromisoformat("2020-12-03 12:00:00"),
|
||||
end=datetime.fromisoformat("2020-12-03 12:00:00"),
|
||||
state=News.PUBLISHED,
|
||||
slug='test-news'
|
||||
)
|
||||
|
||||
self.test_favorites = Favorites.objects.create(user=self.user, content_type=self.test_content_type,
|
||||
object_id=self.test_news.id)
|
||||
self.test_content_type = ContentType.objects.get(
|
||||
app_label="news", model="news")
|
||||
|
||||
self.test_establishment_type = EstablishmentType.objects.create(name={"en-GB": "test establishment type"},
|
||||
use_subtypes=False)
|
||||
self.test_favorites = Favorites.objects.create(
|
||||
user=self.user, content_type=self.test_content_type,
|
||||
object_id=self.test_news.id)
|
||||
|
||||
self.test_establishment = Establishment.objects.create(name="test establishment",
|
||||
description={"en-GB": "description of test establishment"},
|
||||
establishment_type=self.test_establishment_type,
|
||||
is_publish=True)
|
||||
# value for GenericRelation(reverse side) field must be iterable
|
||||
# value for GenericRelation(reverse side) field must be assigned through "set" method of field
|
||||
self.test_establishment_type = EstablishmentType.objects.create(
|
||||
name={"en-GB": "test establishment type"},
|
||||
use_subtypes=False)
|
||||
|
||||
self.test_establishment = Establishment.objects.create(
|
||||
name="test establishment",
|
||||
description={"en-GB": "description of test establishment"},
|
||||
establishment_type=self.test_establishment_type,
|
||||
is_publish=True)
|
||||
self.test_establishment.favorites.set([self.test_favorites])
|
||||
|
||||
|
||||
class FavoritesTestCase(BaseTestCase):
|
||||
|
||||
def test_func(self):
|
||||
response = self.client.get("/api/web/favorites/establishments/")
|
||||
print(response.json())
|
||||
url = reverse('web:favorites:establishment-list')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
|
@ -5,4 +5,9 @@ from gallery.models import Image
|
|||
|
||||
@admin.register(Image)
|
||||
class ImageModelAdmin(admin.ModelAdmin):
|
||||
"""Image model admin"""
|
||||
"""Image model admin."""
|
||||
list_display = ['id', 'title', 'orientation_display', 'image_tag', ]
|
||||
|
||||
def orientation_display(self, obj):
|
||||
"""Get image orientation name."""
|
||||
return obj.get_orientation_display() if obj.orientation else None
|
||||
|
|
|
|||
41
apps/gallery/migrations/0002_auto_20190930_0714.py
Normal file
41
apps/gallery/migrations/0002_auto_20190930_0714.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Generated by Django 2.2.4 on 2019-09-30 07:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import easy_thumbnails.fields
|
||||
import utils.methods
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gallery', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='image',
|
||||
name='orientation',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Horizontal'), (1, 'Vertical')], default=None, null=True, verbose_name='image orientation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='image',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='parent_image', to='gallery.Image', verbose_name='parent image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='image',
|
||||
name='source',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='image',
|
||||
name='title',
|
||||
field=models.CharField(default='', max_length=255, verbose_name='title'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='image',
|
||||
field=easy_thumbnails.fields.ThumbnailerImageField(upload_to=utils.methods.image_path, verbose_name='image file'),
|
||||
),
|
||||
]
|
||||
24
apps/gallery/migrations/0003_auto_20191003_1228.py
Normal file
24
apps/gallery/migrations/0003_auto_20191003_1228.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-03 12:28
|
||||
|
||||
from django.db import migrations
|
||||
import sorl.thumbnail.fields
|
||||
import utils.methods
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gallery', '0002_auto_20190930_0714'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='image',
|
||||
name='parent',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='image',
|
||||
name='image',
|
||||
field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,15 +1,34 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
from sorl.thumbnail import delete
|
||||
from sorl.thumbnail.fields import ImageField as SORLImageField
|
||||
|
||||
from utils.methods import image_path
|
||||
from utils.models import ProjectBaseMixin, ImageMixin
|
||||
from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin
|
||||
|
||||
|
||||
class Image(ProjectBaseMixin, ImageMixin):
|
||||
class ImageQuerySet(models.QuerySet):
|
||||
"""QuerySet for model Image."""
|
||||
|
||||
|
||||
class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
|
||||
"""Image model."""
|
||||
HORIZONTAL = 0
|
||||
VERTICAL = 1
|
||||
|
||||
image = ThumbnailerImageField(upload_to=image_path,
|
||||
verbose_name=_('Image file'))
|
||||
ORIENTATIONS = (
|
||||
(HORIZONTAL, _('Horizontal')),
|
||||
(VERTICAL, _('Vertical')),
|
||||
)
|
||||
|
||||
image = SORLImageField(upload_to=image_path,
|
||||
verbose_name=_('image file'))
|
||||
orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS,
|
||||
blank=True, null=True, default=None,
|
||||
verbose_name=_('image orientation'))
|
||||
title = models.CharField(_('title'), max_length=255, default='')
|
||||
|
||||
objects = ImageQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -18,4 +37,22 @@ class Image(ProjectBaseMixin, ImageMixin):
|
|||
|
||||
def __str__(self):
|
||||
"""String representation"""
|
||||
return str(self.id)
|
||||
return f'{self.id}'
|
||||
|
||||
def delete_image(self, completely: bool = True):
|
||||
"""
|
||||
Deletes an instance and crops of instance from media storage.
|
||||
:param completely: if set to False then removed only crop neither original image.
|
||||
"""
|
||||
try:
|
||||
# Delete from remote storage
|
||||
delete(file_=self.image.file, delete_file=completely)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
finally:
|
||||
if completely:
|
||||
# Delete an instance of image
|
||||
super().delete()
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,20 @@ class ImageSerializer(serializers.ModelSerializer):
|
|||
# RESPONSE
|
||||
url = serializers.ImageField(source='image',
|
||||
read_only=True)
|
||||
orientation_display = serializers.CharField(source='get_orientation_display',
|
||||
read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class"""
|
||||
model = models.Image
|
||||
fields = (
|
||||
fields = [
|
||||
'id',
|
||||
'file',
|
||||
'url'
|
||||
)
|
||||
|
||||
'url',
|
||||
'orientation',
|
||||
'orientation_display',
|
||||
'title',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'orientation': {'write_only': True}
|
||||
}
|
||||
|
|
|
|||
23
apps/gallery/tasks.py
Normal file
23
apps/gallery/tasks.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""Gallery app celery tasks."""
|
||||
import logging
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
from . import models
|
||||
|
||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_image(image_id: int, completely: bool = True):
|
||||
"""Delete an image from remote storage."""
|
||||
image_qs = models.Image.objects.filter(id=image_id)
|
||||
if image_qs.exists():
|
||||
try:
|
||||
image = image_qs.first()
|
||||
image.delete_image(completely=completely)
|
||||
except:
|
||||
logger.error(f'TASK_NAME: delete_image\n'
|
||||
f'DETAIL: Exception occurred while deleting an image '
|
||||
f'and related crops from remote storage: image_id - {image_id}')
|
||||
|
|
@ -6,5 +6,6 @@ from . import views
|
|||
app_name = 'gallery'
|
||||
|
||||
urlpatterns = [
|
||||
path('upload/', views.ImageUploadView.as_view(), name='upload-image')
|
||||
path('', views.ImageListCreateView.as_view(), name='list-create-image'),
|
||||
path('<int:pk>/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy-image'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,30 @@
|
|||
from rest_framework import generics
|
||||
from django.conf import settings
|
||||
from django.db.transaction import on_commit
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from . import models, serializers
|
||||
from . import tasks, models, serializers
|
||||
|
||||
|
||||
class ImageUploadView(generics.CreateAPIView):
|
||||
"""Upload image to gallery"""
|
||||
class ImageBaseView(generics.GenericAPIView):
|
||||
"""Base Image view."""
|
||||
model = models.Image
|
||||
queryset = models.Image.objects.all()
|
||||
serializer_class = serializers.ImageSerializer
|
||||
|
||||
|
||||
class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView):
|
||||
"""List/Create Image view."""
|
||||
|
||||
|
||||
class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView):
|
||||
"""Destroy view for model Image"""
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""Override destroy view"""
|
||||
instance = self.get_object()
|
||||
if settings.USE_CELERY:
|
||||
on_commit(lambda: tasks.delete_image.delay(image_id=instance.id))
|
||||
else:
|
||||
on_commit(lambda: tasks.delete_image(image_id=instance.id))
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import os
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
# Check migration
|
||||
def load_data_from_sql(apps, schema_editor):
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql')
|
||||
sql_statement = open(file_path).read()
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ INSERT INTO codelang (code,country) VALUES
|
|||
,('es-CR','Spanish (Costa Rica)')
|
||||
,('es-DO','Spanish (Dominican Republic)')
|
||||
,('es-EC','Spanish (Ecuador)')
|
||||
,('es-ES','Spanish (Castilian)')
|
||||
,('es-ES','Spanish (Spain)')
|
||||
;
|
||||
INSERT INTO codelang (code,country) VALUES
|
||||
|
|
@ -326,7 +325,7 @@ commit;
|
|||
|
||||
INSERT INTO location_country
|
||||
(code, "name", low_price, high_price, created, modified)
|
||||
select
|
||||
select distinct
|
||||
lpad((row_number() over (order by t.country asc))::text, 3, '0') as code,
|
||||
jsonb_build_object('en-GB', t.country),
|
||||
0 as low_price,
|
||||
|
|
@ -335,7 +334,7 @@ select
|
|||
now() as modified
|
||||
from
|
||||
(
|
||||
select
|
||||
select
|
||||
distinct c.country
|
||||
from country_code c
|
||||
) t
|
||||
|
|
@ -348,6 +347,7 @@ commit;
|
|||
INSERT INTO translation_language
|
||||
(title, locale)
|
||||
select
|
||||
distinct
|
||||
t.country as title,
|
||||
t.code as locale
|
||||
from
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ from django.db.models.signals import post_save
|
|||
from django.db.transaction import on_commit
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField
|
||||
|
||||
from translation.models import Language
|
||||
from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField
|
||||
|
||||
|
||||
class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
|
||||
|
|
@ -21,6 +22,10 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
|
|||
high_price = models.IntegerField(default=50, verbose_name=_('High price'))
|
||||
languages = models.ManyToManyField(Language, verbose_name=_('Languages'))
|
||||
|
||||
@property
|
||||
def country_id(self):
|
||||
return self.id
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
|
|
@ -49,6 +54,14 @@ class Region(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class CityQuerySet(models.QuerySet):
|
||||
"""Extended queryset for City model."""
|
||||
|
||||
def by_country_code(self, code):
|
||||
"""Return establishments by country code"""
|
||||
return self.filter(country__code=code)
|
||||
|
||||
|
||||
class City(models.Model):
|
||||
"""Region model."""
|
||||
|
||||
|
|
@ -64,6 +77,8 @@ class City(models.Model):
|
|||
|
||||
is_island = models.BooleanField(_('is island'), default=False)
|
||||
|
||||
objects = CityQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = _('cities')
|
||||
verbose_name = _('city')
|
||||
|
|
@ -73,7 +88,6 @@ class City(models.Model):
|
|||
|
||||
|
||||
class Address(models.Model):
|
||||
|
||||
"""Address model."""
|
||||
city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE)
|
||||
street_name_1 = models.CharField(
|
||||
|
|
@ -112,6 +126,10 @@ class Address(models.Model):
|
|||
return {'lat': self.latitude,
|
||||
'lon': self.longitude}
|
||||
|
||||
@property
|
||||
def country_id(self):
|
||||
return self.city.country_id
|
||||
|
||||
|
||||
# todo: Make recalculate price levels
|
||||
@receiver(post_save, sender=Country)
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ class CountryBackSerializer(common.CountrySerializer):
|
|||
'code',
|
||||
'svg_image',
|
||||
'name',
|
||||
'country_id'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ from account.models import User
|
|||
from rest_framework import status
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
from location.models import City, Region, Country
|
||||
from location.models import City, Region, Country, Language
|
||||
from django.contrib.gis.geos import Point
|
||||
from account.models import Role, UserRole
|
||||
|
||||
|
||||
class BaseTestCase(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = 'sedragurda'
|
||||
self.password = 'sedragurdaredips19'
|
||||
|
|
@ -18,29 +19,55 @@ class BaseTestCase(APITestCase):
|
|||
self.user = User.objects.create_user(
|
||||
username=self.username, email=self.email, password=self.password)
|
||||
|
||||
# get tokens
|
||||
tokens = User.create_jwt_tokens(self.user)
|
||||
|
||||
tokkens = User.create_jwt_tokens(self.user)
|
||||
self.client.cookies = SimpleCookie(
|
||||
{'access_token': tokkens.get('access_token'),
|
||||
'refresh_token': tokkens.get('refresh_token')})
|
||||
{'access_token': tokens.get('access_token'),
|
||||
'refresh_token': tokens.get('refresh_token')})
|
||||
|
||||
self.lang = Language.objects.get(
|
||||
title='Russia',
|
||||
locale='ru-RU'
|
||||
)
|
||||
|
||||
self.country_ru = Country.objects.get(
|
||||
name={"en-GB": "Russian"}
|
||||
)
|
||||
|
||||
self.role = Role.objects.create(role=Role.COUNTRY_ADMIN,
|
||||
country=self.country_ru)
|
||||
self.role.save()
|
||||
|
||||
self.user_role = UserRole.objects.create(user=self.user, role=self.role)
|
||||
|
||||
self.user_role.save()
|
||||
|
||||
|
||||
class CountryTests(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_country_CRUD(self):
|
||||
response = self.client.get('/api/back/location/countries/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
data = {
|
||||
'name': 'Test country',
|
||||
'code': 'test'
|
||||
'name': {"ru-RU": "NewCountry"},
|
||||
'code': 'test1'
|
||||
}
|
||||
|
||||
response = self.client.post('/api/back/location/countries/', data=data, format='json')
|
||||
response_data = response.json()
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
country = Country.objects.get(pk=response_data["id"])
|
||||
role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=country)
|
||||
role.save()
|
||||
|
||||
user_role = UserRole.objects.create(user=self.user, role=role)
|
||||
|
||||
user_role.save()
|
||||
|
||||
response = self.client.get('/api/back/location/countries/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
response = self.client.get(f'/api/back/location/countries/{response_data["id"]}/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
|
@ -64,6 +91,13 @@ class RegionTests(BaseTestCase):
|
|||
code="test"
|
||||
)
|
||||
|
||||
role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country)
|
||||
role.save()
|
||||
|
||||
user_role = UserRole.objects.create(user=self.user, role=role)
|
||||
|
||||
user_role.save()
|
||||
|
||||
def test_region_CRUD(self):
|
||||
response = self.client.get('/api/back/location/regions/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
|
@ -108,6 +142,13 @@ class CityTests(BaseTestCase):
|
|||
country=self.country
|
||||
)
|
||||
|
||||
role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country)
|
||||
role.save()
|
||||
|
||||
user_role = UserRole.objects.create(user=self.user, role=role)
|
||||
|
||||
user_role.save()
|
||||
|
||||
def test_city_CRUD(self):
|
||||
response = self.client.get('/api/back/location/cities/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
|
@ -142,6 +183,7 @@ class AddressTests(BaseTestCase):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
|
||||
self.country = Country.objects.create(
|
||||
name=json.dumps({"en-GB": "Test country"}),
|
||||
code="test"
|
||||
|
|
@ -160,6 +202,13 @@ class AddressTests(BaseTestCase):
|
|||
country=self.country
|
||||
)
|
||||
|
||||
role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country)
|
||||
role.save()
|
||||
|
||||
user_role = UserRole.objects.create(user=self.user, role=role)
|
||||
|
||||
user_role.save()
|
||||
|
||||
def test_address_CRUD(self):
|
||||
response = self.client.get('/api/back/location/addresses/', format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
|
@ -167,10 +216,8 @@ class AddressTests(BaseTestCase):
|
|||
data = {
|
||||
'city_id': self.city.id,
|
||||
'number': '+79999999',
|
||||
"coordinates": {
|
||||
"latitude": 37.0625,
|
||||
"longitude": -95.677068
|
||||
},
|
||||
"latitude": 37.0625,
|
||||
"longitude": -95.677068,
|
||||
"geo_lon": -95.677068,
|
||||
"geo_lat": 37.0625
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""Location app mobile urlconf."""
|
||||
from location.urls.common import urlpatterns as common_urlpatterns
|
||||
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""Location app web urlconf."""
|
||||
from location.urls.common import urlpatterns as common_urlpatterns
|
||||
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
|
|||
|
|
@ -3,50 +3,62 @@ from rest_framework import generics
|
|||
|
||||
from location import models, serializers
|
||||
from location.views import common
|
||||
|
||||
|
||||
from utils.permissions import IsCountryAdmin
|
||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||
# Address
|
||||
|
||||
|
||||
class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView):
|
||||
"""Create view for model Address."""
|
||||
serializer_class = serializers.AddressDetailSerializer
|
||||
queryset = models.Address.objects.all()
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
"""RUD view for model Address."""
|
||||
serializer_class = serializers.AddressDetailSerializer
|
||||
queryset = models.Address.objects.all()
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
# City
|
||||
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
|
||||
"""Create view for model City."""
|
||||
serializer_class = serializers.CitySerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
"""RUD view for model City."""
|
||||
serializer_class = serializers.CitySerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
# Region
|
||||
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
|
||||
"""Create view for model Region"""
|
||||
serializer_class = serializers.RegionSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
"""Retrieve view for model Region"""
|
||||
serializer_class = serializers.RegionSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
# Country
|
||||
class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView):
|
||||
class CountryListCreateView(generics.ListCreateAPIView):
|
||||
"""List/Create view for model Country."""
|
||||
queryset = models.Country.objects.all()
|
||||
serializer_class = serializers.CountryBackSerializer
|
||||
pagination_class = None
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
|
||||
|
||||
class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""RUD view for model Country."""
|
||||
serializer_class = serializers.CountryBackSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin]
|
||||
queryset = models.Country.objects.all()
|
||||
|
|
@ -10,7 +10,7 @@ class CountryViewMixin(generics.GenericAPIView):
|
|||
"""View Mixin for model Country"""
|
||||
|
||||
serializer_class = serializers.CountrySerializer
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
queryset = models.Country.objects.all()
|
||||
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class RegionRetrieveView(RegionViewMixin, generics.RetrieveAPIView):
|
|||
|
||||
class RegionListView(RegionViewMixin, generics.ListAPIView):
|
||||
"""List view for model Country"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.CountrySerializer
|
||||
|
||||
|
||||
|
|
@ -83,9 +83,15 @@ class CityRetrieveView(CityViewMixin, generics.RetrieveAPIView):
|
|||
|
||||
class CityListView(CityViewMixin, generics.ListAPIView):
|
||||
"""List view for model City"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.CitySerializer
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
if self.request.country_code:
|
||||
qs = qs.by_country_code(self.request.country_code)
|
||||
return qs
|
||||
|
||||
|
||||
class CityDestroyView(CityViewMixin, generics.DestroyAPIView):
|
||||
"""Destroy view for model City"""
|
||||
|
|
@ -110,7 +116,5 @@ class AddressRetrieveView(AddressViewMixin, generics.RetrieveAPIView):
|
|||
|
||||
class AddressListView(AddressViewMixin, generics.ListAPIView):
|
||||
"""List view for model Address"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.AddressDetailSerializer
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,22 +25,6 @@ class AwardAdmin(admin.ModelAdmin):
|
|||
# list_display_links = ['id', '__str__']
|
||||
|
||||
|
||||
@admin.register(models.MetaData)
|
||||
class MetaDataAdmin(admin.ModelAdmin):
|
||||
"""MetaData admin."""
|
||||
|
||||
|
||||
@admin.register(models.MetaDataCategory)
|
||||
class MetaDataCategoryAdmin(admin.ModelAdmin):
|
||||
"""MetaData admin."""
|
||||
list_display = ['id', 'country', 'content_type']
|
||||
|
||||
|
||||
@admin.register(models.MetaDataContent)
|
||||
class MetaDataContentAdmin(admin.ModelAdmin):
|
||||
"""MetaDataContent admin"""
|
||||
|
||||
|
||||
@admin.register(models.Currency)
|
||||
class CurrencContentAdmin(admin.ModelAdmin):
|
||||
"""CurrencContent admin"""
|
||||
|
|
|
|||
19
apps/main/migrations/0017_feature_route.py
Normal file
19
apps/main/migrations/0017_feature_route.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-07 14:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0016_merge_20190919_0954'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='feature',
|
||||
name='route',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Page'),
|
||||
),
|
||||
]
|
||||
18
apps/main/migrations/0018_feature_source.py
Normal file
18
apps/main/migrations/0018_feature_source.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-07 14:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0017_feature_route'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='feature',
|
||||
name='source',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'),
|
||||
),
|
||||
]
|
||||
38
apps/main/migrations/0019_auto_20191022_1359.py
Normal file
38
apps/main/migrations/0019_auto_20191022_1359.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-22 13:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0018_feature_source'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='metadatacategory',
|
||||
name='content_type',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='metadatacategory',
|
||||
name='country',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='metadatacontent',
|
||||
name='content_type',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='metadatacontent',
|
||||
name='metadata',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='MetaData',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='MetaDataCategory',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='MetaDataContent',
|
||||
),
|
||||
]
|
||||
18
apps/main/migrations/0019_award_image_url.py
Normal file
18
apps/main/migrations/0019_award_image_url.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-22 14:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0018_feature_source'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='award',
|
||||
name='image_url',
|
||||
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
|
||||
),
|
||||
]
|
||||
14
apps/main/migrations/0020_merge_20191023_0750.py
Normal file
14
apps/main/migrations/0020_merge_20191023_0750.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 07:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0019_award_image_url'),
|
||||
('main', '0019_auto_20191022_1359'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
18
apps/main/migrations/0021_auto_20191023_0924.py
Normal file
18
apps/main/migrations/0021_auto_20191023_0924.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0020_merge_20191023_0750'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='feature',
|
||||
name='slug',
|
||||
field=models.SlugField(max_length=255, unique=True),
|
||||
),
|
||||
]
|
||||
57
apps/main/migrations/0022_auto_20191023_1113.py
Normal file
57
apps/main/migrations/0022_auto_20191023_1113.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Generated by Django 2.2.4 on 2019-10-23 11:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.models
|
||||
|
||||
|
||||
def fill_currency_name(apps, schema_editor):
|
||||
# We can't import the Person model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
Currency = apps.get_model('main', 'Currency')
|
||||
for currency in Currency.objects.all():
|
||||
currency.name_json = {'en-GB': currency.name}
|
||||
currency.save()
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0021_auto_20191023_0924'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='currency',
|
||||
name='sign',
|
||||
field=models.CharField(default='?', max_length=1, verbose_name='sign'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='currency',
|
||||
name='slug',
|
||||
field=models.SlugField(default='?', max_length=255, unique=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='currency',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Currency'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='currency',
|
||||
name='name_json',
|
||||
field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name'),
|
||||
),
|
||||
migrations.RunPython(fill_currency_name, migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name='currency',
|
||||
name='name',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='currency',
|
||||
old_name='name_json',
|
||||
new_name='name',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +1,24 @@
|
|||
"""Main app models."""
|
||||
from typing import Iterable
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes import fields as generic
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from advertisement.models import Advertisement
|
||||
from configuration.models import TranslationSettings
|
||||
from location.models import Country
|
||||
from main import methods
|
||||
from review.models import Review
|
||||
from utils.models import (ProjectBaseMixin, TJSONField,
|
||||
TranslatedFieldsMixin, ImageMixin)
|
||||
TranslatedFieldsMixin, ImageMixin,
|
||||
PlatformMixin, URLImageMixin)
|
||||
from utils.querysets import ContentTypeQuerySetMixin
|
||||
from configuration.models import TranslationSettings
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
|
|
@ -101,6 +106,22 @@ from configuration.models import TranslationSettings
|
|||
#
|
||||
|
||||
|
||||
class Currency(TranslatedFieldsMixin, models.Model):
|
||||
"""Currency model."""
|
||||
name = TJSONField(
|
||||
_('name'), null=True, blank=True,
|
||||
default=None, help_text='{"en-GB":"some text"}')
|
||||
sign = models.CharField(_('sign'), max_length=1)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('currency')
|
||||
verbose_name_plural = _('currencies')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class SiteSettingsQuerySet(models.QuerySet):
|
||||
"""Extended queryset for SiteSettings model."""
|
||||
|
||||
|
|
@ -131,6 +152,7 @@ class SiteSettings(ProjectBaseMixin):
|
|||
verbose_name=_('Config'))
|
||||
ad_config = models.TextField(blank=True, null=True, default=None,
|
||||
verbose_name=_('AD config'))
|
||||
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, null=True, default=None)
|
||||
|
||||
objects = SiteSettingsQuerySet.as_manager()
|
||||
|
||||
|
|
@ -150,7 +172,8 @@ class SiteSettings(ProjectBaseMixin):
|
|||
|
||||
@property
|
||||
def published_sitefeatures(self):
|
||||
return self.sitefeature_set.filter(published=True)
|
||||
return self.sitefeature_set\
|
||||
.filter(Q(published=True) and Q(feature__source__in=[PlatformMixin.WEB, PlatformMixin.ALL]))
|
||||
|
||||
@property
|
||||
def site_url(self):
|
||||
|
|
@ -159,11 +182,27 @@ class SiteSettings(ProjectBaseMixin):
|
|||
domain=settings.SITE_DOMAIN_URI)
|
||||
|
||||
|
||||
class Feature(ProjectBaseMixin):
|
||||
class Page(models.Model):
|
||||
"""Page model."""
|
||||
|
||||
page_name = models.CharField(max_length=255, unique=True)
|
||||
advertisements = models.ManyToManyField(Advertisement)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
verbose_name = _('Page')
|
||||
verbose_name_plural = _('Pages')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.page_name}'
|
||||
|
||||
|
||||
class Feature(ProjectBaseMixin, PlatformMixin):
|
||||
"""Feature model."""
|
||||
|
||||
slug = models.CharField(max_length=255, unique=True)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
priority = models.IntegerField(unique=True, null=True, default=None)
|
||||
route = models.ForeignKey(Page, on_delete=models.PROTECT, null=True, default=None)
|
||||
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
|
||||
|
||||
class Meta:
|
||||
|
|
@ -181,6 +220,12 @@ class SiteFeatureQuerySet(models.QuerySet):
|
|||
def published(self, switcher=True):
|
||||
return self.filter(published=switcher)
|
||||
|
||||
def by_country_code(self, country_code: str):
|
||||
return self.filter(site_settings__country__code=country_code)
|
||||
|
||||
def by_sources(self, sources: Iterable[int]):
|
||||
return self.filter(feature__source__in=sources)
|
||||
|
||||
|
||||
class SiteFeature(ProjectBaseMixin):
|
||||
"""SiteFeature model."""
|
||||
|
|
@ -200,7 +245,7 @@ class SiteFeature(ProjectBaseMixin):
|
|||
unique_together = ('site_settings', 'feature')
|
||||
|
||||
|
||||
class Award(TranslatedFieldsMixin, models.Model):
|
||||
class Award(TranslatedFieldsMixin, URLImageMixin, models.Model):
|
||||
"""Award model."""
|
||||
award_type = models.ForeignKey('main.AwardType', on_delete=models.CASCADE)
|
||||
title = TJSONField(
|
||||
|
|
@ -230,61 +275,6 @@ class AwardType(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class MetaDataCategory(models.Model):
|
||||
"""MetaData category model."""
|
||||
|
||||
country = models.ForeignKey(
|
||||
'location.Country', null=True, default=None, on_delete=models.CASCADE)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
public = models.BooleanField()
|
||||
|
||||
|
||||
class MetaData(TranslatedFieldsMixin, models.Model):
|
||||
"""MetaData model."""
|
||||
label = TJSONField(
|
||||
_('label'), null=True, blank=True,
|
||||
default=None, help_text='{"en-GB":"some text"}')
|
||||
category = models.ForeignKey(
|
||||
MetaDataCategory, verbose_name=_('category'), on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('metadata')
|
||||
verbose_name_plural = _('metadata')
|
||||
|
||||
def __str__(self):
|
||||
label = 'None'
|
||||
lang = TranslationSettings.get_solo().default_language
|
||||
if self.label and lang in self.label:
|
||||
label = self.label[lang]
|
||||
return f'id:{self.id}-{label}'
|
||||
|
||||
|
||||
class MetaDataContentQuerySet(ContentTypeQuerySetMixin):
|
||||
"""QuerySets for MetaDataContent model."""
|
||||
|
||||
|
||||
class MetaDataContent(models.Model):
|
||||
"""MetaDataContent model."""
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||
metadata = models.ForeignKey(MetaData, on_delete=models.CASCADE)
|
||||
|
||||
objects = MetaDataContentQuerySet.as_manager()
|
||||
|
||||
|
||||
class Currency(models.Model):
|
||||
"""Currency model."""
|
||||
name = models.CharField(_('name'), max_length=50)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('currency')
|
||||
verbose_name_plural = _('currencies')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class CarouselQuerySet(models.QuerySet):
|
||||
"""Carousel QuerySet."""
|
||||
|
||||
|
|
@ -351,19 +341,3 @@ class Carousel(models.Model):
|
|||
def model_name(self):
|
||||
if hasattr(self.content_object, 'establishment_type'):
|
||||
return self.content_object.establishment_type.name_translated
|
||||
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
"""Page model."""
|
||||
|
||||
page_name = models.CharField(max_length=255, unique=True)
|
||||
advertisements = models.ManyToManyField(Advertisement)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
verbose_name = _('Page')
|
||||
verbose_name_plural = _('Pages')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.page_name}'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"""Main app serializers."""
|
||||
from rest_framework import serializers
|
||||
|
||||
from advertisement.serializers.web import AdvertisementSerializer
|
||||
from location.serializers import CountrySerializer
|
||||
from main import models
|
||||
from establishment.models import Establishment
|
||||
from utils.serializers import TranslatedField
|
||||
from utils.serializers import ProjectModelSerializer, TranslatedField
|
||||
|
||||
|
||||
class FeatureSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -25,6 +25,8 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
|
|||
id = serializers.IntegerField(source='feature.id')
|
||||
slug = serializers.CharField(source='feature.slug')
|
||||
priority = serializers.IntegerField(source='feature.priority')
|
||||
route = serializers.CharField(source='feature.route.page_name')
|
||||
source = serializers.IntegerField(source='feature.source')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -32,18 +34,37 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
|
|||
fields = ('main',
|
||||
'id',
|
||||
'slug',
|
||||
'priority'
|
||||
'priority',
|
||||
'route',
|
||||
'source'
|
||||
)
|
||||
|
||||
|
||||
class CurrencySerializer(ProjectModelSerializer):
|
||||
"""Currency serializer."""
|
||||
|
||||
name_translated = TranslatedField()
|
||||
|
||||
class Meta:
|
||||
model = models.Currency
|
||||
fields = [
|
||||
'id',
|
||||
'name_translated',
|
||||
'sign'
|
||||
]
|
||||
|
||||
|
||||
class SiteSettingsSerializer(serializers.ModelSerializer):
|
||||
"""Site settings serializer."""
|
||||
|
||||
published_features = SiteFeatureSerializer(source='published_sitefeatures',
|
||||
many=True, allow_null=True)
|
||||
currency = CurrencySerializer()
|
||||
# todo: remove this
|
||||
country_code = serializers.CharField(source='subdomain', read_only=True)
|
||||
|
||||
country_name = serializers.CharField(source='country.name_translated', read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
|
|
@ -59,6 +80,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
|
|||
'config',
|
||||
'ad_config',
|
||||
'published_features',
|
||||
'currency',
|
||||
'country_name'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -98,6 +121,7 @@ class AwardBaseSerializer(serializers.ModelSerializer):
|
|||
'id',
|
||||
'title_translated',
|
||||
'vintage_year',
|
||||
'image_url',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -109,30 +133,6 @@ class AwardSerializer(AwardBaseSerializer):
|
|||
fields = AwardBaseSerializer.Meta.fields + ['award_type', ]
|
||||
|
||||
|
||||
class MetaDataContentSerializer(serializers.ModelSerializer):
|
||||
"""MetaData content serializer."""
|
||||
|
||||
id = serializers.IntegerField(source='metadata.id', read_only=True)
|
||||
label_translated = TranslatedField(source='metadata.label_translated')
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.MetaDataContent
|
||||
fields = ('id', 'label_translated')
|
||||
|
||||
|
||||
class CurrencySerializer(serializers.ModelSerializer):
|
||||
"""Currency serializer"""
|
||||
|
||||
class Meta:
|
||||
model = models.Currency
|
||||
fields = [
|
||||
'id',
|
||||
'name'
|
||||
]
|
||||
|
||||
|
||||
class CarouselListSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for retrieving list of carousel items."""
|
||||
model_name = serializers.CharField()
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
"""Main app urls."""
|
||||
from django.urls import path
|
||||
from main import views
|
||||
|
||||
app = 'main'
|
||||
|
||||
urlpatterns = [
|
||||
path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'),
|
||||
path('determine-location/', views.DetermineLocation.as_view(), name='determine-location'),
|
||||
path('sites/', views.SiteListView.as_view(), name='site-list'),
|
||||
path('site-settings/<subdomain>/', views.SiteSettingsView.as_view(), name='site-settings'),
|
||||
path('awards/', views.AwardView.as_view(), name='awards_list'),
|
||||
path('awards/<int:pk>/', views.AwardRetrieveView.as_view(), name='awards_retrieve'),
|
||||
path('carousel/', views.CarouselListView.as_view(), name='carousel-list'),
|
||||
]
|
||||
12
apps/main/urls/common.py
Normal file
12
apps/main/urls/common.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""Main app urls."""
|
||||
from django.urls import path
|
||||
from main.views.common import *
|
||||
|
||||
app = 'main'
|
||||
|
||||
common_urlpatterns = [
|
||||
path('awards/', AwardView.as_view(), name='awards_list'),
|
||||
path('awards/<int:pk>/', AwardRetrieveView.as_view(), name='awards_retrieve'),
|
||||
path('carousel/', CarouselListView.as_view(), name='carousel-list'),
|
||||
path('determine-location/', DetermineLocation.as_view(), name='determine-location')
|
||||
]
|
||||
11
apps/main/urls/mobile.py
Normal file
11
apps/main/urls/mobile.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from main.urls.common import common_urlpatterns
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from main.views.mobile import FeaturesView
|
||||
|
||||
urlpatterns = [
|
||||
path('features/', FeaturesView.as_view(), name='features'),
|
||||
]
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
11
apps/main/urls/web.py
Normal file
11
apps/main/urls/web.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from main.urls.common import common_urlpatterns
|
||||
from django.urls import path
|
||||
|
||||
from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView
|
||||
|
||||
urlpatterns = [
|
||||
path('determine-site/', DetermineSiteView.as_view(), name='determine-site'),
|
||||
path('sites/', SiteListView.as_view(), name='site-list'),
|
||||
path('site-settings/<subdomain>/', SiteSettingsView.as_view(), name='site-settings'), ]
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
|
|
@ -1,56 +1,9 @@
|
|||
"""Main app views."""
|
||||
from django.http import Http404
|
||||
from rest_framework import generics, permissions
|
||||
from rest_framework.response import Response
|
||||
|
||||
from main import methods, models, serializers
|
||||
from utils.serializers import EmptySerializer
|
||||
from django.http import Http404
|
||||
|
||||
|
||||
class DetermineSiteView(generics.GenericAPIView):
|
||||
"""Determine user's site."""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = EmptySerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_ip = methods.get_user_ip(request)
|
||||
country_code = methods.determine_country_code(user_ip)
|
||||
url = methods.determine_user_site_url(country_code)
|
||||
return Response(data={'url': url})
|
||||
|
||||
|
||||
class DetermineLocation(generics.GenericAPIView):
|
||||
"""Determine user's location."""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = EmptySerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_ip = methods.get_user_ip(request)
|
||||
longitude, latitude = methods.determine_coordinates(user_ip)
|
||||
city = methods.determine_user_city(user_ip)
|
||||
if longitude and latitude and city:
|
||||
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
|
||||
class SiteSettingsView(generics.RetrieveAPIView):
|
||||
"""Site settings View."""
|
||||
|
||||
lookup_field = 'subdomain'
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
queryset = models.SiteSettings.objects.all()
|
||||
serializer_class = serializers.SiteSettingsSerializer
|
||||
|
||||
|
||||
class SiteListView(generics.ListAPIView):
|
||||
"""Site settings View."""
|
||||
|
||||
pagination_class = None
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
queryset = models.SiteSettings.objects.with_country()
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
||||
|
||||
#
|
||||
|
|
@ -89,6 +42,7 @@ class SiteListView(generics.ListAPIView):
|
|||
# class SiteFeaturesRUDView(SiteFeaturesViewMixin,
|
||||
# generics.RetrieveUpdateDestroyAPIView):
|
||||
# """Site features RUD."""
|
||||
from utils.serializers import EmptySerializer
|
||||
|
||||
|
||||
class AwardView(generics.ListAPIView):
|
||||
|
|
@ -111,3 +65,20 @@ class CarouselListView(generics.ListAPIView):
|
|||
serializer_class = serializers.CarouselListSerializer
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
pagination_class = None
|
||||
|
||||
|
||||
class DetermineLocation(generics.GenericAPIView):
|
||||
"""Determine user's location."""
|
||||
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = EmptySerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_ip = methods.get_user_ip(request)
|
||||
longitude, latitude = methods.determine_coordinates(user_ip)
|
||||
city = methods.determine_user_city(user_ip)
|
||||
if longitude and latitude and city:
|
||||
return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city})
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
16
apps/main/views/mobile.py
Normal file
16
apps/main/views/mobile.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from rest_framework import generics, permissions
|
||||
|
||||
from main import models, serializers
|
||||
from utils.models import PlatformMixin
|
||||
|
||||
|
||||
class FeaturesView(generics.ListAPIView):
|
||||
pagination_class = None
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.SiteFeatureSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return models.SiteFeature.objects\
|
||||
.prefetch_related('feature', 'feature__route') \
|
||||
.by_country_code(self.request.country_code) \
|
||||
.by_sources([PlatformMixin.ALL, PlatformMixin.MOBILE])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user