Plone的Macish控制面板。
项目描述
简介
collective.mcp 是一个Plone产品,它帮助为网站用户创建自定义控制面板。mcp代表Mac Control Panel,因为基本主题灵感来源于Mac OSX控制面板。
目标不是取代Plone的控制面板,而是帮助创建一个全新的、专门为用户设计的控制面板。这可能对基于Plone的Web应用程序很有用。
您可以在本页面上查看产品的截图:https://github.com/zestsoftware/collective.mcp/wiki/Screenshots
此产品不会神奇地为您创建网站页面,它仅提供一些API来创建它们,如我们在本README中稍后所见。
兼容性
此产品已在Plone 3.3.5上进行了测试。
安装collective.mcp
在您的buildout中,将collective.mcp添加到eggs目录。运行buildout,再次启动您的实例,并使用quick_installer(或Plone的等效功能)添加产品。现在您可以通过访问https://127.0.0.1:8080/your_plone_site/control_panel来访问控制面板。由于您尚未添加任何页面,您将收到一条消息,告知您无法进行管理。
>>> from collective.mcp import categories, pages >>> categories [] >>> pages []
查看示例
您可以从collective.mcp加载文件samples.zcml以获取一些示例。例如,在您的主题的configure.zcml文件中
<include package="collective.mcp" file="samples.zcml" />
重启实例,重新加载控制面板页面,你应该会看到一些可供使用的页面。
实现您的控制面板
collective.mcp提供了一个控制面板的位置,您可以在https://127.0.0.1:8080/ourplonesite/control_panel/找到。通常情况下,页面会告诉您“您无法管理任何内容”,因为您还没有添加任何页面。
为了简化,我们假设您已经有一个您想添加控制面板的Plone产品。这个产品有一个“browser”包。在browser包内,创建一个名为“control_panel”的包,包含__init__.py和configure.zcml文件。
您看到的__init__.py文件如下(除了您需要用自己的产品消息工厂替换消息工厂)
>>> from collective.mcp import Category, register_category, register_page >>> from collective.mcp import McpMessageFactory as _
configure.zcml文件如下
<configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:five="http://namespaces.zope.org/five" xmlns:i18n="http://namespaces.zope.org/i18n" i18n_domain="your_product"> </configure>
在browser包的configure.zcml文件中,包含您的新包
<include package=".control_panel" />
现在我们有了一个基础,我们可以开始向控制面板添加内容。
创建类别
第一步是创建页面所属的类别。如果您查看docs文件夹中的截图或项目wiki,您会看到有四个类别
个人偏好
客户
模板
设置
在我们的示例中,我们只创建第一个和最后一个类别。要做到这一点,我们在__init__.py中添加以下代码
>>> register_category( ... Category('personal', ... _(u'label_cpcat_personal_prefs', ... default=u'Personal preferences'))) >>> register_category( ... Category('settings', ... _(u'label_cpcat_settings', ... default=u'Settings'), ... after='personal'))
如您所见,我们指定了“设置”类别将在“个人”类别之后显示。我们也可以指定“个人”在“设置”之前,得到相同的结果。
如果您现在重新加载控制面板,没有任何变化。这是正常的,系统不会显示没有页面(或用户不能使用任何页面)的类别。
>>> categories [Category: personal, Category: settings] >>> pages []
创建一个简单的页面
collective.mcp基于collective.multimodeview。对于页面,我们将依赖于在samples中定义的名为'multimodeview_notes_sample'的视图。如果您已经激活了multimodeview的示例,您不需要做任何事情。否则,请将以下行添加到您的configure.zcml文件中
<browser:page for="*" name="multimodeview_notes_sample" class="collective.multimodeview.samples.notes_view.NotesView" permission="zope2.View" />
我们将创建的第一个页面允许使用上面声明的视图提供的API更新“首页消息”。API很简单,不需要太多解释
get_home_message()
set_home_message(msg)
此消息不会在任何地方显示。它可以,但这不是本README的范围。
要创建我们的页面,我们首先将在控制面板包中创建一个新的Python文件,命名为“home_message.py”,其中包含以下代码
from collective.mcp.browser.control_panel_page import ControlPanelPage class HomeMessage(ControlPanelPage): category = 'settings' zcml_id = 'collective_mcp_home_message' widget_id = 'collective_mcp_home_message' modes = {'back': {}, 'default': {'submit_label': 'Update home message', 'success_msg': 'The home message has been updated'}} default_mode = 'default' @property def notes_view(self): return self.context.restrictedTraverse('@@multimodeview_notes_sample') def _check_default_form(self): return True def _process_default_form(self): self.notes_view.set_home_message( self.request.form.get('msg', '')) return 'back'
让我们看看我们定义了什么
- 'category': this is the category to which our now page belongs - 'zmcl_id': this is the name of the page, as defined in the zcml file (we'll see it later) - 'widget_id': this is a unique identifier for your page. Here we used the same one that for the zcml_id ust to avoid any conflict, but it could have benn 'home_message' for example. - modes: this dictionnary defines the list of modes in which the page can be. We defined a 'back' mode, that means that when the form is submitted or when the user cancels, the home of the conrol panel will be shown instead of the form again. For the default mode, we also defined the name of the button to save and the message displayed on success. Have a look to collective.multimodeview README file to see more options you can define for modes. - notes_view: just a helper property to easily get the view with the API. - _check_default_form: a function that checks that the form submitted did not contain error. Here we do not check anything so it's prettu quick, the second example will show more. see colective.multimodeview for more explanation). - _process_default_form: the function called if no errors were found by the previous method. As you can guess by the name, it processes the form (here it updates the home message).
现在我们需要为我们的视图创建一个模板
<form method="post" tal:attributes="action view/get_form_action"> <div class="field"> <label for="msg">Message:</label> <input type="text" name="msg" tal:attributes="value view/notes_view/get_home_message" /> </div> <span tal:replace="structure view/make_form_extras" /> </form>
这里没有什么特别的,除了使用multimodeview的两个方法
- view/get_form_action: gives the action for the form - view/make_form_extra: generates some HTML code with some hidden input fields and the submit buttons.
再次,请查看collective.multimodeview以获取更多解释。
最后一步是在zcml文件中声明我们的视图并注册它。首先,在__init__.py文件中
>>> from collective.mcp.samples.home_message import HomeMessage >>> register_page(HomeMessage)
这使页面出现在“页面”列表中
>>> pages [<class 'collective.mcp.samples.home_message.HomeMessage'>]
然后在ZCML文件中
<browser:page for="*" name="collective_mcp_home_message" class=".HomeMessage" permission="zope.Public" template="home_message.pt" />
现在您可以重新启动服务器并重新加载控制面板。'设置'类别将出现,包含一个带问号图标的单个页面。
>>> self.browser.open('http://nohost/plone/control_panel/') >>> 'There is nothing you can manage.' in self.browser.contents False>>> '<span class="spacer">Settings</span>' in self.browser.contents True>>> '<span class="spacer">Personal preferences</span>' in self.browser.contents False
首先,让我们解决图标问题。在示例目录中,您将找到从以下集合中获取的两个图标:http://www.iconfinder.com/search/?q=iconset%3A49handdrawing
现在我们在zcml中声明home.png文件
<browser:resource name="collective_mcp_home.png" file="home.png" />
现在我们将在视图中使用这个图标
class HomeMessage(ControlPanelPage): icon = "++resource++collective_mcp_home.png"
第二个问题是我们的页面没有标题,这个问题很容易解决
class HomeMessage(ControlPanelPage): title = 'Home message'
现在图像出现在控制面板中,标题也显示了
>>> '<img src="++resource++collective_mcp_home.png"' in self.browser.contents True >>> '<span>Home message</span>' in self.browser.contents True
如果我们点击图标,主页不再显示,我们看到我们的表单
>>> self.browser.getLink('Home message').click() >>> self.browser.url 'http://nohost/plone/control_panel?mode=default&widget_id=collective_mcp_home_message' >>> '<img src="++resource++collective_mcp_home.png"' in self.browser.contents False >>> '<label for="msg">Message:</label>' in self.browser.contents True
我们可以填写首页消息并验证。我们得到一条成功消息显示,并返回到控制面板主页
>>> self.browser.getControl(name='msg').value = 'My new home message - welcome :)' >>> self.browser.getControl(name='form_submitted').click() >>> "<dd>The home message has been updated</dd>" in self.browser.contents True
如果我们取消,我们会得到不同的消息(这是从collective.multimodeview继承的默认取消消息)
>>> self.browser.getLink('Home message').click() >>> self.browser.getControl(name='form_cancelled').click() >>> "<dd>Changes have been cancelled.</dd>" in self.browser.contents True
就这样,您已经成功开启了控制面板的第一页。好吧,这并不是特别有用,但这是一个良好的开端。在Prettig personeel(www.prettigpersoneel.nl - 该产品开发所基于的网站)中,有许多基于相同原理(两种模式:默认和后退)的页面,例如更改密码、设置用户主题、管理联系信息等。
但现在我们想要做一些更有挑战性的:创建一个管理多个对象的页面。
创建多对象管理页面
如果您查看过“collective_multimodeview_notes_samples”页面,您会看到其主要目标是管理附属于网站门户的笔记列表。我们将创建一个控制面板页面来管理这些笔记。为此,在control_panel包中创建notes.py和notes.pt。
notes.py将看起来像这样
from collective.mcp.browser.control_panel_page import ControlPanelPage class Notes(ControlPanelPage): category = 'settings' zcml_id = 'collective_mcp_notes' widget_id = 'collective_mcp_notes' icon = "++resource++collective_mcp_notes.png" title = 'Notes' modes = {'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'} } default_mode = 'edit' multi_objects = True @property def notes_view(self): return self.context.restrictedTraverse('@@multimodeview_notes_sample') def list_objects(self): notes = self.notes_view.get_notes() return [{'id': note_id, 'title': note_text} for note_id, note_text in enumerate(notes) if note_text] def _get_note_id(self): notes = self.notes_view.get_notes() note_id = self.current_object_id() try: note_id = int(note_id) except: # This should not happen, something wrong happened # with the form. return if note_id < 0 or note_id >= len(notes): # Again, something wrong hapenned. return if notes[note_id] is None: # This note has been deleted, nothing should be done # with it. return return note_id def get_note_title(self): """ Returns the title of the note currently edited. """ if self.errors: return self.request.form.get('title') if self.is_add_mode: return '' note_id = self._get_note_id() if note_id is None: # This should not happen. return '' return self.notes_view.get_notes()[note_id] 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')) self.request.form['obj_id'] = len(self.notes_view.get_notes()) - 1 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()) self.request.form['obj_id'] = None
那么,让我们看看与之前页面相比有哪些不同(显然很多)
模式:不再有“后退”模式,因此在提交表单时,我们仍然会看到相同的页面。一些额外的模式出现以管理笔记。
默认模式:设置为“编辑”。这意味着页面将默认尝试编辑找到的第一个对象。
多对象:设置为True。这意味着这个页面可以用来管理多个对象。一个侧边栏将显示对象列表。
list_objects:当将“multi_objects”设置为True时,您必须定义此方法。它返回一个字典列表,包含两个键:一个定义对象的id,另一个定义显示的标题。
_check_xxx_form和_process_xxx_form与之前看到的内容相当相似。一个需要注意的点是我们修改了请求中的‘obj_id’条目,在_process_add_form和_process_delete_form中。在第一种情况下,我们这样做是为了让刚刚添加的笔记被视为当前笔记。在第二种情况下,我们删除条目,以便系统不会将已删除的笔记视为当前笔记(因为它不再存在),并将选择第一个可用的笔记。
现在让我们为我们的页面创建一个模板
<tal:block tal:define="notes view/notes_view/get_notes; note_exists python: bool([n for n in notes if n])"> <form method="post" tal:condition="python: note_exists or view.is_add_mode" tal:define="note_title view/get_note_title" tal:attributes="action view/get_form_action"> <tal:block tal:condition="python: view.is_add_mode or 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 note_title" /> </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_title" /> </tal:block> <input type="hidden" name="obj_id" tal:define="obj_id view/current_object_id" tal:condition="obj_id" tal:attributes="value obj_id" /> <span tal:replace="structure view/make_form_extras" /> </form> <p tal:condition="not: python: note_exists or view.is_add_mode"> There is no note to manage, click the '+' button to create a new one. </p> </tal:block>
在这个模板中,我们可以看到三个重要的事情
使用view/is_xxx_mode:这是由collective.multimodeview提供的辅助函数,用来根据您正在做什么来显示什么。
有一个名为“obj_id”的隐藏字段。这是非常重要的,因为它用于确定您当前正在编辑的对象。
当没有笔记时,会显示一个默认消息。不要忘记它。如果您的页面渲染了一个空字符串,系统将显示菜单的主页。
现在让我们注册我们的页面。首先在__init__.py文件中
>>> from collective.mcp.samples.notes import Notes >>> register_page(Notes) >>> pages [<class 'collective.mcp.samples.home_message.HomeMessage'>, <class 'collective.mcp.samples.notes.Notes'>]
和在configure.zcml中
<browser:page for="*" name="collective_mcp_notes" class=".Notes" permission="zope.Public" template="notes.pt" />
重新启动您的服务器并重新加载控制面板,现在您有两个页面可供使用。
>>> self.browser.open('http://nohost/plone/control_panel/') >>> self.browser.getLink('Notes').click() >>> self.browser.url 'http://nohost/plone/control_panel?mode=edit&widget_id=collective_mcp_notes'
由于您还没有玩过笔记,右侧的列表是空的,并且会显示一条消息告诉您添加一些笔记
>>> import re >>> re.search('(<ul class="objects">\s*</ul>)', self.browser.contents).groups() ('<ul class="objects">...</ul>',) >>> "There is no note to manage, click the '+' button to create a new one." in self.browser.contents True
collective.mcp自动添加了“+”和“-”按钮,这些按钮将触发您新页面的“添加”和“删除”模式。我们将点击“添加”按钮,这将显示创建笔记的表单
>>> self.browser.getLink('+').click() >>> self.browser.url 'http://nohost/plone/control_panel?mode=add&widget_id=collective_mcp_notes' >>> '<label for="title">Title</label>' in self.browser.contents True
您还可以注意到,在添加新对象时,对象列表中会出现新的一行,并显示为选中状态
>>> re.search('(<li\s*class="current">\s*<a>...</a>\s*</li>)', self.browser.contents).groups() ('<li class="current">...<a>...</a>...</li>',)
现在我们将添加一个笔记对象
>>> self.browser.getControl(name='title').value = 'A new note' >>> self.browser.getControl(name='form_submitted').click()
这次我们没有被重定向到控制面板主页,而是直接转到我们刚刚添加的对象的编辑页面,并得到一个成功消息
>>> '<dd>The note has been added</dd>' in self.browser.contents True >>> re.search('(<li class="current">\s*<a href=".*">A new note</a>\s*</li>)', self.browser.contents).groups() ('<li class="current">...<a href="...">A new note</a>...</li>',) >>> re.search('(<input type="text" name="title"\s*value="A new note" />)', self.browser.contents).groups() ('<input type="text" name="title" value="A new note" />',)
我们现在添加第二个笔记
>>> self.browser.getLink('+').click() >>> self.browser.getControl(name='title').value = 'My second note' >>> self.browser.getControl(name='form_submitted').click()
保存这个笔记时,它会被默认选中
>>> re.search('(<li class="current">\s*<a href=".*">My second note</a>\s*</li>)', self.browser.contents).groups() ('<li class="current">...<a href="...">My second note</a>...</li>',) >>> re.search('(<input type="text" name="title"\s*value="My second note" />)', self.browser.contents).groups() ('<input type="text" name="title"...value="My second note" />',)
更多信息
您可以在collective/mcp/doc中找到更多文档。那里有四个额外的文档
- modes.rst - some extra explanation about the ``modes`` attributes of the class. - restriction.rst - explains the diferent methods to restrict access to the pages. - multiobjects.rst - going a bit deeper with the multi-objects views. - defect.rst - some examples of what you should not do. - theming.rst - some hints for theming the control panel.
变更日志
0.5 (2015-08-27)
代码清理。[maurits]
0.4 (2013-09-24)
迁移到github。稍微清理了一下。[maurits]
0.3 (2012-10-30)
左侧面板中按钮的显示更好。[vincent]
0.2 (2011-12-15)
增加了为子页面设置自定义CSS类的功能。默认情况下,它具有类名‘mcp_widget_XXX’,其中XXX是组件ID。[vincent]
0.1 (2011-02-25)
初始发布。[vincent]
项目详情
collective.mcp-0.5.tar.gz的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 75e816786146fcf20e6ecdead51575bffe11c8a4e6a3e27ee0806ae0628a6124 |
|
MD5 | fb5fcf4fddd810f61bca319280e9a2ea |
|
BLAKE2b-256 | a5623b088cd1138dd236a1f0abb422c2e8c6148940c311f1a6d2583cd51bdfb3 |