almosteffortless.com | djangoproject.com | python.org | linux.com
demongin.org - Automatically Save Current User in Django Admin

Automatically Save Current User in Django Admin

A short how-to in which I describe how to automatically save the user object of the user currently logged-in to the Django admin interface.


Wednesday, 2010-01-20 | Careerism, Django, Programming

"I think we did a really good job making a fantastic piece of bubble gum that was sweet and jam packed with sugar and had a hell of a bang when you put it in your mouth. But for some reason I still have not totally figured out, the flavor faded VERY fast."

David Jaffe

I don't know about you, but every once in a while, I get hung up on a simple problem only to realize (weeks later, usually) that I was trying every possible solution except for the correct one.

One of these problems, in my case, was how to set up an application in Django such that once a user logged in to use the admin interface, I was able to automatically set a ForeignKey relation in my models to the built-in User model for that user. Basically, if I had a blog post or an essay or whatever, I wanted to be able to automatically save an "author" or "editor" value to the database that referred back to the author or editor's User object.

And man! I tried every damn thing to make it work. I won't get into the specifics, but not only did I try all kinds of bizarre, ultra-low-level python shenanigans to get that current user, I was probably importing half of the python modules in existence to do it. I knew, thanks to the insane convolution of my attempts at a solution, that I was barking up the wrong tree, but I could not--for the life of me--figure out which tree was the right tree.

As it turns out, the correct (and simplest, once you start thinking like a Django developer) way to get and save the currently logged-in user's User object is to do by overwriting the save_model() function of the admin.ModelAdmin class in your admin.py file that allows users to interact with your models. Basically, you don't do much of anything in your models.py file: all the work gets done in admin.py.

But, since that's all very abstract and doesn't illustrate the point particularly clearly, let's say that you've got a simple application called "essay" that you plan to use as a kind of shared blog: many users will create essays and also edit them. Let's also say that knowing which users are creating and editing posts is mission-critical to your a.) front-end design and b.) your back-end accounting and administration.

The first thing you need to do to pull this off is import User from django.contrib.auth.models into your models.py file so that you can work with User objects. Once you've got that, create your main "Essay" model and give it a ForeignKey relation to User, which contains all of the users that exist in the Django admin tables in the database.

models.py:

from django.contrib.auth.models import User

class Essay(models.Model):
    title = models.CharField(max_length=666)
    body = models.TextField()
    author = models.ForeignKey(User, null=True, blank=True)
It is important to make sure that the "author" value is allowed to be blank and null: we're going to use its blankness/null-ness when we write the logic that automatically sets the author of the post.

And, speaking of that, how it's going to work is this: first, a user is going to authenticate and log in to the admin interface using the stock Django code. Then, once he's in and he's got his session and everything, he's going to to either a.) create or b.) edit one of your Essay class objects. When he saves his changes, he'll send a plain, old Django-style request object to the admin application.

And that, finally, is the thing that we're going to use when we overwrite the save_model() function in the admin.py file for our "essay" application.

admin.py:

from myapplication.essay.models import Essay
from django.contrib import admin

class EssayAdmin(admin.ModelAdmin):
    list_display = ('title', 'author')
    fieldsets = [
        (None, { 'fields': [('title','body')] } ),
    ]

    def save_model(self, request, obj, form, change):
        if getattr(obj, 'author', None) is None:
            obj.author = request.user
        obj.save()
So, to summarize, what happens is this:
  1. We make a model and in the administrative configuration for that model, we make it so that the user only sees blanks for "title" and "body".
  2. When he creates or edits our model, he therefore only saves a CharField and a TextField, leaving the ForeignKey field null.
  3. Before the ModelAdmin saves his changes, it checks for a "None" from the "author" field and, if it finds one, uses the request that our user sent to the Django admin interface to assign that value automatically.
  4. What's also important to notice is that if you leave the logic like this, it'll only happen once: future saves won't reset or change the "author" attribute of the model object because it won't show up as "None" when the ModelAdmin evaluates it.
This was driving me nuts for weeks, like a cold sore that wouldn't go away, until I accidentally solved it by trying to do some other random craziness with request objects: as painful as it was to live without being able to solve or understand the problem, solving it was definitely teh awesome.