实例

(自动)实体窗体

查看一些可用的多维数据集 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_fieldsrender_buttons 基本表单呈现器的方法来按我们的需要排列字段:这里我们首先有一个两列表,其中包含发件人、收件人和主题字段的标签和值(遵循表单顺序),然后是表单控件,然后是一个包含电子邮件内容文本区域的DIV。

要将此呈现器绑定到我们的窗体,我们应该在上面的窗体定义中添加:

form_renderer_id = 'massmailing'