I have a multivaluefield with a charfield and choicefield. I need to pass choices to the choicefield constructor, however when I try to pass it into my custom multivaluefield I get an error __init__() got an unexpected keyword argument 'choices'.
I know the rest of the code works because when I remove the choices keyword argument from __init__ and super, the multivaluefield displays correctly but without any choices.
This is how I setup my custom multivaluefield:
class InputAndChoice(object):
def __init__(self, text_val='', choice_val=''):
self.text_val=text_val
self.choice_val=choice_val
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
widget = (widgets.TextInput(),
widgets.Select()
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
def decompress(self,value):
if value:
return [value.text_val, value.choice_val]
return [None, None]
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text=None, choices=None):
field = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
super(InputAndChoiceField, self).__init__(fields=field, widget=widget,
label=label, initial=initial, help_text=help_text, choices=choices)
And I call it like so:
input_and_choice = InputAndChoiceField(choices=[(1,'first'),(2,'second')])
So how do I pass the choices to my ChoiceField field?
Edit:
I've tried stefanw's suggestion but still no luck. I've used logging.debug to print out the contents of InputAndChoiceField at the end of the init and self.fields[1].choices contains the correct values as per above however it doesnt display any choices in the browser.
I ran into this exact same problem and solved it like this:
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self,*args,**kwargs):
myChoices = kwargs.pop("choices")
widgets = (
widgets.TextInput(),
widgets.Select(choices=myChoices)
)
super(InputAndChoiceWidget, self).__init__(widgets,*args,**kwargs)
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self,*args,**kwargs):
# you could also use some fn to return the choices;
# the point is, they get set dynamically
myChoices = kwargs.pop("choices",[("default","default choice")])
fields = (
fields.CharField(),
fields.ChoiceField(choices=myChoices),
)
super(InputAndChoiceField,self).__init__(fields,*args,**kwargs)
# here's where the choices get set:
self.widget = InputAndChoiceWidget(choices=myChoices)
Add a "choices" kwarg to the widget's constructor. Then explicitly call the constructor after the field is created.
ModelChoiceField is technically a ChoiceField, but it doesn't actually use any of the ChoiceField's implementations. So, here's how I use it.
class ChoiceInputMultiWidget(MultiWidget):
"""Kindly provide the choices dynamically"""
def __init__(self, attrs=None):
_widget = (
Select(attrs=attrs),
TextInput(attrs=attrs)
)
super().__init__(_widget, attrs)
class ModelChoiceInputField(MultiValueField):
widget = ChoiceInputMultiWidget
def __init__(self, *args, **kwargs):
_fields = (
ModelChoiceField(queryset=Type.objects.all()),
CharField()
)
super().__init__(_fields, *args, **kwargs)
# Use the auto-generated widget.choices by the ModelChoiceField
self.widget.widgets[0].choices = self.fields[0].widget.choices
Have a look at the source of __init__ of forms.MultiValueField:
def __init__(self, fields=(), *args, **kwargs):
super(MultiValueField, self).__init__(*args, **kwargs)
# Set 'required' to False on the individual fields, because the
# required validation will be handled by MultiValueField, not by those
# individual fields.
for f in fields:
f.required = False
self.fields = fields
So I would overwrite the __init__ probably like this:
def __init__(self, *args, **kwargs):
choices = kwargs.pop("choices",[])
super(InputAndChoiceField, self).__init__(*args, **kwargs)
self.fields = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
You might even want to do super(MultiValueField, self).__init__(*args, **kwargs) instead of super(InputAndChoiceField, self).__init__(*args, **kwargs) because you are setting the fields yourself instead of getting them via parameters.
passing the choices in the widget solved this for me
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
choices = [('a', 1), ('b', 2)]
widget = (widgets.TextInput(),
widgets.Select(choices=choices)
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
class HTML5DateInput(DateInput):
input_type = 'date'
class CustomSelectRangeWidget(forms.MultiWidget):
def __init__(self, attrs=None, choices = ()):
widgets = (Select(attrs=attrs, choices=choices), HTML5DateInput(attrs=attrs), HTML5DateInput(attrs=attrs))
super(CustomSelectRangeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.field, value.start, value.stop]
return [None, None, None]
def format_output(self, rendered_widgets):
return '-'.join(rendered_widgets)
class CustomSelectRangeField(forms.MultiValueField):
widget = CustomSelectRangeWidget
def __init__(self, *args, **kwargs):
if kwargs.has_key('choices') :
choices = kwargs.pop('choices')
else:
choices = ()
fields = (
forms.ChoiceField(choices=choices), #field with choices,
# so that clean can be passed
forms.DateField(),
forms.DateField(),
)
super(CustomSelectRangeField, self).__init__(fields=fields, *args, **kwargs)
#initialize widget with choices.
self.widget = CustomSelectRangeWidget(choices=choices)
def compress(self, data_list):
if data_list:
#check if datalist has 3 not null values
if len([v for v in data_list if v not in [None, '']]) == 3:
out_dict = {'field':data_list[0], 'start':data_list[1], 'stop':data_list[2]}
return out_dict
return None
Related
I am trying to build a custom MultiValue field in django that consists of two widgets: RadioSelect and TextInput: if a user chooses 'Other' then they can insert the value there.
Everything works, with one weird exception: the labels for radio buttons are not shown (see picture). Values are rendered ok, but the labels are just not there. What I am doing wrong?
fields.py
from .widgets import OtherSelectorWidget
class OtherModelField(models.CharField):
def __init__(self, *args, **kwargs):
self.inner_choices = kwargs.pop('choices', None)
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
return OtherFormField(choices=self.inner_choices, **kwargs)
class OtherFormField(MultiValueField):
def __init__(self, **kwargs):
self.choices = kwargs.pop('choices')
self.widget = OtherSelectorWidget(choices=self.choices)
fields = (CharField(), CharField(),)
super().__init__(fields=fields, require_all_fields=False, **kwargs)
def compress(self, data_list):
return str(data_list)
widgets.py
from datetime import date
from django.forms import widgets
class OtherSelectorWidget(widgets.MultiWidget):
def __init__(self, choices=None, attrs=None):
self.choices = choices
_widgets = (
widgets.RadioSelect(choices=choices),
widgets.TextInput(attrs=attrs),
)
super().__init__(_widgets, attrs)
def decompress(self, value):
if value:
return [value[0], value[1]]
return [None, None, ]
def format_output(self, rendered_widgets):
return ''.join(rendered_widgets)
def value_from_datadict(self, data, files, name):
datelist = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
radio_data = self.widgets[0].value_from_datadict(data, files, name + '_0')
text_data = self.widgets[1].value_from_datadict(data, files, name + '_1')
try:
D = [radio_data, text_data]
except ValueError:
return ''
else:
return D
it seems to be a glitch in Django. Here is a link to a ticket: https://code.djangoproject.com/ticket/29200
I dealt with it by adding wrap_label to widget's context:
class OtherSelectorWidget(widgets.MultiWidget):
def get_context(self, name, value, attrs):
con = super().get_context(name, value, attrs)
con['wrap_label'] = True
return con
Then everything is rendered properly
I am trying to display the init value (State) in my form, but it keep showing the "-------" value.
I know the value is not empty because it saves the states into my database. It just wont show up in my form
My form class:
class AddressUpdateForm(BaseModelForm):
state = ProvinceModelChoiceField(required=False, queryset=CountryProvincePair.objects.all())
class Meta:
model = Address
fields = ('label', 'postal', 'zip', 'city', 'country', 'state', 'custombillto')
def __init__(self, *args, **kwargs):
super(AddressUpdateForm, self).__init__(*args, **kwargs)
print("THIS IS IT", self.instance.state)
self.fields['state'].instance = self.instance.state
My custom ModelChoiceField:
class ProvinceModelChoiceField(ModelChoiceField):
"""
A model choice field that accepts custom values for province class.
Should be used together with Chosen with extended functionality.
This model will accept everything that is not in its Query initial values
"""
def label_from_instance(self, obj):
return "%s, %s" % (obj.province, obj.country)
def to_python(self, value):
if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
value = self.queryset.get(**{key: value})
except (ValueError, TypeError, self.queryset.model.DoesNotExist):
# If the object does not exist, does not raise an error
pass
return value
BaseModelForm:
class BaseModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BaseModelForm, self).__init__(*args, **kwargs)
for label, field_instance in self.fields.items():
if isinstance(field_instance, ModelChoiceField) or isinstance(field_instance, ChoiceField):
field_instance.widget.attrs['class'] = 'chosen'
field_instance.help_text = ''
if isinstance(field_instance, BaseTemporalField):
field_instance.widget.attrs['class'] = 'dp'
if isinstance(field_instance, NullBooleanField):
field_instance.widget.attrs['class'] = 'switch'
if isinstance(field_instance, BooleanField):
field_instance.widget.attrs['class'] = 'switch'
How can I pass a parameter in to a ModelForm Field constructor?
class ThingSettingsForm(ModelForm):
things = forms.ModelChoiceField(empty_label='--',queryset=self.?????)
def __init__(self, *args, **kwargs):
super(ThingSettingsForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
instance = kwargs['instance']
?????? = instance.visible_things
#self.fields['things'] =
#forms.ModelChoiceField(queryset=instance.visible_things)
class Meta:
model = Gallery
fields = (
'title',
'summary',
'things',
)
In the underlying model 'things' is a models.ForeignKey, and the default of showing every possible relation is not appropriate.
If visible_things is a queryset, you can change the queryset attribute of the form field:
self.fields['things'].queryset = instance.visible_things
It really does have to be a queryset, not an arbitrary iterable, but other than that it's easy.
Just add a kwarg if the arg is optional:
myform = ThingSettingsForm(thing=<my instance>)
You'll have to change the init method to pop your new arg first, as super isn't expecting this arg:
def __init__(self, *args, **kwargs):
instance = kwargs.pop('thing', None)
super(ThingSettingsForm, self).__init__(*args, **kwargs)
Or if its required, just add it into the init signature:
def __init__(self, thing, *args, **kwargs):
pass
and call it thus:
myform = ThingSettingsForm(<my instance>)
I have field in my model:
TYPES_CHOICES = (
(0, _(u'Worker')),
(1, _(u'Owner')),
)
worker_type = models.PositiveSmallIntegerField(max_length=2, choices=TYPES_CHOICES)
When I use it in ModelForm it has "---------" empty value. It's TypedChoiceField so it hasn't empty_label attribute., so I can't override it in form init method.
Is there any way to remove that "---------"?
That method doesn't work too:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
if self.fields['worker_type'].choices[0][0] == '':
del self.fields['worker_type'].choices[0]
EDIT:
I managed to make it work in that way:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
if self.fields['worker_type'].choices[0][0] == '':
worker_choices = self.fields['worker_type'].choices
del worker_choices[0]
self.fields['worker_type'].choices = worker_choices
The empty option for any model field with choices determined within the .formfield() method of the model field class. If you look at the django source code for this method, the line looks like this:
include_blank = self.blank or not (self.has_default() or 'initial' in kwargs)
So, the cleanest way to avoid the empty option is to set a default on your model's field:
worker_type = models.PositiveSmallIntegerField(max_length=2, choices=TYPES_CHOICES,
default=TYPES_CHOICES[0][0])
Otherwise, you're left with manually hacking the .choices attribute of the form field in the form's __init__ method.
self.fields['xxx'].empty_value = None would not work If you field type is TypedChoiceField which do not have empty_label property.
What should we do is to remove first choice:
class JobOpinionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
for field_name in self.fields:
field = self.fields.get(field_name)
if field and isinstance(field , forms.TypedChoiceField):
field.choices = field.choices[1:]
Try:
def __init__(self, *args, **kwargs):
super(JobOpinionForm, self).__init__(*args, **kwargs)
self.fields['worker_type'].empty_value = None
https://docs.djangoproject.com/en/1.3/ref/forms/fields/#typedchoicefield
Is there any easy way to assign class=radio to all elements whose widget is radioselect in a form ?
I know I can write such that
rb = forms.ChoiceField( widget=forms.RadioSelect(attrs='class':'radio'))
for all radio buttons in the form but I think there should be other ways. Because I probably use it for all radio buttons and it is not funny to write this for all.
You can create your own widget, like this:
class MyRadioSelect(forms.RadioSelect):
def __init__(self, *args, **kwargs):
attrs = kwargs.pop("attrs", {})
if "class" in attrs:
attrs["class"] = "%s radio" % attrs["class"]
else:
attrs["class"] = "radio"
kwargs["attrs"] = attrs
super(MyRadioSelect, self).__init__(*args, **kwargs)
like this?
class ClassyRadioSelect(forms.RadioSelect):
def __init__(self, *args, **kwargs):
#yes, i've to look up how to process args and kwargs properly
attrs = kwargs.get('attrs', {})
attrs['class'] = ' '.join((attrs.get('class',''), 'radio'))
kwargs['attrs'] = attrs
super(ClassyRadioSelect, self).__init__(*args, **kwargs)
class ClassyChoiceField(forms.ChoiceField):
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
if not widget:
widget = ClassyRadioSelect()
super(ClassyChoiceField, self).__init__(choices, required, widget, label
, initial, help_text, *args,
**kwargs)
class MyForm(forms.Form):
classy_field1 = ClassyChoiceField()
classy_field2 = ClassyChoiceField()