Merge branch 'develop' into feature/gm-148

This commit is contained in:
evgeniy-st 2019-10-21 20:26:31 +03:00
commit 6c195bc185
43 changed files with 836 additions and 188 deletions

View File

@ -12,7 +12,7 @@ class RoleAdmin(admin.ModelAdmin):
@admin.register(models.UserRole) @admin.register(models.UserRole)
class UserRoleAdmin(admin.ModelAdmin): class UserRoleAdmin(admin.ModelAdmin):
list_display = ['user', 'role'] list_display = ['user', 'role', 'establishment']
@admin.register(models.User) @admin.register(models.User)

View 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 = [
]

View 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 = [
]

View 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'),
),
]

View File

@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from authorization.models import Application from authorization.models import Application
from establishment.models import Establishment
from location.models import Country from location.models import Country
from utils.models import GMTokenGenerator from utils.models import GMTokenGenerator
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
@ -23,14 +24,25 @@ class Role(ProjectBaseMixin):
"""Base Role model.""" """Base Role model."""
STANDARD_USER = 1 STANDARD_USER = 1
COMMENTS_MODERATOR = 2 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'), (STANDARD_USER, 'Standard user'),
(COMMENTS_MODERATOR, 'Comments moderator'), (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, role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES,
null=False, blank=False) 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_list = models.BooleanField(verbose_name=_('list'), default=True, null=False)
# is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False)
# is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False)
@ -225,3 +237,5 @@ class UserRole(ProjectBaseMixin):
"""UserRole model.""" """UserRole model."""
user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) 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)

View File

@ -40,12 +40,13 @@ class CollectionDetailTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
country = Country.objects.first() # country = Country.objects.first()
if not country: # if not country:
country = Country.objects.create( country = Country.objects.create(
name=json.dumps({"en-GB": "Test country"}), name=json.dumps({"en-GB": "Test country"}),
code="en" code="en"
) )
country.save()
self.collection = Collection.objects.create( self.collection = Collection.objects.create(
name='Test collection', name='Test collection',
@ -56,6 +57,8 @@ class CollectionDetailTests(BaseTestCase):
slug='test-collection-slug', slug='test-collection-slug',
) )
self.collection.save()
def test_collection_detail_Read(self): def test_collection_detail_Read(self):
response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en', response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en',
format='json') format='json')
@ -66,7 +69,7 @@ class CollectionGuideTests(CollectionDetailTests):
def test_guide_list_Read(self): def test_guide_list_Read(self):
response = self.client.get('/api/web/collections/guides/', format='json') 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): class CollectionGuideDetailTests(CollectionDetailTests):
@ -78,6 +81,7 @@ class CollectionGuideDetailTests(CollectionDetailTests):
start=datetime.now(pytz.utc), start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc) end=datetime.now(pytz.utc)
) )
self.guide.save()
def test_guide_detail_Read(self): def test_guide_detail_Read(self):
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')

View 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'),
),
]

View File

@ -7,6 +7,8 @@ from account.models import User
from utils.models import ProjectBaseMixin from utils.models import ProjectBaseMixin
from utils.querysets import ContentTypeQuerySetMixin from utils.querysets import ContentTypeQuerySetMixin
from translation.models import Language from translation.models import Language
from location.models import Country
class CommentQuerySet(ContentTypeQuerySetMixin): class CommentQuerySet(ContentTypeQuerySetMixin):
"""QuerySets for Comment model.""" """QuerySets for Comment model."""
@ -41,7 +43,8 @@ class Comment(ProjectBaseMixin):
content_object = generic.GenericForeignKey('content_type', 'object_id') content_object = generic.GenericForeignKey('content_type', 'object_id')
objects = CommentQuerySet.as_manager() 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: class Meta:
"""Meta class""" """Meta class"""

View File

@ -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

View File

