From 38a650839664f54e00c89f6ba047d221d16f4677 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 24 Jan 2020 18:04:32 +0300 Subject: [PATCH 1/6] modified role model --- apps/account/admin.py | 8 +- apps/account/models.py | 28 ++- apps/establishment/models.py | 30 +++- apps/establishment/views/back.py | 26 ++- apps/product/models.py | 18 ++ apps/product/views/back.py | 29 ++- apps/utils/permissions.py | 298 +++++++++++++++---------------- 7 files changed, 265 insertions(+), 172 deletions(-) diff --git a/apps/account/admin.py b/apps/account/admin.py index 3661b3e5..db5db937 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -7,14 +7,14 @@ from account import models @admin.register(models.Role) class RoleAdmin(admin.ModelAdmin): - list_display = ['id', 'role', 'country'] - raw_id_fields = ['country', ] + list_display = ['id', 'role', 'country', 'establishment_subtype', ] + raw_id_fields = ['country', 'establishment_subtype', ] @admin.register(models.UserRole) class UserRoleAdmin(admin.ModelAdmin): - list_display = ['user', 'role', 'establishment', ] - raw_id_fields = ['user', 'role', 'establishment', 'requester', ] + list_display = ['user', 'role', ] + raw_id_fields = ['user', 'role', 'requester', 'establishment', ] @admin.register(models.User) diff --git a/apps/account/models.py b/apps/account/models.py index 08944f0c..853d126c 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -49,8 +49,9 @@ class Role(ProjectBaseMixin): SALES_MAN = 8 WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 - LIQUOR_REVIEWER = 11 + DISTILLERY_LIQUOR_INSPECTOR = 11 PRODUCT_REVIEWER = 12 + ESTABLISHMENT_ADMINISTRATOR = 13 ROLE_CHOICES = ( (STANDARD_USER, _('Standard user')), @@ -63,10 +64,21 @@ class Role(ProjectBaseMixin): (SALES_MAN, _('Sales man')), (WINERY_REVIEWER, _('Winery reviewer')), (SELLER, _('Seller')), - (LIQUOR_REVIEWER, _('Liquor reviewer')), + (DISTILLERY_LIQUOR_INSPECTOR, _('Distillery & Liquor inspector')), (PRODUCT_REVIEWER, _('Product reviewer')), + (ESTABLISHMENT_ADMINISTRATOR, _('Establishment administrator')), ) + ESTABLISHMENT_EDITORS = [ + COUNTRY_ADMIN, + ESTABLISHMENT_MANAGER, + ESTABLISHMENT_ADMINISTRATOR, + ] + + PRODUCT_EDITORS = ESTABLISHMENT_EDITORS + [ + DISTILLERY_LIQUOR_INSPECTOR + ] + role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) country = models.ForeignKey(Country, verbose_name=_('Country'), @@ -480,6 +492,18 @@ class UserRoleQueryset(models.QuerySet): }) return role_counter + def validated(self): + """Filter QuerySet by state.""" + return self.filter(state=self.model.VALIDATED) + + def establishment_editors(self): + """Return QuerySet filtered by role and state.""" + return self.validated().filter(role__role__in=Role.ESTABLISHMENT_EDITORS) + + def product_editors(self): + """Return QuerySet filtered by role and state.""" + return self.validated().filter(role__role__in=Role.PRODUCT_EDITORS) + class UserRole(ProjectBaseMixin): """UserRole model.""" diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5f575c78..b6521255 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -514,6 +514,29 @@ class EstablishmentQuerySet(models.QuerySet): to_attr='main_image') ) + def available_establishments(self, user, country_code: str = None): + """Return QuerySet with establishment that is available for editing.""" + from account.models import UserRole + + available_ids = Subquery( + UserRole.objects.filter(user=user) + .distinct('user', 'establishment') + .values_list('establishment', flat=True) + ) + + filters = {} + + if country_code: + filters.update({'address__city__country__code': country_code, + 'id__in': available_ids}) + return self.filter(**filters) + + def available_objects(self, user, country_code: str = None): + access_roles = user.userrole_set.establishment_editors() + if access_roles.exists(): + return self.available_establishments(user, country_code) + return self.none() + class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin): @@ -767,13 +790,6 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin, """ return self.address.country_id if hasattr(self.address, 'country_id') else None - @property - def establishment_id(self): - """ - Return establishment id of establishment location - """ - return self.id - @property def wines(self): """Return list products with type wine""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 723d3844..394e89f4 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,5 +1,4 @@ """Establishment app views.""" - from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions, status @@ -10,7 +9,9 @@ from establishment import filters, models, serializers from establishment.models import EstablishmentEmployee from timetable.models import Timetable from timetable.serialziers import ScheduleCreateSerializer, ScheduleRUDSerializer -from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer +from utils.permissions import ( + IsCountryAdmin, IsEstablishmentManager, + IsWineryReviewer, IsEstablishmentAdministrator) from utils.views import CreateDestroyGalleryViewMixin @@ -18,7 +19,7 @@ class EstablishmentMixinViews: """Establishment mixin.""" def get_queryset(self): - """Overrided method 'get_queryset'.""" + """Overridden method 'get_queryset'.""" return models.Establishment.objects.with_base_related() @@ -27,10 +28,20 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP filter_class = filters.EstablishmentFilter - permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] + permission_classes = [ + IsWineryReviewer | + IsCountryAdmin | + IsEstablishmentManager | + IsEstablishmentAdministrator + ] queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer + def get_queryset(self): + """Overridden get_queryset method.""" + qs = super(EstablishmentListCreateView, self).get_queryset() + return qs.available_objects(self.request.user, self.request.country_code) + class EmployeeEstablishmentsListView(generics.ListAPIView): """Establishment by employee list view.""" @@ -52,7 +63,12 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): 'establishmentemployee_set__establishment', ) serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] + permission_classes = [ + IsWineryReviewer | + IsCountryAdmin | + IsEstablishmentManager | + IsEstablishmentAdministrator + ] class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/product/models.py b/apps/product/models.py index 8be6ddd4..272fbd0b 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -9,6 +9,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Case, When, F from django.utils.translation import gettext_lazy as _ +from django.db.models import Subquery from location.models import WineOriginAddressMixin from review.models import Review @@ -227,6 +228,23 @@ class ProductQuerySet(models.QuerySet): .distinct(*similarity_rules['distinction'], 'id') + def available_products(self, user): + """Return QuerySet with products that is available for editing.""" + from account.models import UserRole + + available_ids = Subquery( + UserRole.objects.filter(user=user) + .distinct('user', 'establishment') + .values_list('establishment__products', flat=True) + ) + return self.filter(id__in=available_ids) + + def available_objects(self, user): + access_roles = user.userrole_set.product_editors() + if access_roles.exists(): + return self.available_products(user) + return self.none() + class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, HasTagsMixin, FavoritesMixin): diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 539898a4..7d4f5eb1 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -2,12 +2,16 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics, status, permissions, views from rest_framework.response import Response +from django.db.models import Prefetch from product import serializers, models +from location.models import Address, City, Country from product.views import ProductBaseView from utils.serializers import ImageBaseSerializer from utils.views import CreateDestroyGalleryViewMixin -from utils.permissions import IsLiquorReviewer, IsProductReviewer +from utils.permissions import ( + IsDistilleryLiquorInspector, IsProductReviewer, + IsEstablishmentManager, IsEstablishmentAdministrator) class ProductBackOfficeMixinView(ProductBaseView): @@ -17,7 +21,7 @@ class ProductBackOfficeMixinView(ProductBaseView): def get_queryset(self): """Override get_queryset method.""" - qs = models.Product.objects.annotate_in_favorites(self.request.user) + qs = models.Product.objects.with_extended_related().annotate_in_favorites(self.request.user) return qs @@ -92,14 +96,29 @@ class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product back-office R/U/D view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [IsLiquorReviewer | IsProductReviewer] + permission_classes = [ + # IsLiquorReviewer | + # IsProductReviewer | + IsEstablishmentManager | + IsEstablishmentAdministrator + ] -class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOfficeMixinView, +class ProductListCreateBackOfficeView(ProductBackOfficeMixinView, generics.ListCreateAPIView): """Product back-office list-create view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [IsLiquorReviewer | IsProductReviewer] + permission_classes = [ + IsDistilleryLiquorInspector | + IsProductReviewer | + IsEstablishmentAdministrator | + IsEstablishmentManager + ] + + def get_queryset(self): + """Overridden get_queryset method.""" + qs = super(ProductListCreateBackOfficeView, self).get_queryset() + return qs.available_objects(self.request.user) class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2fecf99d..bf06190e 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -7,7 +7,7 @@ from rest_framework_simplejwt.tokens import AccessToken from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken -from establishment.models import EstablishmentSubType +from establishment.models import EstablishmentSubType, Establishment from location.models import Address from product.models import Product, ProductType @@ -47,7 +47,7 @@ class IsRefreshTokenValid(permissions.BasePermission): return False def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request, + # Read permissions are allowed to all 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: @@ -59,24 +59,23 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ - SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') def has_permission(self, request, view): rules = [ - request.user.is_superuser, + request.user.is_anonymous, request.method in permissions.SAFE_METHODS ] - return any(rules) + return all(rules) def has_object_permission(self, request, view, obj): rules = [ - request.user.is_superuser, + request.user.is_anonymous, request.method in permissions.SAFE_METHODS ] - return any(rules) + return all(rules) -class IsStandardUser(IsGuest): +class IsStandardUser(permissions.IsAuthenticated): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. @@ -85,8 +84,7 @@ class IsStandardUser(IsGuest): def has_permission(self, request, view): rules = [super().has_permission(request, view), - request.user.is_authenticated, - hasattr(request, 'user') + request.user.email_confirmed, ] return any(rules) @@ -95,8 +93,7 @@ class IsStandardUser(IsGuest): # Read permissions are allowed to any request rules = [super().has_object_permission(request, view, obj), - request.user.is_authenticated, - hasattr(request, 'user') + request.user.email_confirmed, ] return any(rules) @@ -133,10 +130,10 @@ class IsContentPageManager(IsStandardUser): super().has_permission(request, view) ] - return any(rules) + return all(rules) def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request. + # Read permissions are allowed to all request. if hasattr(obj, 'site_id'): role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, site_id=obj.site_id) \ @@ -156,7 +153,7 @@ class IsContentPageManager(IsStandardUser): super().has_object_permission(request, view, obj) ] - return any(rules) + return all(rules) class IsCountryAdmin(IsStandardUser): @@ -169,11 +166,12 @@ class IsCountryAdmin(IsStandardUser): rules = [ super().has_permission(request, view) ] + + rule = False # and request.user.email_confirmed, if hasattr(request.data, 'user'): if hasattr(request.data, 'site_id'): - # Read permissions are allowed to any request. - + # Read permissions are allowed to all request. role = Role.objects.filter(role=Role.COUNTRY_ADMIN, site_id=request.data.site_id) \ .first() @@ -184,22 +182,24 @@ class IsCountryAdmin(IsStandardUser): ] elif hasattr(request.data, 'country_id'): - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, - country_id=request.data.country_id) \ - .first() - + role = Role.objects.filter( + role=Role.COUNTRY_ADMIN, + country_id=request.data.country_id + ).first() rules = [ UserRole.objects.filter(user=request.user, role=role).exists(), super().has_permission(request, view) ] - return any(rules) + rules.append(rule) + return all(rules) def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request. + # Read permissions are allowed to all request. if hasattr(obj, 'site_id'): - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, - site_id=obj.site_id) \ - .first() + role = Role.objects.filter( + role=Role.COUNTRY_ADMIN, + site_id=obj.site_id + ).first() rules = [ super().has_object_permission(request, view, obj) @@ -225,7 +225,7 @@ class IsCountryAdmin(IsStandardUser): super().has_object_permission(request, view, obj), ] - return any(rules) + return all(rules) class IsCommentModerator(IsStandardUser): @@ -239,8 +239,8 @@ class IsCommentModerator(IsStandardUser): super().has_permission(request, view) ] - if any(rules) and hasattr(request.data, 'site_id'): - # Read permissions are allowed to any request. + if all(rules) and hasattr(request.data, 'site_id'): + # Read permissions are allowed to all request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, site_id=request.data.site_id) \ @@ -251,7 +251,7 @@ class IsCommentModerator(IsStandardUser): super().has_permission(request, view) ] - return any(rules) + return all(rules) def has_object_permission(self, request, view, obj): @@ -270,7 +270,7 @@ class IsCommentModerator(IsStandardUser): obj.user != request.user, super().has_object_permission(request, view, obj) ] - return any(rules) + return all(rules) class IsEstablishmentManager(IsStandardUser): @@ -279,40 +279,69 @@ class IsEstablishmentManager(IsStandardUser): rules = [ super().has_permission(request, view) ] + rule = False + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + user = request.user + role = Role.objects.filter( + role=Role.ESTABLISHMENT_MANAGER, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=user, role__id__in=role.values_list('id', flat=True), + ) + rule = True if user_role.exists() else rule + rules.append(rule) + return all(rules) - if hasattr(request.data, 'user'): - if hasattr(request.data, 'establishment_id'): - role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ - .first() + def has_object_permission(self, request, view, obj): + return self.has_permission(request, view) - rules = [ - UserRole.objects.filter(user=request.user, role=role, - establishment_id=request.data.establishment_id - ).exists(), - super().has_permission(request, view) - ] - return any(rules) + +class IsEstablishmentAdministrator(IsStandardUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + rule = False + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + user = request.user + role = Role.objects.filter( + role=Role.ESTABLISHMENT_ADMINISTRATOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=user, role__id__in=role.values_list('id', flat=True), + ) + rule = True if user_role.exists() else rule + rules.append(rule) + return all(rules) def has_object_permission(self, request, view, obj): rules = [ - # special! super().has_permission(request, view) ] + rule = False + role = Role.objects.filter(role=Role.ESTABLISHMENT_ADMINISTRATOR).only('id') + if request.user.is_authenticated and role.exists() and hasattr(obj, 'id'): + user = request.user + filters = { + 'user': user, + 'role__id__in': role.values_list('id', flat=True), + } + if isinstance(obj, Establishment): + filters.update({'establishment__id': obj.id}) - role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ - .first() - - if hasattr(obj, 'establishment_id'): - rules = [ - UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.establishment_id - ).exists(), - # special! - super().has_permission(request, view) - ] - - return any(rules) + if isinstance(obj, Product): + filters.update({'establishment__products__id': obj.id}) + rule = True if UserRole.objects.filter(**filters).exists() else rule + rules.append(rule) + return all(rules) class IsReviewerManager(IsStandardUser): @@ -324,8 +353,8 @@ class IsReviewerManager(IsStandardUser): # and request.user.email_confirmed, if hasattr(request.data, 'user') and hasattr(request.data, 'site_id'): - role = Role.objects.filter(role=Role.REVIEWER_MANGER) \ - .first() + role = Role.objects.filter(role=Role.REVIEWER_MANGER + ).first() rules = [ UserRole.objects.filter(user=request.user, role=role, @@ -333,7 +362,7 @@ class IsReviewerManager(IsStandardUser): ).exists(), super().has_permission(request, view) ] - return any(rules) + return all(rules) def has_object_permission(self, request, view, obj): role = Role.objects.filter(role=Role.REVIEWER_MANGER, @@ -345,7 +374,7 @@ class IsReviewerManager(IsStandardUser): super().has_object_permission(request, view, obj) ] - return any(rules) + return all(rules) class IsRestaurantReviewer(IsStandardUser): @@ -366,7 +395,7 @@ class IsRestaurantReviewer(IsStandardUser): ).exists(), super().has_permission(request, view) ] - return any(rules) + return all(rules) def has_object_permission(self, request, view, obj): content_type = ContentType.objects.get(app_lable='establishment', @@ -383,7 +412,7 @@ class IsRestaurantReviewer(IsStandardUser): super().has_object_permission(request, view, obj) ] - return any(rules) + return all(rules) class IsWineryReviewer(IsStandardUser): @@ -393,107 +422,78 @@ class IsWineryReviewer(IsStandardUser): 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']) + rule = False + if request.user.is_authenticated: + if hasattr(request, 'data'): + type_id = request.data.get('type_id') + address_id = request.data.get('address_id') - est = EstablishmentSubType.objects.filter(establishment_type_id=request.data['type_id']) - if est.exists(): - 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() + if type_id and address_id: + address_qs = Address.objects.filter(id=address_id) \ + .only('city__country') - rules.append( - UserRole.objects.filter(user=request.user, role=role).exists() - ) + if address_qs.exists(): + country_id = address_qs.values_list('city__country', flat=True) - return any(rules) + est_subtype_qs = EstablishmentSubType.objects.filter(establishment_type_id=type_id).only('id') + if est_subtype_qs.exists(): + role = Role.objects.filter( + establishment_subtype_id=est_subtype_qs.values_list('id', flat=True)[0], + role=Role.WINERY_REVIEWER, + country_id=country_id + ) + rule = True if role.exists() else rule + rules.append(rule) + return all(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 + rule = False + if request.user.is_authenticated: + type_id = None + object_id = None + country_id = None + if hasattr(obj, 'type_id'): type_id = obj.type_id - else: + + if hasattr(obj, 'establishment_type_id'): 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=[est_type.id for est_type in est], - country_id=obj.country_id).first() - - object_id: int if hasattr(obj, 'object_id'): object_id = obj.object_id - else: + + if hasattr(obj, 'establishment_id'): 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) + if hasattr(obj, 'country_id'): + country_id = obj.country_id + if type_id and object_id and country_id: + est_subtype_qs = EstablishmentSubType.objects.filter( + establishment_type_id=type_id + ).only('id') -class IsWineryReviewer(IsStandardUser): + if est_subtype_qs.exists(): + est_subtype_id = est_subtype_qs.values_list('id', flat=True)[0] - def has_permission(self, request, view): - rules = [ - super().has_permission(request, view) - ] + role = Role.objects.filter( + role=Role.WINERY_REVIEWER, + establishment_subtype_id=est_subtype_id, + country_id=country_id + ).first() - 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=[est_type.id for est_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=[est_type.id for est_type 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) + user_role = UserRole.objects.filter( + user=request.user, + role=role, + establishment_id=object_id, + ) + rule = True if user_role.exists() else rule + rules.append(rule) + return all(rules) class IsProductReviewer(IsStandardUser): @@ -526,10 +526,10 @@ class IsProductReviewer(IsStandardUser): .exists() rules.append(permission) - return any(rules) + return all(rules) -class IsLiquorReviewer(IsStandardUser): +class IsDistilleryLiquorInspector(IsStandardUser): def has_permission(self, request, view): rules = [ super().has_permission(request, view) @@ -548,7 +548,7 @@ class IsLiquorReviewer(IsStandardUser): id=request.data['product_type_id']) if product_types.exists(): - roles = Role.objects.filter(role=Role.LIQUOR_REVIEWER, + roles = Role.objects.filter(role=Role.DISTILLERY_LIQUOR_INSPECTOR, site_id=request.data['site_id']) if 'pk' in view.kwargs: @@ -564,7 +564,7 @@ class IsLiquorReviewer(IsStandardUser): id=product.product_type_id) if product_types.exists(): - roles = Role.objects.filter(role=Role.LIQUOR_REVIEWER, + roles = Role.objects.filter(role=Role.DISTILLERY_LIQUOR_INSPECTOR, site_id=product.site_id) if roles is not None: @@ -572,7 +572,7 @@ class IsLiquorReviewer(IsStandardUser): .exists() rules.append(permission) - return any(rules) + return all(rules) # # def has_object_permission(self, request, view, obj): @@ -590,8 +590,8 @@ class IsLiquorReviewer(IsStandardUser): # # 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]) + # # role = Role.objects.filter(role=Role.DISTILLERY_LIQUOR_INSPECTOR, 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 + # return all(rules) \ No newline at end of file From 236b532d9874b6f28604ca55ceebd41f55b15211 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 28 Jan 2020 14:54:14 +0300 Subject: [PATCH 2/6] refactored permission classes --- apps/account/models.py | 34 +- apps/account/views/back.py | 18 +- apps/comment/views/back.py | 11 +- apps/establishment/models.py | 13 +- apps/establishment/views/back.py | 58 ++- apps/news/views.py | 8 +- apps/product/models.py | 2 +- apps/product/views/back.py | 10 +- apps/review/urls/back.py | 10 +- apps/review/views/back.py | 22 +- apps/tag/models.py | 4 + apps/tag/views.py | 6 +- apps/utils/permissions.py | 732 ++++++++++++------------------- 13 files changed, 416 insertions(+), 512 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 853d126c..2e22d24d 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -40,33 +40,35 @@ class RoleQuerySet(models.QuerySet): class Role(ProjectBaseMixin): """Base Role model.""" STANDARD_USER = 1 - COMMENTS_MODERATOR = 2 + MODERATOR = 2 COUNTRY_ADMIN = 3 CONTENT_PAGE_MANAGER = 4 ESTABLISHMENT_MANAGER = 5 - REVIEWER_MANGER = 6 - RESTAURANT_REVIEWER = 7 + REVIEW_MANAGER = 6 + RESTAURANT_INSPECTOR = 7 SALES_MAN = 8 - WINERY_REVIEWER = 9 # Establishments subtype "winery" + WINERY_WINE_INSPECTOR = 9 SELLER = 10 DISTILLERY_LIQUOR_INSPECTOR = 11 - PRODUCT_REVIEWER = 12 + PRODUCER_FOOD_INSPECTOR = 12 ESTABLISHMENT_ADMINISTRATOR = 13 + ARTISAN_INSPECTOR = 14 ROLE_CHOICES = ( (STANDARD_USER, _('Standard user')), - (COMMENTS_MODERATOR, _('Comments moderator')), + (MODERATOR, _('Moderator')), (COUNTRY_ADMIN, _('Country admin')), (CONTENT_PAGE_MANAGER, _('Content page manager')), (ESTABLISHMENT_MANAGER, _('Establishment manager')), - (REVIEWER_MANGER, _('Reviewer manager')), - (RESTAURANT_REVIEWER, _('Restaurant reviewer')), + (REVIEW_MANAGER, _('Review manager')), + (RESTAURANT_INSPECTOR, _('Restaurant inspector')), (SALES_MAN, _('Sales man')), - (WINERY_REVIEWER, _('Winery reviewer')), + (WINERY_WINE_INSPECTOR, _('Winery and wine inspector')), (SELLER, _('Seller')), (DISTILLERY_LIQUOR_INSPECTOR, _('Distillery & Liquor inspector')), - (PRODUCT_REVIEWER, _('Product reviewer')), + (PRODUCER_FOOD_INSPECTOR, _('Producer food inspector')), (ESTABLISHMENT_ADMINISTRATOR, _('Establishment administrator')), + (ARTISAN_INSPECTOR, _('Artisan inspector')), ) ESTABLISHMENT_EDITORS = [ @@ -496,13 +498,17 @@ class UserRoleQueryset(models.QuerySet): """Filter QuerySet by state.""" return self.filter(state=self.model.VALIDATED) - def establishment_editors(self): + def has_access_to_establishments(self): """Return QuerySet filtered by role and state.""" - return self.validated().filter(role__role__in=Role.ESTABLISHMENT_EDITORS) + return self.filter(role__role__in=Role.ESTABLISHMENT_EDITORS).validated() - def product_editors(self): + def has_access_to_products(self): """Return QuerySet filtered by role and state.""" - return self.validated().filter(role__role__in=Role.PRODUCT_EDITORS) + return self.filter(role__role__in=Role.PRODUCT_EDITORS).validated() + + def has_access_to_content_pages(self): + """Return QuerySet filtered by role and state.""" + return self.filter(role__role__in=Role.CONTENT_PAGE_MANAGER).validated() class UserRole(ProjectBaseMixin): diff --git a/apps/account/views/back.py b/apps/account/views/back.py index 0fd9bdde..5433245c 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -10,16 +10,23 @@ from account import models, filters from account.models import User from account.serializers import back as serializers from account.serializers.common import RoleBaseSerializer +from utils.permissions import * class RoleListView(generics.ListCreateAPIView): serializer_class = RoleBaseSerializer queryset = models.Role.objects.all() filter_class = filters.RoleListFilter + permission_classes = [ + IsCountryAdmin + ] class RoleTypeRetrieveView(generics.GenericAPIView): - permission_classes = [permissions.IsAdminUser] + permission_classes = [ + permissions.IsAdminUser | + IsCountryAdmin + ] def get(self, request, *args, **kwargs): """Implement GET-method""" @@ -36,12 +43,19 @@ class RoleTypeRetrieveView(generics.GenericAPIView): class UserRoleListView(generics.ListCreateAPIView): serializer_class = serializers.UserRoleSerializer queryset = models.UserRole.objects.all() + permission_classes = [ + IsCountryAdmin + ] class UserListView(generics.ListCreateAPIView): """User list create view.""" serializer_class = serializers.BackUserSerializer - permission_classes = (permissions.IsAdminUser,) + permission_classes = [ + permissions.IsAdminUser | + IsReviewManager | + IsCountryAdmin + ] filter_class = filters.AccountBackOfficeFilter filter_backends = (OrderingFilter, DjangoFilterBackend) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index a3c05543..b998e4ed 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -2,21 +2,22 @@ from rest_framework import generics from comment import models from comment.serializers import CommentBaseSerializer -from utils.permissions import IsCommentModerator +from utils.permissions import IsModerator, IsCountryAdmin class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = CommentBaseSerializer queryset = models.Comment.objects.all() - - # permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin] + permission_classes = [ + IsModerator | + IsCountryAdmin + ] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsCommentModerator] - # permission_classes = [IsCountryAdmin | IsCommentModerator] + permission_classes = [IsModerator] lookup_field = 'id' diff --git a/apps/establishment/models.py b/apps/establishment/models.py index b6521255..93fd9760 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -112,6 +112,10 @@ class EstablishmentSubType(TypeDefaultImageMixin, TranslatedFieldsMixin, Project verbose_name = _('Establishment subtype') verbose_name_plural = _('Establishment subtypes') + def __str__(self): + """Overridden str dunder.""" + return self.index_name + def clean_fields(self, exclude=None): if not self.establishment_type.use_subtypes: raise ValidationError(_('Establishment type is not use subtypes.')) @@ -514,7 +518,7 @@ class EstablishmentQuerySet(models.QuerySet): to_attr='main_image') ) - def available_establishments(self, user, country_code: str = None): + def user_role_establishments(self, user, country_code: str = None): """Return QuerySet with establishment that is available for editing.""" from account.models import UserRole @@ -532,9 +536,12 @@ class EstablishmentQuerySet(models.QuerySet): return self.filter(**filters) def available_objects(self, user, country_code: str = None): - access_roles = user.userrole_set.establishment_editors() + # role without establishment + + # role that has establishment + access_roles = user.userrole_set.has_access_to_establishments() if access_roles.exists(): - return self.available_establishments(user, country_code) + return self.user_role_establishments(user, country_code) return self.none() diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 394e89f4..80f0b0ce 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -11,7 +11,9 @@ from timetable.models import Timetable from timetable.serialziers import ScheduleCreateSerializer, ScheduleRUDSerializer from utils.permissions import ( IsCountryAdmin, IsEstablishmentManager, - IsWineryReviewer, IsEstablishmentAdministrator) + IsWineryWineInspector, IsEstablishmentAdministrator, + IsReviewManager, IsRestaurantInspector +) from utils.views import CreateDestroyGalleryViewMixin @@ -29,7 +31,8 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP filter_class = filters.EstablishmentFilter permission_classes = [ - IsWineryReviewer | + IsReviewManager | + IsWineryWineInspector | IsCountryAdmin | IsEstablishmentManager | IsEstablishmentAdministrator @@ -46,7 +49,11 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP class EmployeeEstablishmentsListView(generics.ListAPIView): """Establishment by employee list view.""" - permission_classes = [IsWineryReviewer | IsCountryAdmin | IsEstablishmentManager] + permission_classes = [ + IsWineryWineInspector | + IsCountryAdmin | + IsEstablishmentManager + ] queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer @@ -64,10 +71,11 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): ) serializer_class = serializers.EstablishmentRUDSerializer permission_classes = [ - IsWineryReviewer | - IsCountryAdmin | - IsEstablishmentManager | + # IsWineryWineInspector | + # IsCountryAdmin | + # IsEstablishmentManager | IsEstablishmentAdministrator + # IsRestaurantInspector ] @@ -75,7 +83,10 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" lookup_field = 'slug' serializer_class = ScheduleRUDSerializer - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [ + IsWineryWineInspector | + IsEstablishmentManager + ] def get_object(self): """ @@ -101,14 +112,20 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): lookup_field = 'slug' serializer_class = ScheduleCreateSerializer queryset = Timetable.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [ + IsWineryWineInspector | + IsEstablishmentManager + ] class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [ + IsWineryWineInspector | + IsEstablishmentManager + ] filter_backends = (DjangoFilterBackend,) filterset_fields = ( 'establishment', @@ -120,7 +137,10 @@ class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [ + IsWineryWineInspector | + IsEstablishmentManager + ] class SocialChoiceListCreateView(generics.ListCreateAPIView): @@ -158,14 +178,14 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Plate RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class PhonesListCreateView(generics.ListCreateAPIView): @@ -173,14 +193,14 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Phones RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class EmailListCreateView(generics.ListCreateAPIView): @@ -188,18 +208,18 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Email RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] class EmployeeListCreateView(generics.ListCreateAPIView): - """Emplyoee list create view.""" + """Employee list create view.""" permission_classes = (permissions.AllowAny,) filter_class = filters.EmployeeBackFilter serializer_class = serializers.EmployeeBackSerializers @@ -443,7 +463,7 @@ class MenuDishesListCreateView(generics.ListCreateAPIView): """Menu (dessert, main_course, starter) list create view.""" serializer_class = serializers.MenuDishesSerializer queryset = models.Menu.objects.with_schedule_plates_establishment().dishes().distinct() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] filter_class = filters.MenuDishesBackFilter @@ -451,4 +471,4 @@ class MenuDishesRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu (dessert, main_course, starter) RUD view.""" serializer_class = serializers.MenuDishesRUDSerializers queryset = models.Menu.objects.dishes().distinct() - permission_classes = [IsWineryReviewer | IsEstablishmentManager] + permission_classes = [IsWineryWineInspector | IsEstablishmentManager] diff --git a/apps/news/views.py b/apps/news/views.py index 4f0adc1e..b0454deb 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -9,7 +9,7 @@ from django_filters.rest_framework import DjangoFilterBackend from news import filters, models, serializers from rating.tasks import add_rating -from utils.permissions import IsCountryAdmin, IsContentPageManager +from utils.permissions import IsCountryAdmin, IsContentPageManager, IsReviewManager from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView from utils.serializers import ImageBaseSerializer, EmptySerializer @@ -124,7 +124,11 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, filter_class = filters.NewsListFilterSet create_serializers_class = serializers.NewsBackOfficeDetailSerializer filter_backends = (OrderingFilter, DjangoFilterBackend) - permission_classes = [IsCountryAdmin | IsContentPageManager] + permission_classes = [ + IsCountryAdmin | + IsContentPageManager | + IsReviewManager + ] ordering_fields = '__all__' diff --git a/apps/product/models.py b/apps/product/models.py index 272fbd0b..e2584be0 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -240,7 +240,7 @@ class ProductQuerySet(models.QuerySet): return self.filter(id__in=available_ids) def available_objects(self, user): - access_roles = user.userrole_set.product_editors() + access_roles = user.userrole_set.has_access_to_products() if access_roles.exists(): return self.available_products(user) return self.none() diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 7d4f5eb1..fb0d3f82 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -2,16 +2,14 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics, status, permissions, views from rest_framework.response import Response -from django.db.models import Prefetch from product import serializers, models -from location.models import Address, City, Country from product.views import ProductBaseView +from utils.permissions import ( + IsDistilleryLiquorInspector, IsProducerFoodInspector, + IsEstablishmentManager, IsEstablishmentAdministrator) from utils.serializers import ImageBaseSerializer from utils.views import CreateDestroyGalleryViewMixin -from utils.permissions import ( - IsDistilleryLiquorInspector, IsProductReviewer, - IsEstablishmentManager, IsEstablishmentAdministrator) class ProductBackOfficeMixinView(ProductBaseView): @@ -110,7 +108,7 @@ class ProductListCreateBackOfficeView(ProductBackOfficeMixinView, serializer_class = serializers.ProductBackOfficeDetailSerializer permission_classes = [ IsDistilleryLiquorInspector | - IsProductReviewer | + IsProducerFoodInspector | IsEstablishmentAdministrator | IsEstablishmentManager ] diff --git a/apps/review/urls/back.py b/apps/review/urls/back.py index a59aafa8..d3f03ed3 100644 --- a/apps/review/urls/back.py +++ b/apps/review/urls/back.py @@ -6,12 +6,12 @@ from review.views import back as views app_name = 'review' urlpatterns = [ - path('', views.ReviewLstView.as_view(), name='review-list-create'), + path('', views.ReviewListView.as_view(), name='review-list-create'), path('/', views.ReviewRUDView.as_view(), name='review-crud'), - path('/inquiries/', views.InquiriesLstView.as_view(), name='inquiries-list'), - path('inquiries/', views.InquiriesLstView.as_view(), name='inquiries-list-create'), + path('/inquiries/', views.InquiriesListView.as_view(), name='inquiries-list'), + path('inquiries/', views.InquiriesListView.as_view(), name='inquiries-list-create'), path('inquiries//', views.InquiriesRUDView.as_view(), name='inquiries-crud'), - path('inquiries//grid/', views.GridItemsLstView.as_view(), name='grid-list-create'), - path('inquiries/grid/', views.GridItemsLstView.as_view(), name='grid-list-create'), + path('inquiries//grid/', views.GridItemsListView.as_view(), name='grid-list-create'), + path('inquiries/grid/', views.GridItemsListView.as_view(), name='grid-list-create'), path('inquiries/grid//', views.GridItemsRUDView.as_view(), name='grid-crud'), ] diff --git a/apps/review/views/back.py b/apps/review/views/back.py index caf12b62..d1e470db 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -3,11 +3,19 @@ from rest_framework import generics, permissions from review import filters from review import models from review import serializers -from utils.permissions import IsReviewerManager, IsRestaurantReviewer +from utils.permissions import IsReviewManager, IsRestaurantInspector from review.serializers.back import ReviewBackSerializer -class ReviewLstView(generics.ListCreateAPIView): +class ReviewMixinView: + """Review mixin.""" + + def get_queryset(self): + """Overridden method 'get_queryset'.""" + return models.Review.objects.all() + + +class ReviewListView(ReviewMixinView, generics.ListCreateAPIView): """Review list create view. status values: @@ -21,6 +29,10 @@ class ReviewLstView(generics.ListCreateAPIView): permission_classes = [permissions.IsAuthenticatedOrReadOnly, ] filterset_class = filters.ReviewFilter + def get_queryset(self): + """Overridden get_queryset method.""" + return super(ReviewListView, self).get_queryset() + class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): """Review RUD view. @@ -33,11 +45,11 @@ class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): """ serializer_class = ReviewBackSerializer queryset = models.Review.objects.all() - permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer] + permission_classes = [permissions.IsAdminUser | IsReviewManager | IsRestaurantInspector] lookup_field = 'id' -class InquiriesLstView(generics.ListCreateAPIView): +class InquiriesListView(generics.ListCreateAPIView): """Inquiries list create view.""" serializer_class = serializers.InquiriesBaseSerializer @@ -59,7 +71,7 @@ class InquiriesRUDView(generics.RetrieveUpdateDestroyAPIView): lookup_field = 'id' -class GridItemsLstView(generics.ListCreateAPIView): +class GridItemsListView(generics.ListCreateAPIView): """GridItems list create view.""" serializer_class = serializers.GridItemsBaseSerializer queryset = models.GridItems.objects.all() diff --git a/apps/tag/models.py b/apps/tag/models.py index 2a57a1c4..0f8b1d1b 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -11,6 +11,10 @@ from utils.models import IndexJSON class TagQuerySet(models.QuerySet): + def with_base_related(self): + """Return QuerySet with base related.""" + return self.select_related('category', 'translation') + def for_news(self): """Select chosen tags for news.""" return self.filter(category__news_types__isnull=False) diff --git a/apps/tag/views.py b/apps/tag/views.py index de49875f..41fbd77b 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -334,8 +334,8 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, """List/create tag view.""" pagination_class = None - permission_classes = (permissions.IsAuthenticated,) - queryset = models.Tag.objects.all() + permission_classes = (permissions.IsAdminUser,) + queryset = models.Tag.objects.with_base_related() serializer_class = serializers.TagBackOfficeSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer chosen_serializer_class = serializers.ChosenTagBindObjectSerializer @@ -388,7 +388,7 @@ class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, TagCategoryViewSet): """ViewSet for TagCategory model for BackOffice users.""" - permission_classes = (permissions.IsAuthenticated,) + permission_classes = (permissions.IsAdminUser,) queryset = TagCategoryViewSet.queryset.with_extended_related() serializer_class = serializers.TagCategoryBackOfficeDetailSerializer bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index bf06190e..2ea32b18 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -1,15 +1,13 @@ """Project custom permissions""" -from django.contrib.contenttypes.models import ContentType - from rest_framework import permissions +from rest_framework.permissions import SAFE_METHODS as SAFE_HTTP_METHODS from rest_framework_simplejwt.tokens import AccessToken from account.models import UserRole, Role from authorization.models import JWTRefreshToken +from establishment.models import Establishment +from product.models import Product from utils.tokens import GMRefreshToken -from establishment.models import EstablishmentSubType, Establishment -from location.models import Address -from product.models import Product, ProductType class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): @@ -49,7 +47,7 @@ class IsRefreshTokenValid(permissions.BasePermission): def has_object_permission(self, request, view, obj): # Read permissions are allowed to all request, # so we'll always allow GET, HEAD or OPTIONS requests. - if request.method in permissions.SAFE_METHODS or \ + if request.method in SAFE_HTTP_METHODS or \ obj.user == request.user or request.user.is_superuser: return True return False @@ -63,43 +61,15 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): rules = [ request.user.is_anonymous, - request.method in permissions.SAFE_METHODS + request.method in SAFE_HTTP_METHODS ] return all(rules) def has_object_permission(self, request, view, obj): - rules = [ - request.user.is_anonymous, - request.method in permissions.SAFE_METHODS - ] - return all(rules) + return self.has_permission(request, view) -class IsStandardUser(permissions.IsAuthenticated): - """ - Object-level permission to only allow owners of an object to edit it. - Assumes the model instance has an `owner` attribute. - """ - - def has_permission(self, request, view): - - rules = [super().has_permission(request, view), - request.user.email_confirmed, - ] - - 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), - request.user.email_confirmed, - ] - - return any(rules) - - -class IsContentPageManager(IsStandardUser): +class IsApprovedUser(permissions.IsAuthenticated): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. @@ -109,177 +79,98 @@ class IsContentPageManager(IsStandardUser): rules = [ super().has_permission(request, view) ] - - if hasattr(request, 'user'): - if hasattr(request.data, 'site_id'): - role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - site_id=request.data.site_id,) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_permission(request, view) - ] - elif hasattr(request.data, 'country_id'): - role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=request.data.country_id) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_permission(request, view) - ] - - return all(rules) - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to all request. - if hasattr(obj, 'site_id'): - role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - site_id=obj.site_id) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_object_permission(request, view, obj) - ] - elif hasattr(obj, 'country_id'): - role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=obj.country_id) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_object_permission(request, view, obj) - ] - - return all(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_permission(self, request, view): - - rules = [ - super().has_permission(request, view) - ] - - rule = False - # and request.user.email_confirmed, - if hasattr(request.data, 'user'): - if hasattr(request.data, 'site_id'): - # Read permissions are allowed to all request. - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, - site_id=request.data.site_id) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_permission(request, view) - ] - elif hasattr(request.data, 'country_id'): - - role = Role.objects.filter( - role=Role.COUNTRY_ADMIN, - country_id=request.data.country_id - ).first() - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_permission(request, view) - ] - rules.append(rule) - return all(rules) - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to all request. - if hasattr(obj, 'site_id'): - role = Role.objects.filter( - role=Role.COUNTRY_ADMIN, - site_id=obj.site_id - ).first() - - rules = [ - super().has_object_permission(request, view, obj) - ] - elif hasattr(obj, 'country_id'): - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, - country_id=obj.country_id) \ - .first() - - rules = [ - super().has_object_permission(request, view, obj) - ] - - if hasattr(request, 'user') and request.user.is_authenticated: - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_object_permission(request, view, obj), - ] - - if hasattr(request.data, 'user'): - rules = [ - UserRole.objects.filter(user=request.data.user, role=role).exists(), - super().has_object_permission(request, view, obj), - ] - - return all(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_permission(self, request, view): - rules = [ - super().has_permission(request, view) - ] - - if all(rules) and hasattr(request.data, 'site_id'): - # Read permissions are allowed to all request. - - role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - site_id=request.data.site_id) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_permission(request, view) - ] - - return all(rules) - - def has_object_permission(self, request, view, obj): - - rules = [ - super().has_object_permission(request, view, obj) - ] - + has_permission = False if request.user.is_authenticated: - - role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - site_id=obj.site_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) - ] + has_permission = request.user.email_confirmed + rules.append(has_permission) return all(rules) -class IsEstablishmentManager(IsStandardUser): +class IsContentPageManager(IsApprovedUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ def has_permission(self, request, view): rules = [ super().has_permission(request, view) ] - rule = False + has_permission = False + if (request.user.is_authenticated and hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.CONTENT_PAGE_MANAGER, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + has_permission = True if user_role.exists() else has_permission + rules.append(has_permission) + return all(rules) + + +class IsCountryAdmin(IsApprovedUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role + if (request.user.is_authenticated and hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.COUNTRY_ADMIN, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True) + ).only('id') + has_permission = True if user_role.exists() else has_permission + rules.append(has_permission) + return all(rules) + + +class IsModerator(IsApprovedUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role + if (request.user.is_authenticated and hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.MODERATOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + has_permission = True if user_role.exists() else has_permission + rules.append(has_permission) + return all(rules) + + +class IsEstablishmentManager(IsApprovedUser): + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -290,48 +181,43 @@ class IsEstablishmentManager(IsStandardUser): if role.exists(): user_role = UserRole.objects.filter( user=user, role__id__in=role.values_list('id', flat=True), - ) - rule = True if user_role.exists() else rule - rules.append(rule) + ).only('id') + has_permission = True if user_role.exists() else has_permission + rules.append(has_permission) return all(rules) - def has_object_permission(self, request, view, obj): - return self.has_permission(request, view) - -class IsEstablishmentAdministrator(IsStandardUser): +class IsEstablishmentAdministrator(IsApprovedUser): def has_permission(self, request, view): rules = [ super().has_permission(request, view) ] - rule = False + has_permission = False + # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): - user = request.user role = Role.objects.filter( role=Role.ESTABLISHMENT_ADMINISTRATOR, site__country__code=request.country_code, ).only('id') if role.exists(): user_role = UserRole.objects.filter( - user=user, role__id__in=role.values_list('id', flat=True), - ) - rule = True if user_role.exists() else rule - rules.append(rule) + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + has_permission = True if user_role.exists() else has_permission + rules.append(has_permission) return all(rules) def has_object_permission(self, request, view, obj): - rules = [ - super().has_permission(request, view) + super().has_object_permission(request, view, obj) ] - rule = False + has_object_permission = False role = Role.objects.filter(role=Role.ESTABLISHMENT_ADMINISTRATOR).only('id') if request.user.is_authenticated and role.exists() and hasattr(obj, 'id'): - user = request.user filters = { - 'user': user, + 'user': request.user, 'role__id__in': role.values_list('id', flat=True), } if isinstance(obj, Establishment): @@ -339,259 +225,211 @@ class IsEstablishmentAdministrator(IsStandardUser): if isinstance(obj, Product): filters.update({'establishment__products__id': obj.id}) - rule = True if UserRole.objects.filter(**filters).exists() else rule - rules.append(rule) + user_role = UserRole.objects.filter(**filters) + has_object_permission = True if user_role.exists() else has_object_permission + rules.append(has_object_permission) return all(rules) -class IsReviewerManager(IsStandardUser): +class IsReviewManager(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', 'product_type', 'news', 'recipe', 'user', ], + 'WRITE': ['inquiries', 'userrole', 'review', 'establishment', 'product', 'news', 'recipe', ] + } def has_permission(self, request, view): rules = [ super().has_permission(request, view) ] - - # and request.user.email_confirmed, - if hasattr(request.data, 'user') and hasattr(request.data, 'site_id'): - role = Role.objects.filter(role=Role.REVIEWER_MANGER - ).first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role, - establishment_id=request.data.site_id - ).exists(), - super().has_permission(request, view) - ] - return all(rules) - - 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 all(rules) - - -class IsRestaurantReviewer(IsStandardUser): - - def has_permission(self, request, view): - rules = [ - super().has_permission(request, view) - ] - - # and request.user.email_confirmed, - if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'): - role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \ - .first() - - rules = [ - UserRole.objects.filter(user=request.user, role=role, - establishment_id=request.data.object_id - ).exists(), - super().has_permission(request, view) - ] - return all(rules) - - 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 all(rules) - - -class IsWineryReviewer(IsStandardUser): - - def has_permission(self, request, view): - rules = [ - super().has_permission(request, view) - ] - - rule = False - if request.user.is_authenticated: - if hasattr(request, 'data'): - type_id = request.data.get('type_id') - address_id = request.data.get('address_id') - - if type_id and address_id: - address_qs = Address.objects.filter(id=address_id) \ - .only('city__country') - - if address_qs.exists(): - country_id = address_qs.values_list('city__country', flat=True) - - est_subtype_qs = EstablishmentSubType.objects.filter(establishment_type_id=type_id).only('id') - if est_subtype_qs.exists(): - role = Role.objects.filter( - establishment_subtype_id=est_subtype_qs.values_list('id', flat=True)[0], - role=Role.WINERY_REVIEWER, - country_id=country_id - ) - rule = True if role.exists() else rule - rules.append(rule) - return all(rules) - - def has_object_permission(self, request, view, obj): - rules = [ - super().has_object_permission(request, view, obj) - ] - - rule = False - if request.user.is_authenticated: - type_id = None - object_id = None - country_id = None - - if hasattr(obj, 'type_id'): - type_id = obj.type_id - - if hasattr(obj, 'establishment_type_id'): - type_id = obj.establishment_type_id - - if hasattr(obj, 'object_id'): - object_id = obj.object_id - - if hasattr(obj, 'establishment_id'): - object_id = obj.establishment_id - - if hasattr(obj, 'country_id'): - country_id = obj.country_id - - if type_id and object_id and country_id: - est_subtype_qs = EstablishmentSubType.objects.filter( - establishment_type_id=type_id + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.REVIEW_MANAGER, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') - - if est_subtype_qs.exists(): - est_subtype_id = est_subtype_qs.values_list('id', flat=True)[0] - - role = Role.objects.filter( - role=Role.WINERY_REVIEWER, - establishment_subtype_id=est_subtype_id, - country_id=country_id - ).first() - - user_role = UserRole.objects.filter( - user=request.user, - role=role, - establishment_id=object_id, - ) - rule = True if user_role.exists() else rule - rules.append(rule) + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) return all(rules) -class IsProductReviewer(IsStandardUser): +class IsRestaurantInspector(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', ], + 'WRITE': ['inquiries', ] + } 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) + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.RESTAURANT_INSPECTOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) return all(rules) -class IsDistilleryLiquorInspector(IsStandardUser): +class IsArtisanInspector(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', ], + 'WRITE': ['inquiries', ] + } + 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 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.DISTILLERY_LIQUOR_INSPECTOR, - 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: - - product_types = ProductType.objects. \ - filter(index_name=ProductType.LIQUOR, - id=product.product_type_id) - - if product_types.exists(): - roles = Role.objects.filter(role=Role.DISTILLERY_LIQUOR_INSPECTOR, - 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) + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.ARTISAN_INSPECTOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) return all(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.DISTILLERY_LIQUOR_INSPECTOR, site__in=[site for site in product.sites]) - # # permission = UserRole.objects.filter(user=request.user, role=role).exists() - # # - # # rules.append(permission) - # return all(rules) \ No newline at end of file + +class IsWineryWineInspector(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', 'product', ], + 'WRITE': ['inquiries', ] + } + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.WINERY_WINE_INSPECTOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) + return all(rules) + + +class IsProducerFoodInspector(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', 'product', ], + 'WRITE': ['inquiries', ] + } + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.PRODUCER_FOOD_INSPECTOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) + return all(rules) + + +class IsDistilleryLiquorInspector(IsApprovedUser): + + MODEL_PERMISSIONS = { + 'READ': ['establishment', 'product', ], + 'WRITE': ['inquiries', ] + } + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + has_permission = False + # check role + if (request.user.is_authenticated and + hasattr(request, 'country_code') and + request.country_code): + role = Role.objects.filter( + role=Role.DISTILLERY_LIQUOR_INSPECTOR, site__country__code=request.country_code, + ).only('id') + if role.exists(): + user_role = UserRole.objects.filter( + user=request.user, role__id__in=role.values_list('id', flat=True), + ).only('id') + if user_role.exists(): + # check model for read + model_name = view.get_queryset().model._meta.model_name + if ((model_name in self.MODEL_PERMISSIONS.get('READ', []) and + request.method in SAFE_HTTP_METHODS) or + (model_name in self.MODEL_PERMISSIONS.get('WRITE', []))): + has_permission = True + rules.append(has_permission) + return all(rules) From 57e63111fbfb28e286a7a8083802e5b884dab406 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 30 Jan 2020 10:28:01 +0300 Subject: [PATCH 3/6] intermediate commit --- apps/account/models.py | 59 ++++++-- apps/account/views/back.py | 33 ++-- apps/advertisement/views/back.py | 13 +- apps/collection/views/back.py | 17 ++- apps/comment/views/back.py | 10 +- apps/establishment/models.py | 51 +++---- apps/establishment/views/back.py | 244 +++++++++++++++++++----------- apps/favorites/views.py | 43 ++++-- apps/gallery/views.py | 5 + apps/location/views/back.py | 77 +++++++--- apps/main/views/back.py | 52 +++---- apps/news/views.py | 19 +-- apps/notification/views/common.py | 11 +- apps/partner/views/back.py | 17 ++- apps/product/models.py | 32 ++-- apps/product/views/back.py | 77 +++------- apps/recipe/views/common.py | 6 +- apps/review/models.py | 12 +- apps/review/views/back.py | 45 +++--- apps/tag/views.py | 12 +- apps/utils/methods.py | 16 +- apps/utils/permissions.py | 15 +- 22 files changed, 497 insertions(+), 369 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 2e22d24d..a1a5ede4 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -1,8 +1,10 @@ """Account models""" +from collections import Counter from datetime import datetime -from django.contrib.postgres.search import TrigramSimilarity + from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager +from django.contrib.postgres.search import TrigramSimilarity from django.core.mail import send_mail from django.db import models from django.template.loader import render_to_string, get_template @@ -11,8 +13,8 @@ from django.utils.encoding import force_bytes from django.utils.html import mark_safe from django.utils.http import urlsafe_base64_encode from django.utils.translation import ugettext_lazy as _ +from phonenumber_field.modelfields import PhoneNumberField from rest_framework.authtoken.models import Token -from collections import Counter from authorization.models import Application from establishment.models import Establishment, EstablishmentSubType @@ -21,7 +23,6 @@ from main.models import SiteSettings from utils.models import GMTokenGenerator from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.tokens import GMRefreshToken -from phonenumber_field.modelfields import PhoneNumberField class RoleQuerySet(models.QuerySet): @@ -449,6 +450,31 @@ class User(AbstractUser): result.append(item.id) return set(result) + @property + def is_country_admin(self): + if self.userrole_set: + return self.userrole_set.country_admin().exists() + + @property + def is_establishment_manager(self): + if self.userrole_set: + return self.userrole_set.establishment_manager().exists() + + @property + def is_establishment_administrator(self): + if self.userrole_set: + return self.userrole_set.establishment_administrator().exists() + + @property + def administrated_country_codes(self) -> list: + if self.userrole_set: + return list( + self.userrole_set + .exclude(role__site__isnull=True) + .values_list('role__site__country__code', flat=True) + .distinct() + ) + class UserRoleQueryset(models.QuerySet): """QuerySet for model UserRole.""" @@ -498,17 +524,26 @@ class UserRoleQueryset(models.QuerySet): """Filter QuerySet by state.""" return self.filter(state=self.model.VALIDATED) - def has_access_to_establishments(self): - """Return QuerySet filtered by role and state.""" - return self.filter(role__role__in=Role.ESTABLISHMENT_EDITORS).validated() + def country_admin(self): + """Return status by role and state""" + return ( + self.filter(role__role=Role.COUNTRY_ADMIN) + .validated() + ) - def has_access_to_products(self): - """Return QuerySet filtered by role and state.""" - return self.filter(role__role__in=Role.PRODUCT_EDITORS).validated() + def establishment_manager(self): + """Return status by role and state""" + return ( + self.filter(role__role=Role.ESTABLISHMENT_MANAGER) + .validated() + ) - def has_access_to_content_pages(self): - """Return QuerySet filtered by role and state.""" - return self.filter(role__role__in=Role.CONTENT_PAGE_MANAGER).validated() + def establishment_administrator(self): + """Return status by role and state""" + return ( + self.filter(role__role=Role.ESTABLISHMENT_ADMINISTRATOR) + .validated() + ) class UserRole(ProjectBaseMixin): diff --git a/apps/account/views/back.py b/apps/account/views/back.py index 5433245c..c7afc083 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -1,32 +1,29 @@ -from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, permissions, status -from rest_framework.response import Response -from rest_framework.filters import OrderingFilter import csv + from django.http import HttpResponse, HttpResponseNotFound +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import generics, status from rest_framework.authtoken.models import Token +from rest_framework.filters import OrderingFilter +from rest_framework.response import Response from account import models, filters from account.models import User from account.serializers import back as serializers from account.serializers.common import RoleBaseSerializer -from utils.permissions import * +from utils.methods import get_permission_classes +from utils.permissions import IsReviewManager class RoleListView(generics.ListCreateAPIView): serializer_class = RoleBaseSerializer queryset = models.Role.objects.all() filter_class = filters.RoleListFilter - permission_classes = [ - IsCountryAdmin - ] + permission_classes = get_permission_classes() class RoleTypeRetrieveView(generics.GenericAPIView): - permission_classes = [ - permissions.IsAdminUser | - IsCountryAdmin - ] + permission_classes = get_permission_classes() def get(self, request, *args, **kwargs): """Implement GET-method""" @@ -43,21 +40,15 @@ class RoleTypeRetrieveView(generics.GenericAPIView): class UserRoleListView(generics.ListCreateAPIView): serializer_class = serializers.UserRoleSerializer queryset = models.UserRole.objects.all() - permission_classes = [ - IsCountryAdmin - ] + permission_classes = get_permission_classes() class UserListView(generics.ListCreateAPIView): """User list create view.""" serializer_class = serializers.BackUserSerializer - permission_classes = [ - permissions.IsAdminUser | - IsReviewManager | - IsCountryAdmin - ] filter_class = filters.AccountBackOfficeFilter filter_backends = (OrderingFilter, DjangoFilterBackend) + permission_classes = get_permission_classes(IsReviewManager) ordering_fields = ( 'email_confirmed', @@ -77,8 +68,8 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): """User RUD view.""" queryset = User.objects.all() serializer_class = serializers.BackDetailUserSerializer - permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' + permission_classes = get_permission_classes() def get_user_csv(request, id): diff --git a/apps/advertisement/views/back.py b/apps/advertisement/views/back.py index 27a016a2..3590c9b4 100644 --- a/apps/advertisement/views/back.py +++ b/apps/advertisement/views/back.py @@ -1,20 +1,21 @@ """Back office views for app advertisement""" +from django.shortcuts import get_object_or_404 from rest_framework import generics, status from rest_framework.response import Response -from rest_framework import permissions -from django.shortcuts import get_object_or_404 -from main.serializers import PageExtendedSerializer from advertisement.models import Advertisement -from advertisement.serializers import (AdvertisementBaseSerializer, - AdvertisementDetailSerializer) +from advertisement.serializers import ( + AdvertisementBaseSerializer, + AdvertisementDetailSerializer) +from main.serializers import PageExtendedSerializer +from utils.methods import get_permission_classes class AdvertisementBackOfficeViewMixin(generics.GenericAPIView): """Base back office advertisement view.""" pagination_class = None - permission_classes = (permissions.IsAuthenticated, ) + permission_classes = get_permission_classes() def get_queryset(self): """Overridden get queryset method.""" diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 481f70da..cc44c22d 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -9,6 +9,7 @@ from rest_framework.response import Response from collection import models, serializers from collection import tasks +from utils.methods import get_permission_classes from utils.views import BindObjectMixin @@ -16,8 +17,8 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for Collection model.""" # pagination_class = None - permission_classes = (permissions.AllowAny,) serializer_class = serializers.CollectionBackOfficeSerializer + permission_classes = get_permission_classes() def get_queryset(self): """Overridden method 'get_queryset'.""" @@ -30,7 +31,7 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class GuideBaseView(generics.GenericAPIView): """ViewSet for Guide model.""" serializer_class = serializers.GuideBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() def get_queryset(self): """Overridden get_queryset method.""" @@ -47,7 +48,7 @@ class GuideFilterBaseView(generics.GenericAPIView): pagination_class = None queryset = models.GuideFilter.objects.all() serializer_class = serializers.GuideFilterBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() class GuideElementBaseView(generics.GenericAPIView): @@ -55,7 +56,7 @@ class GuideElementBaseView(generics.GenericAPIView): pagination_class = None queryset = models.GuideElement.objects.all() serializer_class = serializers.GuideElementBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() class AdvertorialBaseView(generics.GenericAPIView): @@ -63,7 +64,7 @@ class AdvertorialBaseView(generics.GenericAPIView): pagination_class = None queryset = models.Advertorial.objects.all() serializer_class = serializers.AdvertorialBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() class CollectionBackOfficeViewSet(mixins.CreateModelMixin, @@ -74,11 +75,11 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, CollectionViewSet): """ViewSet for Collection model for BackOffice users.""" - permission_classes = (permissions.IsAuthenticated,) queryset = models.Collection.objects.all() filter_backends = [DjangoFilterBackend, OrderingFilter] serializer_class = serializers.CollectionBackOfficeSerializer bind_object_serializer_class = serializers.CollectionBindObjectSerializer + permission_classes = get_permission_classes() ordering_fields = ('rank', 'start') ordering = ('-start', ) @@ -183,7 +184,7 @@ class GuideElementExportXMLView(generics.ListAPIView): pagination_class = None queryset = models.GuideElement.objects.all() serializer_class = serializers.GuideElementBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() def get(self, request, *args, **kwargs): """Overridden get_queryset method.""" @@ -199,7 +200,7 @@ class GuideElementExportDOCView(generics.ListAPIView): pagination_class = None queryset = models.GuideElement.objects.all() serializer_class = serializers.GuideElementBaseSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes() def get(self, request, *args, **kwargs): """Overridden get_queryset method.""" diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index b998e4ed..090186be 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -2,22 +2,20 @@ from rest_framework import generics from comment import models from comment.serializers import CommentBaseSerializer -from utils.permissions import IsModerator, IsCountryAdmin +from utils.methods import get_permission_classes +from utils.permissions import IsModerator class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [ - IsModerator | - IsCountryAdmin - ] + permission_classes = get_permission_classes(IsModerator) class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsModerator] + permission_classes = get_permission_classes(IsModerator) lookup_field = 'id' diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 93fd9760..21ffed5f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -2,33 +2,33 @@ from datetime import datetime from functools import reduce from operator import or_ -from typing import List +from typing import List, Union import elasticsearch_dsl from django.conf import settings -from django.shortcuts import get_object_or_404 from django.contrib.contenttypes import fields as generic from django.contrib.gis.db.models.functions import Distance from django.contrib.gis.geos import Point from django.contrib.gis.measure import Distance as DistanceMeasure from django.contrib.postgres.fields import ArrayField -from django.contrib.postgres.search import TrigramDistance, TrigramSimilarity from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator, FileExtensionValidator from django.db import models -from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch, Sum +from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch +from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from timezone_field import TimeZoneField from location.models import Address -from timetable.models import Timetable from location.models import WineOriginAddressMixin from main.models import Award, Currency from review.models import Review from tag.models import Tag +from timetable.models import Timetable from utils.methods import transform_into_readable_str from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes, GalleryMixin, @@ -184,9 +184,10 @@ class EstablishmentQuerySet(models.QuerySet): """Return establishments by country code""" return self.filter(address__city__country=country) - def by_country_code(self, code): + def by_country_code(self, codes: Union[iter, str]): """Return establishments by country code""" - return self.filter(address__city__country__code=code) + codes = codes if hasattr(codes, '__iter__') else [codes] + return self.filter(address__city__country__code__in=codes) def published(self): """ @@ -518,31 +519,27 @@ class EstablishmentQuerySet(models.QuerySet): to_attr='main_image') ) - def user_role_establishments(self, user, country_code: str = None): - """Return QuerySet with establishment that is available for editing.""" + def available_establishments(self, user): + """Return QuerySet with establishment that user has an access.""" from account.models import UserRole - available_ids = Subquery( - UserRole.objects.filter(user=user) - .distinct('user', 'establishment') - .values_list('establishment', flat=True) - ) - + administrator_establishment_ids = [] filters = {} - if country_code: - filters.update({'address__city__country__code': country_code, - 'id__in': available_ids}) - return self.filter(**filters) + # put in array administrated establishment ids + if user.is_establishment_administrator: + administrator_establishment_ids.extend( + UserRole.objects.filter(user=user) + .distinct('user', 'establishment') + .values_list('establishment', flat=True) + ) + # check if user is_staff + if not user.is_staff: + filters.update({'address__city__country__code__in': user.administrated_country_codes}) - def available_objects(self, user, country_code: str = None): - # role without establishment - - # role that has establishment - access_roles = user.userrole_set.has_access_to_establishments() - if access_roles.exists(): - return self.user_role_establishments(user, country_code) - return self.none() + return self.filter(**filters).union( + self.filter(id__in=administrator_establishment_ids) + ) class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin, diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 80f0b0ce..ee17fc94 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,7 +1,7 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, permissions, status +from rest_framework import generics, status from rest_framework.response import Response from account.models import User @@ -9,10 +9,9 @@ from establishment import filters, models, serializers from establishment.models import EstablishmentEmployee from timetable.models import Timetable from timetable.serialziers import ScheduleCreateSerializer, ScheduleRUDSerializer +from utils.methods import get_permission_classes from utils.permissions import ( - IsCountryAdmin, IsEstablishmentManager, - IsWineryWineInspector, IsEstablishmentAdministrator, - IsReviewManager, IsRestaurantInspector + IsEstablishmentManager, IsEstablishmentAdministrator, IsReviewManager, ) from utils.views import CreateDestroyGalleryViewMixin @@ -22,40 +21,32 @@ class EstablishmentMixinViews: def get_queryset(self): """Overridden method 'get_queryset'.""" - return models.Establishment.objects.with_base_related() + queryset = models.Establishment.objects.with_base_related + if hasattr(self, 'request') and \ + (hasattr(self.request, 'user') and hasattr(self.request, 'country_code')): + return queryset().available_establishments(self.request.user) + return queryset().none() class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAPIView): """Establishment list/create view.""" filter_class = filters.EstablishmentFilter - - permission_classes = [ - IsReviewManager | - IsWineryWineInspector | - IsCountryAdmin | - IsEstablishmentManager | - IsEstablishmentAdministrator - ] - queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer - - def get_queryset(self): - """Overridden get_queryset method.""" - qs = super(EstablishmentListCreateView, self).get_queryset() - return qs.available_objects(self.request.user, self.request.country_code) + permission_classes = get_permission_classes( + IsReviewManager, + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class EmployeeEstablishmentsListView(generics.ListAPIView): """Establishment by employee list view.""" - - permission_classes = [ - IsWineryWineInspector | - IsCountryAdmin | - IsEstablishmentManager - ] - queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) def get_queryset(self): pk = self.kwargs.get('pk') @@ -63,30 +54,31 @@ class EmployeeEstablishmentsListView(generics.ListAPIView): return employee.establishments.with_extended_related() -class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): +class EstablishmentRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView): lookup_field = 'slug' - queryset = models.Establishment.objects.all().prefetch_related( - 'establishmentemployee_set', - 'establishmentemployee_set__establishment', - ) serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [ - # IsWineryWineInspector | - # IsCountryAdmin | - # IsEstablishmentManager | - IsEstablishmentAdministrator - # IsRestaurantInspector - ] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) + + def get_queryset(self): + """Overridden get_queryset method.""" + qs = super(EstablishmentRUDView, self).get_queryset() + return qs.prefetch_related( + 'establishmentemployee_set', + 'establishmentemployee_set__establishment', + ) -class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): +class EstablishmentScheduleRUDView(EstablishmentMixinViews, generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" lookup_field = 'slug' serializer_class = ScheduleRUDSerializer - permission_classes = [ - IsWineryWineInspector | - IsEstablishmentManager - ] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) def get_object(self): """ @@ -95,7 +87,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): establishment_slug = self.kwargs['slug'] schedule_id = self.kwargs['schedule_id'] - establishment = get_object_or_404(klass=models.Establishment.objects.all(), + establishment = get_object_or_404(klass=super(EstablishmentScheduleRUDView, self).get_queryset(), slug=establishment_slug) schedule = get_object_or_404(klass=establishment.schedule, id=schedule_id) @@ -112,21 +104,21 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): lookup_field = 'slug' serializer_class = ScheduleCreateSerializer queryset = Timetable.objects.all() - permission_classes = [ - IsWineryWineInspector | - IsEstablishmentManager - ] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() - permission_classes = [ - IsWineryWineInspector | - IsEstablishmentManager - ] filter_backends = (DjangoFilterBackend,) + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) filterset_fields = ( 'establishment', 'establishment__slug', @@ -137,10 +129,10 @@ class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() - permission_classes = [ - IsWineryWineInspector | - IsEstablishmentManager - ] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class SocialChoiceListCreateView(generics.ListCreateAPIView): @@ -148,14 +140,20 @@ class SocialChoiceListCreateView(generics.ListCreateAPIView): serializer_class = serializers.SocialChoiceSerializers queryset = models.SocialChoice.objects.all() pagination_class = None - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class SocialChoiceRUDView(generics.RetrieveUpdateDestroyAPIView): """SocialChoice RUD view.""" serializer_class = serializers.SocialChoiceSerializers queryset = models.SocialChoice.objects.all() - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class SocialListCreateView(generics.ListCreateAPIView): @@ -163,14 +161,20 @@ class SocialListCreateView(generics.ListCreateAPIView): serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() pagination_class = None - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class SocialRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() - permission_classes = [IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class PlateListCreateView(generics.ListCreateAPIView): @@ -178,14 +182,20 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Plate RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class PhonesListCreateView(generics.ListCreateAPIView): @@ -193,14 +203,20 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Phones RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class EmailListCreateView(generics.ListCreateAPIView): @@ -208,38 +224,53 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Email RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) class EmployeeListCreateView(generics.ListCreateAPIView): """Employee list create view.""" - permission_classes = (permissions.AllowAny,) filter_class = filters.EmployeeBackFilter serializer_class = serializers.EmployeeBackSerializers queryset = models.Employee.objects.all().distinct().with_back_office_related() + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class EmployeesListSearchViews(generics.ListAPIView): """Employee search view""" pagination_class = None - permission_classes = (permissions.AllowAny,) queryset = models.Employee.objects.all().with_back_office_related().select_related('photo') filter_class = filters.EmployeeBackSearchFilter serializer_class = serializers.EmployeeBackSerializers + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class EstablishmentEmployeeListView(generics.ListCreateAPIView): """Establishment emplyoees list view.""" - permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstEmployeeBackSerializer pagination_class = None + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) def get_queryset(self): establishment_id = self.kwargs['establishment_id'] @@ -251,13 +282,18 @@ class EstablishmentEmployeeListView(generics.ListCreateAPIView): class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): """Employee RUD view.""" serializer_class = serializers.EmployeeBackSerializers - queryset = models.Employee.objects.all().with_back_office_related() + queryset = models.Employee.objects.with_back_office_related() + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + ) class RemoveAwardView(generics.DestroyAPIView): lookup_field = 'pk' serializer_class = serializers.EmployeeBackSerializers - queryset = models.Employee.objects.all().with_back_office_related() + queryset = models.Employee.objects.with_back_office_related() + permission_classes = get_permission_classes() def get_object(self): employee = super().get_object() @@ -272,27 +308,31 @@ class RemoveAwardView(generics.DestroyAPIView): class EstablishmentTypeListCreateView(generics.ListCreateAPIView): """Establishment type list/create view.""" serializer_class = serializers.EstablishmentTypeBaseSerializer - queryset = models.EstablishmentType.objects.all().select_related('default_image') + queryset = models.EstablishmentType.objects.select_related('default_image') pagination_class = None + permission_classes = get_permission_classes() class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment type retrieve/update/destroy view.""" serializer_class = serializers.EstablishmentTypeBaseSerializer - queryset = models.EstablishmentType.objects.all().select_related('default_image') + queryset = models.EstablishmentType.objects.select_related('default_image') + permission_classes = get_permission_classes() class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer - queryset = models.EstablishmentSubType.objects.all().select_related('default_image') + queryset = models.EstablishmentSubType.objects.select_related('default_image') pagination_class = None + permission_classes = get_permission_classes() class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment subtype retrieve/update/destroy view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer - queryset = models.EstablishmentSubType.objects.all().select_related('default_image') + queryset = models.EstablishmentSubType.objects.select_related('default_image') + permission_classes = get_permission_classes() class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, @@ -300,6 +340,7 @@ class EstablishmentGalleryCreateDestroyView(EstablishmentMixinViews, """Resource for a create|destroy gallery for establishment for back-office users.""" lookup_field = 'slug' serializer_class = serializers.EstablishmentBackOfficeGallerySerializer + permission_classes = get_permission_classes() def get_object(self): """ @@ -322,6 +363,7 @@ class EstablishmentGalleryListView(EstablishmentMixinViews, """Resource for returning gallery for establishment for back-office users.""" lookup_field = 'slug' serializer_class = serializers.ImageBaseSerializer + permission_classes = get_permission_classes() def get_object(self): """Override get_object method.""" @@ -344,13 +386,14 @@ class EstablishmentCompanyListCreateView(EstablishmentMixinViews, lookup_field = 'slug' serializer_class = serializers.EstablishmentCompanyListCreateSerializer + permission_classes = get_permission_classes() def get_object(self): """Returns the object the view is displaying.""" - establishment_qs = models.Establishment.objects.all() - filtered_ad_qs = self.filter_queryset(establishment_qs) + establishment_qs = super(EstablishmentCompanyListCreateView, self).get_queryset() + filtered_qs = self.filter_queryset(establishment_qs) - establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug')) + establishment = get_object_or_404(filtered_qs, slug=self.kwargs.get('slug')) # May raise a permission denied self.check_object_permissions(self.request, establishment) @@ -368,10 +411,11 @@ class EstablishmentCompanyRUDView(EstablishmentMixinViews, lookup_field = 'slug' serializer_class = serializers.CompanyBaseSerializer + permission_classes = get_permission_classes() def get_object(self): """Returns the object the view is displaying.""" - establishment_qs = models.Establishment.objects.all() + establishment_qs = super(EstablishmentCompanyRUDView, self).get_queryset() filtered_ad_qs = self.filter_queryset(establishment_qs) establishment = get_object_or_404(filtered_ad_qs, slug=self.kwargs.get('slug')) @@ -389,10 +433,14 @@ class EstablishmentNoteListCreateView(EstablishmentMixinViews, lookup_field = 'slug' serializer_class = serializers.EstablishmentNoteListCreateSerializer + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) def get_object(self): """Returns the object the view is displaying.""" - establishment_qs = models.Establishment.objects.all() + establishment_qs = super(EstablishmentNoteListCreateView, self).get_queryset() filtered_establishment_qs = self.filter_queryset(establishment_qs) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug')) @@ -413,10 +461,14 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews, lookup_field = 'slug' serializer_class = serializers.EstablishmentNoteBaseSerializer + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager, + ) def get_object(self): """Returns the object the view is displaying.""" - establishment_qs = models.Establishment.objects.all() + establishment_qs = super(EstablishmentNoteRUDView, self).get_queryset() filtered_establishment_qs = self.filter_queryset(establishment_qs) establishment = get_object_or_404(filtered_establishment_qs, slug=self.kwargs.get('slug')) @@ -431,27 +483,39 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews, class EstablishmentEmployeeCreateView(generics.CreateAPIView): serializer_class = serializers.EstablishmentEmployeeCreateSerializer queryset = models.EstablishmentEmployee.objects.all() - # TODO send email to all admins and add endpoint for changing status + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) class EstablishmentEmployeeDeleteView(generics.DestroyAPIView): queryset = EstablishmentEmployee.objects.all() - permission_classes = [IsEstablishmentManager | permissions.IsAdminUser] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) class EstablishmentPositionListView(generics.ListAPIView): """Establishment positions list view.""" pagination_class = None - permission_classes = (permissions.AllowAny,) queryset = models.Position.objects.all() serializer_class = serializers.PositionBackSerializer + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) class EstablishmentAdminView(generics.ListAPIView): """Establishment admin list view.""" serializer_class = serializers.EstablishmentAdminListSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) def get_queryset(self): establishment = get_object_or_404( @@ -463,12 +527,18 @@ class MenuDishesListCreateView(generics.ListCreateAPIView): """Menu (dessert, main_course, starter) list create view.""" serializer_class = serializers.MenuDishesSerializer queryset = models.Menu.objects.with_schedule_plates_establishment().dishes().distinct() - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] filter_class = filters.MenuDishesBackFilter + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) class MenuDishesRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu (dessert, main_course, starter) RUD view.""" serializer_class = serializers.MenuDishesRUDSerializers queryset = models.Menu.objects.dishes().distinct() - permission_classes = [IsWineryWineInspector | IsEstablishmentManager] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) diff --git a/apps/favorites/views.py b/apps/favorites/views.py index 1e7a12b3..626b8c07 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -1,30 +1,45 @@ """Views for app favorites.""" from rest_framework import generics -from establishment.models import Establishment + from establishment.filters import EstablishmentFilter -from establishment.serializers import EstablishmentBaseSerializer, EstablishmentSimilarSerializer +from establishment.models import Establishment +from establishment.serializers import EstablishmentSimilarSerializer from news.filters import NewsListFilterSet from news.models import News -from news.serializers import NewsBaseSerializer, NewsListSerializer +from news.serializers import NewsListSerializer +from product.filters import ProductFilterSet from product.models import Product from product.serializers import ProductBaseSerializer -from product.filters import ProductFilterSet -from .models import Favorites +from utils.methods import get_permission_classes +from utils.permissions import ( + IsApprovedUser, IsEstablishmentAdministrator, IsWineryWineInspector, + IsRestaurantInspector, IsContentPageManager, IsEstablishmentManager, + IsReviewManager, IsDistilleryLiquorInspector, IsArtisanInspector, + IsGuest, IsModerator, IsProducerFoodInspector, +) -class FavoritesBaseView(generics.GenericAPIView): - """Base view for Favorites.""" - - def get_queryset(self): - """Override get_queryset method.""" - return Favorites.objects.by_user(self.request.user) +class FavoritesPermissionMixin: + """Permissions for application favorites.""" + permission_classes = get_permission_classes( + IsApprovedUser, IsEstablishmentAdministrator, IsWineryWineInspector, + IsRestaurantInspector, IsContentPageManager, IsEstablishmentManager, + IsReviewManager, IsDistilleryLiquorInspector, IsArtisanInspector, + IsModerator, IsProducerFoodInspector, + ) -class FavoritesEstablishmentListView(generics.ListAPIView): +class FavoritesEstablishmentListView(FavoritesPermissionMixin, generics.ListAPIView): """List views for establishments in favorites.""" serializer_class = EstablishmentSimilarSerializer filter_class = EstablishmentFilter + permission_classes = get_permission_classes( + IsApprovedUser, IsEstablishmentAdministrator, IsWineryWineInspector, + IsRestaurantInspector, IsContentPageManager, IsEstablishmentManager, + IsReviewManager, IsDistilleryLiquorInspector, IsArtisanInspector, + IsGuest, IsModerator, IsProducerFoodInspector, + ) def get_queryset(self): """Override get_queryset method""" @@ -36,7 +51,7 @@ class FavoritesEstablishmentListView(generics.ListAPIView): .with_certain_tag_category_related('distillery_type', 'distillery_type') -class FavoritesProductListView(generics.ListAPIView): +class FavoritesProductListView(FavoritesPermissionMixin, generics.ListAPIView): """List views for products in favorites.""" serializer_class = ProductBaseSerializer @@ -48,7 +63,7 @@ class FavoritesProductListView(generics.ListAPIView): .order_by('-favorites') -class FavoritesNewsListView(generics.ListAPIView): +class FavoritesNewsListView(FavoritesPermissionMixin, generics.ListAPIView): """List views for news in favorites.""" serializer_class = NewsListSerializer diff --git a/apps/gallery/views.py b/apps/gallery/views.py index 1515707f..3b5063ba 100644 --- a/apps/gallery/views.py +++ b/apps/gallery/views.py @@ -3,6 +3,8 @@ from django.db.transaction import on_commit from rest_framework import generics, status from rest_framework.response import Response +from utils.methods import get_permission_classes +from utils.permissions import IsContentPageManager from . import tasks, models, serializers @@ -11,6 +13,9 @@ class ImageBaseView(generics.GenericAPIView): model = models.Image queryset = models.Image.objects.all() serializer_class = serializers.ImageSerializer + permission_classes = get_permission_classes( + IsContentPageManager + ) class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView): diff --git a/apps/location/views/back.py b/apps/location/views/back.py index a0d1bd36..b57c315d 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -1,45 +1,52 @@ """Location app views.""" -from rest_framework import generics from django.contrib.postgres.fields.jsonb import KeyTextTransform -from utils.models import get_current_locale - -from location import models, serializers -from location.views import common -from utils.permissions import IsCountryAdmin -from utils.views import CreateDestroyGalleryViewMixin -from rest_framework.permissions import IsAuthenticatedOrReadOnly -from django.shortcuts import get_object_or_404 -from django.db import IntegrityError -from utils.serializers import ImageBaseSerializer -from location.filters import RegionFilter +from rest_framework import generics from location import filters +from location import models, serializers +from location.filters import RegionFilter +from location.views import common +from utils.methods import get_permission_classes +from utils.models import get_current_locale +from utils.permissions import ( + IsGuest, IsEstablishmentManager, IsEstablishmentAdministrator +) # Address - - class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) # City class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CityBaseSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] queryset = models.City.objects.all() filter_class = filters.CityBackFilter + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) def get_queryset(self): """Overridden method 'get_queryset'.""" @@ -53,18 +60,26 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CityBaseSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] queryset = models.City.objects.all()\ .annotate(locale_name=KeyTextTransform(get_current_locale(), 'name_translated'))\ .order_by('locale_name') filter_class = filters.CityBackFilter pagination_class = None + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CityDetailSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) # Region @@ -72,15 +87,23 @@ class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" pagination_class = None serializer_class = serializers.RegionSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] # ordering_fields = 'name' filter_class = RegionFilter + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve view for model Region""" serializer_class = serializers.RegionSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) # Country @@ -91,11 +114,19 @@ class CountryListCreateView(generics.ListCreateAPIView): .order_by('locale_name') serializer_class = serializers.CountryBackSerializer pagination_class = None - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer - permission_classes = [IsAuthenticatedOrReadOnly | IsCountryAdmin] queryset = models.Country.objects.all() + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator, + IsGuest, + ) diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 23fdc48a..4bb082cb 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -1,25 +1,26 @@ from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, permissions, status +from rest_framework import generics, status from rest_framework.generics import get_object_or_404 from rest_framework.response import Response -from main import serializers -from main.serializers.back import PanelSerializer -from establishment.serializers.back import EmployeeBackSerializers from establishment.models import Employee +from establishment.serializers.back import EmployeeBackSerializers +from main import serializers from main import tasks from main.filters import AwardFilter from main.models import Award, Footer, PageType, Panel, SiteFeature, Feature, AwardType +from main.serializers.back import PanelSerializer from main.views import SiteSettingsView, SiteListView +from utils.methods import get_permission_classes class AwardLstView(generics.ListCreateAPIView): """Award list create view.""" queryset = Award.objects.all().with_base_related() serializer_class = serializers.BackAwardSerializer - permission_classes = (permissions.IsAdminUser,) + permission_classes = get_permission_classes() filterset_class = AwardFilter @@ -27,10 +28,10 @@ class AwardCreateAndBind(generics.CreateAPIView): """Award create and bind to employee by id""" queryset = Award.objects.all().with_base_related() serializer_class = serializers.BackAwardEmployeeCreateSerializer - permission_classes = (permissions.IsAdminUser, ) + permission_classes = get_permission_classes() def create(self, request, *args, **kwargs): - """!!!Overriden!!!""" + """Overridden create method.""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) @@ -43,7 +44,7 @@ class AwardRUDView(generics.RetrieveUpdateDestroyAPIView): """Award RUD view.""" queryset = Award.objects.all().with_base_related() serializer_class = serializers.BackAwardSerializer - permission_classes = (permissions.IsAdminUser,) + permission_classes = get_permission_classes() lookup_field = 'id' @@ -52,14 +53,14 @@ class AwardTypesListView(generics.ListAPIView): pagination_class = None queryset = AwardType.objects.all() serializer_class = serializers.AwardTypeBaseSerializer - permission_classes = (permissions.AllowAny, ) + permission_classes = get_permission_classes() class ContentTypeView(generics.ListAPIView): """ContentType list view""" queryset = ContentType.objects.all() serializer_class = serializers.ContentTypeBackSerializer - permission_classes = (permissions.IsAdminUser,) + permission_classes = get_permission_classes() filter_backends = (DjangoFilterBackend, ) ordering_fields = '__all__' lookup_field = 'id' @@ -74,6 +75,7 @@ class FeatureBackView(generics.ListCreateAPIView): """Feature list or create View.""" serializer_class = serializers.FeatureSerializer queryset = Feature.objects.all() + permission_classes = get_permission_classes() class SiteFeatureBackView(generics.ListCreateAPIView): @@ -81,79 +83,75 @@ class SiteFeatureBackView(generics.ListCreateAPIView): serializer_class = serializers.SiteFeatureSerializer queryset = SiteFeature.objects.all() pagination_class = None - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes() class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): """Feature RUD View.""" serializer_class = serializers.FeatureSerializer queryset = SiteFeature.objects.all() - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes() class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): """Feature RUD View.""" serializer_class = serializers.SiteFeatureSerializer queryset = SiteFeature.objects.all() - permission_classes = [permissions.IsAdminUser] + permission_classes = get_permission_classes() class SiteSettingsBackOfficeView(SiteSettingsView): """Site settings View.""" serializer_class = serializers.SiteSerializer + permission_classes = get_permission_classes() class SiteListBackOfficeView(SiteListView): """Site settings View.""" serializer_class = serializers.SiteSerializer + permission_classes = get_permission_classes() class FooterBackView(generics.ListCreateAPIView): """Footer back list/create view.""" - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.FooterBackSerializer queryset = Footer.objects.all() + permission_classes = get_permission_classes() class FooterRUDBackView(generics.RetrieveUpdateDestroyAPIView): """Footer back RUD view.""" - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.FooterBackSerializer queryset = Footer.objects.all() + permission_classes = get_permission_classes() class PageTypeListCreateView(generics.ListCreateAPIView): """PageType back office view.""" - permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) pagination_class = None serializer_class = serializers.PageTypeBaseSerializer queryset = PageType.objects.all() + permission_classes = get_permission_classes() class PanelsListCreateView(generics.ListCreateAPIView): """Custom panels view.""" - permission_classes = ( - permissions.IsAdminUser, - ) serializer_class = PanelSerializer queryset = Panel.objects.all() + permission_classes = get_permission_classes() class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView): """Custom panels view.""" - permission_classes = ( - permissions.IsAdminUser, - ) serializer_class = PanelSerializer queryset = Panel.objects.all() + permission_classes = get_permission_classes() class PanelsExecuteView(generics.ListAPIView): """Custom panels view.""" - permission_classes = ( - permissions.IsAdminUser, - ) queryset = Panel.objects.all() + permission_classes = get_permission_classes() def list(self, request, *args, **kwargs): panel = get_object_or_404(Panel, id=self.kwargs['pk']) @@ -162,8 +160,8 @@ class PanelsExecuteView(generics.ListAPIView): class PanelsExportCSVView(PanelsExecuteView): """Export panels via csv view.""" - permission_classes = (permissions.IsAdminUser,) queryset = Panel.objects.all() + permission_classes = get_permission_classes() def list(self, request, *args, **kwargs): panel = get_object_or_404(Panel, id=self.kwargs['pk']) @@ -178,8 +176,8 @@ class PanelsExportCSVView(PanelsExecuteView): class PanelsExecuteXLSView(PanelsExecuteView): """Export panels via xlsx view.""" - permission_classes = (permissions.IsAdminUser,) queryset = Panel.objects.all() + permission_classes = get_permission_classes() def list(self, request, *args, **kwargs): panel = get_object_or_404(Panel, id=self.kwargs['pk']) diff --git a/apps/news/views.py b/apps/news/views.py index b0454deb..3fc8284e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -3,15 +3,17 @@ from django.conf import settings from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils import translation +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions, response from rest_framework.filters import OrderingFilter -from django_filters.rest_framework import DjangoFilterBackend from news import filters, models, serializers from rating.tasks import add_rating -from utils.permissions import IsCountryAdmin, IsContentPageManager, IsReviewManager -from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView -from utils.serializers import ImageBaseSerializer, EmptySerializer +from utils.methods import get_permission_classes +from utils.permissions import IsContentPageManager +from utils.serializers import ImageBaseSerializer +from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, \ + CarouselCreateDestroyMixinView class NewsMixinView: @@ -106,7 +108,7 @@ class NewsTypeListView(generics.ListAPIView): class NewsBackOfficeMixinView: """News back office mixin view.""" - permission_classes = (permissions.IsAuthenticated,) + permission_classes = get_permission_classes(IsContentPageManager, IsContentPageManager) def get_queryset(self): """Override get_queryset method.""" @@ -124,11 +126,6 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, filter_class = filters.NewsListFilterSet create_serializers_class = serializers.NewsBackOfficeDetailSerializer filter_backends = (OrderingFilter, DjangoFilterBackend) - permission_classes = [ - IsCountryAdmin | - IsContentPageManager | - IsReviewManager - ] ordering_fields = '__all__' @@ -205,9 +202,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, class NewsBackOfficeRUDView(NewsBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Resource for detailed information about news for back-office users.""" - serializer_class = serializers.NewsBackOfficeDetailSerializer - permission_classes = [IsCountryAdmin | IsContentPageManager] def get(self, request, pk, *args, **kwargs): add_rating(remote_addr=request.META.get('REMOTE_ADDR'), diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index a0534d13..bc935ace 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -6,11 +6,11 @@ from rest_framework.response import Response from notification import models from notification.serializers import common as serializers from utils.methods import get_user_ip +from utils.permissions import IsAuthenticatedAndTokenIsValid class CreateSubscribeView(generics.CreateAPIView): """Create subscribe View.""" - queryset = models.Subscriber.objects.all() permission_classes = (permissions.AllowAny,) serializer_class = serializers.CreateAndUpdateSubscribeSerializer @@ -18,7 +18,6 @@ class CreateSubscribeView(generics.CreateAPIView): class UpdateSubscribeView(generics.UpdateAPIView): """Subscribe info view.""" - lookup_field = 'update_code' lookup_url_kwarg = 'code' permission_classes = (permissions.AllowAny,) @@ -28,7 +27,6 @@ class UpdateSubscribeView(generics.UpdateAPIView): class SubscribeInfoView(generics.RetrieveAPIView): """Subscribe info view.""" - lookup_field = 'update_code' lookup_url_kwarg = 'code' permission_classes = (permissions.AllowAny,) @@ -38,8 +36,7 @@ class SubscribeInfoView(generics.RetrieveAPIView): class SubscribeInfoAuthUserView(generics.RetrieveAPIView): """Subscribe info auth user view.""" - - permission_classes = (permissions.IsAuthenticated,) + permission_classes = (IsAuthenticatedAndTokenIsValid,) serializer_class = serializers.SubscribeSerializer lookup_field = None @@ -62,7 +59,6 @@ class SubscribeInfoAuthUserView(generics.RetrieveAPIView): class UnsubscribeView(generics.UpdateAPIView): """Unsubscribe view.""" - lookup_field = 'update_code' lookup_url_kwarg = 'code' permission_classes = (permissions.AllowAny,) @@ -78,8 +74,7 @@ class UnsubscribeView(generics.UpdateAPIView): class UnsubscribeAuthUserView(generics.GenericAPIView): """Unsubscribe auth user view.""" - - permission_classes = (permissions.IsAuthenticated,) + permission_classes = (IsAuthenticatedAndTokenIsValid,) queryset = models.Subscriber.objects.all() serializer_class = serializers.SubscribeSerializer diff --git a/apps/partner/views/back.py b/apps/partner/views/back.py index 1033d0ee..aba096e6 100644 --- a/apps/partner/views/back.py +++ b/apps/partner/views/back.py @@ -1,9 +1,10 @@ -from django_filters.rest_framework import DjangoFilterBackend, filters -from rest_framework import generics, permissions +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import generics from partner.models import Partner from partner.serializers import back as serializers -from utils.permissions import IsEstablishmentManager +from utils.methods import get_permission_classes +from utils.permissions import IsEstablishmentManager, IsEstablishmentAdministrator class PartnerLstView(generics.ListCreateAPIView): @@ -11,8 +12,11 @@ class PartnerLstView(generics.ListCreateAPIView): queryset = Partner.objects.all() serializer_class = serializers.BackPartnerSerializer pagination_class = None - permission_classes = [permissions.IsAdminUser | IsEstablishmentManager] filter_backends = (DjangoFilterBackend,) + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) filterset_fields = ( 'establishment', 'type', @@ -23,5 +27,8 @@ class PartnerRUDView(generics.RetrieveUpdateDestroyAPIView): """Partner RUD view.""" queryset = Partner.objects.all() serializer_class = serializers.BackPartnerSerializer - permission_classes = [permissions.IsAdminUser | IsEstablishmentManager] lookup_field = 'id' + permission_classes = get_permission_classes( + IsEstablishmentManager, + IsEstablishmentAdministrator + ) diff --git a/apps/product/models.py b/apps/product/models.py index e2584be0..068350e4 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -9,15 +9,14 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Case, When, F from django.utils.translation import gettext_lazy as _ -from django.db.models import Subquery from location.models import WineOriginAddressMixin from review.models import Review +from utils.methods import transform_into_readable_str from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin, TranslatedFieldsMixin, TJSONField, FavoritesMixin, GalleryMixin, IntermediateGalleryModelMixin, TypeDefaultImageMixin) -from utils.methods import transform_into_readable_str class ProductType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin): @@ -229,21 +228,26 @@ class ProductQuerySet(models.QuerySet): 'id') def available_products(self, user): - """Return QuerySet with products that is available for editing.""" + """Return QuerySet with establishment that user has an access.""" from account.models import UserRole - available_ids = Subquery( - UserRole.objects.filter(user=user) - .distinct('user', 'establishment') - .values_list('establishment__products', flat=True) - ) - return self.filter(id__in=available_ids) + administrator_establishment_ids = [] + filters = {} - def available_objects(self, user): - access_roles = user.userrole_set.has_access_to_products() - if access_roles.exists(): - return self.available_products(user) - return self.none() + # put in array administrated establishment ids + if user.is_establishment_administrator: + administrator_establishment_ids.extend( + UserRole.objects.filter(user=user) + .distinct('user', 'establishment') + .values_list('establishment', flat=True) + ) + # check if user is_staff + if not user.is_staff: + filters.update({'establishment__address__city__country__code__in': user.administrated_country_codes}) + + return self.filter(**filters).union( + self.filter(establishment__id__in=administrator_establishment_ids) + ) class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, diff --git a/apps/product/views/back.py b/apps/product/views/back.py index fb0d3f82..639f6bf0 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -1,12 +1,12 @@ """Product app back-office views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, status, permissions, views +from rest_framework import generics, status from rest_framework.response import Response from product import serializers, models from product.views import ProductBaseView +from utils.methods import get_permission_classes from utils.permissions import ( - IsDistilleryLiquorInspector, IsProducerFoodInspector, IsEstablishmentManager, IsEstablishmentAdministrator) from utils.serializers import ImageBaseSerializer from utils.views import CreateDestroyGalleryViewMixin @@ -14,41 +14,36 @@ from utils.views import CreateDestroyGalleryViewMixin class ProductBackOfficeMixinView(ProductBaseView): """Product back-office mixin view.""" - - permission_classes = (permissions.IsAuthenticated, ) + permission_classes = get_permission_classes( + IsEstablishmentAdministrator, + IsEstablishmentManager + ) def get_queryset(self): """Override get_queryset method.""" - qs = models.Product.objects.with_extended_related().annotate_in_favorites(self.request.user) - return qs + queryset = ( + models.Product.objects.with_base_related() + .with_extended_related() + .annotate_in_favorites(self.request.user) + ) + if hasattr(self, 'request') and \ + (hasattr(self.request, 'user') and hasattr(self.request, 'country_code')): + return queryset.available_products(self.request.user) + return queryset.none() class ProductTypeBackOfficeMixinView: """Product type back-office mixin view.""" - - permission_classes = (permissions.IsAuthenticated,) + permission_classes = get_permission_classes() queryset = models.ProductType.objects.all() class ProductSubTypeBackOfficeMixinView: """Product sub type back-office mixin view.""" - - permission_classes = (permissions.IsAuthenticated,) + permission_classes = get_permission_classes() queryset = models.ProductSubType.objects.all() -class BackOfficeListCreateMixin(views.APIView): - """Back-office list-create mixin view.""" - - def check_permissions(self, request): - """ - Check if the request should be permitted. - Raises an appropriate exception if the request is not permitted. - """ - if self.request.method != 'GET': - super().check_permissions(request) - - class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView, CreateDestroyGalleryViewMixin): """Resource for a create gallery for product for back-office users.""" @@ -58,7 +53,7 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView, """ Returns the object the view is displaying. """ - product_qs = self.filter_queryset(self.get_queryset()) + product_qs = self.get_queryset() product = get_object_or_404(product_qs, pk=self.kwargs.get('pk')) gallery = get_object_or_404(product.product_gallery, image_id=self.kwargs.get('image_id')) @@ -73,7 +68,6 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for product for back-office users.""" serializer_class = ImageBaseSerializer - permission_classes = (permissions.IsAuthenticated,) def get_object(self): """Override get_object method.""" @@ -94,40 +88,21 @@ class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product back-office R/U/D view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [ - # IsLiquorReviewer | - # IsProductReviewer | - IsEstablishmentManager | - IsEstablishmentAdministrator - ] class ProductListCreateBackOfficeView(ProductBackOfficeMixinView, generics.ListCreateAPIView): """Product back-office list-create view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer - permission_classes = [ - IsDistilleryLiquorInspector | - IsProducerFoodInspector | - IsEstablishmentAdministrator | - IsEstablishmentManager - ] - - def get_queryset(self): - """Overridden get_queryset method.""" - qs = super(ProductListCreateBackOfficeView, self).get_queryset() - return qs.available_objects(self.request.user) -class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, - ProductTypeBackOfficeMixinView, +class ProductTypeListCreateBackOfficeView(ProductTypeBackOfficeMixinView, generics.ListCreateAPIView): """Product type back-office list-create view.""" serializer_class = serializers.ProductTypeBackOfficeDetailSerializer -class ProductTypeRUDBackOfficeView(BackOfficeListCreateMixin, - ProductTypeBackOfficeMixinView, +class ProductTypeRUDBackOfficeView(ProductTypeBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product type back-office retrieve-update-destroy view.""" serializer_class = serializers.ProductTypeBackOfficeDetailSerializer @@ -143,22 +118,19 @@ class ProductTypeTagCategoryCreateBackOfficeView(ProductTypeBackOfficeMixinView, return Response(status=status.HTTP_201_CREATED) -class ProductSubTypeListCreateBackOfficeView(BackOfficeListCreateMixin, - ProductSubTypeBackOfficeMixinView, +class ProductSubTypeListCreateBackOfficeView(ProductSubTypeBackOfficeMixinView, generics.ListCreateAPIView): """Product sub type back-office list-create view.""" serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer -class ProductSubTypeRUDBackOfficeView(BackOfficeListCreateMixin, - ProductSubTypeBackOfficeMixinView, +class ProductSubTypeRUDBackOfficeView(ProductSubTypeBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product sub type back-office retrieve-update-destroy view.""" serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer class ProductNoteListCreateView(ProductBackOfficeMixinView, - BackOfficeListCreateMixin, generics.ListCreateAPIView): """Retrieve|Update|Destroy product note view.""" @@ -166,7 +138,7 @@ class ProductNoteListCreateView(ProductBackOfficeMixinView, def get_object(self): """Returns the object the view is displaying.""" - product_qs = models.Product.objects.all() + product_qs = super(ProductNoteListCreateView, self).get_queryset() filtered_product_qs = self.filter_queryset(product_qs) product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk')) @@ -182,7 +154,6 @@ class ProductNoteListCreateView(ProductBackOfficeMixinView, class ProductNoteRUDView(ProductBackOfficeMixinView, - BackOfficeListCreateMixin, generics.RetrieveUpdateDestroyAPIView): """Create|Retrieve|Update|Destroy product note view.""" @@ -190,7 +161,7 @@ class ProductNoteRUDView(ProductBackOfficeMixinView, def get_object(self): """Returns the object the view is displaying.""" - product_qs = models.Product.objects.all() + product_qs = super(ProductNoteRUDView, self).get_queryset() filtered_product_qs = self.filter_queryset(product_qs) product = get_object_or_404(filtered_product_qs, pk=self.kwargs.get('pk')) diff --git a/apps/recipe/views/common.py b/apps/recipe/views/common.py index 31e74f20..cfce158b 100644 --- a/apps/recipe/views/common.py +++ b/apps/recipe/views/common.py @@ -1,16 +1,18 @@ """Recipe app common views.""" from django.utils import translation -from rest_framework import generics, permissions +from rest_framework import generics from recipe import models from recipe.serializers import common as serializers +from utils.methods import get_permission_classes +from utils.permissions import IsContentPageManager class RecipeViewMixin(generics.GenericAPIView): """Recipe view mixin.""" pagination_class = None - permission_classes = (permissions.AllowAny,) + permission_classes = get_permission_classes(IsContentPageManager) def get_queryset(self, *args, **kwargs): user = self.request.user diff --git a/apps/review/models.py b/apps/review/models.py index a987cf2e..f17629ba 100644 --- a/apps/review/models.py +++ b/apps/review/models.py @@ -1,11 +1,8 @@ """Review app models.""" -from pprint import pprint from django.contrib.contenttypes import fields as generic from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.db.models.signals import post_init, post_save -from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from utils.models import (BaseAttributes, TranslatedFieldsMixin, @@ -16,6 +13,15 @@ from utils.models import (BaseAttributes, TranslatedFieldsMixin, class ReviewQuerySet(models.QuerySet): """QuerySets for model Review""" + def with_base_related(self): + """Return QuerySet with base related.""" + return self.select_related( + 'reviewer', + 'country', + 'child', + 'content_type', + ) + def by_reviewer(self, user): """Return reviews by user""" return self.filter(reviewer=user) diff --git a/apps/review/views/back.py b/apps/review/views/back.py index d1e470db..0b2f7b42 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -1,21 +1,30 @@ -from rest_framework import generics, permissions +from rest_framework import generics from review import filters from review import models from review import serializers -from utils.permissions import IsReviewManager, IsRestaurantInspector from review.serializers.back import ReviewBackSerializer +from utils.methods import get_permission_classes +from utils.permissions import ( + IsReviewManager, IsRestaurantInspector, IsWineryWineInspector, + IsArtisanInspector, IsProducerFoodInspector, IsDistilleryLiquorInspector, +) + + +class PermissionMixinView: + """Permission mixin view.""" + permission_classes = get_permission_classes( + IsReviewManager, IsRestaurantInspector, IsWineryWineInspector, + IsArtisanInspector, IsProducerFoodInspector, IsDistilleryLiquorInspector, + ) class ReviewMixinView: """Review mixin.""" - - def get_queryset(self): - """Overridden method 'get_queryset'.""" - return models.Review.objects.all() + queryset = models.Review.objects.with_base_related() -class ReviewListView(ReviewMixinView, generics.ListCreateAPIView): +class ReviewListView(PermissionMixinView, ReviewMixinView, generics.ListCreateAPIView): """Review list create view. status values: @@ -25,16 +34,10 @@ class ReviewListView(ReviewMixinView, generics.ListCreateAPIView): READY = 2 """ serializer_class = ReviewBackSerializer - queryset = models.Review.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly, ] filterset_class = filters.ReviewFilter - def get_queryset(self): - """Overridden get_queryset method.""" - return super(ReviewListView, self).get_queryset() - -class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): +class ReviewRUDView(PermissionMixinView, ReviewMixinView, generics.RetrieveUpdateDestroyAPIView): """Review RUD view. status values: @@ -44,17 +47,14 @@ class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): READY = 2 """ serializer_class = ReviewBackSerializer - queryset = models.Review.objects.all() - permission_classes = [permissions.IsAdminUser | IsReviewManager | IsRestaurantInspector] lookup_field = 'id' -class InquiriesListView(generics.ListCreateAPIView): +class InquiriesListView(PermissionMixinView, generics.ListCreateAPIView): """Inquiries list create view.""" serializer_class = serializers.InquiriesBaseSerializer queryset = models.Inquiries.objects.all() - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) def get_queryset(self): review_id = self.kwargs.get('review_id') @@ -63,19 +63,17 @@ class InquiriesListView(generics.ListCreateAPIView): return super().get_queryset() -class InquiriesRUDView(generics.RetrieveUpdateDestroyAPIView): +class InquiriesRUDView(PermissionMixinView, generics.RetrieveUpdateDestroyAPIView): """Inquiries RUD view.""" serializer_class = serializers.InquiriesBaseSerializer queryset = models.Inquiries.objects.all() - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) lookup_field = 'id' -class GridItemsListView(generics.ListCreateAPIView): +class GridItemsListView(PermissionMixinView, generics.ListCreateAPIView): """GridItems list create view.""" serializer_class = serializers.GridItemsBaseSerializer queryset = models.GridItems.objects.all() - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) def get_queryset(self): inquiry_id = self.kwargs.get('inquiry_id') @@ -84,9 +82,8 @@ class GridItemsListView(generics.ListCreateAPIView): return super().get_queryset() -class GridItemsRUDView(generics.RetrieveUpdateDestroyAPIView): +class GridItemsRUDView(PermissionMixinView, generics.RetrieveUpdateDestroyAPIView): """GridItems RUD view.""" serializer_class = serializers.GridItemsBaseSerializer queryset = models.GridItems.objects.all() - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) lookup_field = 'id' diff --git a/apps/tag/views.py b/apps/tag/views.py index 41fbd77b..8e22f9e4 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from rest_framework import generics, mixins, permissions, status, viewsets from rest_framework.decorators import action +from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from rest_framework.serializers import ValidationError @@ -11,6 +12,9 @@ from location.models import WineRegion from product.models import ProductType from search_indexes import views as search_views from tag import filters, models, serializers +from utils.permissions import ( + IsEstablishmentManager +) class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): @@ -334,7 +338,9 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, """List/create tag view.""" pagination_class = None - permission_classes = (permissions.IsAdminUser,) + permission_classes = [ + IsAdminUser, IsEstablishmentManager + ] queryset = models.Tag.objects.with_base_related() serializer_class = serializers.TagBackOfficeSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer @@ -388,10 +394,12 @@ class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, TagCategoryViewSet): """ViewSet for TagCategory model for BackOffice users.""" - permission_classes = (permissions.IsAdminUser,) queryset = TagCategoryViewSet.queryset.with_extended_related() serializer_class = serializers.TagCategoryBackOfficeDetailSerializer bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer + permission_classes = [ + IsAdminUser, IsEstablishmentManager + ] def perform_binding(self, serializer): data = serializer.validated_data diff --git a/apps/utils/methods.py b/apps/utils/methods.py index 58cc3f52..a89f9b03 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -8,12 +8,12 @@ from functools import reduce from io import BytesIO import requests +from PIL import Image from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.gis.geos import Point from django.http.request import HttpRequest from django.utils.timezone import datetime -from PIL import Image from rest_framework import status from rest_framework.request import Request @@ -216,3 +216,17 @@ def get_image_meta_by_url(url) -> (int, int, int): image = Image.open(BytesIO(image_raw.content)) width, height = image.size return int(image_raw.headers.get('content-length')), width, height + + +def get_permission_classes(*args) -> list: + """Return permission_class object with admin permissions.""" + from rest_framework.permissions import IsAdminUser + from utils.permissions import IsCountryAdmin + + admin_permission_classes = [IsCountryAdmin, IsAdminUser] + permission_classes = [ + reduce( + lambda a, b: a | b, admin_permission_classes + list(args) + ) + ] + return permission_classes diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2ea32b18..af6a0806 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -65,11 +65,8 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): ] return all(rules) - def has_object_permission(self, request, view, obj): - return self.has_permission(request, view) - -class IsApprovedUser(permissions.IsAuthenticated): +class IsApprovedUser(IsAuthenticatedAndTokenIsValid): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. @@ -122,7 +119,6 @@ class IsCountryAdmin(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): role = Role.objects.filter( @@ -148,7 +144,6 @@ class IsModerator(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): role = Role.objects.filter( @@ -170,7 +165,6 @@ class IsEstablishmentManager(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -194,7 +188,6 @@ class IsEstablishmentAdministrator(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -243,7 +236,6 @@ class IsReviewManager(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -277,7 +269,6 @@ class IsRestaurantInspector(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -311,7 +302,6 @@ class IsArtisanInspector(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -345,7 +335,6 @@ class IsWineryWineInspector(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -379,7 +368,6 @@ class IsProducerFoodInspector(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): @@ -413,7 +401,6 @@ class IsDistilleryLiquorInspector(IsApprovedUser): super().has_permission(request, view) ] has_permission = False - # check role if (request.user.is_authenticated and hasattr(request, 'country_code') and request.country_code): From c69bc051565525e6126afc1c26250adc7bb71afc Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 30 Jan 2020 12:17:23 +0300 Subject: [PATCH 4/6] fix products serializer --- apps/search_indexes/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 29144189..d679e54f 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -129,7 +129,7 @@ class CityDocumentShortSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(loads(obj.name)) + return get_translated_value(loads(obj.name)) if obj.name else None From ca5440d431a6ca6b221032865be454e3d15be06d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 30 Jan 2020 12:50:14 +0300 Subject: [PATCH 5/6] refactored roles --- apps/account/models.py | 13 +------------ apps/establishment/models.py | 33 ++++++++++++++------------------ apps/establishment/views/back.py | 7 +++---- apps/news/models.py | 8 ++++++-- apps/news/views.py | 19 +++++++++++------- apps/partner/views/back.py | 8 ++++---- apps/product/models.py | 31 +++++++++++++----------------- apps/product/views/back.py | 2 +- apps/utils/permissions.py | 24 +++++++++++------------ 9 files changed, 66 insertions(+), 79 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index f34d8634..c43f7e19 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -1,6 +1,7 @@ """Account models""" from collections import Counter from datetime import datetime +from typing import List from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager @@ -15,8 +16,6 @@ from django.utils.http import urlsafe_base64_encode from django.utils.translation import ugettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from rest_framework.authtoken.models import Token -from collections import Counter -from typing import List from authorization.models import Application from establishment.models import Establishment, EstablishmentSubType @@ -74,16 +73,6 @@ class Role(ProjectBaseMixin): (ARTISAN_INSPECTOR, _('Artisan inspector')), ) - ESTABLISHMENT_EDITORS = [ - COUNTRY_ADMIN, - ESTABLISHMENT_MANAGER, - ESTABLISHMENT_ADMINISTRATOR, - ] - - PRODUCT_EDITORS = ESTABLISHMENT_EDITORS + [ - DISTILLERY_LIQUOR_INSPECTOR - ] - role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) country = models.ForeignKey(Country, verbose_name=_('Country'), diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 0371ae17..2ca28311 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,7 +14,7 @@ from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import ValidationError -from django.core.validators import MinValueValidator, MaxValueValidator, FileExtensionValidator +from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch from django.shortcuts import get_object_or_404 @@ -519,27 +519,22 @@ class EstablishmentQuerySet(models.QuerySet): to_attr='main_image') ) - def available_establishments(self, user): - """Return QuerySet with establishment that user has an access.""" + def available_establishments(self, user, country_code: str): + """Return QuerySet with establishments that user has an access.""" from account.models import UserRole - administrator_establishment_ids = [] - filters = {} - - # put in array administrated establishment ids - if user.is_establishment_administrator: - administrator_establishment_ids.extend( - UserRole.objects.filter(user=user) - .distinct('user', 'establishment') - .values_list('establishment', flat=True) - ) - # check if user is_staff if not user.is_staff: - filters.update({'address__city__country__code__in': user.administrated_country_codes}) - - return self.filter(**filters).union( - self.filter(id__in=administrator_establishment_ids) - ) + filters = {'address__city__country__code': country_code} + if user.is_establishment_administrator and not user.is_establishment_manager: + filters.update({ + 'id__in': models.Subquery( + UserRole.objects.filter(user=user, role__site__country__code=country_code) + .distinct('user', 'establishment') + .values_list('establishment', flat=True) + ) + }) + return self.filter(**filters) + return self def with_contacts(self): return self.prefetch_related('emails', 'phones') diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index e1d97b91..beabf801 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -3,7 +3,7 @@ from django.db.models.query_utils import Q from django.http import Http404 from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, status, permissions +from rest_framework import generics, status from rest_framework.response import Response from account.models import User @@ -13,8 +13,7 @@ from timetable.models import Timetable from timetable.serialziers import ScheduleCreateSerializer, ScheduleRUDSerializer from utils.methods import get_permission_classes from utils.permissions import ( - IsEstablishmentManager, IsEstablishmentAdministrator, IsReviewManager, -) + IsEstablishmentManager, IsEstablishmentAdministrator, ) from utils.views import CreateDestroyGalleryViewMixin @@ -40,7 +39,7 @@ class EstablishmentMixinViews: queryset = models.Establishment.objects.with_base_related if hasattr(self, 'request') and \ (hasattr(self.request, 'user') and hasattr(self.request, 'country_code')): - return queryset().available_establishments(self.request.user) + return queryset().available_establishments(self.request.user, self.request.country_code) return queryset().none() diff --git a/apps/news/models.py b/apps/news/models.py index 758269b8..dc7f8898 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,15 +1,16 @@ """News app models.""" import uuid +from datetime import datetime import elasticsearch_dsl from django.conf import settings from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import HStoreField +from django.contrib.postgres.search import TrigramSimilarity from django.db import models from django.db.models import Case, When, Q, F from django.db.models.functions import Cast -from django.contrib.postgres.search import TrigramSimilarity from django.urls.exceptions import NoReverseMatch from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -21,7 +22,6 @@ from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, Has ProjectBaseMixin, GalleryMixin, IntermediateGalleryModelMixin, FavoritesMixin, TypeDefaultImageMixin) from utils.querysets import TranslationQuerysetMixin -from datetime import datetime class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): @@ -244,6 +244,10 @@ class NewsQuerySet(TranslationQuerysetMixin): 'subtitle_similarity')) ).filter(relevance__gte=0.3).order_by('-relevance') + def available_news(self, user, country_code: str): + """Return QuerySet with news that user has an access.""" + return self.filter(site__country__code=country_code) if not user.is_staff else self + class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin): diff --git a/apps/news/views.py b/apps/news/views.py index 665eb60d..ff6b165e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -129,11 +129,18 @@ class NewsBackOfficeMixinView: permission_classes = get_permission_classes(IsContentPageManager, IsContentPageManager) def get_queryset(self): - """Override get_queryset method.""" - qs = models.News.objects.with_base_related() \ - .annotate_in_favorites(self.request.user) \ - .order_by('-is_highlighted', '-created') - return qs + """Overridden get_queryset method.""" + queryset = models.News.objects + if hasattr(self, 'request') and \ + (hasattr(self.request, 'user') and hasattr(self.request, 'country_code')): + user = self.request.user + return ( + queryset.with_base_related() + .annotate_in_favorites(user) + .available_news(user, self.request.country_code) + .order_by('-is_highlighted', '-created') + ) + return queryset.none() class NewsBackOfficeLCView(NewsBackOfficeMixinView, @@ -165,8 +172,6 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, self.request.query_params['ordering'] = self.request.query_params['ordering']\ .replace('publication_datetime', 'publication_date,publication_time') self.request.GET._mutable = False - if self.request.country_code: - qs = qs.by_country_code(self.request.country_code) return qs diff --git a/apps/partner/views/back.py b/apps/partner/views/back.py index 80d70d62..c130ed4a 100644 --- a/apps/partner/views/back.py +++ b/apps/partner/views/back.py @@ -17,24 +17,24 @@ class PartnerLstView(generics.ListCreateAPIView): queryset = Partner.objects.with_base_related() serializer_class = serializers.BackPartnerSerializer pagination_class = None + filter_class = filters.PartnerFilterSet permission_classes = get_permission_classes( IsEstablishmentManager, IsEstablishmentAdministrator ) - filter_class = filters.PartnerFilterSet class EstablishmentPartners(generics.ListAPIView): queryset = PartnerToEstablishment.objects.prefetch_related('partner', 'partner__country') serializer_class = serializers.PartnersForEstablishmentSerializer pagination_class = None + filter_backends = (OrderingFilter, DjangoFilterBackend) + ordering_fields = '__all__' + ordering = '-partner_bind_date' permission_classes = get_permission_classes( IsEstablishmentManager, IsEstablishmentAdministrator ) - filter_backends = (OrderingFilter, DjangoFilterBackend) - ordering_fields = '__all__' - ordering = '-partner_bind_date' def get_queryset(self): return super().get_queryset().filter(establishment=self.kwargs['establishment_id']) diff --git a/apps/product/models.py b/apps/product/models.py index 068350e4..851bc138 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -227,27 +227,22 @@ class ProductQuerySet(models.QuerySet): .distinct(*similarity_rules['distinction'], 'id') - def available_products(self, user): - """Return QuerySet with establishment that user has an access.""" + def available_products(self, user, country_code: str): + """Return QuerySet with products that user has an access.""" from account.models import UserRole - administrator_establishment_ids = [] - filters = {} - - # put in array administrated establishment ids - if user.is_establishment_administrator: - administrator_establishment_ids.extend( - UserRole.objects.filter(user=user) - .distinct('user', 'establishment') - .values_list('establishment', flat=True) - ) - # check if user is_staff if not user.is_staff: - filters.update({'establishment__address__city__country__code__in': user.administrated_country_codes}) - - return self.filter(**filters).union( - self.filter(establishment__id__in=administrator_establishment_ids) - ) + filters = {'establishment__address__city__country__code': country_code} + if user.is_establishment_administrator and not user.is_establishment_manager: + filters.update({ + 'establishment__id__in': models.Subquery( + UserRole.objects.filter(user=user, role__site__country__code=country_code) + .distinct('user', 'establishment') + .values_list('establishment', flat=True) + ) + }) + return self.filter(**filters) + return self class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 639f6bf0..17902917 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -28,7 +28,7 @@ class ProductBackOfficeMixinView(ProductBaseView): ) if hasattr(self, 'request') and \ (hasattr(self.request, 'user') and hasattr(self.request, 'country_code')): - return queryset.available_products(self.request.user) + return queryset.available_products(self.request.user, self.request.country_code) return queryset.none() diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index af6a0806..7fe145f5 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -100,7 +100,7 @@ class IsContentPageManager(IsApprovedUser): role=Role.CONTENT_PAGE_MANAGER, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') has_permission = True if user_role.exists() else has_permission @@ -125,7 +125,7 @@ class IsCountryAdmin(IsApprovedUser): role=Role.COUNTRY_ADMIN, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True) ).only('id') has_permission = True if user_role.exists() else has_permission @@ -150,7 +150,7 @@ class IsModerator(IsApprovedUser): role=Role.MODERATOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') has_permission = True if user_role.exists() else has_permission @@ -173,7 +173,7 @@ class IsEstablishmentManager(IsApprovedUser): role=Role.ESTABLISHMENT_MANAGER, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=user, role__id__in=role.values_list('id', flat=True), ).only('id') has_permission = True if user_role.exists() else has_permission @@ -195,7 +195,7 @@ class IsEstablishmentAdministrator(IsApprovedUser): role=Role.ESTABLISHMENT_ADMINISTRATOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') has_permission = True if user_role.exists() else has_permission @@ -218,7 +218,7 @@ class IsEstablishmentAdministrator(IsApprovedUser): if isinstance(obj, Product): filters.update({'establishment__products__id': obj.id}) - user_role = UserRole.objects.filter(**filters) + user_role = UserRole.objects.validated().filter(**filters) has_object_permission = True if user_role.exists() else has_object_permission rules.append(has_object_permission) return all(rules) @@ -243,7 +243,7 @@ class IsReviewManager(IsApprovedUser): role=Role.REVIEW_MANAGER, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): @@ -276,7 +276,7 @@ class IsRestaurantInspector(IsApprovedUser): role=Role.RESTAURANT_INSPECTOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): @@ -309,7 +309,7 @@ class IsArtisanInspector(IsApprovedUser): role=Role.ARTISAN_INSPECTOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): @@ -342,7 +342,7 @@ class IsWineryWineInspector(IsApprovedUser): role=Role.WINERY_WINE_INSPECTOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): @@ -375,7 +375,7 @@ class IsProducerFoodInspector(IsApprovedUser): role=Role.PRODUCER_FOOD_INSPECTOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): @@ -408,7 +408,7 @@ class IsDistilleryLiquorInspector(IsApprovedUser): role=Role.DISTILLERY_LIQUOR_INSPECTOR, site__country__code=request.country_code, ).only('id') if role.exists(): - user_role = UserRole.objects.filter( + user_role = UserRole.objects.validated().filter( user=request.user, role__id__in=role.values_list('id', flat=True), ).only('id') if user_role.exists(): From 21b0786dafbf1322fcdc150e45aa2ec2e3ee5ba6 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 30 Jan 2020 13:11:19 +0300 Subject: [PATCH 6/6] fix city model --- apps/location/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/location/models.py b/apps/location/models.py index 579addd1..8e24967a 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -178,12 +178,12 @@ class City(models.Model, TranslatedFieldsMixin): verbose_name = _('city') def __str__(self): - return self.name_dumped + return f'{self.id}: {self.code}' @property def name_dumped(self): """Used for indexing as string""" - return f'{self.id}: {dumps(self.name)}' + return dumps(self.name) @property def image_object(self):