实例¶
(自动)实体窗体¶
查看一些可用的多维数据集 cubicweb forge 我们发现一些形式操纵。以下示例来自 conference 立方体。它为以下情况扩展了更改状态窗体: Talk
实体正在进入 submitted
州。目标是为提交的谈话选择审阅者。
from cubicweb.web import formfields as ff, formwidgets as fwdgs
class SendToReviewerStatusChangeView(ChangeStateFormView):
__select__ = (ChangeStateFormView.__select__ &
is_instance('Talk') &
rql_condition('X in_state S, S name "submitted"'))
def get_form(self, entity, transition, **kwargs):
form = super(SendToReviewerStatusChangeView, self).get_form(entity, transition, **kwargs)
relation = ff.RelationField(name='reviews', role='object',
eidparam=True,
label=_('select reviewers'),
widget=fwdgs.Select(multiple=True))
form.append_field(relation)
return form
表单的简单扩展可以从 FormView 包装窗体。表单视图实例有一个方便的 get_form
返回要呈现的窗体的方法。这里我们添加一个 RelationField
到基本状态更改窗体。
值得注意的一点是 eidparam
参数:它同时告诉字段和 edit controller
字段链接到特定实体。
因此,完全可以添加将由编辑控制器的特定实例处理的特殊字段。
临时字段窗体¶
我们要定义一个表单,它执行的操作不是编辑实体。其思想是提出一个表单,向结果集中的实体发送电子邮件,该结果集中实现了 IEmailable
.让我们用一个简单的版本来解释一下 cubicweb.web.views.massmailing
.
以下是源代码:
def sender_value(form, field):
return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
def recipient_choices(form, field):
return [(e.get_email(), e.eid)
for e in form.cw_rset.entities()
if e.get_email()]
def recipient_value(form, field):
return [e.eid for e in form.cw_rset.entities()
if e.get_email()]
class MassMailingForm(forms.FieldsForm):
__regid__ = 'massmailing'
needs_js = ('cubicweb.widgets.js',)
domid = 'sendmail'
action = 'sendmail'
sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
label=_('From:'),
value=sender_value)
recipient = ff.StringField(widget=CheckBox(),
label=_('Recipients:'),
choices=recipient_choices,
value=recipients_value)
subject = ff.StringField(label=_('Subject:'), max_length=256)
mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
inputid='mailbody'))
form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
_('send email'), 'SEND_EMAIL_ICON'),
ImgButton('cancelbutton', "javascript: history.back()",
stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
让我们详细说明一下上面发生了什么。我们的表单将包含四个字段:
一个发送者字段,该字段被禁用,只包含用户的姓名和电子邮件
“收件人”字段,该字段将在带有复选框的上下文结果集中显示为用户列表,因此用户仍然可以通过选中或不选中复选框来选择接收邮件的用户。默认情况下,所有这些都将被选中,因为字段值返回一个列表,其中包含与词汇表函数返回的EID相同的EID。
主题字段,限制为256个字符(因此我们知道
TextInput
将使用,如中所述StringField
)邮件正文字段。此字段使用Ajax小部件,在中定义 cubicweb.widgets.js ,此处不显示其定义。注意,尽管我们告诉这个表单需要这个javascript文件 needs_js
最后,我们添加了两个按钮控件:一个用于使用javascript发布表单 ($('#sendmail') 作为获取dom id设置为“sendmail”的元素的jquery调用,它是由其指定的表单dom id domid 属性),另一个用于取消将使用另一个javascript调用返回上一页的表单。另外,我们还指定一个图像作为按钮图标作为资源标识符(请参见 步骤1:厌倦了默认外观? )作为最后一个论点给予 cubicweb.web.formwidgets.ImgButton
.
要查看此表单,我们仍然需要将其包装在视图中。这很简单:
class MassMailingFormView(form.FormViewMixIn, EntityView):
__regid__ = 'massmailing'
__select__ = is_instance(IEmailable) & authenticated_user()
def call(self):
form = self._cw.vreg['forms'].select('massmailing', self._cw,
rset=self.cw_rset)
form.render(w=self.w)
如您所见,我们只是用适当的选择器定义一个视图,因此它只应用于包含 IEmailable
实体,以便只有管理员或用户组中的用户才能使用它。然后在 call() 方法,我们只需选择上面的窗体并调用它 .render() 方法,将输出流作为参数。
提交此表单时,将调用ID为“sendmail”的控制器(使用指定的 action )。此控制器将负责将邮件实际发送到指定的收件人。
这是它的样子:
class SendMailController(Controller):
__regid__ = 'sendmail'
__select__ = (authenticated_user() &
match_form_params('recipient', 'mailbody', 'subject'))
def publish(self, rset=None):
body = self._cw.form['mailbody']
subject = self._cw.form['subject']
eids = self._cw.form['recipient']
# eids may be a string if only one recipient was specified
if isinstance(eids, basestring):
rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids})
else:
rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids)))
recipients = list(rset.entities())
msg = format_mail({'email' : self._cw.user.get_email(),
'name' : self._cw.user.dc_title()},
recipients, body, subject)
if not self._cw.vreg.config.sendmails([(msg, recipients)]):
msg = self._cw._('could not connect to the SMTP server')
else:
msg = self._cw._('emails successfully sent')
raise Redirect(self._cw.build_url(__message=msg))
控制器的入口点是发布方法。在这种情况下,我们只需返回请求中的post值 form 属性,根据“收件人”表单值中的EID获取用户实例,并在调用后发送电子邮件 format_mail()
获取正确的电子邮件。如果我们不能发送电子邮件,或者我们成功地发送了电子邮件,我们会重定向到索引页,并用适当的消息通知用户。
另外,请注意,我们的控制器有一个选择器,它拒绝匿名用户访问它(我们不希望将我们的实例用作垃圾邮件中继),但也会检查表单中是否指定了预期的参数。这避免了以后的防御编程(尽管它不足以处理所有可能的错误情况)。
为了总结我们的示例,假设我们希望使用不同的形式布局,并且现有的渲染器不满足要求(当然,我们首先要检查一下)。然后我们必须定义自己的渲染器:
class MassMailingFormRenderer(formrenderers.FormRenderer):
__regid__ = 'massmailing'
def _render_fields(self, fields, w, form):
w(u'<table class="headersform">')
for field in fields:
if field.name == 'mailbody':
w(u'</table>')
w(u'<div id="toolbar">')
w(u'<ul>')
for button in form.form_buttons:
w(u'<li>%s</li>' % button.render(form))
w(u'</ul>')
w(u'</div>')
w(u'<div>')
w(field.render(form, self))
w(u'</div>')
else:
w(u'<tr>')
w(u'<td class="hlabel">%s</td>' %
self.render_label(form, field))
w(u'<td class="hvalue">')
w(field.render(form, self))
w(u'</td></tr>')
def render_buttons(self, w, form):
pass
我们只需覆盖 _render_fields 和 render_buttons 基本表单呈现器的方法来按我们的需要排列字段:这里我们首先有一个两列表,其中包含发件人、收件人和主题字段的标签和值(遵循表单顺序),然后是表单控件,然后是一个包含电子邮件内容文本区域的DIV。
要将此呈现器绑定到我们的窗体,我们应该在上面的窗体定义中添加:
form_renderer_id = 'massmailing'