Showing posts with label django. Show all posts
Showing posts with label django. Show all posts

Friday, May 04, 2007

inline_related_objects

[Edit, Feb 2008: fixed typo—changed extra_content to extra_context]

Today's neat Django trick: getting the create_update generic views for a model to display entries from a subsidiary model in-line—like the admin interface does when you add edit_inline=models.TABULAR.

The (oldforms) Form object that gets generated by the create_update view already has most of the required gubbins inside it; we just need a few extra steps to get at it:

  1. In urls.py, set an extra_context argument for the view to be a dictionary that contains inline_related_objects:
     {'inline_related_objects': model._meta.get_followed_related_objects(None) } 
  2. In the template, include a loop to pull in all of the related objects:
     {% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %} 
    within the <form> tag.
  3. Since the edit_inline template tag is not a built-in Django tag, the template also needs to load up the admin_modify extension:
     {% load admin_modify %} 
  4. Make local copies of the relevant admin templates (such as admin/edit_inline_tabular.html and widget/foreign.html) and tweak appropriately.

Of course, I didn't figure this out until after I'd spent a couple of hours dismantling the admin interface code with a view to stealing the relevant bits. Roll on the Django book.

Wednesday, December 13, 2006

Django Su

It's always a sign of good design when adding a new feature turns out to be easy.

In the Django authentication system, I wanted a way for an administrator to view the site as if they were a particular user; effectively an equivalent to su in UNIX-land.

The first easy step was to invent a URL to correspond to this action, which gets encoded in urls.py:

  (r'^su/(?P<username>.*)/$', 'qlockweb.accounts.views.su', {'redirect_url': '/qlockdata/'}),   

That done, the second and final step is to write some view code.

  @user_passes_test(lambda u: u.is_staff)
  def su(request, username, redirect_url='/'):
      su_user = get_object_or_404(User, username=username)
      if su_user.is_active:
          request.session[SESSION_KEY] = su_user.id
      return HttpResponseRedirect(redirect_url)

Seven lines of code and we're done (modulo a bunch of import statements).

Expanding what's going on here:

    (r'^su/(?P<username>.*)/$', 'qlockweb.accounts.views.su', {'redirect_url': '/qlockdata/'}), 
  • When an HTTP request arrives at the framework, Django goes through its list of URLs until it finds a match. In this case, going to http://mysite/accounts/su/fred/ ends hitting the urls.py line above; the /(?P<username>.*)/ part of the regexp pulls out "fred" and this gets passed as a parameter named username into the function su in qlockweb/accounts/views.py. This function also gets passed a parameter called redirect_url with value '/qlockdata/'.
  • @user_passes_test(lambda u: u.is_staff)
  • Actually, we need to rewind one step before we get into the su function. The line before the function definition is a Python decorator: some extra code wrapping the function that gets executed just before the function itself is executed. This decorator needs some expansion of its own:
      @user_passes_test(lambda u: u.is_staff)
    • The @ sign is the syntactic sugar that indicates that this line is a decorator for the function that comes immediately afterwards.
    • @user_passes_test(lambda u: u.is_staff)
    • This is the decorator function (from contrib/auth/decorators.py); its first argument test_func is a function that does the test. This test function is given a single parameter: the current User. If the test function returns true, the wrapped view code is called; if not, then the user gets redirected to a login page.
    • @user_passes_test(lambda u: u.is_staff)
    • More syntactic sugar. We want a function is_this_a_staff_user(u) that checks whether its argument u is an administrator. However, as this is the only place that the function is used, we don't bother to give it a name—we just use a lambda expression to give the definition right here and now.
    • @user_passes_test(lambda u: u.is_staff)
    • Finally, the body of the lambda expression just uses the method of the User class that indicates whether the user is an administrator or not.
  • def su(request, username, redirect_url='/'):
  • So now we're in the su function itself, and if we've got this far we're guaranteed that the person viewing the page is logged in as an is_staff user. The function has the username and redirect_url parameters mentioned earlier; it also has a request parameter that holds all of the information about the original web request (in a HttpRequest object).
  •   su_user = get_object_or_404(User, username=username)
  • The next line of code gets a User object for the username that was specified—fred in other words. If there isn't a user called fred, then a Http404 exception gets raised, which will percolate up the stack and display a (surprise, surprise) 404 page.
  •   if su_user.is_active:
  • This particular version of our code only allows impersonation of active users, helpfully provided by the is_active field in the standard User model.
  •     request.session[SESSION_KEY] = su_user.id
  • The next line of code is the one that actually does the work. The requesting user's session is modified so that its user ID is the impersonated user's.
  •   return HttpResponseRedirect(redirect_url)
  • The final line of code redirects the web browser off to the redirect_url page.

(Statutory disclaimer: I am not a security expert, nor do I play one on TV. Adding this to a production system is probably not a good idea.)


Another Django snippet: I finally discovered that the follow argument to the standard Manipulators allows you to list fields in the model that the form should leave untouched. Very helpful: the end result is more compact and less brittle than the code I'd put together to manually override all of the hidden fields.

[A:37385 B:3278 C:346 D:9187 E:62544 Total:112740]

Thursday, November 30, 2006

*knolp*

[reading: William Faulkner, "As I Lay Dying"; recently Neil Strauss & Bernard Chang, "How To Make Money Like a Porn Star"]

Now that I've got Django installed and running, I've been setting up my first web application with it.

I spent a bunch of time yesterday trying to figure out how to get extra parameters passed through a generic view; in the end I had to UTSL to get a method that worked.

So of course today I find a nice page that concisely and coherently explains it, rather more quickly than the couple of hours it took me to figure out.

Perhaps I can suggest *knolp* (the reverse of *plonk*) as the sound of an RSS feed hitting my aggregator. (It looks like I'm not the first person to think of this.)

[A:37385 B:3278 C:346 D:9187 E:46320 Total:96516]