Django Patterns
It is a safe way to save the user time and still keep track of the information.
The important parts of the implementation are:
ForeignKey
to User
field needs blank=True
ModelForm
that assigns a fake User
to the field.ModelAdmin.save_model()
function to assign the request.user
to the field.For discussion we’ll use this model with two relations to the User
model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Entry(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField()
pub_date = models.DateField(default=datetime.datetime.today)
author = models.ForeignKey(
User,
related_name='entries',
blank=True)
body = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
last_modified_by = models.ForeignKey(
User,
related_name='entry_modifiers',
blank=True)
|
This Entry
model has two fields that we want to fill automatically: author
and last_modified_by
. Notice both fields have blank=True
. This is important so we can get past some initial Django validation.
Whenever you save a model, Django attempts to validate it. Validation will fail without special validation tomfoolery. The first stage of validation fakery was setting blank=True
on the fields. The next stage involves setting a temporary value for each field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from django.contrib.auth.models import User
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
def clean_author(self):
if not self.cleaned_data['author']:
return User()
return self.cleaned_data['author']
def clean_last_modified_by(self):
if not self.cleaned_data['last_modified_by']:
return User()
return self.cleaned_data['last_modified_by']
|
The lower-level Django model validation actually checks if the value of a related field is an instance of the correct class. Since a form’s validation happens before any attempt to save the model, we create a new ModelForm
, called EntryForm
. The clean_author()
and clean_last_modified_by()
methods check for an empty value and assigns it an unsaved and empty instance of User
, and Django is happy.
In the model’s ModelAdmin
, we make a few adjustments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class EntryAdmin(admin.ModelAdmin):
form = EntryForm
list_display = ('title', 'pub_date', 'author')
prepopulated_fields = { 'slug': ['title'] }
readonly_fields = ('last_modified', 'last_modified_by',)
fieldsets = ((
None, {
'fields': ('title', 'body', 'pub_date')
}), (
'Other Information', {
'fields': ('last_modified', 'last_modified_by', 'slug'),
'classes': ('collapse',)
})
)
def save_model(self, request, obj, form, change):
if not obj.author.id:
obj.author = request.user
obj.last_modified_by = request.user
obj.save()
|
First, we set the form
attribute to the EntryForm
we just created.
Then, since we don’t want the author to worry about selecting themselves in the author
field, we left it out of the fieldsets
. We left in the last_modified_by
field for reference and made it a read-only field.
The final magic comes in the overridden save_model()
method on line 17. We check to see if the author
attribute actually has an id
. If it doesn’t, it must be the empty instance we set in EntryForm
, so we assign the author
field to the current user. Since we always want to assign or re-assign the user modifying this record, the last_modified_by
field is set every time.