开发者

Country/State/City dropdown menus inside the Django admin inline

I have a city foreign key in by BusinessBranch model. My City model also has a state and country foreign keys for State and County models. I'm having hard time displaying State and Country dropdown menus inside my BusinessBranchInline. What would be the best way to achieve开发者_运维百科 this? It would be great if the dropdowns filter items based on the value of its parent.

Country/State/City dropdown menus inside the Django admin inline


With a little hackery, it's quite doable.

In the following example, County is used instead of State and Municipality instead of City. So, the models are as follows:

class County(models.Model):
    name = models.CharField(_('Name'), max_length=100, unique=True)

class Municipality(models.Model):
    county = models.ForeignKey(County, verbose_name=_('County'))
    name = models.CharField(_('Name'), max_length=100)

class Location(models.Model):
    name = models.CharField(max_length=100)
    county = models.ForeignKey(County, verbose_name=_('County'))
    municipality = models.ForeignKey(Municipality,
            verbose_name=_("Municipality"))

There are two sides of the problem: client-side JavaScript and server side field rendering.

The client side JavaScript (with JQuery, assumed to be served from /site_media/js/municipality.js) is as follows:

var response_cache = {};

function fill_municipalities(county_id) {
  if (response_cache[county_id]) {
    $("#id_municipality").html(response_cache[county_id]);
  } else {
    $.getJSON("/municipalities_for_county/", {county_id: county_id},
      function(ret, textStatus) {
        var options = '<option value="" selected="selected">---------</option>';
        for (var i in ret) {
          options += '<option value="' + ret[i].id + '">'
            + ret[i].name + '</option>';
        }
        response_cache[county_id] = options;
        $("#id_municipality").html(options);
      });
  }
}

$(document).ready(function() {
  $("#id_county").change(function() { fill_municipalities($(this).val()); });
});

Now you need the Ajax view for serving municipalities that belong to a given county (assumed to be served from /municipalities_for_county/):

from django.http import JSONResponse
from django.utils.encoding import smart_unicode
from django.utils import simplejson

from myproject.places.models import Municipality

def municipalities_for_county(request):
    if request.is_ajax() and request.GET and 'county_id' in request.GET:
        objs = Municipality.objects.filter(county=request.GET['county_id'])
        return JSONResponse([{'id': o.id, 'name': smart_unicode(o)}
            for o in objs])
    else:
        return JSONResponse({'error': 'Not Ajax or no GET'})

And finally the server side code in admin.py for rendering the field is as follows. First, the imports:

from django import forms
from django.forms import widgets
from django.forms.utils import flatatt
from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
from django.contrib import admin
from django.utils.translation import gettext_lazy

from myproject.places.models import Municipality, Location

Then, the widget:

class MunicipalityChoiceWidget(widgets.Select):
    def render(self, name, value, attrs=None, choices=()):
        self.choices = [(u"", u"---------")]
        if value is None:
            # if no municipality has been previously selected,
            # render either an empty list or, if a county has
            # been selected, render its municipalities
            value = ''
            model_obj = self.form_instance.instance
            if model_obj and model_obj.county_id:
                for m in model_obj.county.municipality_set.all():
                    self.choices.append((m.id, smart_text(m)))
        else:
            # if a municipality X has been selected,
            # render only these municipalities, that belong
            # to X's county
            obj = Municipality.objects.get(id=value)
            for m in Municipality.objects.filter(county=obj.county):
                self.choices.append((m.id, smart_text(m)))

        s = widgets.Select(choices=self.choices)
        select_html = s.render(name=name,value=value,attrs=attrs)

        return mark_safe(''.join(select_html))

Next, the form:

class LocationForm(forms.ModelForm):
    municipality = forms.ModelChoiceField(Municipality.objects,
            widget=MunicipalityChoiceWidget(),
            label=gettext_lazy("Municipality"), required=False)

    class Meta:
        model = Location
        fields = ['name', 'county', 'municipality']

    def __init__(self, *args, **kwargs):
        """
        We need access to the county field in the municipality widget, so we
        have to associate the form instance with the widget.
        """
        super(LocationForm, self).__init__(*args, **kwargs)
        self.fields['municipality'].widget.form_instance = self

And finally, the admin class:

class LocationAdmin(admin.ModelAdmin):
    form = LocationForm
    class Media:
        js = ('http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js',
                '/site_media/js/municipality.js')

admin.site.register(Location, LocationAdmin)

Let me know if something remains unclear.


You may want to look in to creating a custom "address" widget that handles the the cascading with three dropdowns. You might want to look at the source code for the DateTime widget for guidance on this.

Also, have a look at tutorials on creating custom widgets, such as this one.


It would be great if the dropdowns filter items based on the value of its parent.

You can use Ajax Form Machine of the dajaxproject for that part

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