I'm trying to understand how to create forms by subclassing MultiWidgets and MultiValueFields. I have a simple Address model and associated forms:
class Address(models.Model):
user = models.ForeignKey(User)
city = models.CharField(max_length=255)
state = models.CharField(choices = settings.STATES, max_length=50)
postal = models.CharField(max_length=10)
address = models.TextField()
class Meta:
verbose_name_plural = 'Addresses'
class AddressFieldWidget(forms.MultiWidget):
def decompress(self,value):
if value:
return [value[0],value[1],value[2]]
return ''
def format_output(self, rendered_widgets):
str = ''
line_1 = '<td class="align_left"><label for="contact_phone">Address Line 1</label></td>'
for field in rendered_widgets:
str += '<tr>' + line_1
str += '<td class="align_right">%s</td></tr>' % field
return '<tr>' + str + '</tr>'
def value_from_datadict(self,data,files,name):
line_list = [widget.value_from_datadict(data,files,name+'_%s' %i) for i,widget in enumerate(self.widgets)]
try:
return line_list[0] + ' ' + line_list[1] + ' ' + line_list[2]
except:
return ''
class AddressField(forms.MultiValueField):
def __init__(self,*args,**kwargs):
fields = (
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
)
super(AddressField,self).__init__(*args,**kwargs)
self.widget = AddressFieldWidget(widgets=[fields[0].widget, fields[1].widget, fields[2].widget])
def compress(self, data_list):
return data_list[0] + ' ' + data_list[1] + ' ' + data_list[2]
class AddressFormNew(forms.ModelForm):
postal = forms.CharField(widget=forms.TextInput(attrs={'class':'small'}))
address = AddressField()
city = forms.CharField(widget=forms.TextInput(attrs={'class':'big'}))
class Meta:
model = Address
Well I can't figure out how to use this form in my view. I'm trying to do :
@login_required
def render_addresses(request):
address_form = AddressFormNew()
if request.method == 'POST':
address_form = AddressFormNew(request.POST)
if address_form.is_valid():
address_form.save()
return HttpResponse('ok')
else:
return HttpRe开发者_开发问答sponse(address_form.errors['address'])
return render_to_response('profile/addresses.html',context_instance=RequestContext(request,{'address_form':address_form}))
As a result, Django gives me this error:
Enter a list of values.
Also, when I try to print request.POST.items(), it gives address response as 3 seperated datas.
I'm quite lost here, I have to get my address data in one line. How should I achieve that by only saving my form ?
I really appreciate that if someone gives me a clear explanation.
Here are the issues that I see in your code which should solve it:
(1). In your AddressField init method when you are calling the init of super class, you should pass fields as argument.
class AddressField(forms.MultiValueField): def __init__(self,*args,**kwargs): fields = ( forms.CharField(widget=forms.TextInput(attrs={'class':'big'})), forms.CharField(widget=forms.TextInput(attrs={'class':'big'})), forms.CharField(widget=forms.TextInput(attrs={'class':'big'})), ) self.widget = AddressFieldWidget(widgets=[fields[0].widget, fields[1].widget, fields[2].widget]) super(AddressField,self).__init__(fields=fields,*args,**kwargs)
(2). You are correct, your value_from_datadict is incorrect. The point is that, you have used a MultiValueField to get populated by the widget. So the widget must return a list of values to the corresponding sub-fields in AddressField
You can just call the value_from_datadict of the the super class and that will do the job, or use this (which i think is the same):
def value_from_datadict(self,data,files,name): res = [] for i, widget in enumerate(self.widgets): res.append(widget.value_from_datadict(data, files, name + '_%s' % i)) return res
Its important to understand the underlying concept. You could have used this widget with a CharField too. In that case the value_from_datadict should have returned a string. But since you are using MultiValueField, the return type should be a list. That is the very reason for getting the "enter a list of values" as error
Just an additional thought, you should not use space as delimiter if you are planning to re-create address line1, 2 and 3 from values stored in database to a form. If not then all is good :)
I did not find good examples for MultiValueField and MultiWidget in documentation or on the net, but since I had to use them in one of my projects, I had to dig into it myself. Hope this helps :)
i think you also need a decompress method, and fields = (...) should be outside of __init__
Django's source files can often be a good source of inspiration. (Pun not intended) You could for instance check out django.forms.fields.SplitDateTimeField
, which gives an example of how to do something similar.
Some possible errors could be that you are setting self.widget after you initialize (super(AddressField,self).__init__()
), so the field just uses the standard widget. And you didn't send fields
in with the __init__
either. Here's a quick draft of how I think you could do AddressField
:
class AddressField(forms.MultiValueField):
widget = MultiValueWidget(widgets=(forms.TextInput, forms.TextInput, forms.TextInput)
def __init__(self, *args, **kwargs):
fields = (
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
forms.CharField(widget=forms.TextInput(attrs={'class':'big'})),
)
super(AddressField,self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
return "%s %s %s" % data_list[0:2]
This excludes all the mumbo-jumbo you had in AddressFieldWidget
, because I frankly didn't quite understand what you were trying to do :)
精彩评论