I want to create permissions in Django Rest Framework, based on view + method + user permissions.
Is there a way to achieve this without manually writing each permission, and checking the permissions of the group that the user is in?
Also, another problem I am facing is that permission objects are tied up to a certain model. Since I have views that affect different models, or I want to grant different permissions on the method PUT, depending on what view I accessed (because it affects different fields), I want my permissions to be tied to a certain view, and not to a certain model.
Does anyone know how this can be done?
I am looking for a solution in the sort of:
Create a Permissions object with the following parameters: View_affected, list_of_allowed_methods(GET,POST,etc.)
Create a Group object that has that permission associated
Add a user to the group
Have my default permission class take care of everything.
From what I have now, the step that is giving me problems is step 1. Because I see no way of tying a Permission with a View, and because Permissions ask for a model, and I do not want a model.
Well, the first step could be done easy with DRF. See http://www.django-rest-framework.org/api-guide/permissions#custom-permissions.
You must do something like that:
from functools import partial
from rest_framework import permissions
class MyPermission(permissions.BasePermission):
def __init__(self, allowed_methods):
super().__init__()
self.allowed_methods = allowed_methods
def has_permission(self, request, view):
return request.method in self.allowed_methods
class ExampleView(APIView):
permission_classes = (partial(MyPermission, ['GET', 'HEAD']),)
Custom permission can be created in this way, more info in official documentation( https://www.django-rest-framework.org/api-guide/permissions/):
from rest_framework.permissions import BasePermission
# Custom permission for users with "is_active" = True.
class IsActive(BasePermission):
"""
Allows access only to "is_active" users.
"""
def has_permission(self, request, view):
return request.user and request.user.is_active
# Usage
from rest_framework.views import APIView
from rest_framework.response import Response
from .permissions import IsActive # Path to our custom permission
class ExampleView(APIView):
permission_classes = (IsActive,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
I took this idea and got it to work like so:
class genericPermissionCheck(permissions.BasePermission):
def __init__(self, action, entity):
self.action = action
self.entity = entity
def has_permission(self, request, view):
print self.action
print self.entity
if request.user and request.user.role.access_rights.filter(action=self.action,entity=self.entity):
print 'permission granted'
return True
else:
return False
I used partially in the decorator for the categories action in my viewset class like so:
#list_route(methods=['get'],permission_classes=[partial(genericPermissionCheck,'Fetch','Categories')])
def Categories(self, request):
"access_rights" maps to an array of objects with a pair of actions and object e.g. 'Edit' and 'Blog'
Related
I want to get hobbys according to log in user but I am always getting
TypeError at /backend/api/hobbys/
init() takes 1 positional argument but 2 were given
this is my views.py
class ListCreateHobbyView(GenericAPIView):
queryset = Hobby.objects.all()
serializer_class = HobbySerializer
# Filtering by logged in user
def get(self, request, *args, **kwargs):
queryset = Hobby.objects.filter(user=request.user)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
What can be wrong?
Please do not override the entire .get(…) method. This means that (nearly) all boilerplate code that Django has written is no longer applied. You filter in the .get_queryset(…) method [drf-doc]:
from rest_framework.generics import ListAPIView
class ListCreateHobbyView(ListAPIView):
queryset = Hobby.objects.all()
serializer_class = HobbySerializer
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
user=self.request.user
)
You can also make a custom filter backend: this is a reusable component that you then can use in other views. This thus looks like:
from rest_framework import filters
class UserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(user=request.user)
then you use this as filter backend:
from rest_framework.generics import ListAPIView
class ListCreateHobbyView(ListAPIView):
queryset = Hobby.objects.all()
serializer_class = HobbySerializer
filter_backends = [UserFilterBackend]
The advantage of this approach is that if you later construct extra views that need the same filtering, you can simply "plug" these in.
If the current user role = admin then show all the records in the table. If not, then limit the rows by created user.
I can get the user name if I define a function in the View Class, but need it before the list is constructed. See source code below.
from flask_appbuilder.models.sqla.filters import FilterEqualFunction
from app import appbuilder, db
from app.models import Language
from wtforms import validators, TextField
from flask import g
from flask_appbuilder.security.sqla.models import User
def get_user():
return g.user
class LanguageView(ModelView):
datamodel = SQLAInterface(Language)
list_columns = ["id", "name"]
base_order = ("name", "asc")
page_size = 50
#This is the part that does not work - unable to import app Error: Working outside of application context
#If the user role is admin show all, if not filter only to the specific user
if g.user.roles != "admin":
base_filters = [['created_by', FilterEqualFunction, get_user]]
This is the error I'm getting:
Was unable to import app Error: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
In this case it is better to create two different ModelViews, one for users with base_filters = [['created_by', FilterEqualFunction, get_user]] and the second for admins only without any filtering.
And do not forget to specify correct permissions for both.
In my case, I created new filter FilterStartsWithFunction by coping FilterStartsWith(You can find the source code in Flask-Appbuilder pack easily. ). see the codes
from flask_appbuilder.models.sqla.filters import get_field_setup_query
class FilterStartsWithFunction(BaseFilter):
name = "Filter view with a function"
arg_name = "eqf"
def apply(self, query, func):
query, field = get_field_setup_query(query, self.model, self.column_name)
return query.filter(field.ilike(func() + "%"))
def get_user():
if 'Admin' in [r.name for r in g.user.roles]:
return ''
else:
return g.user.username
...
...
base_filters = [['created_by',FilterStartsWithFunction,get_user]]
To catch all redirection paths, including when the final url was already crawled, I wrote a custom duplicate filter:
import logging
from scrapy.dupefilters import RFPDupeFilter
from seoscraper.items import RedirectionItem
class CustomURLFilter(RFPDupeFilter):
def __init__(self, path=None, debug=False):
super(CustomURLFilter, self).__init__(path, debug)
def request_seen(self, request):
request_seen = super(CustomURLFilter, self).request_seen(request)
if request_seen is True:
item = RedirectionItem()
item['sources'] = [ u for u in request.meta.get('redirect_urls', u'') ]
item['destination'] = request.url
return request_seen
Now, how can I send the RedirectionItem directly to the pipeline?
Is there a way to instantiate the pipeline from the custom filter so that I can send data directly? Or shall I also create a custom scheduler and get the pipeline from there but how?
I am trying to implement a user registration with password hashing.
The problem is that the password is saved raw (as it was typed).
For some reason, I think the create method in the serializer is not called.
Doesn't matter if I comment the method out or not comment it out, and try to register, same result - is saves the user to the database without hashing the password. It means that the code doesn't execute?
Views.py
class UserViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsCreationOrIsAuthenticated,)
Serizliers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'password', )
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
first_name=validated_data['first_name'],
username=validated_data['username'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
I have been struggling with this for a while - can't has the password.
Any ideas?
The create function is within the target class, it must be inside the main class, remove an indentation tab
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'password', )
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
first_name=validated_data['first_name'],
username=validated_data['username'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
Instead of writing your create method in serializers.py, do this work by overriding the perform_create() method in your views.py. To do some extra work on creation of an object, DRF provides this hook. This will make the code more clean and DRY.
As per the DRF docs,
Save and deletion hooks:
The following methods are provided by the mixin classes, and provide
easy overriding of the object save or deletion behavior.
perform_create(self, serializer) - Called by CreateModelMixin when
saving a new object instance.
perform_update(self, serializer) -
Called by UpdateModelMixin when saving an existing object instance.
perform_destroy(self, instance) - Called by DestroyModelMixin when
deleting an object instance.
These hooks are particularly useful for
setting attributes that are implicit in the request, but are not part
of the request data.
You can do that by:
views.py
def perform_create(self, serializer):
user = User(
first_name=serializer.data['first_name'],
username=serializer.data['username'],
last_name=serializer.data['last_name']
)
user.set_password(serializer.data['password'])
user.save()
It drives me crazy... I read tons of posts about how to hash your password when creating a user, but for some reason is just won't work and I can't authenticate.
I am using django 1.8.1 and django-rest-framework 3.1.2
My code:
views.py:
class UserViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated, )
----EDIT----
With this code, the password appears as is in the database and is not hashed, so I can't authenticate.
serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'password' )
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
first_name=validated_data['first_name'],
username=validated_data['username'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
And also - what method does serializer.save() call??
Any idea??? any help would be appreciated!
Serializers don't have a post_save method, not even before v3. You must be confused with the post_save in generic views. The generic view's pre_save and post_save hooks no longer exist, but are replaced with perform_create and perform_update.
You just need to do obj.set_password in user serializer's create method. There's an example in the docs that does exactly what you're looking for.