我在做Django表单错了吗?

时间:2011-12-06 07:47:49

标签: django django-forms

我一直觉得我从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类拥有的AddressPhoneNumber个实例。

验证变得非常复杂。我需要确保1.如果某个国家/地区有地区,则必须选择,并且2.如果选择了某个地区,则该地区必须属于所选国家/地区。此外,我最终在我的模板中提供了一个空白的选择控件,因为必须根据所选国家/地区动态提取区域。如果简单地拥有一个像“ModelOptgroupChoiceField”这样的表单字段,这将允许我按照国家/地区的缩写对我的地区进行分组,在每个国家/地区使用optgroup进行选择控制,然后在JavaScript中过滤掉这些内容本来是很好的,但无论如何。经过深思熟虑和实验,我至少能够让它工作。

验证的另一个复杂因素是验证电话号码和邮政编码:我应该如何验证它们?当然,UserProfile提供了控件,但基本上没有提供单个自动本地化控件。我可以编写一些疯狂的逻辑,它将使用输入国家的缩写在django.contrib.localflavors包中查找并动态将我的表单中的django.contrib.localflavorsphone_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表单错了吗?

2 个答案:

答案 0 :(得分:1)

我认为这将有助于进行出色的维基讨论。

  

验证变得非常复杂。我需要确定   如果一个国家有领土,必须选择一个领土,   2.如果选择了某个地区,则必须属于所选地区   国家。另外,我最终提供了一个空白的选择控件   在我的模板中,因为区域必须基于动态获取   所选国家。简单地拥有一个表单会很不错   像“ModelOptgroupChoiceField”这样的字段可以让我这样做   按照国家/地区的缩写将我的地区分组   控制每个国家的optgroup然后过滤这些   JavaScript,但无论如何。我至少能够让它继续工作   很多考虑和实验。

当我遇到这个问题时,我使用javascript的客户端验证来解决“如果这个选中,那么确保选中”问题。

至于分组,我通常使用来自jquery的multiselect widget

  

验证的另一个复杂因素是电话验证   数字和邮政编码:我该如何验证它们?当然,   django.contrib.localflavors提供控件,但基本上提供   没有单一的自动本地化控制可供使用。我可以写一些疯狂的东西   使用输入国家的缩写来查看事物的逻辑   在django.contrib.localflavors包中并动态设置我的   我的表单中的phone_numberpostal_code字段为正确的值,   不过实话说?我是否需要在一个极端的长度上进行攻击   把事情搞定了?我基本上完全放弃了   这些字段的验证/格式化。

对于预填充/屏蔽字段,请使用javascript;对于查找,请使用ajax调用。这样就容易多了。

关于后端验证;我发现自定义字段和验证器有很长的路要走。

django-uni-form是一种优雅的表单渲染方法,可以在某种程度上清理模板。

答案 1 :(得分:0)

  1. 你真的需要一个巨大的形式为你的三个模型?三个单独的形式怎么样?

  2. 为什么不使用ModelForm从模型生成表单?您无需手动设置姓名缩写。

  3. 您可以在validator中进行手机验证。我不认为你可以摆脱领土验证,但最好放在模型clean中。

  4. 您可以将模板重写为更干。使用custom template tag(包含一个)输出表单。或者它有问题吗?

  5. 使用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')})
    
  6. 我认为您的模特组织并不好。为什么不将default字段添加到PhoneAddress?像这样的东西(用于电话)。

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