Django/jQuery Kaskadierende Auswahlfelder?

Lesezeit: 6 Minuten

Ich möchte einen Country/State Selector bauen. Zuerst wählen Sie ein Land aus, und die Staaten für dieses Land werden im zweiten Auswahlfeld angezeigt. Dies ist in PHP und jQuery ziemlich einfach, aber ich finde Django-Formulare in diesem Sinne etwas restriktiv.

Ich könnte das State-Feld so einstellen, dass es beim Laden der Seite leer ist, und es dann mit etwas jQuery füllen, aber wenn es dann Formfehler gibt, kann es sich nicht “merken”, welchen State Sie ausgewählt haben. Ich bin mir auch ziemlich sicher, dass es einen Validierungsfehler auslöst, weil Ihre Wahl nicht zu denjenigen gehört, die im Formular auf der Python-Seite aufgeführt sind.

Wie also umgehe ich diese Probleme?

Hier ist meine Lösung. Es verwendet die undokumentierte Form-Methode _raw_value(), um einen Blick in die Daten der Anfrage zu werfen. Dies funktioniert auch für Formulare, die ein Präfix haben.

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-Code:

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

Callback-Code, der JSON zurückgibt:

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

  • Das hat mir sehr geholfen. Aber _raw_value() wurde seit 1.9 entfernt. Ich verwende folgendes: link. Kennt jemand einen besseren Weg, um den nicht übermittelten Wert abzufangen? Vielen Dank.

    – Maxime VAST

    8. August 17 um 18:57 Uhr


DjangojQuery Kaskadierende Auswahlfelder
Mike Desimone

Sie könnten ein verstecktes Feld so einstellen, dass es den echten “Status” -Wert enthält, und dann jQuery verwenden, um das zu erstellen <select> Liste und weiter .select(), kopieren Sie seinen Wert in das ausgeblendete Feld. Beim Laden der Seite kann Ihr jQuery-Code dann den Wert des verborgenen Felds abrufen und ihn verwenden, um das richtige Element in der auszuwählen <select> -Element, nachdem es ausgefüllt wurde.

Das Schlüsselkonzept hier ist, dass das State-Popup-Menü eine Fiktion ist, die vollständig in jQuery erstellt wurde und nicht Teil des Django-Formulars ist. Dadurch haben Sie die volle Kontrolle darüber, während alle anderen Felder normal funktionieren.

BEARBEITEN: Es gibt einen anderen Weg, aber es verwendet nicht die Formularklassen von Django.

In der Ansicht:

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)

Dann in der Vorlage:

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

Außerdem benötigen Sie das übliche JavaScript zum Neuladen der Bundesstaatsauswahl, wenn das Land geändert wird.

Ich habe das nicht getestet, also gibt es wahrscheinlich ein paar Löcher darin, aber es sollte die Idee vermitteln.

Ihre Auswahlmöglichkeiten sind also:

  • Verwenden Sie ein verstecktes Feld im Django-Formular für den tatsächlichen Wert und lassen Sie die Auswahlmenüs clientseitig über AJAX erstellen, oder
  • Vergessen Sie Djangos Formularkram und initialisieren Sie die Menüs selbst.
  • Ein … kreieren benutzerdefiniertes Django-Formular-Widget, was ich nicht gemacht habe und daher nicht kommentieren werde. Ich habe keine Ahnung, ob das machbar ist, aber es sieht so aus, als würden Sie ein paar brauchen Selects in a MultiWidgetwobei letzteres in den regulären Dokumenten nicht dokumentiert ist, sodass Sie die Quelle lesen müssen.

  • Das ist eine clevere Idee. Sieht ein bisschen dreckig aus, aber damit kann ich leben.

    – mp

    13. Juli 10 um 3:12 Uhr

  • Es ist nicht schmutzig, wenn es richtig dokumentiert ist. ^_-

    – Mike DeSimone

    13. Juli 10 um 11:17 Uhr

  • Scheint nur nicht so, als müsste ich ein verstecktes Element haben, um einige von Djangos Macken zu umgehen.

    – mp

    14. Juli 10 um 1:52 Uhr

  • Django macht nicht wirklich mutierende Formen. Wann immer ich einen habe, mache ich das Formular einfach selbst.

    – Mike DeSimone

    15. Juli 10 um 1:42 Uhr

  • Ich mag Ihre Bearbeitung auch nicht besonders, obwohl sie praktikabel ist: p Ich bleibe vorerst bei der versteckten Eingabe/AJAX-Lösung.

    – mp

    15. Juli 10 um 7:59 Uhr

DjangojQuery Kaskadierende Auswahlfelder
mpen

Basierend auf Mikes Vorschlag:

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

  • Hmm … ich habe noch keine Funktion an jQuery übergeben, daher bin ich mir nicht sicher, was das bewirkt. Normalerweise sehe ich (function($){...})(jQuery); für wann du willst $ etwas anderes sein als jQuery. Auch in der for (i in provinces) Schleife würde ich verwenden $('<option/>').val(provinces[i][0]).text(provinces[i][1]).appendTo($provSelect);. Ich schätze, mein Weg ist langsamer, aber es ist einfacher zu lesen, wenn Sie die Klauseln jeweils in eine Zeile setzen. Auch IIRC können Sie verwenden $('#id_country') anstatt $('.country'); Dasselbe gilt für das Provinz-Widget, mit dem Sie das ausgeben können class Zeug im Formular.

    – Mike DeSimone

    13. Juli 10 um 11:27 Uhr

  • @Mike: $(function() ist eine Abkürzung für $(document).ready(function(). Ich verwende absichtlich eine Klasse, weil es auf einer Seite möglicherweise mehrere Länder-/Provinz-Selektoren gibt … nicht, dass dieses Skript so wie es ist mit mehreren funktioniert.

    – mp

    14. Juli 10 um 1:49 Uhr

.

720670cookie-checkDjango/jQuery Kaskadierende Auswahlfelder?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy