跳转到主要内容

一个简单的包,用于管理多模式的视图。

项目描述

简介

collective.multimode view 是一个 Plone 包,用于简化创建多个状态下的视图(或视图小部件),例如包含表单或包含多个步骤的指南的页面。

此产品不能单独使用,您需要手动定义页面,就像您在创建浏览器视图时通常所做的那样。

本README将展示三个如何使用该产品的简单示例。所有示例都可以在samples目录中的源代码中找到。

兼容性

此产品已在 Plone 3.3.5 上进行了测试。

视图示例

示例1:具有两个状态的简单视图

假设您想定义一个显示使用网站的条件或您与用户提供的资料所承担的承诺的视图。

首先,我们需要定义Python视图

from collective.multimodeview.browser import MultiModeView

class Sample1View(MultiModeView):
    modes = ['conditions',
             'data_use']
    default_mode = 'conditions'
    view_name = 'multimodeview_sample1'

“modes”是视图可以采用的模式的列表。对于简单情况,一个列表就足够了。接下来的示例将展示在更复杂情况下使用字典的方法。“default_mode”顾名思义,是该页默认显示的模式。“view_name”是在zcml文件中定义的视图名称(我们将在后面看到)。定义页面基本URL或使用Ajax获取内容时(主要用于视图组件)需要它。

第二步是定义我们页面的模板。

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      metal:use-macro="here/main_template/macros/master"
      xml:lang="en"
      lang="en"
      i18n:domain="collective.multimodeview">
  <body>
    <div metal:fill-slot="main">
      <div tal:condition="view/is_conditions_mode">
        <p>By using this site, you agree on the fact that you will
        not do stupid things.</p>

        <p class="discreet">
          <a tal:attributes="href view/data_use_link">See how we use your data</a>
        </p>
      </div>
      <div tal:condition="view/is_data_use_mode">
        <p>We will sell your email to all known spam database, we need money.</p>

        <p class="discreet">
          <a tal:attributes="href view/conditions_link">See the conditions to use the site</a>
        </p>
      </div>
    </div>
  </body>
</html>

在这个示例中,我们可以看到multimodeviews自动生成的两个属性示例。“is_conditions_mode”:提供一个布尔值,指示视图是否处于“conditions”模式。对于你定义的每个模式,你都可以使用这个快捷方式(“is_xxx_mode”,其中“xxx”是你为该模式定义的名称)。“conditions_link”:提供一个切换页面到“conditions”模式的链接。这可以用于任何模式,除了如果你有一个名为“make”的模式(它会与“make_link”方法冲突)。如果你有“make”模式,那么你将不得不手动使用“make_link”(稍后将进行描述)。

现在你可以在zcml文件中定义你的视图。

<browser:page
    for="*"
    name="multimodeview_sample1"
    class=".views.Sample1View"
    template="sample1.pt"
    permission="zope2.View"
    />

这就完成了,你现在可以访问这个视图并在两种模式之间切换。

现在让我们做一些更有趣的事情。

示例2:玩转表单

第一个示例非常基础,可以通过使用两个页面或浏览器视图简单地完成。第二个示例将展示如何使用视图来管理一些数据。我们将在门户对象上添加一些注释(基本上是一个简单的字符串列表)。视图将能够列出、添加、编辑和删除这些注释。我们假设我们有一个名为“multimodeview_notes_sample”的视图,它提供了一个API来列出、添加、编辑和删除注释(见samples/notes_view.py)。

像往常一样,我们首先定义视图

class Sample2View(MultiModeView):
    """ A view that adds annotations on the portal.
    """
    modes = ['list',
             'add',
             'delete']
    default_mode = 'list'
    view_name = 'multimodeview_sample2'

    @property
    def notes_view(self):
        return self.context.restrictedTraverse('@@multimodeview_notes_sample')

    def _get_note_id(self):
        """ Extracts the note_id from the form, cast it
        to an int.
        Returns None if there is no corresponding note.
        """

    def _check_add_form(self):
        if not self.request.form.get('title'):
            self.errors['title'] = 'You must provide a title'

        return True

    def _check_edit_form(self):
        if self._get_note_id() is None:
            return

        return self._check_add_form()

    def _check_delete_form(self):
        return self._get_note_id() is not None

    def _process_add_form(self):
        self.notes_view.add_note(self.request.form.get('title'))

    def _process_edit_form(self):
        self.notes_view.edit_note(
            self._get_note_id(),
            self.request.form.get('title'))

    def _process_delete_form(self):
        self.notes_view.delete_note(self._get_note_id())

