Pre-populate Django ModelForm with Specific Queryset
I just had a situation where I was trying to filter the queryset for a ModelMultipleChoiceField based on the currently logged-on user. I was going crazy trawling through the Django docs and eventually Google. It seemed like something which should be so simple, but there was no obvious way to do it. Eventually I found the answer, and it IS simple! As an example, let’s say you have the following two models as part of a simple photo gallery app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Photo(Model): name = CharField(max_length=100) caption = CharField(max_length = 150) user = ForeignKey(User) upload_timestamp = DateTimeField(auto_now_add=True) image = ImageField(upload_to="user_images/%Y/%m/%d/") thumbnail = ImageField(upload_to="user_images/%Y/%m/%d") def __unicode__(self): return self.name class Album(Model): name = CharField(max_length=100) caption = CharField(max_length = 150) user = ForeignKey(User) photos = ManyToManyField(Photo, related_name="albums", blank=True) creation_timestamp = DateTimeField(auto_now_add=True) cover_photo = ForeignKey(Photo, related_name="cover_photos") def __unicode__(self): return self.name |
We then define a ModelForm based on the Album model, which allows users to create albums with photos they’ve previously uploaded (pretend we’ve already made that possible). We only expose the “name”, “caption” and “photos” fields because we’ll fill in the others automatically as part of our view:
1 2 3 4 | class AlbumCreationForm(ModelForm): class Meta: model = Album fields = ("name", "caption", "photos") |
Now here’s the real magic. Ordinarily, when first showing the form (pre-POST) we would create it like this and pass it to the template:
form = AlbumCreationForm() |
The problem here is that by default we’ll get all photo objects, i.e. the result of “Photo.objects.all()”. That’s a problem because in this case we just want to list the photos belonging to the current user. To do this, just add the following line:
form.fields["photos"].queryset = Photo.objects.filter(user=request.user) |
It turns out that form fields can be accessed as a dictionary attached to the form instance, and that if the field is model-related, like “photos” in the example, you can update its queryset dynamically.
Here’s a partial view which uses the last two code samples, to provide some context:
1 2 3 4 5 6 7 8 9 10 11 12 13 | def create_album(request): if request.method == "POST": # Process form. else: form = AlbumCreationForm() # Get just the photos belonging to this user. form.fields["photos"].queryset = Photo.objects.filter(user=request.user) template_vars = RequestContext(request, { "form": form, "user": request.user }) return render_to_response("create_album.html", template_vars) |
That’s it! Adding the one extra line to the view gives us the filtering we need.
