开发者

Saving a form model with using MultiWidget and a MultiValueField

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