像之前的示例一样,我们定义了我们的模式列表、默认模式和视图名称。我们还定义了一些有用的函数(请参阅源代码以获取完整代码,我已从此处删除以聚焦于重要部分)来管理注释。

重要的函数是_check_xxx_form和_process_xxx_form。

第一个(_check_xxx_form)检查提交的表单是否不包含错误。如果发现错误,则将其添加到类的“errors”字典中,正如我们在_check_add_form中可以看到的,如果标题为空。该方法总是返回True,除非表单(某些字段未提交或用户在正常使用情况下不能更改的值错误)。在这种情况下,该方法返回“False”或None。用户将看到不同的消息。我们可以在_check_delete_form中看到一个示例,它只检查提供的note_id是否正确。

第二个(_process_xxx_form)执行给定模式的代码。它只有在相应的检查方法返回True且未发现任何错误时才会被调用。如果需要,它可以返回一个“mode”名称,这样视图在表单处理完毕后就可以切换回此模式。默认情况下,它将切换到默认模式。

第二步是为此视图定义模板。我们首先创建默认显示的div(或其他任何东西)

<div tal:condition="view/is_list_mode">
  <tal:block tal:define="notes view/notes_view/get_notes;
                         note_exists python: bool([n for n in notes if n])">
    <table class="listing"
           tal:condition="note_exists">
      <thead>
        <tr>
          <th colspan="3">
            Notes
          </th>
        </tr>
      </thead>
      <tbody>
        <tal:block tal:repeat="note python: enumerate(notes)">
          <tr tal:define="note_id python: note[0];
                          note_text python: note[1]"
              tal:condition="note_text">
            <td tal:content="note_text" />
            <td>
              <a tal:attributes="href python: view.make_link('edit', {'note_id': note_id})"
                 title="edit this note">
                <img tal:attributes="src python: '%s/edit.gif' % context.absolute_url()"
                     alt="edit" />
              </a>
            </td>
            <td>
              <a tal:attributes="href python: view.make_link('delete', {'note_id': note_id})"
                 title="delete this note">
                <img tal:attributes="src python: '%s/delete_icon.gif' % context.absolute_url()"
                     alt="delete" />
              </a>
            </td>
          </tr>
        </tal:block>
      </tbody>
    </table>

    <p tal:condition="not: note_exists">
      You do not have any notes for the moment.
    </p>

    <a tal:attributes="href view/add_link">
      Add a new note
    </a>
  </tal:block>
</div>

在这个简短的示例中,我们可以看到“make_link”方法的使用。我们使用它来创建编辑或删除注释的链接。我们无法使用“edit_link”或“delete_link”,因为我们还需要指定要编辑或删除的注释。使用view.make_link(‘edit’,{‘note_id’: note_id})将生成如下链接:http://…./multimodeview_sample2?mode=edit&note_id=2

现在让我们用添加注释的表单来完善我们的模板

<div tal:condition="not: view/is_list_mode">
  <form name="manage_notes_form"
        method="POST"
        tal:define="notes view/notes_view/get_notes;
                    note_id view/_get_note_id;
                    note_text python: (note_id is not None) and notes[note_id] or '';"
        tal:attributes="action view/get_form_action">
    <tal:block tal:condition="view/is_add_mode">
      <div tal:attributes="class python: view.class_for_field('title')">
        <label for="title">Title</label>
        <div class="error_msg"
             tal:condition="view/errors/title|nothing"
             tal:content="view/errors/title" />
        <input type="text"
               name="title"
               tal:attributes="value view/request/form/title | nothing" />
      </div>

      <span tal:replace="structure view/make_form_extras" />

      <input type="submit"
             name="form_submitted"
             value="Add note" />
      <input type="submit"
             name="form_cancelled"
             value="Cancel" />
    </tal:block>
  </form>
</div>