@ -1,32 +1,16 @@
from rest_framework.test import APITestCase from utils.tests.tests_permissions import BasePermissionTests
from rest_framework import status from rest_framework import status
from authorization.tests.tests_authorization import get_tokens_for_user from authorization.tests.tests_authorization import get_tokens_for_user
from django.urls import reverse from django.urls import reverse
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from location.models import Country
from account.models import Role, User, UserRole from account.models import Role, User, UserRole
from comment.models import Comment from comment.models import Comment
from translation.models import Language
class CommentModeratorPermissionTests(APITestCase): class CommentModeratorPermissionTests(BasePermissionTests):
def setUp(self): def setUp(self):
super().setUp()
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()
self.role = Role.objects.create( self.role = Role.objects.create(
role=2, role=2,
@ -51,14 +35,11 @@ class CommentModeratorPermissionTests(APITestCase):
user=self.user_test["user"], user=self.user_test["user"],
object_id= self.country_ru.pk, object_id= self.country_ru.pk,
content_type_id=content_type.id, content_type_id=content_type.id,
language=self.lang country=self.country_ru
) )
self.comment.save() self.comment.save()
self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) 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_put_moderator(self): def test_put_moderator(self):
tokens = User.create_jwt_tokens(self.moderator) tokens = User.create_jwt_tokens(self.moderator)
@ -76,6 +57,10 @@ class CommentModeratorPermissionTests(APITestCase):
response = self.client.put(self.url, data=data, format='json') response = self.client.put(self.url, data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) 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_401_UNAUTHORIZED)
def test_put_other_user(self): def test_put_other_user(self):
other_user = User.objects.create_user(username='test', other_user = User.objects.create_user(username='test',
email='test@mail.com', email='test@mail.com',
@ -120,4 +105,3 @@ class CommentModeratorPermissionTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -1,7 +1,7 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from comment.serializers import back as serializers from comment.serializers import back as serializers
from comment import models from comment import models
from comment.permissions import IsCommentModerator from utils.permissions import IsCommentModerator, IsCountryAdmin
class CommentLstView(generics.ListCreateAPIView): class CommentLstView(generics.ListCreateAPIView):
@ -15,5 +15,5 @@ class CommentRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Comment RUD view.""" """Comment RUD view."""
serializer_class = serializers.CommentBaseSerializer serializer_class = serializers.CommentBaseSerializer
queryset = models.Comment.objects.all() queryset = models.Comment.objects.all()
permission_classes = [IsCommentModerator] permission_classes = [IsCountryAdmin|IsCommentModerator]
lookup_field = 'id' lookup_field = 'id'

View File

@ -24,7 +24,7 @@ class Migration(migrations.Migration):
name='index_name', name='index_name',
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_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(fill_establishment_subtype, migrations.RunPython.noop),
migrations.AlterField( migrations.AlterField(
model_name='establishmentsubtype', model_name='establishmentsubtype',
name='index_name', name='index_name',

View File

@ -466,6 +466,20 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \ return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \
.latest(field_name='vintage_year') .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): class Position(BaseAttributes, TranslatedFieldsMixin):
"""Position model.""" """Position model."""
@ -600,6 +614,10 @@ class Plate(TranslatedFieldsMixin, models.Model):
menu = models.ForeignKey( menu = models.ForeignKey(
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE) 'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
@property
def establishment_id(self):
return self.menu.establishment.id
class Meta: class Meta:
verbose_name = _('plate') verbose_name = _('plate')
verbose_name_plural = _('plates') verbose_name_plural = _('plates')

View File

@ -7,10 +7,11 @@ from main.models import Currency
from establishment.models import Establishment, EstablishmentType, Menu from establishment.models import Establishment, EstablishmentType, Menu
# Create your tests here. # Create your tests here.
from translation.models import Language from translation.models import Language
from account.models import Role, UserRole
from location.models import Country, Address, City, Region
class BaseTestCase(APITestCase): class BaseTestCase(APITestCase):
def setUp(self): def setUp(self):
self.username = 'sedragurda' self.username = 'sedragurda'
self.password = 'sedragurdaredips19' self.password = 'sedragurdaredips19'
@ -27,11 +28,44 @@ class BaseTestCase(APITestCase):
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
# Create lang object # Create lang object
Language.objects.create( self.lang = Language.objects.get(
title='English', title='Russia',
locale='en-GB' 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): class EstablishmentBTests(BaseTestCase):
def test_establishment_CRUD(self): def test_establishment_CRUD(self):
@ -43,25 +77,25 @@ class EstablishmentBTests(BaseTestCase):
'name': 'Test establishment', 'name': 'Test establishment',
'type_id': self.establishment_type.id, 'type_id': self.establishment_type.id,
'is_publish': True, 'is_publish': True,
'slug': 'test-establishment-slug', 'slug': 'test-establishment-slug'
} }
response = self.client.post('/api/back/establishments/', data=data, format='json') response = self.client.post('/api/back/establishments/', data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
establishment = response.json() response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json')
response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = { update_data = {
'name': 'Test new establishment' '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) 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) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -96,39 +130,45 @@ class EmployeeTests(BaseTestCase):
class ChildTestCase(BaseTestCase): class ChildTestCase(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.establishment = Establishment.objects.create(
name="Test establishment",
establishment_type_id=self.establishment_type.id,
is_publish=True,
slug="test"
)
# Test childs # Test childs
class EmailTests(ChildTestCase): 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') response = self.client.get('/api/back/establishments/emails/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_post(self):
data = { data = {
'email': "test@test.com", 'email': "test@test.com",
'establishment': self.establishment.id 'establishment': self.establishment.id
} }
response = self.client.post('/api/back/establishments/emails/', data=data) 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) 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) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_patch(self):
self.test_post()
update_data = { update_data = {
'email': 'testnew@test.com' '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) 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) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
@ -285,7 +325,7 @@ class EstablishmentWebTagTests(BaseTestCase):
def test_tag_Read(self): def test_tag_Read(self):
response = self.client.get('/api/web/establishments/tags/', format='json') 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): class EstablishmentWebSlugTests(ChildTestCase):

View File

@ -2,6 +2,7 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics
from utils.permissions import IsCountryAdmin, IsEstablishmentManager
from establishment import models, serializers from establishment import models, serializers
from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer
@ -18,11 +19,13 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP
"""Establishment list/create view.""" """Establishment list/create view."""
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentListCreateSerializer serializer_class = serializers.EstablishmentListCreateSerializer
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
queryset = models.Establishment.objects.all() queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentRUDSerializer serializer_class = serializers.EstablishmentRUDSerializer
permission_classes = [IsCountryAdmin|IsEstablishmentManager]
class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView):
@ -57,12 +60,14 @@ class MenuListCreateView(generics.ListCreateAPIView):
"""Menu list create view.""" """Menu list create view."""
serializer_class = serializers.MenuSerializers serializer_class = serializers.MenuSerializers
queryset = models.Menu.objects.all() queryset = models.Menu.objects.all()
permission_classes = [IsEstablishmentManager]
class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): class MenuRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Menu RUD view.""" """Menu RUD view."""
serializer_class = serializers.MenuRUDSerializers serializer_class = serializers.MenuRUDSerializers
queryset = models.Menu.objects.all() queryset = models.Menu.objects.all()
permission_classes = [IsEstablishmentManager]
class SocialListCreateView(generics.ListCreateAPIView): class SocialListCreateView(generics.ListCreateAPIView):
@ -70,12 +75,14 @@ class SocialListCreateView(generics.ListCreateAPIView):
serializer_class = serializers.SocialNetworkSerializers serializer_class = serializers.SocialNetworkSerializers
queryset = models.SocialNetwork.objects.all() queryset = models.SocialNetwork.objects.all()
pagination_class = None pagination_class = None
permission_classes = [IsEstablishmentManager]
class SocialRUDView(generics.RetrieveUpdateDestroyAPIView): class SocialRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Social RUD view."""
serializer_class = serializers.SocialNetworkSerializers serializer_class = serializers.SocialNetworkSerializers
queryset = models.SocialNetwork.objects.all() queryset = models.SocialNetwork.objects.all()
permission_classes = [IsEstablishmentManager]
class PlateListCreateView(generics.ListCreateAPIView): class PlateListCreateView(generics.ListCreateAPIView):
@ -83,12 +90,14 @@ class PlateListCreateView(generics.ListCreateAPIView):
serializer_class = serializers.PlatesSerializers serializer_class = serializers.PlatesSerializers
queryset = models.Plate.objects.all() queryset = models.Plate.objects.all()
pagination_class = None pagination_class = None
permission_classes = [IsEstablishmentManager]
class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): class PlateRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Social RUD view."""
serializer_class = serializers.PlatesSerializers serializer_class = serializers.PlatesSerializers
queryset = models.Plate.objects.all() queryset = models.Plate.objects.all()
permission_classes = [IsEstablishmentManager]
class PhonesListCreateView(generics.ListCreateAPIView): class PhonesListCreateView(generics.ListCreateAPIView):
@ -96,12 +105,14 @@ class PhonesListCreateView(generics.ListCreateAPIView):
serializer_class = serializers.ContactPhoneBackSerializers serializer_class = serializers.ContactPhoneBackSerializers
queryset = models.ContactPhone.objects.all() queryset = models.ContactPhone.objects.all()
pagination_class = None pagination_class = None
permission_classes = [IsEstablishmentManager]
class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Social RUD view."""
serializer_class = serializers.ContactPhoneBackSerializers serializer_class = serializers.ContactPhoneBackSerializers
queryset = models.ContactPhone.objects.all() queryset = models.ContactPhone.objects.all()
permission_classes = [IsEstablishmentManager]
class EmailListCreateView(generics.ListCreateAPIView): class EmailListCreateView(generics.ListCreateAPIView):
@ -109,12 +120,14 @@ class EmailListCreateView(generics.ListCreateAPIView):
serializer_class = serializers.ContactEmailBackSerializers serializer_class = serializers.ContactEmailBackSerializers
queryset = models.ContactEmail.objects.all() queryset = models.ContactEmail.objects.all()
pagination_class = None pagination_class = None
permission_classes = [IsEstablishmentManager]
class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): class EmailRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Social RUD view."""
serializer_class = serializers.ContactEmailBackSerializers serializer_class = serializers.ContactEmailBackSerializers
queryset = models.ContactEmail.objects.all() queryset = models.ContactEmail.objects.all()
permission_classes = [IsEstablishmentManager]
class EmployeeListCreateView(generics.ListCreateAPIView): class EmployeeListCreateView(generics.ListCreateAPIView):

View File

@ -10,7 +10,7 @@ from establishment import models, serializers
from main import methods from main import methods
from main.models import MetaDataContent from main.models import MetaDataContent
from utils.pagination import EstablishmentPortionPagination from utils.pagination import EstablishmentPortionPagination
from utils.permissions import IsCountryAdmin
class EstablishmentMixinView: class EstablishmentMixinView:
"""Establishment mixin.""" """Establishment mixin."""

View File

@ -3,7 +3,7 @@ import os
class Migration(migrations.Migration): class Migration(migrations.Migration):
# Check migration
def load_data_from_sql(apps, schema_editor): def load_data_from_sql(apps, schema_editor):
file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql')
sql_statement = open(file_path).read() sql_statement = open(file_path).read()

View File

@ -87,7 +87,6 @@ INSERT INTO codelang (code,country) VALUES
,('es-CR','Spanish (Costa Rica)') ,('es-CR','Spanish (Costa Rica)')
,('es-DO','Spanish (Dominican Republic)') ,('es-DO','Spanish (Dominican Republic)')
,('es-EC','Spanish (Ecuador)') ,('es-EC','Spanish (Ecuador)')
,('es-ES','Spanish (Castilian)')
,('es-ES','Spanish (Spain)') ,('es-ES','Spanish (Spain)')
; ;
INSERT INTO codelang (code,country) VALUES INSERT INTO codelang (code,country) VALUES
@ -326,7 +325,7 @@ commit;
INSERT INTO location_country INSERT INTO location_country
(code, "name", low_price, high_price, created, modified) (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, lpad((row_number() over (order by t.country asc))::text, 3, '0') as code,
jsonb_build_object('en-GB', t.country), jsonb_build_object('en-GB', t.country),
0 as low_price, 0 as low_price,
@ -348,6 +347,7 @@ commit;
INSERT INTO translation_language INSERT INTO translation_language
(title, locale) (title, locale)
select select
distinct
t.country as title, t.country as title,
t.code as locale t.code as locale
from from

View File

@ -23,6 +23,10 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
high_price = models.IntegerField(default=50, verbose_name=_('High price')) high_price = models.IntegerField(default=50, verbose_name=_('High price'))
languages = models.ManyToManyField(Language, verbose_name=_('Languages')) languages = models.ManyToManyField(Language, verbose_name=_('Languages'))
@property
def country_id(self):
return self.id
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -119,6 +123,10 @@ class Address(models.Model):
return {'lat': self.latitude, return {'lat': self.latitude,
'lon': self.longitude} 'lon': self.longitude}
@property
def country_id(self):
return self.city.country_id
# todo: Make recalculate price levels # todo: Make recalculate price levels
@receiver(post_save, sender=Country) @receiver(post_save, sender=Country)

View File

@ -5,11 +5,12 @@ from account.models import User
from rest_framework import status from rest_framework import status
from http.cookies import SimpleCookie 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): class BaseTestCase(APITestCase):
def setUp(self): def setUp(self):
self.username = 'sedragurda' self.username = 'sedragurda'
self.password = 'sedragurdaredips19' self.password = 'sedragurdaredips19'
@ -20,27 +21,57 @@ class BaseTestCase(APITestCase):
# get tokens # get tokens
# self.user.is_superuser = True
# self.user.save()
tokkens = User.create_jwt_tokens(self.user) tokkens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie( self.client.cookies = SimpleCookie(
{'access_token': tokkens.get('access_token'), {'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token')}) 'refresh_token': tokkens.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): class CountryTests(BaseTestCase):
def setUp(self):
super().setUp()
def test_country_CRUD(self): 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 = { data = {
'name': 'Test country', 'name': {"ru-RU": "NewCountry"},
'code': 'test' 'code': 'test1'
} }
response = self.client.post('/api/back/location/countries/', data=data, format='json') response = self.client.post('/api/back/location/countries/', data=data, format='json')
response_data = response.json() response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_201_CREATED) 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') response = self.client.get(f'/api/back/location/countries/{response_data["id"]}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -64,6 +95,14 @@ class RegionTests(BaseTestCase):
code="test" 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): def test_region_CRUD(self):
response = self.client.get('/api/back/location/regions/', format='json') response = self.client.get('/api/back/location/regions/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -108,6 +147,13 @@ class CityTests(BaseTestCase):
country=self.country 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): def test_city_CRUD(self):
response = self.client.get('/api/back/location/cities/', format='json') response = self.client.get('/api/back/location/cities/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -142,6 +188,7 @@ class AddressTests(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.country = Country.objects.create( self.country = Country.objects.create(
name=json.dumps({"en-GB": "Test country"}), name=json.dumps({"en-GB": "Test country"}),
code="test" code="test"
@ -160,6 +207,13 @@ class AddressTests(BaseTestCase):
country=self.country 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): def test_address_CRUD(self):
response = self.client.get('/api/back/location/addresses/', format='json') response = self.client.get('/api/back/location/addresses/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -167,10 +221,8 @@ class AddressTests(BaseTestCase):
data = { data = {
'city_id': self.city.id, 'city_id': self.city.id,
'number': '+79999999', 'number': '+79999999',
"coordinates": {
"latitude": 37.0625, "latitude": 37.0625,
"longitude": -95.677068 "longitude": -95.677068,
},
"geo_lon": -95.677068, "geo_lon": -95.677068,
"geo_lat": 37.0625 "geo_lat": 37.0625
} }

View File

@ -3,50 +3,57 @@ from rest_framework import generics
from location import models, serializers from location import models, serializers
from location.views import common from location.views import common
from utils.permissions import IsCountryAdmin
# Address # Address
class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView):
"""Create view for model Address.""" """Create view for model Address."""
serializer_class = serializers.AddressDetailSerializer serializer_class = serializers.AddressDetailSerializer
queryset = models.Address.objects.all() queryset = models.Address.objects.all()
permission_classes = [IsCountryAdmin]
class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model Address.""" """RUD view for model Address."""
serializer_class = serializers.AddressDetailSerializer serializer_class = serializers.AddressDetailSerializer
queryset = models.Address.objects.all() queryset = models.Address.objects.all()
permission_classes = [IsCountryAdmin]
# City # City
class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView):
"""Create view for model City.""" """Create view for model City."""
serializer_class = serializers.CitySerializer serializer_class = serializers.CitySerializer
permission_classes = [IsCountryAdmin]
class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model City.""" """RUD view for model City."""
serializer_class = serializers.CitySerializer serializer_class = serializers.CitySerializer
permission_classes = [IsCountryAdmin]
# Region # Region
class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView):
"""Create view for model Region""" """Create view for model Region"""
serializer_class = serializers.RegionSerializer serializer_class = serializers.RegionSerializer
permission_classes = [IsCountryAdmin]
class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""Retrieve view for model Region""" """Retrieve view for model Region"""
serializer_class = serializers.RegionSerializer serializer_class = serializers.RegionSerializer
permission_classes = [IsCountryAdmin]
# Country # Country
class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView): class CountryListCreateView(generics.ListCreateAPIView):
"""List/Create view for model Country.""" """List/Create view for model Country."""
queryset = models.Country.objects.all()
serializer_class = serializers.CountryBackSerializer serializer_class = serializers.CountryBackSerializer
pagination_class = None pagination_class = None
permission_classes = [IsCountryAdmin]
class CountryRUDView(generics.RetrieveUpdateDestroyAPIView):
class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView):
"""RUD view for model Country.""" """RUD view for model Country."""
serializer_class = serializers.CountryBackSerializer serializer_class = serializers.CountryBackSerializer
permission_classes = [IsCountryAdmin]
queryset = models.Country.objects.all()

View File

@ -0,0 +1,57 @@
# Generated by Django 2.2.4 on 2019-10-21 13:06
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import utils.models
class Migration(migrations.Migration):
dependencies = [
('location', '0012_data_migrate'),
('news', '0021_auto_20191009_1408'),
]
operations = [
migrations.CreateModel(
name='NewsBanner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('title', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='title')),
('image_url', models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path')),
('content_url', models.URLField(blank=True, default=None, null=True, verbose_name='Content URL path')),
],
options={
'abstract': False,
},
bases=(models.Model, utils.models.TranslatedFieldsMixin),
),
migrations.CreateModel(
name='Agenda',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('event_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Event datetime')),
('content', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='content')),
('address', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address')),
],
options={
'abstract': False,
},
bases=(models.Model, utils.models.TranslatedFieldsMixin),
),
migrations.AddField(
model_name='news',
name='agenda',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.Agenda', verbose_name='agenda'),
),
migrations.AddField(
model_name='news',
name='banner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.NewsBanner', verbose_name='banner'),
),
]

View File

@ -4,7 +4,7 @@ from django.contrib.contenttypes import fields as generic
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin
from rating.models import Rating from rating.models import Rating
@ -38,7 +38,7 @@ class NewsQuerySet(models.QuerySet):
def with_extended_related(self): def with_extended_related(self):
"""Return qs with related objects.""" """Return qs with related objects."""
return self.select_related('created_by') return self.select_related('created_by', 'agenda', 'banner')
def by_type(self, news_type): def by_type(self, news_type):
"""Filter News by type""" """Filter News by type"""
@ -70,6 +70,30 @@ class NewsQuerySet(models.QuerySet):
by_tags(news.tags.all()).distinct().order_by('-start') by_tags(news.tags.all()).distinct().order_by('-start')
class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
"""News agenda model"""
event_datetime = models.DateTimeField(default=timezone.now, editable=False,
verbose_name=_('Event datetime'))
address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'),
on_delete=models.SET_NULL)
content = TJSONField(blank=True, null=True, default=None,
verbose_name=_('content'),
help_text='{"en-GB":"some text"}')
class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin):
"""News banner model"""
title = TJSONField(blank=True, null=True, default=None,
verbose_name=_('title'),
help_text='{"en-GB":"some text"}')
image_url = models.URLField(verbose_name=_('Image URL path'),
blank=True, null=True, default=None)
content_url = models.URLField(verbose_name=_('Content URL path'),
blank=True, null=True, default=None)
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
"""News model.""" """News model."""
@ -140,6 +164,14 @@ class News(BaseAttributes, TranslatedFieldsMixin):
gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery') gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
ratings = generic.GenericRelation(Rating) ratings = generic.GenericRelation(Rating)
agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
on_delete=models.SET_NULL,
verbose_name=_('agenda'))
banner = models.ForeignKey('news.NewsBanner', blank=True, null=True,
on_delete=models.SET_NULL,
verbose_name=_('banner'))
objects = NewsQuerySet.as_manager() objects = NewsQuerySet.as_manager()
class Meta: class Meta:

View File

@ -5,7 +5,7 @@ from rest_framework import serializers
from account.serializers.common import UserBaseSerializer from account.serializers.common import UserBaseSerializer
from gallery.models import Image from gallery.models import Image
from location import models as location_models from location import models as location_models
from location.serializers import CountrySimpleSerializer from location.serializers import CountrySimpleSerializer, AddressBaseSerializer
from news import models from news import models
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer
from utils.serializers import TranslatedField, ProjectModelSerializer from utils.serializers import TranslatedField, ProjectModelSerializer
@ -13,6 +13,7 @@ from utils.serializers import TranslatedField, ProjectModelSerializer
class CropImageSerializer(serializers.Serializer): class CropImageSerializer(serializers.Serializer):
"""Serializer for crop images for News object.""" """Serializer for crop images for News object."""
preview_url = serializers.SerializerMethodField() preview_url = serializers.SerializerMethodField()
promo_horizontal_web_url = serializers.SerializerMethodField() promo_horizontal_web_url = serializers.SerializerMethodField()
promo_horizontal_mobile_url = serializers.SerializerMethodField() promo_horizontal_mobile_url = serializers.SerializerMethodField()
@ -81,6 +82,40 @@ class NewsImageSerializer(serializers.ModelSerializer):
} }
class AgendaSerializer(ProjectModelSerializer):
event_datetime = serializers.DateTimeField()
address = AddressBaseSerializer()
content_translated = TranslatedField()
class Meta:
"""Meta class."""
model = models.Agenda
fields = (
'id',
'event_datetime',
'address',
'content_translated'
)
class NewsBannerSerializer(ProjectModelSerializer):
title_translated = TranslatedField()
image_url = serializers.URLField()
content_url = serializers.URLField()
class Meta:
"""Meta class."""
model = models.NewsBanner
fields = (
'id',
'title_translated',
'image_url',
'content_url'
)
class NewsTypeSerializer(serializers.ModelSerializer): class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer.""" """News type serializer."""
@ -95,7 +130,7 @@ class NewsBaseSerializer(ProjectModelSerializer):
"""Base serializer for News model.""" """Base serializer for News model."""
# read only fields # read only fields
title_translated = TranslatedField() title_translated = TranslatedField(source='title')
subtitle_translated = TranslatedField() subtitle_translated = TranslatedField()
# related fields # related fields
@ -151,6 +186,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
same_theme = NewsBaseSerializer(many=True, read_only=True) same_theme = NewsBaseSerializer(many=True, read_only=True)
should_read = NewsBaseSerializer(many=True, read_only=True) should_read = NewsBaseSerializer(many=True, read_only=True)
agenda = AgendaSerializer()
banner = NewsBannerSerializer()
class Meta(NewsDetailSerializer.Meta): class Meta(NewsDetailSerializer.Meta):
"""Meta class.""" """Meta class."""
@ -158,6 +195,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
fields = NewsDetailSerializer.Meta.fields + ( fields = NewsDetailSerializer.Meta.fields + (
'same_theme', 'same_theme',
'should_read', 'should_read',
'agenda',
'banner'
) )

