2010-07-13 12 views
9

Desidero creare un selettore Paese/Stato. Per prima cosa scegli un paese e gli Stati per quel paese vengono visualizzati nella seconda casella di selezione. Farlo in PHP e jQuery è abbastanza facile, ma trovo che le forme di Django siano un po 'restrittive in questo senso.Django/jQuery Cascading Select Boxes?

Potrei impostare il campo Stato per essere vuoto al caricamento della pagina, quindi popolarlo con un po 'di jQuery, ma se ci sono errori nel modulo non sarà in grado di "ricordare" quale stato selezionato. Sono anche abbastanza sicuro che genererà un errore di convalida perché la tua scelta non era tra quelle elencate nel modulo sul lato Python delle cose.

Quindi come posso aggirare questi problemi?

+0

provare questo https://github.com/digi604/django-smart-selects/ –

risposta

8

è possibile impostare un campo nascosto per avere il valore reale "stato", quindi utilizzare jQuery per creare l'elenco <select> e, .select(), copiare il suo valore per il campo nascosto. Quindi, al caricamento della pagina, il tuo codice jQuery può recuperare il valore del campo nascosto e usarlo per selezionare l'elemento giusto nell'elemento dopo che è stato popolato.

Il concetto chiave qui è che il menu a comparsa Stato è una fiction creata interamente in jQuery e non fa parte del modulo Django. Questo ti dà il pieno controllo su di esso, lasciando che tutti gli altri campi funzionino normalmente.

EDIT: C'è un altro modo per farlo, ma non usa le classi di forma di Django.

Nella vista:

context = {'state': None, 'countries': Country.objects.all().order_by('name')} 
if 'country' in request.POST: 
    context['country'] = request.POST['country'] 
    context['states'] = State.objects.filter(
     country=context['country']).order_by('name') 
    if 'state' in request.POST: 
     context['state'] = request.POST['state'] 
else: 
    context['states'] = [] 
    context['country'] = None 
# ...Set the rest of the Context here... 
return render_to_response("addressform.html", context) 

Poi nel modello:

<select name="country" id="select_country"> 
    {% for c in countries %} 
    <option value="{{ c.val }}"{% ifequal c.val country %} selected="selected"{% endifequal %}>{{ c.name }}</option> 
    {% endfor %} 
</select> 

<select name="state" id="select_state"> 
    {% for s in states %} 
    <option value="{{ s.val }}"{% ifequal s.val state %} selected="selected"{% endifequal %}>{{ s.name }}</option> 
    {% endfor %} 
</select> 

avrete anche bisogno del solito JavaScript per ricaricare il selettore stati quando il paese è cambiato.

Non ho provato questo, quindi probabilmente ci sono un paio di buchi, ma dovrebbe dare l'idea.

Così le scelte sono:

  • utilizzare un campo nascosto nel modulo Django per il valore reale e hanno i selezionare i menu creati sul lato client tramite la tecnologia AJAX, o
  • roba Modulo di
  • Ditch Django e inizializzare il menu te stesso.
  • Creare un custom Django form widget, che non ho fatto e quindi non commenterò. Non ho idea se sia fattibile, ma sembra che avrai bisogno di un paio Select s in un MultiWidget, quest'ultimo non documentato nei documenti regolari, quindi dovrai leggere il codice sorgente.
+0

Ecco un'idea intelligente Sembra un po 'sporco, ma posso conviverci. – mpen

+0

Non è sporco se è adeguatamente documentato.^_- –

+0

Proprio non sembra che avrei bisogno di avere un elemento nascosto per aggirare alcune delle stranezze di Django. – mpen

0

Basato su suggerimento di Mike:

