2014-07-16 5 views
9

Wonach ich suche: Ein einzelnes Widget, das dem Benutzer eine Dropdown-Liste von Auswahlmöglichkeiten gibt, aber auch ein Texteingabefeld darunter hat, damit der Benutzer einen neuen Wert eingeben kann.Django Formular mit Auswahl aber auch mit Freetext Option?

Das Back-End-Modell hätte eine Reihe von Standardoptionen (würde aber das Schlüsselwort options nicht für das Modell verwenden). Ich weiß, dass ich dies (und ich) implementiert haben kann, indem das Formular sowohl ein ChoicesField als auch CharField hat und Code das CharField verwendet, wenn ChoicesField auf der Standardeinstellung belassen wird, aber das fühlt sich "un-django" an.

Gibt es einen Weg (entweder mit Django-Builtins oder einem Django-Plugin), um etwas wie ChoiceEntryField (nach dem GtkComboboxEntry, was IIRC dies tut) für ein Formular zu definieren?

Falls jemand diese findet, beachten Sie, dass es eine ähnliche Frage, wie am besten tun, was ich suchte aus einer UX Perspektive bei https://ux.stackexchange.com/questions/85980/is-there-a-ux-pattern-for-drop-down-preferred-but-free-text-allowed

Antwort

16

Ich würde einen benutzerdefinierten Widget Ansatz empfehlen, HTML5 erlaubt Sie haben eine freie Texteingabe mit einer Dropdown-Liste, die als eine Auswahl-oder-Schreiben-andere Art von Feld arbeiten würde, so habe ich es gemacht:

fields.py

from django import forms 

class ListTextWidget(forms.TextInput): 
    def __init__(self, data_list, name, *args, **kwargs): 
     super(ListTextWidget, self).__init__(*args, **kwargs) 
     self._name = name 
     self._list = data_list 
     self.attrs.update({'list':'list__%s' % self._name}) 

    def render(self, name, value, attrs=None): 
     text_html = super(ListTextWidget, self).render(name, value, attrs=attrs) 
     data_list = '<datalist id="list__%s">' % self._name 
     for item in self._list: 
      data_list += '<option value="%s">' % item 
     data_list += '</datalist>' 

     return (text_html + data_list) 

Formen.py

from django import forms 
from myapp.fields import ListTextWidget 

class FormForm(forms.Form): 
    char_field_with_list = forms.CharField(required=True) 

    def __init__(self, *args, **kwargs): 
     _country_list = kwargs.pop('data_list', None) 
     super(FormForm, self).__init__(*args, **kwargs) 

    # the "name" parameter will allow you to use the same widget more than once in the same 
    # form, not setting this parameter differently will cuse all inputs display the 
    # same list. 
     self.fields['char_field_with_list'].widget = ListTextWidget(data_list=_country_list, name='country-list') 

views.py

from myapp.forms import FormForm 

def country_form(request): 
    # instead of hardcoding a list you could make a query of a model, as long as 
    # it has a __str__() method you should be able to display it. 
    country_list = ('Mexico', 'USA', 'China', 'France') 
    form = FormForm(data_list=country_list) 

    return render(request, 'my_app/country-form.html', { 
     'form': form 
    }) 
+1

Interessant. Hoffe, dass es Ihnen nichts ausmacht, aber ich habe ein paar kleine Änderungen vorgenommen (fühlen Sie sich frei, wenn Sie es tun). Aus Neugier, gibt es einen Weg, um es alle Optionen zeigen zu zeigen (Was ich bekomme ist, dass wenn ich einen Buchstaben eintippe, es alles zeigt, was dazu passt; ich bin eher in der Lage, eine vollständige Liste aller möglichen Eingaben zu bekommen) – Foon

+0

