How do I set up add, delete, update and view permissions for a particular model instance?
I am a Python and Django newbie working on a Django app and I have a requirement in which I have to manage permissions for model instance Foo in my Django. There are other model instances available but the permissions only apply for foo.
I have overridden has_add_permission
, has_delete_permission
in my FooAdmin class but nothing seems to be wo开发者_开发问答rking.
Requirement:
- Add Foo object: The user must be a super admin
- Delete Foo object: The user must be a super admin
- Edit existing Foo object: The user must belong to ldap group 'foo-edit'. If the user belongs to this group, then he/she can only edit one field (building) (Not Yet Implemented)
- View Foo: If the user belongs to ldap group 'foo-access', then the user has only view rights. (To Be Investigated)
- If any other model instance is to be added, updated, deleted or modified, then rules 1 to 4 don't apply.
My implementation:
class FooAdmin(ModelAdmin):
...
...
...
def has_add_permission(self, request):
permissions = self.opts.app_label + '.' + self.opts.get_add_permission()
# Other objects are fine.
user = request.user
if (user.has_perm('survey.add_foo') and user.is_authenticated
and user.is_superuser):
return True
return False
def has_delete_permission(self, request, obj=None):
permissions = self.opts.app_label + '.' + self.opts.get_delete_permission()
user = request.user
if (user.has_perm('survey.delete_foo') and user.is_authenticated
and user.is_superuser):
return True
return False
Issue:
I can create another instance of type Bar but can not delete what I have just created. This is not a problem with instances of type FooBar. BarAdmin implementation does not have any code that manages permissions as is the case of FooBarAdmin implementation.
Is there a way to resolve this issue and make Bar and FooBar deletable again?
How do I implement required # 3 and 4? I can override has_change_permission method to determine if the person has change permissions. How do I give the user editing rights for only one field.
Django does not seem to support view only permissions. I can create has_view_permission(self, request) and get_model_perms(self, request) and other places? But I am not sure how to implement this though. I have been looking through the Django source code but can't think of anything.
After a bit of Googling and random hacks, I have able to come up with a solution. I don't know what the protocol is to answer to your question.
Create a Middle ware implementation (threadlocals.py) that stores the user information in a thread local object.
try: from threading import local except ImportError: from django.utils._threading_local import local _thread_locals = local() def get_current_user(): return getattr(_thread_locals, 'user', None) class ThreadLocals(object): """Middleware that gets various objects from the request object and saves them in thread local storage.""" def process_request(self, request): _thread_locals.user = getattr(request, 'user', None)
Create an authorization utility module that will check for permissions.
import ldap from django.contrib.auth.models import User from myapp.middleware import threadlocals def can_edit(): user_obj = threadlocals.get_current_user() if user_obj and (is_superadmin(user_obj.username) or \ is_part_of_group(user_obj.username, 'foo-edit')): return True return False def can_edit_selectively(): user = threadlocals.get_current_user() if (user and not user.is_superuser and is_part_of_group(user.username, 'foo-edit')): return True return False def can_view_only(): user_obj = threadlocals.get_current_user() if (user_obj and not user_obj.is_superuser and is_part_of_group(user_obj.username, 'foo-access') and not is_part_of_group(user_obj.username, 'foo-edit')): return True return False def has_add_foo_permission(): user = threadlocals.get_current_user() if (user and user.has_perm('survey.add_foo') and user.is_authenticated and user.is_superuser): return True return False def has_delete_foo_permission(): user = threadlocals.get_current_user() if (user and user.has_perm('survey.delete_foo') and user.is_authenticated and user.is_superuser): return True return False def is_superadmin(logged_in_user): admin = False try: user = User.objects.get(username=logged_in_user) if user.is_superuser: admin = True except User.DoesNotExist: pass return admin def is_part_of_group(user, group): try: user_obj = User.objects.get(username=user) if user_obj.groups.filter(name=group): return True except User.DoesNotExist: pass conn = ldap.open("ldap.corp.mycompany.com") conn.simple_bind_s('', '') base = "ou=Groups,dc=mycompany,dc=com" scope = ldap.SCOPE_SUBTREE filterstr = "cn=%s" % group retrieve_attributes = None try: ldap_result_id = conn.search_s(base, scope, filterstr, retrieve_attributes) if ldap_result_id: results = ldap_result_id[0][1] return user in results['memberUid'] except ldap.LDAPError: pass return False
Set up Admin. In the actions, the permissions are evaluated. Also if the user does not have delete permissions, then the action 'delete Foo' is not available.
class FooAdmin(ModelAdmin): ... def get_actions(self, request): actions = super(FooAdmin, self).get_actions(request) delete_permission = self.has_delete_permission(request) if actions and not delete_permission: del actions['delete_selected'] return actions def has_add_permission(self, request): return auth_utils.has_add_foo_permission() def has_delete_permission(self, request, obj=None): return auth_utils.has_delete_foo_permission() ....
Make changes to init function in the implementation of ModelForm
class FooForm(ModelForm): def __init__(self, *args, **kwargs): self.can_view_only = (auth_utils.can_view_only() and not auth_utils.can_edit_selectively()) self.can_edit_selectively = (auth_utils.can_edit_selectively() or auth_utils.can_view_only()) if self.can_edit_selectively: ... Set the widget attributes to read only. # If the user has view only rights, then disable edit rights for building id. if self.can_view_only: self.fields['buildingid'].widget = forms.widgets.TextInput() self.fields['buildingid'].widget.attrs['readonly'] = True elif (auth_utils.can_edit() or auth_utils.can_edit_selectively()): self.fields['buildingid'].widget=forms.Select(choices= utils.get_table_values('building'))
I also had to disable the actions that modified the submit line.
def submit_edit_selected_row(context): opts = context['opts'] change = context['change'] is_popup = context['is_popup'] save_as = context['save_as'] if opts.object_name.lower() != 'foo': has_delete = (not is_popup and context['has_delete_permission'] and (change or context['show_delete'])) has_save_as_new = not is_popup and change and save_as has_save_and_add_another = (context['has_add_permission'] and not is_popup and (not save_as or context['add'])) has_save_and_continue = (context['has_add_permission'] and not is_popup and (not save_as or context['add'])) has_save = True else: has_delete = auth_utils.has_delete_pop_permission() has_save_as_new = auth_utils.has_add_pop_permission() has_save_and_add_another = auth_utils.has_add_pop_permission() has_save_and_continue = (auth_utils.can_edit() or auth_utils.can_edit_selectively()) has_save = auth_utils.can_edit() or auth_utils.can_edit_selectively() return { 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), 'show_delete_link': has_delete, 'show_save_as_as_new': has_save_as_new, 'show_save_and_add_another': has_save_and_add_another, 'show_save_and_continue': has_save_and_continue, 'is_popup': is_popup, 'show_save': has_save } register.inclusion_tag('admin/submit_line.html', takes_context=True) (submit_edit_selected_row)
I had to modify extend the admin template. The rest remains the same.
{% if save_on_top %} {% submit_edit_selected_row %} {% endif}
精彩评论