42432

ModelAdmin thread-safety/caching issues

Question:

Ultimately, my goal is to extend Django's ModelAdmin to provide field-level permissions—that is, given properties of the request object and values of the fields of the object being edited, I would like to control whether or not the fields/inlines are visible to the user. I ultimately accomplished this by adding a can_view_field() method to the ModelAdmin and modifying the built-in get_form() and get_fieldset() methods to remove/exclude fields+inlines that the user does not have permissions (as determined by can_view_field()) to see. If you'd like to see the code, I placed it <a href="http://pastebin.com/pHgizEQL" rel="nofollow">in a pastebin</a>, since it's long and only somewhat relevant.

It works great...almost. I appear to have run into some sort of thread-safety or caching issue, where the state of the ModelAdmin object is being leaked from one request to another in a reproducible manner.

I'll illustrate the problem with a simple example. Suppose that I have a model whose ModelAdmin I have extended with the field-level permissions code. This model has two fields: - public_field, which can be seen/edited by any staff member - secret_field, which can only be seen/edited by superusers

In this case, the can_view_field() method would look like this:

def can_view_field(self, request, obj, field_name): """ Returns boolean indicating whether the user has necessary permissions to view the passed field. """ if obj is None: return request.user.has_perm('%s.%s_%s' % ( self.opts.app_label, action, obj.__class__.__name__.lower() )) else: if field_name == "public_field": return True if field_name == "secret_field" and request.is_superuser: return True return False

Test case 1: with a fresh server restart, if you first view the changelist form as a superuser, you see the form as should happen, with both public_field and secret_field visible. If you log out and view it as a staff member (but not superuser), you only see public_field.

Test case 2: with a fresh server restart, if you log in as a staff member first, you still only see public_field. However, if you then log out and view as a superuser, you do <em>not</em> see secret_field. This is 100% reproducible.

I've done some basic thread-safety diagnostics:

<ol><li>At the end of get_form(), I've printed out the memory address of the ModelForm object. As it should be, it is unique with each request. Therefore, the ModelForm object is not the problem.</li> <li>Immediately before the admin registration, I tried printing the memory address of the ModelAdmin object. In test case 1, it is unique with both requests. However with test case 2, it does not print at all on the second request.</li> </ol>

At this point, I'm clueless. My next point of research will be the admin registration system (which I admittedly know nothing about). The state resets with a server restart, so it seems that the ModelAdmin must be cached? Or is it a thread-safety issue? If I turn it into a factory and return a deepcopy() of the ModelAdmin, would it serve a fresh ModelAdmin with each request? I'm clueless and would appreciate any thoughts. Thanks!

Answer1:

I'm confused about why you think ModelAdmin should be a new instance on each request. The admin objects are instantiated by the admin.site.register(Model) calls in each admin.py, which in turn is called from admin.autodiscover() in urls.py. In other words, this happens on process startup. Given the dynamic multi-process nature of most web serving environments, you may or may not get a new process with any particular request - certainly you won't get one every single time.

Because of this, it's not wise to store or alter state on a global object like ModelAdmin. I haven't looked through your linked code properly, but there was at least one case where you were altering an attribute on self as a result of a method call. Don't do that - you'll need to find some other way of passing dynamic values between methods.

Recommend

  • Actual implementation of private variables in python class [duplicate]
  • Error validating access token: The session is invalid because the user logged out
  • Javascript: Object or Object.prototype?
  • Compose exported value with MEF 2
  • “Display P3” screenshots from iOS Simulator
  • Keycloak: Access token validation end point
  • import data from excel 2003 to dataTable
  • AngularJS and internet explorer 10: Curly braces substitution is not happening
  • Swift where condition to check if a property is implemented
  • Run a code in fortran multiple times with different input parameters
  • Spring Data Neo4j 4returning cached results?
  • How to see size of MySQL internal innodb temporary tables
  • remove user from group Mac Os X (El Capitan)
  • ORA-01843: not a valid month
  • Express: Req.body is undefined after POST req
  • How an included partial insert code into parent's block?
  • window.opener does not work in Excel for Windows
  • grep: matching on literal “+”
  • Newtonsoft inline formatting for subelement while serializing
  • Why is RAM in powers of 2?
  • Visual basic auto imports namespaces
  • closing WCF proxy
  • Express.js : POST data as KEY of a req.body object instead of VALUE of req.body?
  • WPF Listbox commands
  • Can I have a variable number of URI parameters or key-value pairs in Laravel 4?
  • as3-flash: any way to access all the instances placed in different frames from document class?
  • Unable to install Git-core+svn by MacPorts
  • Django simple Captcha “No module named fields” error
  • Does it make sense to call System.gc() and Thread.sleep() when working on Bitmaps?
  • Caching attributes in superclass
  • Could not find rake using whenever rails
  • Join two tables and save into third-sql
  • How to model a transition system with SPIN
  • ORA-29908: missing primary invocation for ancillary operator
  • PHP: When would you need the self:: keyword?
  • Memory offsets in inline assembly
  • Can Visual Studio XAML designer handle font family names with spaces as a resource?
  • Binding checkboxes to object values in AngularJs
  • How can I use `wmic` in a Windows PE script?
  • Converting MP3 duration time