我一直觉得我从Django表格的一半功能中受益,但在“功能”的另一半中遭受了很大的痛苦。
这是一个有趣的用例。我有一个表单,允许用户编辑他们的“个人资料”。这包含一些对象,即:
class UserProfile(models.Model):
default_address = models.ForeignKey("Address")
default_phone_number = models.ForeignKey("PhoneNumber")
class Address(models.Model):
name = models.CharField()
street_address = models.CharField()
street_address_2 = models.CharField()
city = models.CharField()
country = models.ForeignKey("locality.Country")
territory = models.ForeignKey("locality.Territory", blank=True, null=True)
postal_code = models.CharField()
class PhoneNumber(models.Model):
name = models.CharField()
number = models.CharField()
“locality。*”模型来自我写的另一个名为django-locality的项目,可以查看here。
(我写了django-locality,因为当时根本没有办法做我想做的事情。我只想创建这个表格,其中包括一个国家和一个地区。由于没有任何东西可以让我访问国家及其地区的数据库,我建立了一些工作。我需要允许用户选择一个国家,只有当该国家有地区时才能选择该国家的地区很简单,但显然以前没有做过。)
所以这里的事情变得有点复杂。我的表单会修改django.contrib.auth.models.User
的{{1}}和first_name
字段,以及创建或更新last_name
类拥有的Address
和PhoneNumber
个实例。
验证变得非常复杂。我需要确保1.如果某个国家/地区有地区,则必须选择,并且2.如果选择了某个地区,则该地区必须属于所选国家/地区。此外,我最终在我的模板中提供了一个空白的选择控件,因为必须根据所选国家/地区动态提取区域。如果简单地拥有一个像“ModelOptgroupChoiceField”这样的表单字段,这将允许我按照国家/地区的缩写对我的地区进行分组,在每个国家/地区使用optgroup进行选择控制,然后在JavaScript中过滤掉这些内容本来是很好的,但无论如何。经过深思熟虑和实验,我至少能够让它工作。
验证的另一个复杂因素是验证电话号码和邮政编码:我应该如何验证它们?当然,UserProfile
提供了控件,但基本上没有提供单个自动本地化控件。我可以编写一些疯狂的逻辑,它将使用输入国家的缩写在django.contrib.localflavors
包中查找并动态将我的表单中的django.contrib.localflavors
和phone_number
字段设置为正确的值,但严重吗?我是否需要在极长的时间内进行黑客攻击才能让事情发生?我基本上完全放弃了对这些字段的验证/格式化。
postal_code
如果您认为我的表单有点复杂,请等到您看到我的模板以便正确输出内容:
class ProfileEditForm(forms.Form):
default_error_messages = {
'invalid_territory': _("Please select a territory."),
'invalid_country': _("Please select a country."),
}
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
street_address = forms.CharField(max_length=128)
street_address_2 = forms.CharField(max_length=128, required=False)
city = forms.CharField(max_length=128)
country = forms.ModelChoiceField(Country.objects.all().order_by('name'),
empty_label=u'', to_field_name='iso2')
territory = forms.ModelChoiceField(Territory.objects.all().order_by(
'country__name', 'name'), empty_label=u'', to_field_name='pk')
zipcode = forms.CharField(max_length=12)
phone_number = forms.CharField(max_length=16)
def __init__(self, *args, **kwargs):
if 'user' in kwargs:
user = kwargs['user']
del kwargs['user']
kwargs['initial'] = {
'first_name': user.first_name,
'last_name': user.last_name,
'street_address': user.profile.default_address.street_address
if user.profile.default_address != None else '',
'street_address_2': user.profile.default_address.street_address_2
if user.profile.default_address != None else '',
'city': user.profile.default_address.city
if user.profile.default_address != None else '',
'country': user.profile.default_address.country.iso2
if user.profile.default_address != None else None,
'territory': user.profile.default_address.territory.pk
if user.profile.default_address != None else None,
'zipcode': user.profile.default_address.postal_code
if user.profile.default_address != None else '',
'phone_number': user.profile.default_phone_number.number
if user.profile.default_phone_number != None else None,
}
super(ProfileEditForm, self).__init__(*args, **kwargs)
def clean(self):
territory = self.cleaned_data.get('territory', None)
country = self.cleaned_data.get('country', None)
if territory == None or Territory.objects.filter(country__id = country.pk,
pk=territory.pk).count() == 0:
self._errors['territory'] = self.error_class([
self.default_error_messages['invalid_territory']])
if territory != None:
del self.cleaned_data.territory
else:
self.cleaned_data['territory'] = Territory.objects.get(
country__id = country.pk, abbr = territory.abbr)
# format phone-number
if re.match(r'^\d{10}$', self.cleaned_data['phone_number']):
match = re.match(r'^(\d{3})(\d{3})(\d{4})$', self.cleaned_data[
'phone_number'])
self.cleaned_data['phone_number'] = "%s-%s-%s" % (match.group(1),
match.group(2), match.group(3))
return self.cleaned_data
好像这还不够,我的观点同样臃肿而复杂:
<form method="post" action="">
<fieldset>
{% csrf_token %}
<legend>Your Name</legend>
<div class="clearfix{% if form.first_name.errors %} error{% endif %}">
<label for="first_name_input">First Name</label>
<div class="input">
<input id="first_name_input" name="first_name" class="span5" type="text"{% if form.first_name.value %} value="{{form.first_name.value}}"{% endif %}></input>
{{ form.first_name.errors }}
</div>
</div>
<div class="clearfix{% if form.last_name.errors %} error{% endif %}">
<label for="last_name_input">Last Name</label>
<div class="input">
<input id="last_name_input" name="last_name" class="span5" type="text"{% if form.last_name.value %} value="{{form.last_name.value}}"{% endif %}></input>
{{ form.last_name.errors }}
</div>
</div>
</fieldset>
<div class="row">
<div class="span7">
<fieldset>
<legend>Your Address</legend>
<div class="clearfix{% if form.street_address.errors %} error{% endif %}">
<label for="street_address_input">Address Line 1</label>
<div class="input">
<input id="street_address_input" name="street_address" class="span5" type="text"{% if form.street_address.value %} value="{{form.street_address.value}}"{% endif %}></input>
{{ form.street_address.errors }}
</div>
</div>
<div class="clearfix{% if form.street_address_2.errors %} error{% endif %}">
<label for="street_address_2_input">Address Line 2</label>
<div class="input">
<input id="street_address_2_input" name="street_address_2" class="span5" type="text"{% if form.street_address_2.value %} value="{{form.street_address_2.value}}"{% endif %}></input>
{{ form.street_address_2.errors }}
</div>
</div>
<div class="clearfix{% if form.city.errors %} error{% endif %}">
<label for="city_input">City</label>
<div class="input">
<input id="city_input" name="city" data-placeholder="Your City" class="span5"{% if form.city.value %} value="{{form.city.value}}"{% endif %}></input>
{{ form.country.errors }}
</div>
</div>
<div class="clearfix{% if form.country.errors %} error{% endif %}">
<label for="country_input">Country</label>
<div class="input">
<select id="country_input" name="country" data-placeholder="Choose a Country..."
class="chzn-select span5"{% if form.country.value %} data-initialvalue="{{form.country.value}}"{% endif %}>
<option value=""></option>
{% for country in countries %}
<option value="{{country.abbr}}"{% if form.country.value == country.iso2 %} selected{% endif %}>{{country.name}}</option>
{% endfor %}
</select>
{{ form.country.errors }}
</div>
</div>
<div class="clearfix{% if form.territory.errors %} error{% endif %}">
<label for="territory_input">Territory</label>
<div class="input">
<select id="territory_input" name="territory" data-placeholder="Choose a State..."
class="chzn-select span5" {% if form.territory.value %} data-initialvalue="{{form.territory.value}}"{% endif %}>
<option value=""></option>
</select>
{{ form.territory.errors }}
</div>
</div>
<div class="clearfix{% if form.zipcode.errors %} error{% endif %}">
<label for="zipcode_input">Postal Code</label>
<div class="input">
<input id="zipcode_input" name="zipcode" class="span5" text="text"{% if form.zipcode.value %} value="{{form.zipcode.value}}"{% endif %}></input>
{{ form.zipcode.errors }}
</div>
</div>
</fieldset>
</div>
</div>
<fieldset>
<legend>Your Phone Number</legend>
<div class="clearfix{% if form.phone_number.errors %} error{% endif %}">
<label for="phone_input" text="text">Phone Number</label>
<div class="input">
<input id="phone_input" name="phone_number" class="span5" text="text"{% if form.phone_number.value %} value="{{form.phone_number.value}}"{% endif %}></input>
{{ form.phone_number.errors }}
</div>
</div>
</fieldset>
<div class="actions clearfix">
<input type="submit" class="btn primary" style="float:right" value="Save Changes"></input>
</div>
</form>
总而言之,写这个表格需要12个多小时,不包括我在django-locality上工作的时间。
对我来说这似乎只是错误。当我被介绍给Django时,我确信它会加速我的发展十倍。不知怎的,我对此印象不足。当然,我必须在这里做一些非常错误的事情。 我在做Django表单错了吗?
答案 0 :(得分:1)
我认为这将有助于进行出色的维基讨论。
验证变得非常复杂。我需要确定 如果一个国家有领土,必须选择一个领土, 2.如果选择了某个地区,则必须属于所选地区 国家。另外,我最终提供了一个空白的选择控件 在我的模板中,因为区域必须基于动态获取 所选国家。简单地拥有一个表单会很不错 像“ModelOptgroupChoiceField”这样的字段可以让我这样做 按照国家/地区的缩写将我的地区分组 控制每个国家的optgroup然后过滤这些 JavaScript,但无论如何。我至少能够让它继续工作 很多考虑和实验。
当我遇到这个问题时,我使用javascript的客户端验证来解决“如果这个选中,那么确保选中”问题。
至于分组,我通常使用来自jquery的multiselect widget。
验证的另一个复杂因素是电话验证 数字和邮政编码:我该如何验证它们?当然,
django.contrib.localflavors
提供控件,但基本上提供 没有单一的自动本地化控制可供使用。我可以写一些疯狂的东西 使用输入国家的缩写来查看事物的逻辑 在django.contrib.localflavors
包中并动态设置我的 我的表单中的phone_number
和postal_code
字段为正确的值, 不过实话说?我是否需要在一个极端的长度上进行攻击 把事情搞定了?我基本上完全放弃了 这些字段的验证/格式化。
对于预填充/屏蔽字段,请使用javascript;对于查找,请使用ajax调用。这样就容易多了。
关于后端验证;我发现自定义字段和验证器有很长的路要走。
django-uni-form是一种优雅的表单渲染方法,可以在某种程度上清理模板。
答案 1 :(得分:0)
你真的需要一个巨大的形式为你的三个模型?三个单独的形式怎么样?
为什么不使用ModelForm
从模型生成表单?您无需手动设置姓名缩写。
您可以在validator中进行手机验证。我不认为你可以摆脱领土验证,但最好放在模型clean
中。
您可以将模板重写为更干。使用custom template tag(包含一个)输出表单。或者它有问题吗?
使用ModelForm
会让您的观点更加清晰。您有时只需要覆盖它save
方法。它看起来应该是这样的。
@login_required
def profile_edit(request):
user_form = forms.UserForm(request.POST or None, prefix='user', instance=request.user)
address_form = forms.AddressForm(request.POST or None, prefix='address', instance=request.user.profile.default_address)
phone_form = forms.PhoneForm(request.POST or None, prefix='phone', instance=request.user.profile.default_phone_number)
if user_form.is_valid() and address_form.is_valid() and \
phone_form.is_valid():
user = user_form.save()
address = address_form.save(commit=False)
address.user_profile = user.profile
address.save()
phone_number = phone_form.save(commit=False)
phone_number.user_profile = user.profile
phone_number.save()
user.profile.default_address = address
user.profile.default_phone_number = phone_number
user.profile.save()
return redirect("/me/profile/")
return dto(request, "desktop/profile/edit.html", {"form": form,
"countries": Country.objects.all().order_by('name'),
"territories": Territory.objects.all().order_by('country__iso2')})
我认为您的模特组织并不好。为什么不将default
字段添加到Phone
和Address
?像这样的东西(用于电话)。
class UserProfile(models.Model):
... your fields here ...
@property
def default_phone(self):
return self.phones.filter(default=True)[0]
class PhoneNumber(models.Model):
profile = models.ForeignKey(UserProfile, related_name='phones')
default = models.BooleanField()
name = models.CharField()
number = models.CharField()