2009-11-04 30 views
8

Tengo una clave externa de ciudad en modelo BusinessBranch. El modelo de My City también tiene claves extranjeras estatales y nacionales para los modelos de estado y condado. Tengo problemas para mostrar los menús desplegables de estado y país dentro de BusinessBranchInline. ¿Cuál sería la mejor manera de lograr esto? Sería genial si las listas desplegables filtran elementos en función del valor de su elemento primario.Menús desplegables de país/estado/ciudad dentro de la administración de Django en línea

django admin screenshot http://i33.tinypic.com/15n69mq.png

Respuesta

23

Con un poco de hackery, es bastante factible.

En el siguiente ejemplo, Condado se utiliza en lugar de Estado y Municipio en lugar de Ciudad. Así, los modelos son los siguientes:

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")) 

Hay dos aspectos del problema: JavaScript del lado cliente y el procesamiento de campos del lado del servidor.

El lado del cliente JavaScript (con jQuery, supone que se sirve de /site_media/js/municipality.js) es el siguiente:

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()); }); 
}); 

Ahora necesita la opinión de Ajax para servir municipios que pertenecen a un condado determinado (que se supone que se sirve de /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'}) 

Y, finalmente, el código del lado del servidor en administrador. py para representar el campo es el siguiente. En primer lugar, las importaciones:

from django import forms 
from django.forms import widgets 
from django.forms.util import flatatt 
from django.utils.encoding import smart_unicode 
from django.utils.safestring import mark_safe 
from django.contrib import admin 
from django.utils.translation import ugettext_lazy 

from myproject.places.models import Municipality, Location 

Entonces, el widget de:

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: 
       for m in model_obj.county.municipality_set.all(): 
        self.choices.append((m.id, smart_unicode(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_unicode(m))) 

     # copy-paste from widgets.Select.render 
     final_attrs = self.build_attrs(attrs, name=name) 
     output = [u'<select%s>' % flatatt(final_attrs)] 
     options = self.render_options(choices, [value]) 
     if options: 
      output.append(options) 
     output.append('</select>') 
     return mark_safe(u'\n'.join(output)) 

A continuación, el formulario:

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

    class Meta: 
     model = Location 

    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 

Y, por último, la clase de administración:

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) 

Avísame si algunos Lo que sigue no está claro.

+5

+1 para una respuesta increíblemente minuciosa.Voy a hacer algo similar y aunque no estoy usando el administrador, esto debería ser de gran ayuda. ¡Gracias! –

+1

gracias, un caballero y erudito. – snakesNbronies

+0

'mimitype' es (django 1.6) obsoleto. Creo que debes reemplazarlo por 'content_type'. – suhailvs

0

Es posible que desee ver en la creación de un widget personalizado "dirección" que se encarga de la cascada del con tres menús desplegables. Es posible que desee consultar el código fuente del widget DateTime para obtener orientación sobre esto.

Además, eche un vistazo a los tutoriales sobre cómo crear widgets personalizados, como this one.

0

Sería genial si las listas desplegables filtran elementos en función del valor de su elemento primario.

Puede utilizar Ajax Form Machine del dajaxproject para esa parte

Cuestiones relacionadas