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=TrueModelForm 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:
coolblog/models.py
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.
coolblog/forms.py
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.
coolblog/admin.py
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.