Reputation: 4504
All,
I have managed to get MultiValueField and MultiValueWidget working. But, apart from "choices", I cannot seem to add attributes (like "label" or "initial") to the sub-fields that make up the MultiValueField.
Here is my (simplified) code:
class MyMultiWidget(django.forms.widgets.MultiWidget):
def __init__(self,*args,**kwargs):
myChoices = kwargs.pop("choices",[])
widgets = (
django.forms.fields.TextInput(),
django.forms.fields.TextInput(),
django.forms.fields.Select(choices=myChoices),
)
super(MyMultiWidget, self).__init__(widgets,*args,**kwargs)
class MyMultiValueField(django.forms.fields.MultiValueField):
widget = MyMultiWidget
def __init__(self,*args,**kwargs):
myLabel = "my label"
myInitial = "my initial value"
myChoices = [("a","a"),("b","b")]
fields = (
django.forms.fields.CharField(label=myLabel),
django.forms.fields.CharField(initial=myInitial),
django.forms.fields.ChoiceField(choices=myChoices),
)
super(MyMultiValueField,self).__init__(fields,*args,**kwargs)
self.widget=MyMultiWidget(choices=myChoices)
class MyField(models.Field):
def formfield(self,*args,**kwargs):
return MyMultiValueField()
class MyModel(django.models.Model):
myField = MyField(blank=True)
MyForm = modelform_factory(MyModel)
The "myField" field of MyModel is almost rendered correctly in MyForm; It shows three widgets: two TextInputs and a Select. The latter is restricted to the appropriate choices. But the former two don't have their label or initial value set.
Any suggestions on what I'm doing wrong?
Thanks.
Upvotes: 1
Views: 3944
Reputation: 94
I know this thread is over 9 years old but I just solved this problem myself (Python 3.9, Django 3.2).
What I ended up doing was to define an tuple of CharField
objects to be passed to the MultiValueField
constructor. The CharField objects were instantiated with the attributes I wanted to pass to the MultiValueField
(widget
including placeholder
& value
attributes, and help_text
for each).
placeholder
is a pretty effective label for each field and value
can be used as the default.
Note that you can't hardcode value
into the field if you're using generic class-based views on a ModelForm
because it causes the field to be overwritten with the hardcoded value when opening an UpdateView
. To account for this, you have to set a default value for the field in the model (I used a JSONField
), then grab the relevant data from the form instance after it initialises and pass that to the field constructor in the multi_fields tuple.
EDIT: Don't forget to include the model Field in your form's Meta
class. I found out the hard way that your form won't save new values if you miss this.
I then passed that tuple to the constructor as the fields
argument, and used list comprehensions on the tuple to dynamically define the widget
(a Custom MultiWidget constructed with the list comprehension) and help_text
attributes.
# MODELS.PY
from django import models
def default_multi_values():
return {
'Field1': 'Field1Value',
'Field2': 'Field2Value'
}
class ExampleModel(models.Model):
example_field = models.JSONField(
default=default_multi_values()
)
# FORMS.PY
from django import forms
class CustomMultiWidget(forms.widgets.MultiWidget):
def decompress(self, value):
**do decompress**
class CustomMultiValueField(forms.MultiValueField):
widget = CustomMultiWidget
def compress(self, data_list):
**do compress**
class CustomModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
mf_values = self.instance.multi_value_field_here
multi_fields = (
forms.CharField(
help_text='HelpText1',
widget=forms.TextInput({
'placeholder': 'Placeholder1',
'value': mf_values['Field1']
})
),
forms.CharField(
help_text='HelpText2',
widget=forms.TextInput({
'placeholder': 'Placeholder2',
'value': mf_values['Field2']
})
),
)
self.fields['multi_value_field_here'] = CustomMultiValueField(
fields=multi_fields,
widget=ColumnHeadersWidget(
widgets=[field.widget for field in multi_fields],
# Display an ordered list below the MultiWidget
# constructed from help_text messages from each consecutive field.
help_text=
f"<u><strong>MultiField Guide</strong></u></br>"
f"{'</br>'.join(f'{multi_fields.index(field) + 1}: {field.help_text}' for field in multi_fields]}"
)
)
class Meta:
model = ExampleModel
fields = [
'example_field'
]
Upvotes: 0
Reputation: 364
@george is right that the value
can be defined statically, but you can also override get_context
to figure out the value at run-time.
class MyInput(forms.CharField):
def get_context(self, name, value, attrs):
return super().get_context('some_name', 'some_value', attrs)
Will render
<input type="text" name="some_name" value="some_value" ...>
Upvotes: 0
Reputation: 507
The answer provided by ben is a bit hacky, I would pass the initial value using 'attrs':
forms.TextInput(attrs={'value': 'some text'})
Its interesting, that some of the attribute seems to be passed correctly - for example in my case setting 'max_length' on the CharField worked.
Upvotes: 0
Reputation: 2017
You should define the format_output
function of your Widget - see: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
This lets you format the form html any way you like. I think the default is just to concatenate the field elements.
Upvotes: 2