I’m trying to teach myself Django and the Django Rest Framework, but I'm having a hard time understanding how object ownership is defined.
I want to apply the custom “IsOwnerOrReadOnly” permissions given in the DRF documentation with the default User model. My goal: Users will be able to PUT/PATCH/DELETE the information in their own account, but won't be able to change any other users’ information.
My user serializer:
class UserSerializer(serializers.ModelSerializer):
rides = serializers.PrimaryKeyRelatedField(many = True, queryset = Ride.objects.all())
class Meta:
model = User
fields = [
'pk',
'username',
'first_name',
'last_name',
'email',
'password',
'rides'
]
write_only_fields = ['password']
read_only_fields = ['pk', 'username']
The IsOwnerOrReadOnly code:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
The problem is with the line return obj.owner == request.user. It always returns AttributeError: 'User' object has no attribute 'owner’. Why is the "owner" of a user not the user itself? How can I say “owner = this object” so that my permissions classes work?
Follow-up question: I would like to eventually extend the default User model to add more fields. Would my permissions still work if I made a new User model by inheriting from AbstractBaseUser? (I just started learning Django, so I'm not entirely sure what the process for creating a new User model would be. But I thought it might be good to mention in case that would change how I define permissions.)
You're getting the error because there's no such abstract concept as owner for all models. You will have to define the relationship between the object and the user in the model and apply that in the permission for each model.
In this case, you want to be sure that the user is trying to modify his own user object so you can just do this:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
So in essence, this will only work for the User model and you will have to extend it to work for other models based on the relationship of the model with the user.
You may want to define different permission classes for each resource than trying to put all the logic in the same permission class.
As for your question on extending the user model. This page in the docs explains the different methods of extending the existing user model which is basically by either extending from the AbstractUser or AbstractbaseUser. The Django permissions framework will still work as it is implemenmted against those base classes.
Bear in mind however that DRF permission classes are different from Django permissions. You can Django permissions to implement the logic inside the permission classes but they can be implemented without the permissions.
EDIT FROM COMMENT
If you have a Ride model with owner, you can do this to combine them.
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
if isinstance(obj, Ride):
return obj.owner == request.user
return obj == request.user
Ownership
Say there is a Model A. To define ownership, you need to create a field in Model A that will point to User model. You can do this easily. Make sure you use django.contrib.auth.get_user_model for same.
Getting a list of owned objects from API
When it comes to getting a list of objects owned by the authenticated user (request.user), you are looking at creating a filter. To do same there are two ways:
Create a filter class and use in it generic views. This is more extensible. I have done same in drfaddons.filters.IsOwnerFilterBackend. Here's the source code.
Override def filter_queryset(self, queryset) in each API Class.
Ensuring in Retrieve/Update/Delete
This time you want to make sure that permission check is being applied. Here, you will want to implement has_object_permission and has_permission from permissions.BasePermission which you're doing.
In general, you'll want to use both filtering and permission check.
Implementing across the project
To implement this across the whole project, you need to set your filter and permission as default one in settings.py's REST_FRAMEWORK configuration. You'll also need to ensure that an owner field (with same name preferably) is present in each model. (Check Abstract model).
I have done same in all my project and hence, I have created a package of the same. Check DRF Addons. You can also install it via pip: pip install drfaddons. It will do all of the above said task.
Related
By default, Django adds 4 permissions for each registered model:
add_modelname
change_modelname
delete_modelname
view_modelname
When a user has one of these permissions it applies to all instances of that model. That's not what I want, I want per instance permission.
When the creator (created_by in database, or some related table of users with access) has delete_modelname permission he can delete only that instance, and not some other created by another user.
I've been looking at other answers and none of them mention the auto-created CRUD permissions that are created for all models, it's mostly new tables or third-party libraries.
What about giving creators of models these permissions (add, change, delete, view) which normally would give them access to all other models, but also having a custom auth backend which checks the object:
from django.contrib.auth.backends import BaseBackend
class ObjectPermissionBackend(BaseBackend):
def has_perm(self, user_obj, perm, obj=None):
if not obj:
return False
return obj.created_by == user_obj.pk # or something like this
And then using it:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.ObjectPermissionBackend']
Is there some downside to this?
I am trying to write a permission class for django rest api view.
this is my models
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
name = models.CharField(max_length=50)
auth_user = models.OneToOneField(User, on_delete=models.CASCADE)
class Group(models.Model):
admin = models.ManyToManyField(Profile, related_name='g_admin')
members = models.ManyToManyField(Profile, related_name='g_mess')
I want only group admin can perform an action in particular pages, for this i am writing a permission class:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def has_permission(self, request, view):
# return bool() do something here
I am going through trouble to write the permission class to check if group admin or not. i am facing issue coz, this is many to many field.
can anyone help me to write this?
You can get the User from request.user. I don't know how you can get the Group from request (it may be in the request payload, it depends on your design) but lets assume that you pass the Group to the permission class, then it would be something like that:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def __init__(self, group):
super().__init__()
self.group = group
def has_permission(self, request, view):
return self.group.profile_set.filter(auth_user=request.user).any()
Note: If you can get the group from request so you don't need to pass group to the IsGroupAdmin class and this would be much easier. But if you cant do that and you'r going to use just the above permission class, you'r going to need the partial function. so you need to import it:
from functools import partial
And then in the views, initiate permission_classes like that:
permission_classes = [partial(IsGroupAdmin, group)]
However, I suggest you to use the easier way, cause I think the group must be in request. Lets say that group is in the request payload, then you must implement IsGroupAdmin as:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def has_permission(self, request, view):
group = request.data.get('group', '')
return group.profile_set.filter(auth_user=request.user).any()
And use it just like a normal permission. (no need to use partial function)
your data structure is wrong
admin and members should be m2m with user not with Profile.
for permission, you have to make a query like
Group.objects.filter(admin__auth_user__id=request.id) the true else return flase.
use through table it will reduce your work and make some sort of flag which gives you an idea that request user is the admin or not(in User model use signal) so every time you don't have to fire a query in the database.
I am working on a Django Project, where one model (lets say Document) has the following field:
#In models.py
class Document (models.Model):
choice = (('Yes','Yes'), ('No','No'))
authorized = models.CharField (max_length=3, choices=choice, default='No')
Now, as a normal user creates a Document object, the authorized field is no. However, the superior needs to authorize the same before it is actually approved. Now, is there a permission system in django where one user can create an object but not authorize, whereas some other user - who has the permission - can authorize? If not, is the only way to do it is to create a custom field in user model and check it every time?
First of all, why you need to store possible values in CharField and not in BooleanField? I think you should consider changing to BooleanField.
You can do that by providing custom ModelAmin class in admin.py:
from django.contrib import admin
from .models import Document
#admin.register(Document)
class DocumentModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not request.user.is_superuser:
self.exclude = ['authorized']
return super(DocumentModelAdmin, self).get_form(request, obj, **kwargs)
So now on admin page of Document if it is not a superuser, user won't see authorized field. You can change that code for checking if it should be specific user, or has some permissions, or if user belongs to some Group and etc.
UPDATE
If you want it to be in general views, you can just pass different forms to users, depend on their roles|permissions|groups(i don't know how your so called senior is different from rest of the users). So the answer would be: create two forms, then pass on of them in template based on your request.user attributes.
Django has awesome auth system. I couldn't understand you scenario.
But you could try something like this below
By default every Model object comes with three Permission object like (add_document, change_document and delete_document in your case).
If you want some custom permission you can add it in model Meta class like this:
You can add these permission to User object or Group object.
models.py
class Document (models.Model):
######
class Meta:
permissions = (("Can see document dashbaord", "see_document" ),)
and run python manage.py migrate to create new Permission object with codename as "see_document".
You can implement permissions in request handled by view like this:
view.py
from django.contrib.auth.mixins import PermissionRequiredMixin, permission_required
# For function based view
#pemission_required('document.see_document')
def someview(request):
######
pass
# For class based views
class SomeView(PermissionRequiredMixin, BaseView):
permission_required = 'document.see_document'
This could redirect any user with out the permssion to permission denied page. For more go through this https://docs.djangoproject.com/en/1.10/topics/auth/
I don't understand this behaviour. Let's say I open a Django shell and type:
from django.contrib.auth.models import User
user = User.objects.create(username="toto", email="titi")
Why does Django let me create this user (with an invalid email) without raising an error?
I have the same "no verification behaviour" creating a user in a POST in my API created with tastypie.
The question is:
As Django does not seem to check this by itself, where am I supposed to perform this kind of verifications sothat I don't have to write them several times (since a user can be created in several ways like website, API, etc.)?
Thanks.
Django doesn't implicitly do any validation if you just call .create() or .save() - you need to explicitly use model validation, or save the object via a
ModelForm. Your example with model validation would look like this:
user = User(username="toto", email="titi")
try:
user.full_clean()
except ValidationError as e:
# handle the error...
pass
user.save()
Or using a ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
f = UserForm(dict(username="toto", email="titi"))
if f.is_valid():
user = f.save()
else:
# handle error, ...
pass
Both model validation and ModelForms invoke the model field's validators, so in the case of the User's email, no additional work is needed for validation. If you need to do custom validation, you can do this in the ModelForm class - it is common to have a forms.py file in the app as a central place for Forms and ModelForms.
The same goes for Tastypie - the default configuration assumes the data submitted is valid. You can override this with the FormValidation class, which uses a Django Form or ModelForm for its validation. A full example would look something like this:
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
validation = FormValidation(form_class=UserForm) # UserForm from above
# more options here....
I'm trying to figure out, what is the best way to manage model access permissions with Django.
I have a table of items which belong to the users created them. All the items a managed via a RESTful API. When using this API I want to limit access to the items created by a given user.
Do I have to create several tables or is it possible to achieve the same with just one table?
If I have to use multiple tables, how do I correlate API requests with a particular table?
Ok, I found a way to do it via both API and admin. This basically resembles Rob's idea.
First of all, every time I create a new user via admin panel, I need to append a user to my items:
class MyAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'user', None) is None:
obj.user = request.user
obj.save()
admin.site.register(MyItem, MyAdmin)
Then when accessing my model, I just filter by user (which is btw a foreign key to django.contrib.auth.models.User):
MyItem.objects.filter(user=request.user)
Finally to make it work with Django REST Framework, I need to add a couple of methods to My custom ModelViewSet:
class MyItemViewSet(viewsets.ModelViewSet):
model = MyItem
serializer_class = MyItemSerializer
def get_queryset(self):
return MyItem.objects.filter(user=self.request.user)
def pre_save(self, obj):
obj.user = self.request.user
I've used documentation and (lots) trial and error to figure this out.
if you want generic object-level permissions then you can use a permission backend like django-guardian, here is examples for integrating with django-restframework
You could have a created_by field on your objects. Then compare the django user to that.
It's a little difficult help further without a concrete example.
Using django-rest-framework and django-rest-auth, I had to perform additional steps as described in the answer by Lucas Weyne to this question.
Here's what he suggested that worked for me.
Make sure to include the authorization token in the request:
curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" <url>
This is key because otherwise the server doesn't know who the user is.
Add this to your viewset definition:
permission_classes = [permissions.IsAuthenticated, ]
This is important if the query is user dependent.