From 6b8fdf7eed9707858d1316abce935576899efec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 10 Dec 2019 10:45:35 +0300 Subject: [PATCH 1/8] Add migrate for product models --- apps/account/models.py | 5 +- apps/product/migrations/0021_product_sites.py | 19 +++++ apps/product/models.py | 2 + apps/product/tests.py | 79 +++++++++++++++++++ apps/product/views/back.py | 2 + apps/utils/permissions.py | 15 ++++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 apps/product/migrations/0021_product_sites.py create mode 100644 apps/product/tests.py diff --git a/apps/account/models.py b/apps/account/models.py index 8f6c6233..c5031ed7 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -36,6 +36,8 @@ class Role(ProjectBaseMixin): SALES_MAN = 8 WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 + LIQUOR_REVIEWER = 11 + ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -47,7 +49,8 @@ class Role(ProjectBaseMixin): (RESTAURANT_REVIEWER, 'Restaurant reviewer'), (SALES_MAN, 'Sales man'), (WINERY_REVIEWER, 'Winery reviewer'), - (SELLER, 'Seller') + (SELLER, 'Seller'), + (LIQUOR_REVIEWER, 'Liquor reviewer') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/product/migrations/0021_product_sites.py b/apps/product/migrations/0021_product_sites.py new file mode 100644 index 00000000..c68229fb --- /dev/null +++ b/apps/product/migrations/0021_product_sites.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2019-12-10 07:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0039_sitefeature_old_id'), + ('product', '0020_merge_20191209_0911'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='sites', + field=models.ManyToManyField(to='main.SiteSettings'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index b125c7eb..902073ab 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -222,6 +222,8 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, default=None, null=True, verbose_name=_('Serial number')) + sites = models.ManyToManyField(to='main.SiteSettings') + objects = ProductManager.from_queryset(ProductQuerySet)() class Meta: diff --git a/apps/product/tests.py b/apps/product/tests.py new file mode 100644 index 00000000..91315117 --- /dev/null +++ b/apps/product/tests.py @@ -0,0 +1,79 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from account.models import User +from http.cookies import SimpleCookie +from django.urls import reverse + +# Create your tests here. +from translation.models import Language +from account.models import Role, UserRole +from location.models import Country, Address, City, Region +from main.models import SiteSettings + + +class BaseTestCase(APITestCase): + def setUp(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.newsletter = True + self.user = User.objects.create_user( + username=self.username, + email=self.email, + password=self.password, + is_staff=True, + ) + # get tokens + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) + + + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + + self.country_ru = Country.objects.create( + name={'en-GB': 'Russian'}, + code='RU', + ) + + 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.site = SiteSettings.objects.create( + subdomain='ru', + country=self.country_ru + ) + + self.site.save() + + self.role = Role.objects.create(role=Role.LIQUOR_REVIEWER, + site=self.site) + self.role.save() + + self.user_role = UserRole.objects.create( + user=self.user, role=self.role) + + self.user_role.save() + + +class LiquorReviewerTests(BaseTestCase): + def test_get(self): + url = reverse("back:product:list-create") + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/product/views/back.py b/apps/product/views/back.py index fc5e108f..3029e194 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -7,6 +7,7 @@ from product import serializers, models from product.views import ProductBaseView from utils.serializers import ImageBaseSerializer from utils.views import CreateDestroyGalleryViewMixin +from utils.permissions import IsLiquorReviewer class ProductBackOfficeMixinView(ProductBaseView): @@ -97,6 +98,7 @@ class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOffi generics.ListCreateAPIView): """Product back-office list-create view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer + permission_classes = [IsLiquorReviewer] class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 498f5932..3f1212d9 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -448,4 +448,19 @@ class IsWineryReviewer(IsStandardUser): ).exists(), super().has_object_permission(request, view, obj) ] + return any(rules) + + +class IsLiquorReviewer(IsStandardUser): + # Через establishment получать страну + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + return any(rules) + + def has_object_permission(self, request, view, obj): + rules = [ + super().has_object_permission(request, view, obj) + ] return any(rules) \ No newline at end of file From 0132a03852222dcf040acbca454caf217b9e5d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 10 Dec 2019 12:49:59 +0300 Subject: [PATCH 2/8] Develop rules --- apps/product/views/back.py | 2 ++ apps/utils/permissions.py | 63 +++++++++++++++++++++++++------------- project/settings/local.py | 3 +- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 3029e194..504172a0 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -92,6 +92,7 @@ class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product back-office R/U/D view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer + permission_classes = [IsLiquorReviewer] class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOfficeMixinView, @@ -101,6 +102,7 @@ class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOffi permission_classes = [IsLiquorReviewer] + class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, ProductTypeBackOfficeMixinView, generics.ListCreateAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 3f1212d9..63571314 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -8,7 +8,9 @@ from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken from establishment.models import EstablishmentSubType -from location.models import Address +from location.models import Address +from product.models import Product + class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): """ @@ -81,33 +83,21 @@ class IsStandardUser(IsGuest): """ def has_permission(self, request, view): - rules = [ - super().has_permission(request, view) - ] - # and request.user.email_confirmed, - if hasattr(request, 'user'): - rules = [ - request.user.is_authenticated, - super().has_permission(request, view) - ] + rules = [super().has_permission(request, view), + request.user.is_authenticated, + hasattr(request, 'user') + ] return any(rules) 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 - and request.user.is_authenticated, - - super().has_object_permission(request, view, obj) - ] + rules = [super().has_object_permission(request, view, obj), + request.user.is_authenticated, + hasattr(request, 'user') + ] return any(rules) @@ -452,15 +442,44 @@ class IsWineryReviewer(IsStandardUser): class IsLiquorReviewer(IsStandardUser): - # Через establishment получать страну def has_permission(self, request, view): rules = [ super().has_permission(request, view) ] + + pk_object = None + product = None + permission = False + if 'pk' in view.kwargs: + pk_object = view.kwargs['pk'] + + if pk_object is not None: + product = Product.objects.get(pk=pk_object) + + if hasattr(product, 'sites') and product.sites.exists(): + role = Role.objects.filter(role=Role.LIQUOR_REVIEWER, site__in=[site for site in product.sites]) + permission = UserRole.objects.filter(user=request.user, role=role).exists() + + rules.append(permission) return any(rules) def has_object_permission(self, request, view, obj): rules = [ super().has_object_permission(request, view, obj) ] + pk_object = None + product = None + permission = False + + if 'pk' in view.kwargs: + pk_object = view.kwargs['pk'] + + if pk_object is not None: + product = Product.objects.get(pk=pk_object) + + if product.sites.exists(): + role = Role.objects.filter(role=Role.LIQUOR_REVIEWER, site__in=[site for site in product.sites]) + permission = UserRole.objects.filter(user=request.user, role=role).exists() + + rules.append(permission) return any(rules) \ No newline at end of file diff --git a/project/settings/local.py b/project/settings/local.py index d9c7cab8..5581a50d 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -29,8 +29,7 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) # SORL thumbnails THUMBNAIL_DEBUG = True -# ADDED TRANSFER APP -INSTALLED_APPS.append('transfer.apps.TransferConfig') + # DATABASES DATABASES = { From 3aa1ca98e0b8eaf7704e653c79656501158e855f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 10 Dec 2019 12:50:38 +0300 Subject: [PATCH 3/8] Add test post --- apps/product/tests.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/product/tests.py b/apps/product/tests.py index 91315117..82fa8708 100644 --- a/apps/product/tests.py +++ b/apps/product/tests.py @@ -9,7 +9,7 @@ from translation.models import Language from account.models import Role, UserRole from location.models import Country, Address, City, Region from main.models import SiteSettings - +from product.models import Product class BaseTestCase(APITestCase): def setUp(self): @@ -71,9 +71,44 @@ class BaseTestCase(APITestCase): self.user_role.save() + self.product = Product.objects.create() + self.product.save() + class LiquorReviewerTests(BaseTestCase): def test_get(self): url = reverse("back:product:list-create") response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + url = reverse("back:product:rud", kwargs={'pk': self.product.id}) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_post_patch_put_delete(self): + # POST + + data_post = { + "slug": None, + "public_mark": None, + "vintage": None, + "average_price": None, + "description": None, + "available": False, + "establishment": None, + "wine_village": None, + "in_favorites": 'false', + "state": Product.PUBLISHED + } + + url = reverse("back:product:list-create") + response = self.client.post(url, data=data_post, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data_patch = { + 'name': 'Test product' + } + url = reverse("back:product:rud", kwargs={'pk': self.product.id}) + response = self.client.patch(url, data=data_patch, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + From 0a8db7e3adb9432ce3d0eeb092d98371b6d91432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 10 Dec 2019 18:36:18 +0300 Subject: [PATCH 4/8] permission --- apps/product/migrations/0021_product_site.py | 20 +++++ apps/product/migrations/0021_product_sites.py | 19 ----- .../migrations/0022_auto_20191210_1517.py | 18 +++++ apps/product/models.py | 11 ++- apps/product/serializers/back.py | 6 +- apps/product/serializers/common.py | 2 +- apps/product/tests.py | 23 ++++-- apps/product/views/back.py | 1 - apps/utils/permissions.py | 74 ++++++++++++------- 9 files changed, 116 insertions(+), 58 deletions(-) create mode 100644 apps/product/migrations/0021_product_site.py delete mode 100644 apps/product/migrations/0021_product_sites.py create mode 100644 apps/product/migrations/0022_auto_20191210_1517.py diff --git a/apps/product/migrations/0021_product_site.py b/apps/product/migrations/0021_product_site.py new file mode 100644 index 00000000..5c92d3b1 --- /dev/null +++ b/apps/product/migrations/0021_product_site.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.7 on 2019-12-10 14:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0040_footer'), + ('product', '0020_merge_20191209_0911'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='site', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='main.SiteSettings'), + ), + ] diff --git a/apps/product/migrations/0021_product_sites.py b/apps/product/migrations/0021_product_sites.py deleted file mode 100644 index c68229fb..00000000 --- a/apps/product/migrations/0021_product_sites.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.7 on 2019-12-10 07:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0039_sitefeature_old_id'), - ('product', '0020_merge_20191209_0911'), - ] - - operations = [ - migrations.AddField( - model_name='product', - name='sites', - field=models.ManyToManyField(to='main.SiteSettings'), - ), - ] diff --git a/apps/product/migrations/0022_auto_20191210_1517.py b/apps/product/migrations/0022_auto_20191210_1517.py new file mode 100644 index 00000000..30aa083c --- /dev/null +++ b/apps/product/migrations/0022_auto_20191210_1517.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-10 15:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0021_product_site'), + ] + + operations = [ + migrations.AlterField( + model_name='producttype', + name='index_name', + field=models.CharField(choices=[('food', 'food'), ('wine', 'wine'), ('liquor', 'liquor'), ('souvenir', 'souvenir'), ('book', 'book')], db_index=True, max_length=50, unique=True, verbose_name='Index name'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index 902073ab..15ddfb2e 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -25,10 +25,17 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin): SOUVENIR = 'souvenir' BOOK = 'book' + INDEX_CHOICES = ( + (FOOD, 'food'), + (WINE, 'wine'), + (LIQUOR, 'liquor'), + (SOUVENIR, 'souvenir'), + (BOOK, 'book') + ) name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Name'), help_text='{"en-GB":"some text"}') index_name = models.CharField(max_length=50, unique=True, db_index=True, - verbose_name=_('Index name')) + verbose_name=_('Index name'), choices=INDEX_CHOICES) use_subtypes = models.BooleanField(_('Use subtypes'), default=True) tag_categories = models.ManyToManyField('tag.TagCategory', related_name='product_types', @@ -222,7 +229,7 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, default=None, null=True, verbose_name=_('Serial number')) - sites = models.ManyToManyField(to='main.SiteSettings') + site = models.ForeignKey(to='main.SiteSettings', null=True, blank=True, on_delete=models.CASCADE) objects = ProductManager.from_queryset(ProductQuerySet)() diff --git a/apps/product/serializers/back.py b/apps/product/serializers/back.py index 55dc5ebc..575a64c4 100644 --- a/apps/product/serializers/back.py +++ b/apps/product/serializers/back.py @@ -8,7 +8,7 @@ from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializ ProductSubTypeBaseSerializer from tag.models import TagCategory from account.serializers.common import UserShortSerializer - +from main.serializers import SiteSettingsSerializer class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): """Serializer class for model ProductGallery.""" @@ -54,6 +54,7 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): class ProductBackOfficeDetailSerializer(ProductDetailSerializer): """Product back-office detail serializer.""" + in_favorites = serializers.BooleanField(allow_null=True, read_only=True) class Meta(ProductDetailSerializer.Meta): """Meta class.""" @@ -67,9 +68,10 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer): # 'wine_sub_region', 'wine_village', 'state', + 'site', + 'product_type' ] - class ProductTypeBackOfficeDetailSerializer(ProductTypeBaseSerializer): """Product type back-office detail serializer.""" diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index e0617e63..c964a047 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -95,7 +95,7 @@ class ProductBaseSerializer(serializers.ModelSerializer): wine_colors = TagBaseSerializer(many=True, read_only=True) preview_image_url = serializers.URLField(allow_null=True, read_only=True) - in_favorites = serializers.BooleanField(allow_null=True) + in_favorites = serializers.BooleanField(allow_null=True, read_only=True) wine_origins = EstablishmentWineOriginBaseSerializer(many=True, read_only=True) class Meta: diff --git a/apps/product/tests.py b/apps/product/tests.py index 82fa8708..d2d9ebd0 100644 --- a/apps/product/tests.py +++ b/apps/product/tests.py @@ -9,7 +9,7 @@ from translation.models import Language from account.models import Role, UserRole from location.models import Country, Address, City, Region from main.models import SiteSettings -from product.models import Product +from product.models import Product, ProductType class BaseTestCase(APITestCase): def setUp(self): @@ -71,12 +71,19 @@ class BaseTestCase(APITestCase): self.user_role.save() - self.product = Product.objects.create() + self.product_type = ProductType.objects.create(index_name=ProductType.LIQUOR) + self.product_type.save() + + self.product = Product.objects.create(name='Product') self.product.save() + class LiquorReviewerTests(BaseTestCase): def test_get(self): + self.product.product_type = self.product_type + self.product.save() + url = reverse("back:product:list-create") response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -86,8 +93,6 @@ class LiquorReviewerTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_post_patch_put_delete(self): - # POST - data_post = { "slug": None, "public_mark": None, @@ -97,13 +102,13 @@ class LiquorReviewerTests(BaseTestCase): "available": False, "establishment": None, "wine_village": None, - "in_favorites": 'false', - "state": Product.PUBLISHED + "state": Product.PUBLISHED, + "site_id": self.site.id, + "product_type_id": self.product_type.id } - url = reverse("back:product:list-create") response = self.client.post(url, data=data_post, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) data_patch = { 'name': 'Test product' @@ -112,3 +117,5 @@ class LiquorReviewerTests(BaseTestCase): response = self.client.patch(url, data=data_patch, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 504172a0..836f9d36 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -102,7 +102,6 @@ class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOffi permission_classes = [IsLiquorReviewer] - class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, ProductTypeBackOfficeMixinView, generics.ListCreateAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 63571314..1f30a4c6 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -9,7 +9,7 @@ from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken from establishment.models import EstablishmentSubType from location.models import Address -from product.models import Product +from product.models import Product, ProductType class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): @@ -448,38 +448,62 @@ class IsLiquorReviewer(IsStandardUser): ] pk_object = None - product = None + roles = None permission = False + + if 'site_id' in request.data and 'product_type_id' in request.data: + if request.data['site_id'] is not None \ + and request.data['product_type_id'] is not None: + + product_types = ProductType.objects. \ + filter(index_name=ProductType.LIQUOR, + id=request.data['product_type_id']) + + if product_types.exists(): + roles = Role.objects.filter(role=Role.LIQUOR_REVIEWER, + site_id=request.data['site_id']) + if 'pk' in view.kwargs: pk_object = view.kwargs['pk'] if pk_object is not None: product = Product.objects.get(pk=pk_object) + if product.site_id is not None \ + and product.product_type_id is not None: - if hasattr(product, 'sites') and product.sites.exists(): - role = Role.objects.filter(role=Role.LIQUOR_REVIEWER, site__in=[site for site in product.sites]) - permission = UserRole.objects.filter(user=request.user, role=role).exists() + product_types = ProductType.objects. \ + filter(index_name=ProductType.LIQUOR, + id=product.product_type_id) + + if product_types.exists(): + roles = Role.objects.filter(role=Role.LIQUOR_REVIEWER, + site_id=product.site_id) + + if roles is not None: + permission = UserRole.objects.filter(user=request.user, role__in=[role for role in roles])\ + .exists() rules.append(permission) return any(rules) - def has_object_permission(self, request, view, obj): - rules = [ - super().has_object_permission(request, view, obj) - ] - pk_object = None - product = None - permission = False - - if 'pk' in view.kwargs: - pk_object = view.kwargs['pk'] - - if pk_object is not None: - product = Product.objects.get(pk=pk_object) - - if product.sites.exists(): - role = Role.objects.filter(role=Role.LIQUOR_REVIEWER, site__in=[site for site in product.sites]) - permission = UserRole.objects.filter(user=request.user, role=role).exists() - - rules.append(permission) - return any(rules) \ No newline at end of file + # + # def has_object_permission(self, request, view, obj): + # rules = [ + # super().has_object_permission(request, view, obj) + # ] + # # pk_object = None + # # product = None + # # permission = False + # # + # # if 'pk' in view.kwargs: + # # pk_object = view.kwargs['pk'] + # # + # # if pk_object is not None: + # # product = Product.objects.get(pk=pk_object) + # # + # # if product.sites.exists(): + # # role = Role.objects.filter(role=Role.LIQUOR_REVIEWER, site__in=[site for site in product.sites]) + # # permission = UserRole.objects.filter(user=request.user, role=role).exists() + # # + # # rules.append(permission) + # return any(rules) \ No newline at end of file From c32d3e48278382611cbefbeacf352d680805f897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 10 Dec 2019 18:54:49 +0300 Subject: [PATCH 5/8] permission --- .../migrations/0026_auto_20191210_1553.py | 18 ++++ apps/account/models.py | 5 +- apps/product/views/back.py | 6 +- apps/utils/permissions.py | 88 +++++++++++++++++++ 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 apps/account/migrations/0026_auto_20191210_1553.py diff --git a/apps/account/migrations/0026_auto_20191210_1553.py b/apps/account/migrations/0026_auto_20191210_1553.py new file mode 100644 index 00000000..f6186bc4 --- /dev/null +++ b/apps/account/migrations/0026_auto_20191210_1553.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-10 15:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0025_auto_20191210_0623'), + ] + + 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'), (8, 'Sales man'), (9, 'Winery reviewer'), (10, 'Seller'), (11, 'Liquor reviewer'), (12, 'Product reviewer')], verbose_name='Role'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 280260df..8ad3dcb0 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -37,7 +37,7 @@ class Role(ProjectBaseMixin): WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 LIQUOR_REVIEWER = 11 - + PRODUCT_REVIEWER = 12 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -50,7 +50,8 @@ class Role(ProjectBaseMixin): (SALES_MAN, 'Sales man'), (WINERY_REVIEWER, 'Winery reviewer'), (SELLER, 'Seller'), - (LIQUOR_REVIEWER, 'Liquor reviewer') + (LIQUOR_REVIEWER, 'Liquor reviewer'), + (PRODUCT_REVIEWER, 'Product reviewer'), ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 836f9d36..539898a4 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -7,7 +7,7 @@ from product import serializers, models from product.views import ProductBaseView from utils.serializers import ImageBaseSerializer from utils.views import CreateDestroyGalleryViewMixin -from utils.permissions import IsLiquorReviewer +from utils.permissions import IsLiquorReviewer, IsProductReviewer class ProductBackOfficeMixinView(ProductBaseView): @@ -92,14 +92,14 @@ class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product back-office R/U/D view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [IsLiquorReviewer] + permission_classes = [IsLiquorReviewer | IsProductReviewer] class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOfficeMixinView, generics.ListCreateAPIView): """Product back-office list-create view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [IsLiquorReviewer] + permission_classes = [IsLiquorReviewer | IsProductReviewer] class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 1f30a4c6..e08b25f4 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -441,6 +441,94 @@ class IsWineryReviewer(IsStandardUser): return any(rules) +class IsWineryReviewer(IsStandardUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + if 'type_id' in request.data and 'address_id' in request.data and request.user: + countries = Address.objects.filter(id=request.data['address_id']) + + est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) + if est.exists(): + role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est], + role=Role.WINERY_REVIEWER, + country_id__in=[country.id for country in countries]) \ + .first() + + rules.append( + UserRole.objects.filter(user=request.user, role=role).exists() + ) + + return any(rules) + + def has_object_permission(self, request, view, obj): + rules = [ + super().has_object_permission(request, view, obj) + ] + + if hasattr(obj, 'type_id') or hasattr(obj, 'establishment_type_id'): + type_id: int + if hasattr(obj, 'type_id'): + type_id = obj.type_id + else: + type_id = obj.establishment_type_id + + est = EstablishmentSubType.objects.filter(establishment_type_id=type_id) + role = Role.objects.filter(role=Role.WINERY_REVIEWER, + establishment_subtype_id__in=[id for type.id in est], + country_id=obj.country_id).first() + + object_id: int + if hasattr(obj, 'object_id'): + object_id = obj.object_id + else: + object_id = obj.establishment_id + + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=object_id + ).exists(), + super().has_object_permission(request, view, obj) + ] + return any(rules) + + +class IsProductReviewer(IsStandardUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + pk_object = None + roles = None + permission = False + + if 'site_id' in request.data: + if request.data['site_id'] is not None: + roles = Role.objects.filter(role=Role.PRODUCT_REVIEWER, + site_id=request.data['site_id']) + + if 'pk' in view.kwargs: + pk_object = view.kwargs['pk'] + + if pk_object is not None: + product = Product.objects.get(pk=pk_object) + if product.site_id is not None: + roles = Role.objects.filter(role=Role.PRODUCT_REVIEWER, + site_id=product.site_id) + + if roles is not None: + permission = UserRole.objects.filter(user=request.user, role__in=[role for role in roles])\ + .exists() + + rules.append(permission) + return any(rules) + + class IsLiquorReviewer(IsStandardUser): def has_permission(self, request, view): rules = [ From 41670729d5fb67abd2761212cd79775c396ce8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 17 Dec 2019 14:29:20 +0300 Subject: [PATCH 6/8] Fix --- .../account/migrations/0028_merge_20191217_1127.py | 14 ++++++++++++++ .../product/migrations/0023_merge_20191217_1127.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 apps/account/migrations/0028_merge_20191217_1127.py create mode 100644 apps/product/migrations/0023_merge_20191217_1127.py diff --git a/apps/account/migrations/0028_merge_20191217_1127.py b/apps/account/migrations/0028_merge_20191217_1127.py new file mode 100644 index 00000000..0cb2121e --- /dev/null +++ b/apps/account/migrations/0028_merge_20191217_1127.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.7 on 2019-12-17 11:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0027_auto_20191211_1444'), + ('account', '0026_auto_20191210_1553'), + ] + + operations = [ + ] diff --git a/apps/product/migrations/0023_merge_20191217_1127.py b/apps/product/migrations/0023_merge_20191217_1127.py new file mode 100644 index 00000000..e398ebce --- /dev/null +++ b/apps/product/migrations/0023_merge_20191217_1127.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.7 on 2019-12-17 11:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0022_auto_20191210_1517'), + ('product', '0021_auto_20191212_0926'), + ] + + operations = [ + ] From 1d8b707c3cb92920715e55587516d4172aee89a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 17 Dec 2019 17:51:56 +0300 Subject: [PATCH 7/8] Fix est_type.id --- apps/utils/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index e08b25f4..4270ad74 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -398,7 +398,7 @@ class IsWineryReviewer(IsStandardUser): est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) if est.exists(): - role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est], + role = Role.objects.filter(establishment_subtype_id__in=[est_type.id for est_type in est], role=Role.WINERY_REVIEWER, country_id__in=[country.id for country in countries]) \ .first() @@ -423,7 +423,7 @@ class IsWineryReviewer(IsStandardUser): est = EstablishmentSubType.objects.filter(establishment_type_id=type_id) role = Role.objects.filter(role=Role.WINERY_REVIEWER, - establishment_subtype_id__in=[id for type.id in est], + establishment_subtype_id__in=[est_type.id for est_type in est], country_id=obj.country_id).first() object_id: int From eaefae399db7f59d6e1c080d464510fc3f73a764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 17 Dec 2019 17:52:54 +0300 Subject: [PATCH 8/8] Fix est_type.id --- apps/utils/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 4270ad74..2fecf99d 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -453,7 +453,7 @@ class IsWineryReviewer(IsStandardUser): est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) if est.exists(): - role = Role.objects.filter(establishment_subtype_id__in=[type.id for type in est], + role = Role.objects.filter(establishment_subtype_id__in=[est_type.id for est_type in est], role=Role.WINERY_REVIEWER, country_id__in=[country.id for country in countries]) \ .first() @@ -478,7 +478,7 @@ class IsWineryReviewer(IsStandardUser): est = EstablishmentSubType.objects.filter(establishment_type_id=type_id) role = Role.objects.filter(role=Role.WINERY_REVIEWER, - establishment_subtype_id__in=[id for type.id in est], + establishment_subtype_id__in=[est_type.id for est_type in est], country_id=obj.country_id).first() object_id: int