[英]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)
注意: 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()
的参数中删除name
和value
是错误的决定,因为确实违反了LSP原则 。 输入字段的最基本内容也是name
和value
,它们也包含在POST请求中。
之后,我发现了Forms.py内部的渲染过程。 该小部件呈现在BoundField()
类内,该类在被调用时会自动调用as_widget()
函数。 因此, forms.py完成了渲染的所有过程。
接下来,我用示例应用程序演示该过程。
1)首先,我们有一个表单类:
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)其次,我们的观点是:
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)模板:
{% 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()
中。 此函数负责通过调用其他较小的函数或类来输出呈现的最终结果。
form = ContactForm()
我们知道称为ContactForm()
类。 但是,这个类是子类Form
,其住在forms.py并Form()
是一个小孩BaseForm()
类。 这意味着ContactForm()
继承了BaseForm()
所有内容。 Form()
类为空。 仅从BaseForm()
继承,并且用于继承目的。 每个表单的真正父对象都是BaseForm()
类。 ContactForm()
及其父母Form()
和BaseForm()
被调用时,发生了一些非常重要的变量,例如self.fields
,其中包含所有子级字段。 在我们的示例中: subject
, email
, message
。 其他重要变量是:
self.data
:包含POST数据 self._errors
:在调用clean()之后存储错误。 _html_output()
内部 此函数遍历self.fields
(在我们的示例中遍历subject
, email
和message
)并为每个类调用BoundField()
类,如下所示:
bf = BoundField(frm_clss, field, name)
第一个参数frm_clss
是BaseForm()
类。 第二个是字段列表中的当前字段(例如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))
默认情况下,调用窗口小部件的render()
方法,并将html字符串返回给BaseForm()
类( _html_output()
)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.