(Hmm ... eigentlich ist der HTML-Code im Allgemeinen ein bisschen wackelig, was vielleicht an meinem übermäßig schnellen und schmutzigen Testen liegen könnte ... was ist das Verhalten, das ich sehen sollte? Drop-down rechts neben dem Textfeld bei Klick oder Weiter Typ oder etwas anderes? – Foon

+0

Leider ist die Idee hinter dieser Eingabe ein Suchfeld, ich glaube nicht, dass wir es alle Optionen anzeigen lassen können, wenn Sie einen Buchstaben eingeben:/ –

2

Würde des Eingangstyp sowohl in der Auswahl und Textfeldern identisch sein ? Wenn ja, würde ich ein einzelnes CharField (oder Textfeld) in der Klasse machen und einige Front-End-Javascript/Jquery kümmern, welche Daten übergeben werden, indem Sie eine Klausel "wenn keine Informationen in Dropdown, Daten in Textfeld verwenden" anwenden.

Ich habe eine jsFiddle gemacht, um zu demonstrieren, wie Sie dies am Frontend tun können.

HTML:

<div class="formarea"> 

<select id="dropdown1"> 
<option value="One">"One"</option> 
<option value="Two">"Two"</option> 
<option value="Three">or just write your own</option> 
</select> 

<form><input id="txtbox" type="text"></input></form> 
    <input id="inputbutton" type="submit" value="Submit"></input> 

</div> 

JS:

var txt = document.getElementById('txtbox'); 
var btn = document.getElementById('inputbutton'); 
txt.disabled=true; 

$(document).ready(function() { 
    $('#dropdown1').change(function() { 
     if($(this).val() == "Three"){ 
      document.getElementById('txtbox').disabled=false; 
     } 
     else{ 
      document.getElementById('txtbox').disabled=true; 
     } 
    }); 
}); 

btn.onclick = function() { 
    if((txt).disabled){ 
     alert('input is: ' + $('#dropdown1').val()); 
    } 
    else{ 
     alert('input is: ' + $(txt).val()); 
    } 
}; 

Sie können dann auf einreichen, angeben, welche Wert auf Ihre Ansicht übergeben werden.

+1

Ja, so weit als Eingangstyp gleich sind. Dies könnte für jemand anderen hilfreich sein, aber ich habe versucht, einen Weg zu finden, django die ganze Arbeit machen zu lassen, anstatt JavaScript/custom html einzubetten. – Foon

3

Edit: aktualisiert, um es mit UpdateView funktioniert auch

Also, was ich suchte

utils.py zu sein scheint:

from django.core.exceptions import ValidationError 
from django import forms 


class OptionalChoiceWidget(forms.MultiWidget): 
    def decompress(self,value): 
     #this might need to be tweaked if the name of a choice != value of a choice 
     if value: #indicates we have a updating object versus new one 
      if value in [x[0] for x in self.widgets[0].choices]: 
       return [value,""] # make it set the pulldown to choice 
      else: 
       return ["",value] # keep pulldown to blank, set freetext 
     return ["",""] # default for new object 

class OptionalChoiceField(forms.MultiValueField): 
    def __init__(self, choices, max_length=80, *args, **kwargs): 
     """ sets the two fields as not required but will enforce that (at least) one is set in compress """ 
     fields = (forms.ChoiceField(choices=choices,required=False), 
        forms.CharField(required=False)) 
     self.widget = OptionalChoiceWidget(widgets=[f.widget for f in fields]) 
     super(OptionalChoiceField,self).__init__(required=False,fields=fields,*args,**kwargs) 
    def compress(self,data_list): 
     """ return the choicefield value if selected or charfield value (if both empty, will throw exception """ 
     if not data_list: 
      raise ValidationError('Need to select choice or enter text for this field') 
     return data_list[0] or data_list[1] 

Beispiel verwenden

(forms.py)

from .utils import OptionalChoiceField 
from django import forms 
from .models import Dummy 

class DemoForm(forms.ModelForm): 
    name = OptionalChoiceField(choices=(("","-----"),("1","1"),("2","2"))) 
    value = forms.CharField(max_length=100) 
    class Meta: 
     model = Dummy 

(Beispiel Dummy model.py :)

from django.db import models 
from django.core.urlresolvers import reverse 

class Dummy(models.Model): 
    name = models.CharField(max_length=80) 
    value = models.CharField(max_length=100) 
    def get_absolute_url(self): 
     return reverse('dummy-detail', kwargs={'pk': self.pk}) 

(Probe Dummy views.py:)

from .forms import DemoForm 
from .models import Dummy 
from django.views.generic.detail import DetailView 
from django.views.generic.edit import CreateView, UpdateView 


class DemoCreateView(CreateView): 
    form_class = DemoForm 
    model = Dummy 

class DemoUpdateView(UpdateView): 
    form_class = DemoForm 
    model = Dummy 


class DemoDetailView(DetailView): 
    model = Dummy 
+0

Beachten Sie, dass Sie Community-Wiki Ihre eigene Antwort auf Ihre eigene Frage nicht benötigen. Es ist erlaubt und empfohlen, selbst Antworten zu schreiben – jamylak

+0

Danke, @jamylak. Das funktioniert super mit CreateView. Mit UpdateView wird das Widget jedoch nicht mit dem vorhandenen Wert initialisiert. Es ist nicht an die Instanz gebunden und ich kann nicht herausfinden, wie Sie den Wert von ChoiceField und CharField manuell festlegen. Hast du zufällig eine Anleitung? – Carrie

+0

@Carrie Diese Antwort war nicht von mir – jamylak

6

Ich weiß, ich bin ein bisschen spät, um die Partei, aber es gibt eine andere Lösung, die ich vor kurzem verwendet haben.

Ich habe die Input widget von django-floppyforms mit einem datalist Argument verwendet. Dies erzeugt ein HTML5 <datalist> Element, für das Ihr Browser automatisch eine Liste von Vorschlägen erstellt (siehe auch this SO answer).

Hier ist, was eine Modellform könnte dann einfach wie folgt aussehen:

class MyProjectForm(ModelForm): 
    class Meta: 
     model = MyProject 
     fields = "__all__" 
     widgets = { 
      'name': floppyforms.widgets.Input(datalist=_get_all_proj_names()) 
     } 
+0

Beeindruckend. Ich suche seit Jahren nach Django-Floppyform. – emyller

Verwandte Themen