0

我已经实现了一个扩展类,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类)究竟如何从 中获取原始值(html),Widget以及如何应用验证过滤器并返回结果。请提供一个带有描述的小示例,以便理解该过程。

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

谢谢是提前。

4

1 回答 1

6

首先我不得不说我的方法是不正确的。由于确实违反了LSP 原则name,因此删除和value退出的论点是错误的决定。此外,输入字段最基本的东西是它们也包含在POST请求中。render()namevalue

之后我发现了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)其次我们的观点:

视图.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) 模板:

联系人.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.pyform = ContactForm()中执行时,我们知道它被称为类。但是这个类是一个子类,它存在于forms.py中,也是类的子类。这意味着继承自.ContactForm()FormForm()BaseForm()ContactForm()BaseForm()
  • Form()班级是空的。仅继承自BaseForm()并用于继承目的。每个表单的真正父级是BaseForm()类。
  • 在初始化的时候当ContactForm()和他的父母Form()BaseForm()被称为一些非常重要的变量发生时,比如self.fields其中包含了所有孩子的字段。在我们的示例中:subject, email, message. 其他重要的变量是:
    • self.data: 包含POST数据
    • self._errors:在调用 clean() 后存储错误。

代替_html_output()


这个函数迭代self.fields(在我们的例子中是 over subject, email, )并为每一个message调用类,如下所示: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())。

于 2013-03-18T18:07:14.123 回答