Features: 1) Add support for 'create' action with explicit 'ALLOW' flag in AddressViewSet; 2) Introduce refined permission handling with scoped user actions and public model visibility checks in EvibesPermission.

Fixes: 1) Ensure 'user' is set to authenticated user in AddressSerializer.create method.

Extra: 1) Refactor EvibesPermission for clarity and comprehensive action handling; 2) Add detailed class-level docstring for EvibesPermission; 3) Simplify queryset filtering logic with USER_SCOPED_ACTIONS and explicit permission checks.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-26 15:06:24 +03:00
parent 89f6594751
commit 475b0a0c94
3 changed files with 71 additions and 49 deletions

View file

@ -14,59 +14,77 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
class EvibesPermission(permissions.BasePermission): class EvibesPermission(permissions.BasePermission):
def _get_permission_codename(self, action, view): """
action_permission_map = { Custom permission class for EvibesViewSet endpoints.
"retrieve": "view",
"list": "view",
"create": "add",
"update": "change",
"partial_update": "change",
"destroy": "delete",
}
model_name = view.queryset.model._meta.model_name # Get the model name
permission_type = action_permission_map.get(action)
if permission_type: - 'create' may be explicitly allowed via view.additional['create'] == 'ALLOW'.
return f"core.{permission_type}_{model_name}" - Certain actions are scoped to the request.users own objects.
return None - Standard model perms ('add', 'view', 'change', 'delete') are enforced for all other actions,
including for staff users.
- Publicly visible models allow anonymous list/retrieve.
"""
def has_queryset_permission(self, request, view, queryset): ACTION_PERM_MAP = {
if request.user.is_staff: 'retrieve': 'view',
return queryset 'list': 'view',
if view.action in [ 'create': 'add',
"buy", 'update': 'change',
"current", 'partial_update': 'change',
"add_order_product", 'destroy': 'delete',
"remove_order_product", }
"add_wishlist_product",
"remove_wishlist_product", USER_SCOPED_ACTIONS = {
"bulk_add_wishlist_products", 'buy', 'buy_unregistered', 'current',
"bulk_remove_wishlist_products", 'add_order_product', 'remove_order_product',
"autocomplete", 'add_wishlist_product', 'remove_wishlist_product',
]: 'bulk_add_wishlist_products', 'bulk_remove_wishlist_products',
return queryset.filter(user=request.user) 'autocomplete',
return queryset.filter(is_active=True) }
def has_permission(self, request, view): def has_permission(self, request, view):
action = getattr(view, "action", None) action = getattr(view, 'action', None)
model = view.queryset.model
app_label = model._meta.app_label
model_name = model._meta.model_name
if action in [ if action == 'create' and view.additional.get('create') == 'ALLOW':
"buy",
"buy_unregistered",
"current",
"add_order_product",
"remove_order_product",
"add_wishlist_product",
"remove_wishlist_product",
"bulk_add_wishlist_products",
"bulk_remove_wishlist_products",
"autocomplete",
]:
return True return True
required_permission = self._get_permission_codename(action, view) if action in self.USER_SCOPED_ACTIONS:
if required_permission and request.user.has_perm(required_permission):
return True return True
return view.queryset.model.is_publicly_visible and action in ["retrieve", "list"] perm_prefix = self.ACTION_PERM_MAP.get(action)
if perm_prefix:
codename = f"{perm_prefix}_{model_name}"
if request.user.has_perm(f"{app_label}.{codename}"):
return True
return bool(action in ('list', 'retrieve') and getattr(model, 'is_publicly_visible', False))
def has_queryset_permission(self, request, view, queryset):
"""
Filter the base queryset according to the action and user.
Staff users still require view permissions to see records.
"""
model = view.queryset.model
app_label = model._meta.app_label
model_name = model._meta.model_name
if view.action in self.USER_SCOPED_ACTIONS:
return queryset.filter(user=request.user)
if view.action in ('list', 'retrieve'):
if request.user.has_perm(f"{app_label}.view_{model_name}"):
if request.user.is_staff:
return queryset
return queryset.filter(is_active=True)
return queryset.none()
base = queryset.filter(is_active=True)
if view.action in ('update', 'partial_update'):
if request.user.has_perm(f"{app_label}.change_{model_name}"):
return base
if view.action == 'destroy':
if request.user.has_perm(f"{app_label}.delete_{model_name}"):
return base
return queryset.none()

View file

@ -160,8 +160,11 @@ class AddressCreateSerializer(ModelSerializer): # noqa: F405
class Meta: class Meta:
model = Address model = Address
fields = ["raw_data", "user"] fields = ["raw_data"]
def create(self, validated_data): def create(self, validated_data):
raw = validated_data.pop("raw_data") raw = validated_data.pop("raw_data")
return Address.objects.create(raw_data=raw, **validated_data) user = None
if self.context["request"].user.is_authenticated:
user = self.context["request"].user
return Address.objects.create(raw_data=raw, user=user, **validated_data)

View file

@ -473,6 +473,7 @@ class WishlistViewSet(EvibesViewSet):
class AddressViewSet(EvibesViewSet): class AddressViewSet(EvibesViewSet):
queryset = Address.objects.all() queryset = Address.objects.all()
serializer_class = AddressSerializer serializer_class = AddressSerializer
additional = {"create": "ALLOW"}
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'create': if self.action == 'create':