My decorator looks like this
def require_feature(feature_name):
def decorator(view_func):
print(f"process_view - view_func: {view_func}") # Debugging
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
return view_func(request, *args, **kwargs)
_wrapped_view.required_feature = feature_name
return _wrapped_view
return decorator
and the middleware looks like this
class EntitlementMiddleware(MiddlewareMixin):
def __init__(self, get_response) -> None:
self.get_response = get_response
def __call__(self, request) -> Any:
if request.user.is_authenticated:
request.user.features = get_user_features(request.user)
else:
request.user.features = []
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
print(f"process_view - view_func: {view_func}") # Debugging
required_feature = getattr(view_func, "required_feature", None)
if required_feature and not request.user.features.filter(name=required_feature):
raise PermissionDenied("You do not have access to this feature")
return None # if none is returned then view is called normally
def process_response(self, request, response):
return response
and this is how I am using it in my viewset
class MyViewSet(ValidatePkMixin, viewsets.ViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = My.objects.all()
@method_decorator(require_feature("Basic"))
def list(self, request):
pass
the decorator sets the required_feature when server starts. the middleware gets called when I make a /get call but this
required_feature = getattr(view_func, "required_feature", None)
returns None
what am I missing here?
You are check from middleware function(method) dispatch
which will be called from middleware. And you decorate completely other function(method) list
.
in Django, if you work with Generic CBVs or ViewSets request goes through middlewares, and through Class.as_view()
the request-dispatcher will be returned.
For both - for Generic CBVs and for ViewSets it is:
class View:
...
def dispatch(self, request, *args, **kwargs):
return handler # exactly here will be called your cls.list
...
But right now i see, you want reinvent user permissions/permission classes.
The best way for you goal is - to create child class from BasePermission
.
More here: https://www.django-rest-framework.org/api-guide/permissions/
You want to catch all requests on middleware level. For that DRF offer you settings.DEFAULT_PERMISSION_CLASSES
:
#settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'myapp.permissions.IsFeatured',
]
}
This class check "feature" by user for choosen handler. BTW I still don't understand, why you don't use custom user.permissions
.
After you need the permission class:
#myapp/permissions.py
from rest_framework.permissions import BasePermission
class IsFeatured(BasePermission):
def has_permission(self, request, view):
handler = getattr(view, view.action)
feature = getattr(handler, "required_feature", None)
if feature:
return request.user.features.filter(name=feature).exists()
return True
Thats all. It should work as you want, but i dont check it on errors. BTW, this solution repeated already existed solution to work with user.permission