繁体   English   中英

Django:Form或Field类如何呈现窗口小部件? 什么过程?

[英]Django: How Form or Field class render the widget? What's the process?

我已经实现了扩展Widget的类,并且需要为该类实现一个验证系统,但是我认为这与Field类不兼容,因为我应用了自定义render()方法,并且可能违反了LSP原理 (不确定) 。 这是一个例子:

from django import forms
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
from django.utils import formats
from django_future import format_html, flatatt

class InputGeneric(forms.Widget):
"""
Base class for all <input> widgets
"""
input_type = None # Subclasses must define this.
_to_str = None

def __init__(self, attrs=None, single_attrs=None):
    super(InputGeneric, self).__init__(attrs)
    self.single_attrs = single_attrs or ''

def get_attrs(self):
    return self.attrs

def get_attr(self, key):
    return self.attrs.get(key, None)

def render(self, name=None, value=None, attrs=None, single_attrs=None):
    '''
    *The default arguments of this function are:
    (self, name, value, attrs=None)
    - - - -
    single_attrs: is a string of HTML5 single attributes like "required", disabled"
    Example:
    render(single_attrs='required disabled')
    '''
    name = name or self.attrs.get('name', None)

    if name:
        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
    else:
        final_attrs = self.build_attrs(attrs, type=self.input_type)

    value = self.attrs.get('value', None)
    if value:
        # Only add the 'value' attribute if a value is non-empty.
        final_attrs['value'] = force_unicode(self._format_value(value))

    self._to_str = format_html('<input{0} {1} />', flatatt(final_attrs), single_attrs)
    return self._to_str

def get_rendered(self):
    return self.render(attrs=self.attrs, single_attrs=self.single_attrs)

def __str__(self):
    if self._to_str:
        return self._to_str
    self._to_str = self.render()
    return self._to_str

class InputText(InputGeneric):
input_type = 'text'

def __init__(self, attrs=None, single_attrs=None):
    if attrs.get('type', None) is not None:
        del attrs['type']
    super(InputText, self).__init__(attrs, single_attrs)

django_future.py:

注意: Django1.3的six库在这里可用: https : //github.com/django/django/blob/1.5/django/utils/six.py

'''
This Lib contains functions from future implemantations of Django (After v1.3).
'''
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
from django.utils import encoding
import datetime
from decimal import Decimal

    #The six lib is not included in Django 1.3
    #If you have 1.3 (as i have) you can search here in a future version of Django:
    #django.utils -> six
import six

def flatatt(attrs):
    """
    Convert a dictionary of attributes to a single string.
    The returned string will contain a leading space followed by key="value",
    XML-style pairs. It is assumed that the keys do not need to be XML-escaped.
    If the passed dictionary is empty, then return an empty string.

    The result is passed through 'mark_safe'.
    """
    return format_html_join('', ' {0}="{1}"', sorted(attrs.items()))

def format_html(format_string, *args, **kwargs):
    #django.utils.html
    """
    Similar to str.format, but passes all arguments through conditional_escape,
    and calls 'mark_safe' on the result. This function should be used instead
    of str.format or % interpolation to build up small HTML fragments.
    """
    args_safe = map(conditional_escape, args)
    kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
                        six.iteritems(kwargs)])
    return mark_safe(format_string.format(*args_safe, **kwargs_safe))


def format_html_join(sep, format_string, args_generator):
    #django.utils.html
    """
    A wrapper of format_html, for the common case of a group of arguments that
    need to be formatted using the same format string, and then joined using
    'sep'. 'sep' is also passed through conditional_escape.

    'args_generator' should be an iterator that returns the sequence of 'args'
    that will be passed to format_html.

    Example:

    format_html_join('\n', "<li>{0} {1}</li>", ((u.first_name, u.last_name)
    for u in users))

    """
    return mark_safe(conditional_escape(sep).join(
            format_html(format_string, *tuple(args))
            for args in args_generator))


def is_protected_type(obj):
    return isinstance(obj, six.integer_types + (type(None), float, Decimal,
        datetime.datetime, datetime.date, datetime.time))

def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
    if isinstance(s, six.text_type):
        return s
    if strings_only and is_protected_type(s):
        return s
    try:
        if not isinstance(s, six.string_types):
            if hasattr(s, '__unicode__'):
                s = s.__unicode__()
            else:
                if six.PY3:
                    if isinstance(s, bytes):
                        s = six.text_type(s, encoding, errors)
                    else:
                        s = six.text_type(s)
                else:
                    s = six.text_type(bytes(s), encoding, errors)
        else:
            s = s.decode(encoding, errors)
    except UnicodeDecodeError as e:
        if not isinstance(s, Exception):
            raise encoding.DjangoUnicodeDecodeError(s, *e.args)
        else:
            s = ' '.join([force_text(arg, encoding, strings_only,
                    errors) for arg in s])
    return s

