【问题标题】:Refactoring a Django form which renders fields manually重构手动呈现字段的 Django 表单
【发布时间】:2018-02-20 22:23:38
【问题描述】:

我正在编写一个代码库,其中包含按照https://docs.djangoproject.com/en/2.0/topics/forms/#rendering-fields-manually 编写的表单。这是表单模板的 sn-p,它使用Django Widget Tweaks

{% load widget_tweaks %}
{% csrf_token %}

<div class="row">
  <div class="input-field col s12">
    {{ form.session_number|add_error_class:"invalid" }}
    {% if form.session_number.errors %}
      <span id="{{ form.session_number.id_for_label }}-error" class="error">{{ form.session_number.errors|join:", " }}</span>
    {% endif %}
    <label for="{{ form.session_number.id_for_label }}"
        class="{% if form.session_number.value %}active{% endif %} {% if form.name.errors %}invalid{% endif %}">
      Session Number
    </label>
  </div>
</div>

<div class="row">
  <div class="col s12">
    <label for="email">Scheduled For</label>
  </div>
  {{ form.scheduled_for }}
</div>

{% if form.family.value %}
  <input name="family" required="" id="id_family" type="hidden" value="{{ form.family.value }}" />
{% else %}
  <div class="row">
    <div class="input-field col s12">

      {{ form.family|add_error_class:"invalid" }}
      {% if form.family.errors %}
        <span id="{{ form.family.id_for_label }}-error" class="error">
          {{ form.family.errors|join:", " }}
        </span>
      {% endif %}
      <label for="{{ form.family.id_for_label }}" class="{% if form.family.errors %}invalid{% endif %}">
        Family
      </label>
    </div>
  </div>
{% endif %}

<div class="row">
  <div class="input-field col s12">
    {{ form.expert|add_error_class:"invalid" }}
    {% if form.expert.errors %}
      <span id="{{ form.expert.id_for_label }}-error" class="error">
        {{ form.expert.errors|join:", " }}
      </span>
    {% endif %}
    <label for="{{ form.expert.id_for_label }}" class="{% if form.expert.errors %}invalid{% endif %}">
      Expert
    </label>
  </div>
</div>

<div class="row">
  <div class="input-field col s12">
    {{ form.session_type|add_error_class:"invalid" }}
    {% if form.session_type.errors %}
      <span id="{{ form.session_type.id_for_label }}-error" class="error">
        {{ form.session_type.errors|join:", " }}
      </span>
    {% endif %}
    <label for="{{ form.session_type.id_for_label }}" class="{% if form.session_type.errors %}invalid{% endif %}">
      Session Type
    </label>
  </div>
</div>

<div class="row">
  <div class="input-field col s12">
    {{ form.status|add_error_class:"invalid" }}
    {% if form.status.errors %}
      <span id="{{ form.status.id_for_label }}-error" class="error">
        {{ form.status.errors|join:", " }}
      </span>
    {% endif %}
    <label for="{{ form.status.id_for_label }}" class="{% if form.status.errors %}invalid{% endif %}">
      Status
    </label>
  </div>
</div>

下面是表单对应部分的外观:

如你所见,代码远非 DRY,django.forms.Form 类与模板之间也存在耦合:每当向前者添加字段时,都需要在模板。

我正在考虑是否使用例如https://docs.djangoproject.com/en/2.0/ref/forms/renderers/#the-low-level-render-api 中描述的自定义渲染器来重构此代码。但是,我犹豫这是否值得,因为像 https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html 这样的博客实际上描述了如何通过手动呈现字段来获得对表单的更多控制。

我的问题是:是否有可能使用简单的模板来获得与表单相同的“外观”,例如

<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

使用适当的表单渲染器?或者在这个例子中还有其他方法可以减少代码重复吗?

【问题讨论】:

标签: html django


【解决方案1】:

我最终仍然手动添加字段,但定义了一些自定义过滤器以减少模板中的代码重复。下面是重构后对应的模板sn-p:

{% load widget_tweaks %}
{% load custom_errors label_with_classes %}
{% csrf_token %}

<div class="row">
  <div class="input-field col s12">
    {{ form.session_number|add_error_class:"invalid" }}
    {{ form.session_number|custom_errors }}
    {{ form.session_number|label_with_classes }}
  </div>
</div>

<div class="row">
  <div class="col s12">
    <label for="email">Scheduled For</label>
  </div>
  {{ form.scheduled_for }}
</div>

{% if form.family.value %}
  <input name="family" required="" id="id_family" type="hidden" value="{{ form.family.value }}" />
{% else %}
  <div class="row">
    <div class="input-field col s12">
      {{ form.family|add_error_class:"invalid" }}
      {{ form.family|custom_errors }}
      {{ form.family|label_with_classes }}
    </div>
  </div>
{% endif %}

<div class="row">
  <div class="input-field col s12">
    {{ form.expert|add_error_class:"invalid" }}
    {{ form.expert|custom_errors }}
    {{ form.expert|label_with_classes }}
  </div>
</div>

<div class="row">
  <div class="input-field col s12">
    {{ form.session_type|add_error_class:"invalid" }}
    {{ form.session_type|custom_errors }}
    {{ form.session_type|label_with_classes }}
  </div>
</div>

<div class="row">
  <div class="input-field col s12">
    {{ form.status|add_error_class:"invalid" }}
    {{ form.status|custom_errors }}
    {{ form.status|label_with_classes }}
  </div>
</div>

custom_errors 过滤器在templatetags/custom_errors.py 中定义如下:

from django import template
from django.utils.safestring import mark_safe

register = template.Library()


@register.filter
def custom_errors(bound_field):
    return mark_safe(f'<span id="{bound_field.id_for_label}-error" \
                     class="error">{", ".join(bound_field.errors)}</span>' if bound_field.errors else '')

自定义标签标签定义在templatetags/label_with_classes.py:

from django import template
from django.forms import ChoiceField, BooleanField, DateField
from titlecase import titlecase

register = template.Library()


def pretty_name(name):
    """Convert 'first_name' to 'First Name'."""
    return titlecase(name.replace('_', ' '))


def should_be_active(bound_field):
    '''Determine whether the field's label should have Material's "active" class added to it'''
    return (bound_field.value() and not isinstance(bound_field.field, (ChoiceField, BooleanField)))\
        or isinstance(bound_field.field, DateField)


@register.filter(is_safe=True)
def label_with_classes(bound_field, contents=None):
    '''Extend Django's label_tag() method to provide the appropriate Materialize CSS classes'''
    active = 'active' if should_be_active(bound_field) else ''
    invalid = 'invalid' if bound_field.errors else ''
    classes = f"{active} {invalid}".strip()
    contents = contents or pretty_name(bound_field.name)
    return bound_field.label_tag(attrs={'class': classes},
                                 contents=contents,
                                 label_suffix='')

这使代码更简洁一些,也明确了各个字段“复制粘贴”背后的一些逻辑。

【讨论】:

    猜你喜欢
    • 2020-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 2016-04-08
    • 2019-07-21
    相关资源
    最近更新 更多