在这段代码中,我们可以看到multimodeview提供的一些有用方法

  • “view/get_for_action”:提供应用于表单的操作。

  • ‘view.class_for_field(field)’:此方法在没有发现此字段的错误时返回‘field’,如果发现错误,则返回‘field error’。这些类名是Archetype提供的默认类名,因此错误将以红色显示在默认的Plone主题中。

  • ‘view/make_form_extras’:此方法应在多模式页面中的每个表单中使用。它添加一些隐藏字段,例如当前正在使用的模式。

我们还可以在表单中看到一些特定性

  • 方法应始终为‘POST’:如果您不使用‘POST’方法,则表单将不会被处理。

  • 用于处理表单的提交输入称为‘form_submitted’。

  • 用于取消的提交输入称为‘form_cancelled’。如果您使用其他名称,则表单将不会被处理。

现在我们可以完成模板,以便也能够管理‘编辑’和‘删除’模式

<tal:block tal:condition="view/is_edit_mode">
  <div tal:attributes="class python: view.class_for_field('title')">
    <label for="title">Title</label>
    <div class="error_msg"
         tal:condition="view/errors/title|nothing"
         tal:content="view/errors/title" />
    <input type="text"
           name="title"
           tal:attributes="value view/request/form/title | note_text" />
    <input type="hidden"
           name="note_id"
           tal:attributes="value note_id" />
  </div>

  <span tal:replace="structure view/make_form_extras" />
  <input type="submit"
         name="form_submitted"
         value="Edit note" />
  <input type="submit"
         name="form_cancelled"
         value="Cancel" />
</tal:block>

<tal:block tal:condition="view/is_delete_mode">
  <p>Are you sure you want to delete this note ?</p>
  <p class="discreet" tal:content="note_text" />

  <input type="hidden"
         name="note_id"
         tal:attributes="value note_id" />

  <span tal:replace="structure view/make_form_extras" />
  <input type="submit"
         name="form_submitted"
         value="Delete note" />
  <input type="submit"
         name="form_cancelled"
         value="Cancel" />
</tal:block>

新代码中没有真正的新内容,但我们现在能够管理注释。

现在系统已完整,我们可以看到一些即将出现的问题

  • 模板代码中存在一些重复,主要针对提交按钮。取消按钮可以被因子化,但处理表单的按钮每次都有不同的名称。

  • 消息总是说“您的更改已保存”,无论您做什么。

让我们快速改进。

示例2.1:使用字典来处理模式

在定义带有字典的模式的列表时,可以快速修复之前看到的两个问题。

让我们定义新的视图,从上一个视图继承

class Sample21View(Sample2View):
    """ A view that adds annotations on the portal.
    """
    modes = {'list': {},
             'add': {'success_msg': 'The note has been added',
                     'error_msg': 'Impossible to add a note: please correct the form',
                     'submit_label': 'Add note'},
             'edit': {'success_msg': 'The note has been edited',
                     'submit_label': 'Edit note'},
             'delete': {'success_msg': 'The note has been deleted',
                        'submit_label': 'Delete note'}
             }

    view_name = 'multimodeview_sample21'

如您所见,对于每个模式,都提供了一个包含三个值的字典

  • success_msg:当表单成功处理时显示的消息。

  • error_msg:在表单中发现错误时显示的消息。

  • submit_label:提交表单按钮的标题。

现在我们也可以更新我们的模板。列出注释的部分没有变化,我们只更新表单

<form name="manage_notes_form"
      method="POST"
      tal:define="notes view/notes_view/get_notes;
                  note_id view/_get_note_id;
                  note_text python: (note_id is not None) and notes[note_id] or '';"
      tal:attributes="action view/get_form_action">
  <tal:block tal:condition="view/is_add_mode">
    <div tal:attributes="class python: view.class_for_field('title')">
      <label for="title">Title</label>
      <div class="error_msg"
           tal:condition="view/errors/title|nothing"
           tal:content="view/errors/title" />
      <input type="text"
             name="title"
             tal:attributes="value view/request/form/title | nothing" />
    </div>
  </tal:block>

  <tal:block tal:condition="view/is_edit_mode">
    <div tal:attributes="class python: view.class_for_field('title')">
      <label for="title">Title</label>
      <div class="error_msg"
           tal:condition="view/errors/title|nothing"
           tal:content="view/errors/title" />
      <input type="text"
             name="title"
             tal:attributes="value view/request/form/title | note_text" />
      <input type="hidden"
             name="note_id"
             tal:attributes="value note_id" />
    </div>
  </tal:block>

  <tal:block tal:condition="view/is_delete_mode">
    <p>Are you sure you want to delete this note ?</p>
    <p class="discreet" tal:content="note_text" />

    <input type="hidden"
           name="note_id"
           tal:attributes="value note_id" />
  </tal:block>

  <span tal:replace="structure view/make_form_extras" />