因此,我想问一下Field类(或Form类)究竟如何从Widget获取原始值(html),以及如何应用验证过滤器并返回结果。 请提供一个带有说明的小示例,以了解此过程。

* 请注意 ,我已经看过[Django代码] [3],不幸的是我无法完全理解该过程。

感谢是前进。

首先,我不得不说我的方法是不正确的。 render()的参数中删除namevalue是错误的决定,因为确实违反了LSP原则 输入字段的最基本内容也是namevalue ,它们也包含在POST请求中。

之后,我发现了Forms.py内部的渲染过程。 该小部件呈现在BoundField()类内,该类在被调用时会自动调用as_widget()函数。 因此, forms.py完成了渲染的所有过程。

接下来,我用示例应用程序演示该过程。

应用程式结构


1)首先,我们有一个表单类:

my_form.py:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(min_length = 2, max_length = 100, widget = forms.TextInput(attrs = {'placeholder':'Type a subject...'}))
    email = forms.EmailField(required=True, widget = forms.TextInput(attrs={'class': 'span3', 'placeholder': 'Your email...'}))
    message = forms.CharField(widget=forms.Textarea(attrs={'rows': '5', 'placeholder': 'Type a message...'}))

    def clean_message(self):
        message = self.cleaned_data['message']
        num_words = len(message.split())
        if num_words < 4:
            raise forms.ValidationError("Not enough words!")
            return message

2)其次,我们的观点是:

views.py:

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            try:
                send_mail(
                    cd['subject'],
                    cd['message'],
                    cd.get('email', 'noreply@anonymous.com'),
                    ['myemail@ahost.com'],
                )
            except BadHeaderError:
                return HttpResponse('Invalid header found.')

            return HttpResponseRedirect('thank-you/')
    else:
        form = ContactForm()

    return render_to_response('/contact.html', {'form': form})

3)模板:

contact.html

{% if form.errors %}
    <p style="color: red;">
        Please correct the error{{ form.errors|pluralize }} below.
    </p>
{% endif %}

<form action="" method="post">
    <fieldset>
        <legend>Contact</legend>
        <div class="field">
            {{ form.subject.errors }}
        </div>
        {{ form.as_table }}
        <br />
        <button type="submit" class="btn">Submit</button>
    </fieldset>
</form>

处理步骤


1)用户单击指向我们views.py contact()函数的链接,该函数调用我们的表单并将其与我们的模板一起提供。

2)在模板中,当调用{{ form.as_table }} ,应用程序进入Forms.py并找到BaseForm()类,然后查找此函数(复制粘贴):

def as_table(self):
    "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
    return self._html_output(
        normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
        error_row = u'<tr><td colspan="2">%s</td></tr>',
        row_ender = u'</td></tr>',
        help_text_html = u'<br /><span class="helptext">%s</span>',
        errors_on_separate_row = False)

3)上面的函数调用另一个称为self._html_output(...)函数,它当然位于BaseForm()中。 此函数负责通过调用其他较小的函数或类来输出呈现的最终结果。

一些注意事项


  • 当在views.py中执行form = ContactForm()我们知道称为ContactForm()类。 但是,这个类是子类Form ,其住在forms.pyForm()是一个小孩BaseForm()类。 这意味着ContactForm()继承了BaseForm()所有内容。
  • Form()类为空。 仅从BaseForm()继承,并且用于继承目的。 每个表单的真正父对象都是BaseForm()类。
  • 在初始化时, ContactForm()及其父母Form()BaseForm()被调用时,发生了一些非常重要的变量,例如self.fields ,其中包含所有子级字段。 在我们的示例中: subjectemailmessage 其他重要变量是:
    • self.data :包含POST数据
    • self._errors :在调用clean()之后存储错误。

_html_output()内部


此函数遍历self.fields (在我们的示例中遍历subjectemailmessage )并为每个类调用BoundField()类,如下所示:

bf = BoundField(frm_clss, field, name)

第一个参数frm_clssBaseForm()类。 第二个是字段列表中的当前字段(例如subject ),第三个是ContactForm()内给定字段的实际名称,因此三个名称是:“主题”,“电子邮件”和“消息”。

最后,此函数返回所有已渲染小部件的“已编译”且标记为安全的字符串。

output.append(normal_row % {
    'errors': force_unicode(bf_errors),
    'label': force_unicode(label),
    'field': unicode(bf),
    'help_text': help_text,
    'html_class_attr': html_class_attr
    })

    .
    .
    .

return mark_safe(u'\n'.join(output))

BoundField()类


默认情况下,调用窗口小部件的render()方法,并将html字符串返回给BaseForm()类( _html_output() )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM