Is there any way to set up a permissions system where permissions relate User objects together, i.e. where one User may have the permission to read another User's info (but not necessarily the other way around)? I've read the documentation but it's sparse and from what I can tell permissions are not capable of handling dynamic use-cases.
Basically, I want a permission system that determines what two+ model objects can "do" to each other.
Bonus points if you have any input on how to integrate this into Django Rest Framework.
One approach is to override the BasePermission class to create your custom permission. You can specify the access permission using an extra field in the User model.
class UserPermission(BasePermission):
def has_permission(self, request, view):
is_allowed_user = False
try:
permission = Permission.objects.get(user=request.user.id, user1=request.GET.get('user'))
if permission.is_read_enabled:
is_allowed_user = True
else
is_allowed_user = False
except Permission.DoesNotExist as e:
is_allowed_user = False
return is_allowed_user
Create a model to specify the Permissions
class Permission(models.Model):
user = models.ForeignKey(User,...)
user1 = models.ForeignKey(User,...)
is_read_enabled = models.BooleanField(defualt=False)
Add this permission to view as needed.
Related
I want to make use of django-guardian's object permissions and grant specific rights for specific users to one or more Django users.
I have tried to add some permissions to my Process class like this:
class TestProcess(Process):
title = models.CharField(max_length=64)
something = models.ForeignKey(ForInheritage, null=True, on_delete=models.CASCADE)
no_approval = models.BooleanField(default=False)
approved = models.BooleanField(default=False)
def something_is_approved(self):
try:
return self.something.approved
except:
return None
class Meta:
permissions = (
('view_process', 'View Process'),
)
Unfortunately this causes viewflow to immediately throw an error after starting runserver:
File "/home/me/.virtualenvs/viewflow3/lib/python3.4/site-packages/viewflow/mixins.py", line 253, in ready
self.flow_class.process_class._meta.permissions.append(
AttributeError: 'tuple' object has no attribute 'append'
My initial plan was to subclass Start and View flow classes to change how the Permission function, that is inherited from the PermissionMixin, works. But this seems to be more work than just this, too.
django-guardian is already mentioned in one of the cookbook sections here but currently leads to a 404 page.
What would be the recommended/cleanest way to use guardian permissions on Processes and Tasks?
Your specific problem happens b/c you specify permissions like a tuple, try list instead
class Meta:
permissions = [
('view_process', 'View Process'),
]
Viewflow already adds the 'view' and 'manage' permissions so you can reuse them.
But further restriction per-process view permissions on the object level with django-guardian is not very practical. On each new process creation, in a start view, you will have to grant view permission to all process participant. That leads to hudge permission table grows and slow down the permissions lookup.
The reasonable use case for the object-level permission could be something like to restrict user access to a task based on a user department, for example.
deliver = flow.View(
views.deliver
).Permission(
'parcel.land_on_planet',
obj=lambda process: process.department
).Next(this.report)
Object level permissions
Example from http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
My need: Queryset of all objects a user can edit
I want to have a django-orm queryset which contains all objects which a given user can edit.
I guess I could solve this by creating a complex django-orm filter (with OR and distinct)
Not DRY
But that's not DRY. That's not DRY because I need to code the stuff twice. One time in has_object_permission() and one time in the django-orm filter.
Question
How to solve my need (queryset of all objects a user can edit) without duplication the permission checking?
If you want that hard to keep things DRY, you'll have to load the entire database entries and apply permission check to every one.
I doubt that's what you really want.
Sometime you can't keep things DRY.
It's the same when you display data to a user. You'll usually apply basic permissions implicitly when performing the query and then ensure the full permissions are valid or not.
I have a model, Package:
class Package(models.Model):
VIP = models.BooleanField()
name = models.CharField(max_length=200)
contents = models.CharField(max_length=200)
owner = # a string, user name goes here
For any particular instance of Package (that is, each row of the database), I want that only the user whose username matches owner can modify this instance, via the admin interface. How can I do this?
Override the has_change_permission for your model admin.
class PackageAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj):
if request.user.is_super_user():
# allow superusers to edit all packages
return True
if obj is None:
# The docs say that the method should handle obj=None
# Don't allow user to edit packages in general
return False
# Let the user edit the package if they are the owner.
return obj.owner == request.user
The code above assumes that owner is a foreign key, which I recommend. If you really want to store the username as a string, then you would change the last line to:
return obj.owner == request.user.username
You could add an extra check to make sure that the user has the 'change' permission for the Package model as well (see the docs for more info).
Note there is a has_delete_permission model admin method, which you might want to override as well.
I have the following models (simplified example):
class Book(models.Model):
users = models.ManyToManyField(User, through=Permission)
class Permission(models.Model):
user = models.ForeignKey(User)
role = models.ForeignKey(Group)
active = models.BooleanField()
book = models.ForeignKey(Book)
What I need is that for a Book instance there cannot be more than one User of with the same Role and Active.
So this is allowed:
Alice, Admin, False (not active), BookA
Dick, Admin, True (active), BookA
Chris, Editor, False (not active), BookA
Matt, Editor, False (not active), BookA
But this is not allowed:
Alice, Admin, True (active), BookA
Dick, Admin, True (active), BookA
Now this cannot be done with unique_together, because it only counts when active is True. I've tried to write a custom clean method (like how I have done here). But it seems that when you save a Book and it runs the validation on each Permission, the already validated Permission instances aren't saved until they've all been validated. This makes sense, because you don't want them to be saved in case something doesn't validate.
Could anyone tell me if there is a way to perform the validation described above?
P.S. I could imagine using the savepoint feature (http://docs.djangoproject.com/en/1.2/topics/db/transactions/), but I only really want to consider that as a last resort.
Maybe you can do something like: unique_together = [[book, role, active=1],] ?
Edit Sep. 23, 2010 14:00 Response to Manoj Govindan:
My admin.py (simplified version for clarity):
class BookAdmin(admin.ModelAdmin):
inlines = (PermissionInline,)
class PermissionInline(admin.TabularInline):
model = Permission
In the shell your validation would work. Because you first have to create the book instance and then you create all the Permission instances one by one: http://docs.djangoproject.com/en/1.2/topics/db/models/#extra-fields-on-many-to-many-relationships. So in the shell if you add 2 Permission instances the 1st Permission instance has been saved by the time 2nd is being validated, and so the validation works.
However when you use the Admin interface and you add all the book.users instances at the same time via the book users inline, I believe it does all the validation on all the book.users instances first, before it saves them.
When I tried it, the validation didn't work, it just succeeded without an error when there should have been a ValidationError.
You can use signals to prevent the saving of data that is invalid: I'm still working on a nice solution on how to get the validation to bubble up in a nice way in the admin.
#receiver(models.signals.m2m_changed, sender=Book.users.through)
def prevent_duplicate_active_user(sender, instance, action, reverse, model, pk_set, **kwargs):
if action != "pre_add":
return
if reverse:
# Editing the Permission, not the Book.
pass
else:
# At this point, look for already saved Users with the book/active.
if instance.permissions.filter(active=True).exists():
raise forms.ValidationError(...)
Note that this is not a complete solution, but is a pointer how I am doing something similar.
One way to do this is to use the newfangled model validation. Specifically, you can add a custom validate_unique method to Permission models to achieve this effect. For e.g.
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
class Permission(models.Model):
...
def validate_unique(self, exclude = None):
options = dict(book = self.book, role = self.role, active = True)
if Permission.objects.filter(**options).count() != 0:
template = """There cannot be more than one User of with the
same Role and Active (book: {0})"""
message = template.format(self.book)
raise ValidationError({NON_FIELD_ERRORS: [message]})
I did some rudimentary testing using one of my projects' Admin app and it seemed to work.
Now this cannot be done with unique_together, because it only counts when active is True.
Simplest way, imo, is to change type of active from BooleanField to CharField. Store 'Y' and 'N' in active.
That way you can use built-in unique_together = [[book, role, active],]
I was wondering if the django admin page can be used for external users.
Let's say that I have these models:
class Publisher(models.Model):
admin_user = models.ForeignKey(Admin.User)
..
class Publication(models.Model):
publisher = models.ForeignKey(Publisher)
..
I'm not exactly sure what admin_user would be -- perhaps it could be the email of an admin user?
Anyways. Is there a way allow an admin user to only add/edit/delete Publications whose publisher is associated with that admin user?
-Thanks!
-Chris
If you need finer-grained permissions in your own applications, it should be noted that Django's administrative application supports this, via the following methods which can be overridden on subclasses of ModelAdmin. Note that all of these methods receive the current HttpRequest object as an argument, allowing for customization based on the specific authenticated user:
queryset(self, request): Should return a QuerySet for use in the admin's list of objects for a model. Objects not present in this QuerySet will not be shown.
has_add_permission(self, request): Should return True if adding an object is permitted, False otherwise.
has_change_permission(self, request, obj=None): Should return True if editing obj is permitted, False otherwise. If obj is None, should return True or False to indicate whether editing of objects of this type is permitted in general (e.g., if False will be interpreted as meaning that the current user is not permitted to edit any object of this type).
has_delete_permission(self, request, obj=None): Should return True if deleting obj is permitted, False otherwise. If obj is None, should return True or False to indicate whether deleting objects of this type is permitted in general (e.g., if False will be interpreted as meaning that the current user is not permitted to delete any object of this type).
[django.com]
I see chris's answer was useful at the time question was asked.
But now it's almost 2016 and I guess it gets more easier to enable restricted access of Django Admin panel to end user.
Django authentication system provides:
Groups: A generic way of applying labels and permissions to more than one user.
Where one can add specific permissions and apply that group to user via admin panel or with writing codes.
After adding user to those specific groups, Admin need to enable is_staff flag for those users.
User will be able access restricted registered models in admin.
I hope this helps.
django admin can, to a certain extent, be restricted. For a given user, first, they must have admin rights in order to log into the admin site. Anyone with this flag set can view all admin pages. If you want to restrict viewing, you're out of luck, because that just isn't implemented. From there, each user has a host of permissions, for create, update and delete, for each model in the admin site. The most convenient way to handle this is to create groups, and then assign permissions to the groups.