</form>

如您所见,这个版本比以前的版本短得多。我们甚至可以因子化标题的输入,但这与多模式视图无关,这是正常的Zope/Plone/TAL编码。

您现在可能想知道“我的输入在哪里定义?”。这是view/make_form_extras创建它们的。如果没有找到提交按钮的标签,则不会显示任何按钮。如果找到标签,它将自动生成两个提交按钮。

示例3:创建多步表单

最后一个示例显示了如何处理多步表单。这里使用的方法不是最好的,因为我们通过隐藏输入将数据从一页传递到另一页。对于HTML5爱好者来说,最好使用会话、cookie或甚至本地存储,但这里的目的是更多地展示如何从一个模式导航到另一个模式。

像往常一样,我们首先定义视图

class Sample3View(MultiModeView):
    modes = {'step1': {'submit_label': 'Go to step 2'},
             'step2': {'submit_label': 'Go to step 3'},
             'step3': {'submit_label': 'Go to step 4'},
             'step4': {'submit_label': 'Go to step 5'},
             'step5': {}}

    default_mode = 'step1'
    view_name = 'multimodeview_sample3'

    def check_form(self):
        return True

    def _process_step1_form(self):
        return 'step2'

    def _process_step2_form(self):
        return 'step3'

    def _process_step3_form(self):
        return 'step4'

    def _process_step4_form(self):
        return 'step5'

    def _process_step5_form(self):
        return 'step5'

    @property
    def cancel_mode(self):
        mapping = {'step1': 'step1',
                   'step2': 'step1',
                   'step3': 'step2',
                   'step4': 'step3',
                   'step5': 'step4'}
        return mapping.get(self.mode)

我们已经覆盖了‘check_form’方法,使其始终返回True(我们在这里并不关心值)。_process_xxx_form方法现在返回用户完成步骤后要发送到的步骤。因此,一旦完成第一步,就会显示第二步,依此类推。

‘cancel_mode’属性已被定义为属性,因此其值可以随视图当前使用的模式而变化。您也可以将其定义为简单的属性,但在这种情况下,当取消时,它将始终返回到相同模式。

现在我们可以为我们的视图定义一个简单的模板

<form method="POST"
      tal:attributes="action view/get_form_action">
  <input type="hidden"
         name="step1_value"
         tal:attributes="value view/request/form/step1_value|nothing"
         tal:condition="not: view/is_step1_mode" />

  <input type="hidden"
         name="step2_value"
         tal:attributes="value view/request/form/step2_value|nothing"
         tal:condition="not: view/is_step2_mode" />

  <input type="hidden"
         name="step3_value"
         tal:attributes="value view/request/form/step3_value|nothing"
         tal:condition="not: view/is_step3_mode" />

  <input type="hidden"
         name="step4_value"
         tal:attributes="value view/request/form/step4_value|nothing"
         tal:condition="not: view/is_step4_mode" />

  <div class="field"
       tal:condition="view/is_step1_mode">
    <label for="step1">What is your name?</label>
    <input type="text"
           name="step1_value"
           tal:attributes="value view/request/form/step1_value|nothing" />
  </div>

  <div class="field"
       tal:condition="view/is_step2_mode">
    <label for="step1">What is your quest?</label>
    <input type="text"
           name="step2_value"
           tal:attributes="value view/request/form/step2_value|nothing" />
  </div>

  <div class="field"
       tal:condition="view/is_step3_mode">
    <label for="step1">What is your favorite color?</label>
    <input type="text"
           name="step3_value"
           tal:attributes="value view/request/form/step3_value|nothing" />
  </div>

  <div class="field"
       tal:condition="view/is_step4_mode">
    <label for="step1">What is the air-speed velocity of an unladen swallow?</label>
    <input type="text"
           name="step4_value"
           tal:attributes="value view/request/form/step4_value|nothing" />
  </div>

  <div tal:condition="view/is_step5_mode">
    <p>Yer answers to the questions were:</p>
    <ul>
      <li>What is your name? <span tal:replace="view/request/form/step1_value|nothing" /></li>
      <li>What is your quest? <span tal:replace="view/request/form/step2_value|nothing" /></li>
      <li>What is your favorite color? <span tal:replace="view/request/form/step3_value|nothing" /></li>
      <li>What is the air-speed velocity of an unladen swallow? <span tal:replace="view/request/form/step4_value|nothing" /></li>
    </ul>
  </div>

  <span tal:replace="structure view/make_form_extras" />
