views:

49

answers:

2

I have 2 models with a 1-1 relation (essentially a resource pool). For the example code, I will simply use nuts and bolts. There will be many more nuts (available resources) than bolts (which will each require 1 nut). However, if a nut can only be assigned to one bolt.

The constraint is easy enough to set up with the unique=True named param to the ForeignKey method.

The problem arises from the ModelForm. When rendered, the form will contain every nut in the dropdown. I would like to restrict it to only show nuts that haven't already been claimed by a bolt.

I am aware of the fields attribute of the ModelForm class, but am unable to come up with a query set filter that adequately addresses the issue. Here is example code of my problem:

from django.db import models
from django.forms import ModelForm

# Create your models here.

class Nut(models.Model):
    size = models.CharField()

class Bolt(models.Model):
    size = models.CharField()
    nut = models.ForeignKey( Nut, unique=True )

class BoltForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(BoltForm, self).__init__(*args, **kwargs)
        self.fields['nut'].queryset = # All unassigned nuts
+4  A: 

Try this:

self.fields['nut'].queryset = Nut.objects.exclude(
    pk__in=Bolt.objects.values('nut').query)

Update:

Of three expressions generating the same sql query:

pk__in=Bolt.objects.values('nut')
pk__in=Bolt.objects.values_list('nut')
pk__in=Bolt.objects.values('nut').query

I'd choose the last one as most straight-forward (although in other two cases the list and dict aren't created in fact: django 'understands' the intention without explicit mentioning of .query)

Also, consider Daniel Roseman's answer. It is another approach to do the same thing.

Antony Hatchkins
Worked perfectly. I had a feeling it was going to be something short and simple. Thanks for the answer!
Bob
I actually found this to be more accurate, although it's probably because my real classes have many more fields than the example ones in the question. self.fields['nut'].queryset = Nut.objects.exclude( pk__in=Bolt.objects.values_list( 'nut' ) )I would never have found this, however, without you pointing me to pk__in.Thanks again!
Bob
Thanks for pointing it out. Yes, mentioning `.values_list('nut')` is obligatory here unless two models share the primary key (as they would in case `Bolt.nut=ForeignKey(Nut, primary_key=True)` or `Bolt.nut=OneToOneField(Nut, primary_key=True)`). Updated my answer
Antony Hatchkins
+1  A: 
Nut.objects.filter(bolt=None)
Daniel Roseman
Does this work? The nuts do not reference the bolts, so there is no `bolt` attribute in the nut class.
jellybean
There is an attribute for bolts on `nut` objects, although it is called `bolt_set` when using a `ForeignKey` (this can be overwritten with the `related_name` argument, see docs: http://docs.djangoproject.com/en/dev/ref/models/fields/#module-django.db.models.fields.related). When using a `OneToOneField`, it would be called `nut`. But since not every nut has a bolt, `ForeignKey` with `unique=True` is the right choice.
piquadrat
@jellybean: Yes, using reverse relationship in this way is possible in queries.
Antony Hatchkins