View File

@ -1,3 +1,4 @@
from django.urls import reverse
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
@ -5,8 +6,9 @@ from rest_framework import status
from datetime import datetime, timedelta from datetime import datetime, timedelta
from news.models import NewsType, News from news.models import NewsType, News
from account.models import User from account.models import User, Role, UserRole
from translation.models import Language
from location.models import Country
# Create your tests here. # Create your tests here.
@ -22,23 +24,51 @@ class BaseTestCase(APITestCase):
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token')}) 'refresh_token': tokkens.get('refresh_token')})
self.test_news_type = NewsType.objects.create(name="Test news type") 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"}, self.lang = Language.objects.get(
title='Russia',
locale='ru-RU'
)
self.country_ru = Country.objects.get(
name={"en-GB": "Russian"}
)
role = Role.objects.create(
role=Role.CONTENT_PAGE_MANAGER,
country=self.country_ru
)
role.save()
user_role = UserRole.objects.create(
user=self.user,
role=role
)
user_role.save()
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=datetime.now() + timedelta(hours=-2), playlist=1, start=datetime.now() + timedelta(hours=-2),
end=datetime.now() + timedelta(hours=2), end=datetime.now() + timedelta(hours=2),
state=News.PUBLISHED, slug='test-news-slug',) state=News.PUBLISHED, slug='test-news-slug',
country=self.country_ru)
class NewsTestCase(BaseTestCase): class NewsTestCase(BaseTestCase):
def setUp(self):
super().setUp()
def test_news_list(self): def test_web_news(self):
response = self.client.get("/api/web/news/") response = self.client.get("/api/web/news/")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_news_web_detail(self):
response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.get("/api/web/news/types/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_news_back_detail(self): def test_news_back_detail(self):
response = self.client.get(f"/api/back/news/{self.test_news.id}/") response = self.client.get(f"/api/back/news/{self.test_news.id}/")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -47,6 +77,18 @@ class NewsTestCase(BaseTestCase):
response = self.client.get("/api/back/news/") response = self.client.get("/api/back/news/")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_news_type_list(self): def test_news_back_detail_put(self):
response = self.client.get("/api/web/news/types/") # retrieve-update-destroy
url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id})
data = {
'id': self.test_news.id,
'description': {"en-GB": "Description test news!"},
'slug': self.test_news.slug,
'start': self.test_news.start,
'playlist': self.test_news.playlist,
'news_type_id':self.test_news.news_type_id,
'country_id': self.country_ru.id
}
response = self.client.put(url, data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -8,6 +8,7 @@ from rest_framework.response import Response
from gallery.tasks import delete_image from gallery.tasks import delete_image
from news import filters, models, serializers from news import filters, models, serializers
from rating.tasks import add_rating from rating.tasks import add_rating
from utils.permissions import IsCountryAdmin, IsContentPageManager
class NewsMixinView: class NewsMixinView:
@ -65,6 +66,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView,
serializer_class = serializers.NewsBackOfficeBaseSerializer serializer_class = serializers.NewsBackOfficeBaseSerializer
create_serializers_class = serializers.NewsBackOfficeDetailSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer
permission_classes = [IsCountryAdmin|IsContentPageManager]
def get_serializer_class(self): def get_serializer_class(self):
"""Override serializer class.""" """Override serializer class."""
@ -140,6 +142,7 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
"""Resource for detailed information about news for back-office users.""" """Resource for detailed information about news for back-office users."""
serializer_class = serializers.NewsBackOfficeDetailSerializer serializer_class = serializers.NewsBackOfficeDetailSerializer
permission_classes = [IsCountryAdmin|IsContentPageManager]
def get(self, request, pk, *args, **kwargs): def get(self, request, pk, *args, **kwargs):
add_rating(remote_addr=request.META.get('REMOTE_ADDR'), add_rating(remote_addr=request.META.get('REMOTE_ADDR'),

View File

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-10-17 12:17
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('location', '0012_data_migrate'),
('review', '0003_review_text'),
]
operations = [
migrations.AddField(
model_name='review',
name='country',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='country', to='location.Country', verbose_name='Country'),
),
]

View File

@ -65,6 +65,9 @@ class Review(BaseAttributes, TranslatedFieldsMixin):
validators=[MinValueValidator(1900), validators=[MinValueValidator(1900),
MaxValueValidator(2100)]) MaxValueValidator(2100)])
country = models.ForeignKey('location.Country', on_delete=models.CASCADE,
related_name='country', verbose_name=_('Country'),
null=True)
objects = ReviewQuerySet.as_manager() objects = ReviewQuerySet.as_manager()
class Meta: class Meta:

View File

@ -0,0 +1,18 @@
"""Review app common serializers."""
from review import models
from rest_framework import serializers
class ReviewBaseSerializer(serializers.ModelSerializer):
class Meta:
model = models.Review
fields = ('id',
'reviewer',
'text',
'language',
'status',
'child',
'published_at',
'vintage',
'country'
)

11
apps/review/urls/back.py Normal file
View File

@ -0,0 +1,11 @@
"""Back review URLs"""
from django.urls import path
from review.views import back as views
app_name = 'review'
urlpatterns = [
path('', views.ReviewLstView.as_view(), name='review-list-create'),
path('<int:id>/', views.ReviewRUDView.as_view(), name='review-crud'),
]

19
apps/review/views/back.py Normal file
View File

@ -0,0 +1,19 @@
from rest_framework import generics, permissions
from review.serializers import back as serializers
from review import models
from utils.permissions import IsReviewerManager, IsRestaurantReviewer
class ReviewLstView(generics.ListCreateAPIView):
"""Comment list create view."""
serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Comment RUD view."""
serializer_class = serializers.ReviewBaseSerializer
queryset = models.Review.objects.all()
permission_classes = [IsReviewerManager|IsRestaurantReviewer]
lookup_field = 'id'

View File

@ -29,12 +29,16 @@ def update_document(sender, **kwargs):
registry.update(establishment) registry.update(establishment)
if app_label == 'establishment': if app_label == 'establishment':
# todo: remove after migration
from establishment import models as establishment_models
if model_name == 'establishmenttype': if model_name == 'establishmenttype':
if isinstance(instance, establishment_models.EstablishmentType):
establishments = Establishment.objects.filter( establishments = Establishment.objects.filter(
establishment_type=instance) establishment_type=instance)
for establishment in establishments: for establishment in establishments:
registry.update(establishment) registry.update(establishment)
if model_name == 'establishmentsubtype': if model_name == 'establishmentsubtype':
if instance(instance, establishment_models.EstablishmentSubType):
establishments = Establishment.objects.filter( establishments = Establishment.objects.filter(
establishment_subtypes=instance) establishment_subtypes=instance)
for establishment in establishments: for establishment in establishments:

View File

@ -0,0 +1,22 @@
# Generated by Django 2.2.4 on 2019-10-21 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('translation', '0004_auto_20191018_0832'),
]
operations = [
migrations.AlterField(
model_name='language',
name='locale',
field=models.CharField(max_length=10, verbose_name='Locale identifier'),
),
migrations.AlterUniqueTogether(
name='language',
unique_together={('title', 'locale')},
),
]

View File

@ -22,7 +22,7 @@ class Language(models.Model):
title = models.CharField(max_length=255, title = models.CharField(max_length=255,
verbose_name=_('Language title')) verbose_name=_('Language title'))
locale = models.CharField(max_length=10, unique=True, locale = models.CharField(max_length=10,
verbose_name=_('Locale identifier')) verbose_name=_('Locale identifier'))
objects = LanguageQuerySet.as_manager() objects = LanguageQuerySet.as_manager()
@ -32,6 +32,7 @@ class Language(models.Model):
verbose_name = _('Language') verbose_name = _('Language')
verbose_name_plural = _('Languages') verbose_name_plural = _('Languages')
unique_together = ('title', 'locale')
def __str__(self): def __str__(self):
"""String method""" """String method"""

View File

@ -1,12 +1,15 @@
"""Project custom permissions""" """Project custom permissions"""
from rest_framework.permissions import BasePermission from django.contrib.contenttypes.models import ContentType
from rest_framework import permissions
from rest_framework_simplejwt.tokens import AccessToken from rest_framework_simplejwt.tokens import AccessToken
from account.models import UserRole, Role
from authorization.models import JWTRefreshToken from authorization.models import JWTRefreshToken
from utils.tokens import GMRefreshToken from utils.tokens import GMRefreshToken
class IsAuthenticatedAndTokenIsValid(BasePermission): class IsAuthenticatedAndTokenIsValid(permissions.BasePermission):
""" """
Check if user has a valid token and authenticated Check if user has a valid token and authenticated
""" """
@ -24,7 +27,7 @@ class IsAuthenticatedAndTokenIsValid(BasePermission):
return False return False
class IsRefreshTokenValid(BasePermission): class IsRefreshTokenValid(permissions.BasePermission):
""" """
Check if user has a valid refresh token and authenticated Check if user has a valid refresh token and authenticated
""" """
@ -38,3 +41,158 @@ class IsRefreshTokenValid(BasePermission):
return refresh_token_qs.exists() return refresh_token_qs.exists()
else: else:
return False return False
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
return False
class IsGuest(permissions.IsAuthenticatedOrReadOnly):
"""
Object-level permission to only allow owners of an object to edit it.
"""
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
rules = [
request.user.is_superuser,
request.method in permissions.SAFE_METHODS
]
return any(rules)
class IsStandardUser(IsGuest):
"""
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
rules = [
super().has_object_permission(request, view, obj)
]
if hasattr(obj, 'user'):
rules = [
obj.user == request.user and obj.user.email_confirmed,
super().has_object_permission(request, view, obj)
]
return any(rules)
class IsContentPageManager(IsStandardUser):
"""
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.
role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER,
country_id=obj.country_id)\
.first() # 'Comments moderator'
rules = [
UserRole.objects.filter(user=request.user, role=role).exists(),
# and obj.user != request.user,
super().has_object_permission(request, view, obj)
]
return any(rules)
class IsCountryAdmin(IsStandardUser):
"""
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.
role = Role.objects.filter(role=Role.COUNTRY_ADMIN,
country_id=obj.country_id) \
.first() # 'Comments moderator'
rules = [
UserRole.objects.filter(user=request.user, role=role).exists(),
super().has_object_permission(request, view, obj),
]
return any(rules)
class IsCommentModerator(IsStandardUser):
"""
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.
role = Role.objects.filter(role=Role.COMMENTS_MODERATOR,
country_id=obj.country_id)\
.first() # 'Comments moderator'
rules = [
UserRole.objects.filter(user=request.user, role=role).exists() and
obj.user != request.user,
super().has_object_permission(request, view, obj)
]
return any(rules)
class IsEstablishmentManager(IsStandardUser):
def has_object_permission(self, request, view, obj):
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\
.first() # 'Comments moderator'
rules = [
UserRole.objects.filter(user=request.user, role=role,
establishment_id=obj.establishment_id
).exists(),
super().has_object_permission(request, view, obj)
]
return any(rules)
class IsReviewerManager(IsStandardUser):
def has_object_permission(self, request, view, obj):
role = Role.objects.filter(role=Role.REVIEWER_MANGER,
country_id=obj.country_id)\
.first()
rules = [
UserRole.objects.filter(user=request.user, role=role).exists(),
super().has_object_permission(request, view, obj)
]
return any(rules)
class IsRestaurantReviewer(IsStandardUser):
def has_object_permission(self, request, view, obj):
content_type = ContentType.objects.get(app_lable='establishment',
model='establishment')
role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER,
country=obj.country_id).first()
rules = [
obj.content_type_id == content_type.id and
UserRole.objects.filter(user=request.user, role=role,
establishment_id=obj.object_id
).exists(),
super().has_object_permission(request, view, obj)
]
return any(rules)

View File

@ -33,8 +33,8 @@ def validate_tjson(value):
code='invalid_json', code='invalid_json',
params={'value': value}, params={'value': value},
) )
lang_count = Language.objects.filter(locale__in=value.keys()).count() is_lang = Language.objects.filter(locale__in=value.keys()).exists()
if lang_count != len(value.keys()): if not is_lang:
raise exceptions.ValidationError( raise exceptions.ValidationError(
'invalid_translated_keys', 'invalid_translated_keys',
code='invalid_translated_keys', code='invalid_translated_keys',

View File

View File

@ -0,0 +1,37 @@
from django.test import TestCase
from translation.models import Language
from django.core import exceptions
from utils.serializers import validate_tjson
class ValidJSONTest(TestCase):
def test_valid_json(self):
lang = Language.objects.create(title='English', locale='en-GB')
lang.save()
data = 'str'
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_json')
data = {
"string": "value"
}
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_translated_keys')
data = {
"en-GB": "English"
}
try:
validate_tjson(data)
self.assertTrue(True)
except exceptions.ValidationError:
self.assert_(False, "Test json translated FAILED")

View File

@ -0,0 +1,18 @@
from rest_framework.test import APITestCase
from location.models import Country
from translation.models import Language
class BasePermissionTests(APITestCase):
def setUp(self):
self.lang = Language.objects.get(
title='Russia',
locale='ru-RU'
)
self.country_ru = Country.objects.get(
name={"en-GB": "Russian"}
)

View File

@ -8,11 +8,6 @@ from http.cookies import SimpleCookie
from account.models import User from account.models import User
from news.models import News, NewsType from news.models import News, NewsType
from django.test import TestCase
from translation.models import Language
from django.core import exceptions
from .serializers import validate_tjson
from establishment.models import Establishment, EstablishmentType, Employee from establishment.models import Establishment, EstablishmentType, Employee
@ -42,6 +37,7 @@ class TranslateFieldTests(BaseTestCase):
super().setUp() super().setUp()
self.news_type = NewsType.objects.create(name="Test news type") self.news_type = NewsType.objects.create(name="Test news type")
self.news_type.save()
self.news_item = News.objects.create( self.news_item = News.objects.create(
created_by=self.user, created_by=self.user,
@ -58,9 +54,11 @@ class TranslateFieldTests(BaseTestCase):
slug='test', slug='test',
state=News.PUBLISHED, state=News.PUBLISHED,
) )
self.news_item.save()
def test_model_field(self): def test_model_field(self):
self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) self.assertTrue(hasattr(self.news_item, "title_translated"))
def test_read_locale(self): def test_read_locale(self):
response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json')
@ -69,7 +67,7 @@ class TranslateFieldTests(BaseTestCase):
self.assertIn("title_translated", news_data) self.assertIn("title_translated", news_data)
self.assertEqual(news_data['title_translated'], "Test news item") self.assertIn("Test news item", news_data['title_translated'])
class BaseAttributeTests(BaseTestCase): class BaseAttributeTests(BaseTestCase):
@ -125,36 +123,3 @@ class BaseAttributeTests(BaseTestCase):
employee.refresh_from_db() employee.refresh_from_db()
self.assertEqual(modify_user, employee.modified_by) self.assertEqual(modify_user, employee.modified_by)
self.assertEqual(self.user, employee.created_by) self.assertEqual(self.user, employee.created_by)
class ValidJSONTest(TestCase):
def test_valid_json(self):
lang = Language.objects.create(title='English', locale='en-GB')
lang.save()
data = 'str'
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_json')
data = {
"string": "value"
}
with self.assertRaises(exceptions.ValidationError) as err:
validate_tjson(data)
self.assertEqual(err.exception.code, 'invalid_translated_keys')
data = {
"en-GB": "English"
}
try:
validate_tjson(data)
self.assertTrue(True)
except exceptions.ValidationError:
self.assert_(False, "Test json translated FAILED")

View File

@ -9,5 +9,7 @@ urlpatterns = [
path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')),
path('location/', include('location.urls.back')), path('location/', include('location.urls.back')),
path('news/', include('news.urls.back')), path('news/', include('news.urls.back')),
path('review/', include('review.urls.back')),
path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')),
] ]