</form>

如前所述,此代码远非完美,但展示了通过在‘_process_xxx_form’中返回下一个模式并覆盖‘cancel_mode’属性,如何轻松地在表单之间导航。

但让我们再次使其更简洁。

示例3.1:再次在模式之间导航

我们将使用与先前视图相同的模板,但更新一些内容

  • 取消消息将在每个模式中有所不同。

  • 取消模式将在‘modes’字典中定义。

  • 下一个要使用的模式也将在此定义。

如前所述,我们覆盖了‘check_form’以避免为每个步骤定义一个_check_stepx_form方法。我们定义空方法来处理每个步骤

class Sample31View(MultiModeView):
    modes = {'step1': {'submit_label': 'Go to step 2',
                       'cancel_label': 'Cancel',
                       'success_mode': 'step2',
                       'cancel_mode': 'step1',
                       'cancel_msg': 'You can not go back, mwahaha'}},
             'step2': {'submit_label': 'Go to step 3',
                       'cancel_label': 'Back to step 1',
                       'success_mode': 'step3',
                       'cancel_mode': 'step1'},
             'step3': {'submit_label': 'Go to step 4',
                       'cancel_label': 'Back to step 2',
                       'success_mode': 'step4',
                       'cancel_mode': 'step2'},
             'step4': {'submit_label': 'Go to step 5',
                       'cancel_label': 'Back to step 3',
                       'success_mode': 'step5',
                       'cancel_mode': 'step3'},
             'step5': {}}

    default_mode = 'step1'
    view_name = 'multimodeview_sample31'

    def check_form(self):
        return True

    def _process_step1_form(self):
        pass

    def _process_step2_form(self):
        pass

    def _process_step3_form(self):
        pass

    def _process_step4_form(self):
        pass

您可能已经看到,对于step1,我们还定义了一个‘cancel_msg’。这与示例2.1中显示的‘success_msg’或‘error_msg’具有相同的效果,但它是在用户取消时显示的。

带有视图组件的示例

目前没有带有视图组件的示例,原因很好,因为它们的工作方式与视图完全相同,除了两点

  • 类必须继承collective.multimodeview.browser.MultiModeViewlet而不是collective.multimodeview.browser.MultiModeView。

  • 您必须为类定义一个‘widget_id’属性,以便在处理定义了多个视图组件的页面上的表单时不会发生冲突。

当将视图组件的自动化Ajax版本集成时,将添加示例。

变更日志

0.3 (2015-08-27)

  • 代码清理。[maurits]

0.2 (2013-09-24)

  • 现在‘add_portal_message’只在存在消息时显示消息。例如,它可以设置给定模式的空成功消息。[vincent]

  • 为模式添加了自动处理。当声明此类模式时,在切换到此模式时将自动处理表单。[vincent]

  • 为模式添加了自动重定向到另一个页面的可能性。[vincent]

0.1 (2011-02-25)

  • 为每个模式添加了定义自定义取消按钮标签和自定义取消消息的可能性。[vincent]

  • 现在可以在‘modes’字典中定义在表单处理或用户取消后切换到的模式。[vincent]

  • 添加了示例和README。[vincent]

  • 从Products.plonehrm提取了代码。[vincent]

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

collective.multimodeview-0.3.tar.gz (35.4 kB 查看哈希值)

上传时间

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面