15

I have these models (I have limited the number of fields to just those needed)

class unit(models.Model):
    name = models.CharField(max_length=200)

class project(models.Model):
    name = models.CharField(max_length=200)

class location(address):
    project = models.ForeignKey(project)

class project_unit(models.Model):
    project = models.ForeignKey(project)         
    unit = models.ForeignKey(unit)

class location_unit(models.Model):
    project = models.ForeignKey(project)    
      #Limit the selection of locations based on which project has been selected
    location = models.ForeignKey(location)
      #The same here for unit. But I have no idea how.
    unit = models.ForeignKey(project_unit)       

My newbie head just cannot grasp how to limit the two fields, location and unit, in the location_unit model to only show the choices which refers to the selected project in location_unit. Should I override the modelform and make a query there or can I use the limit_choices_to. Either way I have failed trying both

Edit: Just to clarify, I want this to happen in the Django Admin. I have also tried formfield_for_foreignkey, but still a no go for me.

EDIT 2:

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == "unit":
        kwargs["queryset"] = project_unit.objects.filter(project=1)
        return db_field.formfield(**kwargs)
    return super(location_unit_admin, self).formfield_for_foreignkey(db_field, request, **kwargs)

The above code snippet works. But of course I don't want the project to point to 1. How do I reference to the models project_id? I tried this:

kwargs["queryset"] = project_unit.objects.filter(project=self.model.project.project_id)

But that doesn't work (actually I have tried a lot of variations, yes I am a django newbie)

Brian
  • 392
  • 3
  • 15

2 Answers2

10

This is the answer, it is brilliant: https://github.com/digi604/django-smart-selects

Brian
  • 392
  • 3
  • 15
5

Your formfield_for_foreignkey looks like it might be a good direction, but you have to realize that the ModelAdmin (self) won't give you a specific instance. You'll have to derive that from the request (possibly a combination of django.core.urlresolvers.resolve and request.path)


If you only want this functionality in the admin (and not model validation in general), you can use a custom form with the model admin class:

forms.py:

from django import forms

from models import location_unit, location, project_unit

class LocationUnitForm(forms.ModelForm):
    class Meta:
        model = location_unit

    def __init__(self, *args, **kwargs):
        inst = kwargs.get('instance')
        super(LocationUnitForm, self).__init__(*args, **kwargs)
        if inst:
            self.fields['location'].queryset = location.objects.filter(project=inst.project)
            self.fields['unit'].queryset = project_unit.objects.filter(project=inst.project)

admin.py:

from django.contrib import admin

from models import location_unit
from forms import LocationUnitForm

class LocationUnitAdmin(admin.ModelAdmin):
    form = LocationUnitForm

admin.site.register(location_unit, LocationUnitAdmin)

(Just wrote these on the fly with no testing, so no guarantee they'll work, but it should be close.)

eternicode
  • 6,805
  • 4
  • 33
  • 39
  • 1
    Hi Eternicode, thank you for taking your time on my problem. But I have a question, is this code of yours dynamic, because it seems to me that when I want to create a location_unit, the __init__ has already been run, and wont be run again, no matter what project I select. So that I have to select a project first, then save and then reenter the location_unit to run the __init__ again and first then, the units will show only those that belongs to said project. Have I understood that correctly? – Brian Nov 10 '10 at 08:56
  • Maybe something like this can be used: http://stackoverflow.com/questions/3750097/how-to-read-write-input-fields-in-django-admin-tabular-inlines-using-ajax but I have no idea how to implement this in the admin – Brian Nov 10 '10 at 10:33
  • 3
    Okay, this does exactly what I want :) https://github.com/digi604/django-smart-selects – Brian Nov 10 '10 at 10:51
  • 3
    It's true that this will only work when editing a location_unit, not creating a new one, because, when creating a new one, there is no `instance` yet, and thus no project to limit choices to. You would have to "Save and continue editing" to get the limits, or implement some sort of AJAX to fetch valid choices based on project selection (which I've also done in the past). If you think you've found the answer yourself, please accept your answer as the solution. Thanks. – eternicode Nov 10 '10 at 15:51
  • Unfortunately, this only works with admin, and does not enforce the choices at the model level. – Cerin Dec 04 '12 at 17:42
  • It's a pity that this answer only works editing data. A clever idea like this one but for adding data would be a great solution. – dani herrera Jun 04 '23 at 13:00