// the jQuery 
$(function() { 
     var $country = $('.country'); 
     var $provInput = $('.province'); 
     var $provSelect = $('<select/>').insertBefore($provInput).change(function() { 
       $provInput.val($provSelect.val());  
     }); 
     $country.change(function() { 
       $provSelect.empty().addClass('loading'); 
       $.getJSON('/get-provinces.json', {'country':$(this).val()}, function(provinces) { 
         $provSelect.removeClass('loading'); 
         for(i in provinces) { 
           $provSelect.append('<option value="'+provinces[i][0]+'">'+provinces[i][1]+'</option>'); 
         } 
         $provSelect.val($provInput.val()).trigger('change'); 
       }); 
     }).trigger('change'); 
}); 

# the form 
country = CharField(initial='CA', widget=Select(choices=COUNTRIES, attrs={'class':'country'})) 
province = CharField(initial='BC', widget=HiddenInput(attrs={'class':'province'})) 

# the view 
def get_provinces(request): 
    from django.utils import simplejson 
    data = { 
     'CA': CA_PROVINCES, 
     'US': US_STATES 
    }.get(request.GET.get('country', None), None) 
    return HttpResponse(simplejson.dumps(data), mimetype='application/json') 
+0

Hmm ... non ho ancora passato una funzione a jQuery, quindi non sono sicuro di cosa faccia. Di solito vedo '(function ($) {...}) (jQuery);' per quando si desidera che '$' sia qualcosa di diverso da 'jQuery'. Inoltre, nel ciclo 'for (i in province)' userei '$ ('

+0

@Mike: '$ (function()' è short-hand per '$ (document) .ready (function()'. Uso deliberatamente una classe perché potrebbero esserci più selettori di paesi/province su una pagina. non che questo script possa funzionare con più di uno come è. – mpen

14

Ecco la mia soluzione. Utilizza il metodo Form non documentato _raw_value() per dare un'occhiata ai dati della richiesta. Questo funziona per le forme, che hanno anche un prefisso.Codice

class CascadeForm(forms.Form): 
    parent=forms.ModelChoiceField(Parent.objects.all()) 
    child=forms.ModelChoiceField(Child.objects.none()) 

    def __init__(self, *args, **kwargs): 
     forms.Form.__init__(self, *args, **kwargs) 
     parents=Parent.objects.all() 
     if len(parents)==1: 
      self.fields['parent'].initial=parents[0].pk 

     parent_id=self.fields['parent'].initial or self.initial.get('parent') \ 
        or self._raw_value('parent') 
     if parent_id: 
      # parent is known. Now I can display the matching children. 
      children=Child.objects.filter(parent__id=parent_id) 
      self.fields['children'].queryset=children 
      if len(children)==1: 
       self.fields['children'].initial=children[0].pk 

jquery:

function json_to_select(url, select_selector) { 
/* 
Fill a select input field with data from a getJSON call 
Inspired by: http://stackoverflow.com/questions/1388302/create-option-on-the-fly-with-jquery 
*/ 
    $.getJSON(url, function(data) { 
    var opt=$(select_selector); 
    var old_val=opt.val(); 
     opt.html(''); 
     $.each(data, function() { 
      opt.append($('<option/>').val(this.id).text(this.value)); 
     }); 
     opt.val(old_val); 
     opt.change(); 
    }) 
} 


    $(function(){ 
    $('#id_parent').change(function(){ 
     json_to_select('PATH_TO/parent-to-children/?parent=' + $(this).val(), '#id_child'); 
    }) 
    }); 

Codice richiamata, che restituisce JSON:

def parent_to_children(request): 
    parent=request.GET.get('parent') 
    ret=[] 
    if parent: 
     for children in Child.objects.filter(parent__id=parent): 
      ret.append(dict(id=child.id, value=unicode(child))) 
    if len(ret)!=1: 
     ret.insert(0, dict(id='', value='---')) 
    return django.http.HttpResponse(simplejson.dumps(ret), 
       content_type='application/json') 
+0

Questo ha funzionato per me. Ottimo approccio –

+1

Questo mi ha aiutato molto, ma _raw_value() viene rimosso dal 1.9. Sto usando il seguente: [link] (https: //stackoverflow.com/a/39349085/5324537) Qualcuno conosce un modo migliore per catturare il valore non inviato? Grazie mille. –

Problemi correlati