Django表单有选项,但也有freetext选项?

时间:2021-11-21 02:04:47

What I'm looking for: A single widget that gives the user a drop down list of choices but then also has a text input box underneath for the user to enter a new value.

我要找的是:一个小部件,它给用户一个下拉列表的选项,但同时也有一个文本输入框供用户输入新值。

The backend model would have a set of default choices (but wouldn't use the choices keyword on the model). I know I can (and I have) implemented this by having the form have both a ChoicesField and CharField and have code use the CharField if ChoicesField is left at the default, but this feels "un-django" like.

后端模型将具有一组默认选项(但不会在模型中使用choice关键字)。我知道我可以(而且我已经)实现了这一点,通过让表单拥有一个ChoicesField和CharField,并且如果ChoicesField在默认情况下被保留,那么就有代码使用CharField,但是这感觉像“undjango”。

Is there a way (either using Django-builtins or a Django plugin) to define something like ChoiceEntryField (modeled after the GtkComboboxEntry which IIRC does this) for a form?

是否有一种方法(使用Django-builtins或Django插件)来定义类似ChoiceEntryField(模仿IIRC所做的GtkComboboxEntry)的表单?

In case anyone finds this, note that there is a similar question on how to best do what I was looking for from a UX perspective at https://ux.stackexchange.com/questions/85980/is-there-a-ux-pattern-for-drop-down-preferred-but-free-text-allowed

如果有人发现这一点,请注意,在https://ux.stackexchange.com/questions/85980/is-there-a- UX模式-for drop-down- preferences -but-free-text允许的情况下,有一个类似的问题,关于如何从用户体验的角度最好地完成我所寻找的内容

5 个解决方案

#1


16  

I would recommend a custom Widget approach, HTML5 allows you to have a free text input with a dropdown list which would work as a pick-one-or-write-other type of field, this is how I made it:

我推荐一种定制的Widget方法,HTML5允许你有一个免费的文本输入和一个下拉列表,这个下拉列表可以作为一种选择- 1 -or-write-other类型的字段,我就是这样做的:

fields.py

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)

forms.py

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

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

#2


6  

I know I’m a bit late to the party but there is another solution which I have recently used.

我知道我对聚会有点晚了,但我最近又用了另一种解决方法。

I have used the Input widget of django-floppyforms with a datalist argument. This generates an HTML5 <datalist> element for which your browser automatically creates a list of suggestions (see also this SO answer).

我使用了django- dro pyforms的输入小部件并使用了datalist参数。这将生成一个HTML5 元素,您的浏览器会自动创建一个建议列表(参见这个答案)。

Here’s what a model form could then simply look like:

下面是一种模型形式的简单形式:

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

#3


3  

Edit: updated to make it work with UpdateView as well

编辑:更新使它与UpdateView一起工作

So what I was looking for appears to be

所以我要找的是

utils.py:

utils.py:

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]

Example use

(forms.py)

(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

(Sample dummy model.py:)

(示例假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})

(Sample dummy views.py:)

(示例假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

#4


2  

Would the input type be identical in both the choice and text fields? If so, I would make a single CharField (or Textfield) in the class and have some front end javascript/jquery take care of what data will be passed by applying a "if no information in dropdown, use data in textfield" clause.

在选择字段和文本字段中输入类型是否相同?如果是,我将在类中创建一个CharField(或Textfield),并让一些前端javascript/jquery通过应用“如果下拉中没有信息,请在Textfield中使用数据”子句来处理将要传递的数据。

I made a jsFiddle to demonstrate how you can do this on the frontend.

我做了一个小提琴演示如何在前端做这个。

HTML:

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:

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

you can then, on submit, specify which value will be passed to your view.

然后,在提交时,可以指定将传递给视图的值。

#5


0  

Here's how i solved this problem. I retrieve choices from passed to template form object and fill datalist manually:

我是这样解决这个问题的。我检索了从传递到模板表单对象并手动填充datalist的选项:

{% for field in form %}
  <div class="form-group">
    {{ field.label_tag }}
    <input list="options" name="test-field"  required="" class="form-control" id="test-field-add">
    <datalist id="options">
      {% for option in field.subwidgets %}
        <option value="{{ option.choice_label }}"/>
      {% endfor %}
    </datalist>
   </div>
{% endfor %}

#1


16  

I would recommend a custom Widget approach, HTML5 allows you to have a free text input with a dropdown list which would work as a pick-one-or-write-other type of field, this is how I made it:

我推荐一种定制的Widget方法,HTML5允许你有一个免费的文本输入和一个下拉列表,这个下拉列表可以作为一种选择- 1 -or-write-other类型的字段,我就是这样做的:

fields.py

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)

forms.py

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

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

#2


6  

I know I’m a bit late to the party but there is another solution which I have recently used.

我知道我对聚会有点晚了,但我最近又用了另一种解决方法。

I have used the Input widget of django-floppyforms with a datalist argument. This generates an HTML5 <datalist> element for which your browser automatically creates a list of suggestions (see also this SO answer).

我使用了django- dro pyforms的输入小部件并使用了datalist参数。这将生成一个HTML5 元素,您的浏览器会自动创建一个建议列表(参见这个答案)。

Here’s what a model form could then simply look like:

下面是一种模型形式的简单形式:

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

#3


3  

Edit: updated to make it work with UpdateView as well

编辑:更新使它与UpdateView一起工作

So what I was looking for appears to be

所以我要找的是

utils.py:

utils.py:

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]

Example use

(forms.py)

(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

(Sample dummy model.py:)

(示例假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})

(Sample dummy views.py:)

(示例假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

#4


2  

Would the input type be identical in both the choice and text fields? If so, I would make a single CharField (or Textfield) in the class and have some front end javascript/jquery take care of what data will be passed by applying a "if no information in dropdown, use data in textfield" clause.

在选择字段和文本字段中输入类型是否相同?如果是,我将在类中创建一个CharField(或Textfield),并让一些前端javascript/jquery通过应用“如果下拉中没有信息,请在Textfield中使用数据”子句来处理将要传递的数据。

I made a jsFiddle to demonstrate how you can do this on the frontend.

我做了一个小提琴演示如何在前端做这个。

HTML:

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:

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

you can then, on submit, specify which value will be passed to your view.

然后,在提交时,可以指定将传递给视图的值。

#5


0  

Here's how i solved this problem. I retrieve choices from passed to template form object and fill datalist manually:

我是这样解决这个问题的。我检索了从传递到模板表单对象并手动填充datalist的选项:

{% for field in form %}
  <div class="form-group">
    {{ field.label_tag }}
    <input list="options" name="test-field"  required="" class="form-control" id="test-field-add">
    <datalist id="options">
      {% for option in field.subwidgets %}
        <option value="{{ option.choice_label }}"/>
      {% endfor %}
    </datalist>
   </div>
{% endfor %}