Сохранение модели формы с использованием MultiWidget и MultiValueField
Я пытаюсь понять, как создавать формы путем создания подклассов MultiWidgets и MultiValueFields. У меня есть простая модель адреса и связанные формы:
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
Ну, я не могу понять, как использовать эту форму на мой взгляд. Я пытаюсь сделать:
@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 HttpResponse(address_form.errors['address'])
return render_to_response('profile/addresses.html',context_instance=RequestContext(request,{'address_form':address_form}))
В результате Django выдает мне эту ошибку:
Введите список значений.
Кроме того, когда я пытаюсь напечатать request.POST.items(), он дает адрес ответа как 3 отдельных данных.
Я совершенно заблудился здесь, я должен получить данные об адресе в одну строку. Как мне этого добиться, сохранив только форму?
Я действительно ценю это, если кто-то дает мне четкое объяснение.
3 ответа
Вот проблемы, которые я вижу в вашем коде, которые должны решить эту проблему:
(1). В вашем методе инициализации AddressField, когда вы вызываете init суперкласса, вы должны передать поля в качестве аргумента.
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). Вы правы, ваш value_from_datadict неверен. Дело в том, что вы использовали MultiValueField для заполнения виджетом. Таким образом, виджет должен возвращать список значений в соответствующие подполя в AddressField
Вы можете просто вызвать value_from_datadict суперкласса, и он сделает эту работу, или использовать это (что я думаю, то же самое):
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
Важно понимать основную концепцию. Вы могли бы использовать этот виджет с CharField тоже. В этом случае value_from_datadict должен был вернуть строку. Но так как вы используете MultiValueField, тип возвращаемого значения должен быть списком. Это и есть причина, по которой "введите список значений" в качестве ошибки
Просто дополнительная мысль, вы не должны использовать пробел в качестве разделителя, если вы планируете воссоздать адресные строки 1, 2 и 3 из значений, хранящихся в базе данных, в форму. Если нет то все хорошо:)
Я не нашел хороших примеров для MultiValueField и MultiWidget в документации или в сети, но, поскольку мне приходилось использовать их в одном из моих проектов, мне пришлось самому в них разбираться. Надеюсь это поможет:)
Я думаю, что вам также нужен метод распаковки, и поля = (...) должны быть за пределами __init__
Исходные файлы Django часто могут быть хорошим источником вдохновения. (Пан не предназначен) Вы можете, например, проверить django.forms.fields.SplitDateTimeField
, который дает пример того, как сделать что-то подобное.
Некоторые возможные ошибки могут быть в том, что вы устанавливаете self.widget после инициализации (super(AddressField,self).__init__()
), поэтому поле просто использует стандартный виджет. А ты не отправил fields
в с __init__
или. Вот краткий набросок того, как я думаю, вы могли бы сделать 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]
Это исключает все фетиш, который вы имели в AddressFieldWidget
потому что я, честно говоря, не совсем понял, что ты пытался сделать:)