Q:

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 working.
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.

A:

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}

