{ "info": { "author": "Zest Software", "author_email": "info@zestsoftware.nl", "bugtrack_url": null, "classifiers": [ "Framework :: Plone", "Framework :: Plone :: 3.3", "Programming Language :: Python", "Programming Language :: Python :: 2.4" ], "description": "Introduction\n============\n\ncollective.mcp is a Plone product that helps creating a custom control\npanel for the site's users.\nmcp stands for Mac Control Panel, as the base theme is inspired by the\nMac OSX control panel.\n\nThe goal is not to replace Plone's control panel but to help creating\na new one decidated to the users. This will might be usefull for web\napplications based on Plone.\n\nYou can see some screenshots of the product at this page:\nhttps://github.com/zestsoftware/collective.mcp/wiki/Screenshots\n\nThis product does not magically create the pages for your site, it\nonly provides some API to create them, as we'll see later in this\nREADME.\n\n\nCompatibility\n=============\n\nThis has been tested with Plone 3.3.5.\n\n\n.. Note: this doctest is included in the long_description of the page\n.. on PyPI.\n\n\nInstalling collective.mcp\n=========================\n\nIn your buildout, add collective.mcp in the eggs directory. Run\nbuildout, start your instance again and add the product using the\nquick_installer (or the Plone equivqlent).\nYou can now access the control panel by accessing\n``http://localhost:8080/your_plone_site/control_panel``. As you have\nnot added any page yet, you will get a message telling you that you\ncan not manage anything.\n\n >>> from collective.mcp import categories, pages\n >>> categories\n []\n >>> pages\n []\n\n\nViewing samples\n===============\n\nYou can load the file ``samples.zcml`` file from collective.mcp to get\nsome samples.\nFor example, in the ``configure.zcml`` file of your theme::\n\n \n\nRestart the instance, reload the control panel page and you should see\nsome pages ready to be used.\n\n\nImplementing your control panel\n===============================\n\ncollective.mcp provides a place for the control panel, that you can\nfind at http://localhost:8080/ourplonesite/control_panel/.\nNormally, the page will tell you \"There is nothing you can manage.\",\nas you did not add any page yet.\n\nTo simplify, we'll consider that you already have a Plone product for\nwhich you want to add a control panel. This one has a 'browser'\npackage. Inside the browser package, create a 'control_panel' package,\ncontaining __init__.py and configure.zcml.\n\nThe __init__.py file you look like this (except you replace the\nmessage factory by your own product message factory)::\n\n >>> from collective.mcp import Category, register_category, register_page\n >>> from collective.mcp import McpMessageFactory as _\n\nAnd the configure.zcml file like this::\n\n \n \n\nIn the configure.zcml file of the browser package, include your new\npackage::\n\n \n\nNow you have the base, we can start adding thing to the control panel.\n\n\nCreating categories\n-------------------\n\nThe first step is to create the categories to which the pages will\nbelong. If you have a look at the screenshots in the docs folder or on\nthe project wiki, there is four categories:\n\n - personal preferences\n\n - clients\n\n - templates\n\n - settings\n\nIn our example, we'll only create the first and last categories. To do\nso, in the __init__.py, we'll add the following code::\n\n >>> register_category(\n ... Category('personal',\n ... _(u'label_cpcat_personal_prefs',\n ... default=u'Personal preferences')))\n >>> register_category(\n ... Category('settings',\n ... _(u'label_cpcat_settings',\n ... default=u'Settings'),\n ... after='personal'))\n\nAs you can see, we specified that the 'settings' category will appear\nafter the 'personal' one. We could also have specified that 'personal'\nis before 'settings' and get the same result.\n\nIf you reload now the control panel, nothing has changed. That is\nnormal, the system does not display categories for which there is no\npage (or the user can not use any of the pages).\n\n >>> categories\n [Category: personal, Category: settings]\n >>> pages\n []\n\n\nCreating a simple page\n----------------------\n\ncollective.mcp is based on collective.multimodeview. For the pages,\nwe will rely on a view defined in the samples called\n'multimodeview_notes_sample'. If you have already activated the\nsamples for multimodeview, you do not have to do anything.\nIn the other case, add the following lines to your configure.zcml\nfile::\n\n \n\nThe first page we will create allows to update the 'home message',\nusing the API provded by the view declared above.\nThe API is pretty simple and do not realy need explanations:\n\n- get_home_message()\n\n- set_home_message(msg)\n\nThis message is not displayed anywhere. It could, but that's not\ncovered by this README.\n\nTo create our page, we'll first create a new python file in the\ncontrol panel package, called 'home_message.py', that contains the\nfollowing code::\n\n from collective.mcp.browser.control_panel_page import ControlPanelPage\n\n class HomeMessage(ControlPanelPage):\n category = 'settings'\n zcml_id = 'collective_mcp_home_message'\n widget_id = 'collective_mcp_home_message'\n\n modes = {'back': {},\n 'default': {'submit_label': 'Update home message',\n 'success_msg': 'The home message has been updated'}}\n\n default_mode = 'default'\n\n @property\n def notes_view(self):\n return self.context.restrictedTraverse('@@multimodeview_notes_sample')\n\n def _check_default_form(self):\n return True\n\n def _process_default_form(self):\n self.notes_view.set_home_message(\n self.request.form.get('msg', ''))\n return 'back'\n\nLet's have a look to what we defined::\n\n - 'category': this is the category to which our now page belongs\n\n - 'zmcl_id': this is the name of the page, as defined in the zcml\n file (we'll see it later)\n\n - 'widget_id': this is a unique identifier for your page. Here we\n used the same one that for the zcml_id ust to avoid any conflict,\n but it could have benn 'home_message' for example.\n\n - modes: this dictionnary defines the list of modes in which the page\n can be. We defined a 'back' mode, that means that when the form is\n submitted or when the user cancels, the home of the conrol panel\n will be shown instead of the form again. For the default mode, we\n also defined the name of the button to save and the message\n displayed on success. Have a look to collective.multimodeview\n README file to see more options you can define for modes.\n\n - notes_view: just a helper property to easily get the view with the\n API.\n\n - _check_default_form: a function that checks that the form submitted\n did not contain error. Here we do not check anything so it's prettu\n quick, the second example will show more. see\n colective.multimodeview for more explanation).\n\n - _process_default_form: the function called if no errors were found\n by the previous method. As you can guess by the name, it processes\n the form (here it updates the home message).\n\nNow we need a template for our view::\n\n
\n\n
\n \n \n
\n \n \n\nThere is nothing fancy here, except the use of two methods from\nmultimodeview::\n\n - view/get_form_action: gives the action for the form\n\n - view/make_form_extra: generates some HTML code with some hidden\n input fields and the submit buttons.\n\nOnce again, have a look to collective.multimodeview for more\nexplanations.\n\nThe last step is to declare our view in the zcml file and register\nit. First, in the __init__.py file::\n\n >>> from collective.mcp.samples.home_message import HomeMessage\n >>> register_page(HomeMessage)\n\nThis makes the page appear in the ``pages`` list::\n\n >>> pages\n []\n\nThen in the ZCML file::\n\n \n\nNow you can restart the server and reload the control panel. The\n'settings' category will appear, containing one page with a question\nmark icon.\n\n >>> self.browser.open('http://nohost/plone/control_panel/')\n >>> 'There is nothing you can manage.' in self.browser.contents\n False\n\n >>> 'Settings' in self.browser.contents\n True\n\n >>> 'Personal preferences' in self.browser.contents\n False\n\n\nFirst, let's solve the icon problem. In the sample directory you will\nfind two icons taken from this set:\nhttp://www.iconfinder.com/search/?q=iconset%3A49handdrawing\n\nLet's declare the home.png file in the zcml::\n\n \n\nAnd now in our view, we will use this icon::\n\n class HomeMessage(ControlPanelPage):\n icon = \"++resource++collective_mcp_home.png\"\n\nThe second problem is that our page does not have a title, this\nproblem can easily be solved too::\n\n class HomeMessage(ControlPanelPage):\n title = 'Home message'\n\nThe image now appears in the control panel and the title is also displayed::\n\n >>> '>> 'Home message' in self.browser.contents\n True\n\nIf we click on the icon, the main page is not displayed anymore and we\nsee our form instead::\n\n >>> self.browser.getLink('Home message').click()\n >>> self.browser.url\n 'http://nohost/plone/control_panel?mode=default&widget_id=collective_mcp_home_message'\n\n >>> '>> '' in self.browser.contents\n True\n\nWe can fill the home message and validate. We get a sucess message\ndisplayed and we are back on the control panel home page::\n\n >>> self.browser.getControl(name='msg').value = 'My new home message - welcome :)'\n >>> self.browser.getControl(name='form_submitted').click()\n >>> \"
The home message has been updated
\" in self.browser.contents\n True\n\nIf we had cancelled, we would have got a different message (which is\nthe default cancel message inherited from collective.multimodeview) ::\n\n >>> self.browser.getLink('Home message').click()\n >>> self.browser.getControl(name='form_cancelled').click()\n >>> \"
Changes have been cancelled.
\" in self.browser.contents\n True\n\nAnd that's all, you have your first page of the control panel\nworking. Ok it's not really usefull, but that's a good start. In\nPrettig personeel (www.prettigpersoneel.nl - the website for which\nthis product has been developed), there is many pages based on the\nsame principle (two modes: default and back) such as changing the\npassword, setting the user's theme, managing contact information etc.\n\nBut now we want to do something a bit harder: create a page to manage\nmultiple objects.\n\n\nCreating a multi-object managing page\n-------------------------------------\n\nIf ou had a look at the 'collective_multimodeview_notes_samples' page,\nyou see that its main goal it to manage a list of notes attached to\nthe portal of the site.\nWe will create a control panel page to manage those notes. To do so,\ncreates notes.py and notes.pt in the control_panel package.\n\nThe notes.py will look like this::\n\n from collective.mcp.browser.control_panel_page import ControlPanelPage\n\n class Notes(ControlPanelPage):\n category = 'settings'\n zcml_id = 'collective_mcp_notes'\n widget_id = 'collective_mcp_notes'\n icon = \"++resource++collective_mcp_notes.png\"\n title = 'Notes'\n\n modes = {'add': {'success_msg': 'The note has been added',\n 'error_msg': 'Impossible to add a note: please correct the form',\n 'submit_label': 'Add note'},\n 'edit': {'success_msg': 'The note has been edited',\n 'submit_label': 'Edit note'},\n 'delete': {'success_msg': 'The note has been deleted',\n 'submit_label': 'Delete note'}\n }\n default_mode = 'edit'\n multi_objects = True\n\n @property\n def notes_view(self):\n return self.context.restrictedTraverse('@@multimodeview_notes_sample')\n\n def list_objects(self):\n notes = self.notes_view.get_notes()\n\n return [{'id': note_id, 'title': note_text}\n for note_id, note_text in enumerate(notes)\n if note_text]\n\n def _get_note_id(self):\n notes = self.notes_view.get_notes()\n note_id = self.current_object_id()\n\n try:\n note_id = int(note_id)\n except:\n # This should not happen, something wrong happened\n # with the form.\n return\n\n if note_id < 0 or note_id >= len(notes):\n # Again, something wrong hapenned.\n return\n\n if notes[note_id] is None:\n # This note has been deleted, nothing should be done\n # with it.\n return\n\n return note_id\n\n def get_note_title(self):\n \"\"\" Returns the title of the note currently edited.\n \"\"\"\n if self.errors:\n return self.request.form.get('title')\n\n if self.is_add_mode:\n return ''\n\n note_id = self._get_note_id()\n if note_id is None:\n # This should not happen.\n return ''\n\n return self.notes_view.get_notes()[note_id]\n\n def _check_add_form(self):\n if not self.request.form.get('title'):\n self.errors['title'] = 'You must provide a title'\n\n return True\n\n def _check_edit_form(self):\n if self._get_note_id() is None:\n return\n\n return self._check_add_form()\n\n def _check_delete_form(self):\n return self._get_note_id() is not None\n\n def _process_add_form(self):\n self.notes_view.add_note(self.request.form.get('title'))\n\t self.request.form['obj_id'] = len(self.notes_view.get_notes()) - 1\n\n def _process_edit_form(self):\n self.notes_view.edit_note(\n self._get_note_id(),\n self.request.form.get('title'))\n\n def _process_delete_form(self):\n self.notes_view.delete_note(self._get_note_id())\n self.request.form['obj_id'] = None\n\nSo let's see what is different from the previous page (obviously a\nlot):\n\n - modes: there is no more 'back' mode, so when submitting the form,\n we will still see the same page. Some extra modes appears to manage\n the notes.\n\n - default_mode: it is set to 'edit'. It means that the page will try,\n by default, to edit the first object found.\n\n - multi_objects: is is set to True. That means that this page can be\n used to manage multiple object. A sidebar will be shown to display\n the list of objects.\n\n - list_objects: when setting 'multi_objects' to True, you have to\n define this method. It returns a list of dictionnary having two\n keys: one define the id of the object and the second one the title\n displayed. \n\nThe _check_xxx_form amd _process_xxx_form are quite similar to what we\nsaw previously. One point to look at is the fact that we modify the\n'obj_id' entry of the request in both ``_process_add_form`` and\n``_process_delete_form``. In the first case, we do that so the note\nthat has just been added with be considered as the current one. In the\nsecond case, we delete the entry so the system will not consider the\ndeleted note as the current one (as it does not exist anymore) and\nwill pick the first available one.\n\nNow let's create a template for our page::\n\n \n
\n\n \n
\n \n
\n \n
\n \n\n \n

Are you sure you want to delete this note ?

\n\n

\n \n\n \n\n \n \n\n

\n There is no note to manage, click the '+' button to create a new one.\n

\n
\n\nIn this template, we can see three important things:\n\n - the use of view/is_xxx_mode: this is a helper provided by\n collective.multimodeview to now what o display depending on what\n you are doing.\n\n - there is an hidden field called 'obj_id'. This is important, as it\n is used to know which object you are currently editing.\n\n - there is a default message displayed when there is no notes. Do not\n forget it. If your page rendered an empty string, the system will\n show the home page of the menu instead.\n\nNow let's register our page. First in the __init__.py file::\n\n >>> from collective.mcp.samples.notes import Notes\n >>> register_page(Notes)\n\n >>> pages\n [,\n ]\n\nand in the configure.zcml::\n\n \n\nRestart your server and reload the control panel, you now have two\npages available.\n\n >>> self.browser.open('http://nohost/plone/control_panel/')\n >>> self.browser.getLink('Notes').click()\n >>> self.browser.url\n 'http://nohost/plone/control_panel?mode=edit&widget_id=collective_mcp_notes'\n\nAs you have not played with the notes yet, the list on the right\nis empty and you get a message telling you to add some notes::\n\n >>> import re\n >>> re.search('(
    \\s*
)', self.browser.contents).groups()\n ('
    ...
',)\n\n >>> \"There is no note to manage, click the '+' button to create a new one.\" in self.browser.contents\n True\n\n``collective.mcp`` automatically added a '+' and a '-' button that\nwill trigger the ``add`` and `delete` modes of your new page.\nWe'll click on the ``add`` button that will display the form to create\na note::\n\n >>> self.browser.getLink('+').click()\n >>> self.browser.url\n 'http://nohost/plone/control_panel?mode=add&widget_id=collective_mcp_notes'\n\n >>> '' in self.browser.contents\n True\n\nYou can also notice that, when adding a new object, a new line appears\nin the objects list and is shown as selected::\n\n >>> re.search('(\\s*...\\s*)', self.browser.contents).groups()\n ('
  • .........
  • ',)\n\nNow we'll add a note objects::\n\n >>> self.browser.getControl(name='title').value = 'A new note'\n >>> self.browser.getControl(name='form_submitted').click()\n\nThis time we are not redirected to the control panel home page but to\nthe ``edit`` page of the object we just added and we get a success\nmessage::\n\n >>> '
    The note has been added
    ' in self.browser.contents\n True\n\n >>> re.search('(
  • \\s*A new note\\s*
  • )', self.browser.contents).groups()\n ('
  • ...A new note...
  • ',)\n\n >>> re.search('()', self.browser.contents).groups()\n ('',)\n\nWe now add a second note::\n\n >>> self.browser.getLink('+').click()\n >>> self.browser.getControl(name='title').value = 'My second note'\n >>> self.browser.getControl(name='form_submitted').click()\n\nWhen saving this note is selected by default::\n\n >>> re.search('(
  • \\s*My second note\\s*
  • )', self.browser.contents).groups()\n ('
  • ...My second note...
  • ',)\n \n >>> re.search('()', self.browser.contents).groups()\n ('',)\n\n\nMore documentation\n------------------\n\nYou will find more documentation in ``collective/mcp/doc``. There is\nfour extra documentations there::\n\n - modes.rst - some extra explanation about the ``modes`` attributes\n of the class.\n\n - restriction.rst - explains the diferent methods to restrict access\n to the pages.\n\n - multiobjects.rst - going a bit deeper with the multi-objects views.\n\n - defect.rst - some examples of what you should not do.\n\n - theming.rst - some hints for theming the control panel.\n\n\nChangelog\n=========\n\n\n0.5 (2015-08-27)\n----------------\n\n- Code cleanup.\n [maurits]\n\n\n0.4 (2013-09-24)\n----------------\n\n- Moved to github. Cleanup a bit.\n [maurits]\n\n\n0.3 (2012-10-30)\n----------------\n\n- better display of the buttons in the left panel. [vincent]\n\n\n0.2 (2011-12-15)\n----------------\n\n- Added possibility to set custom CSS class to the subpage. By\n default, it has the class 'mcp_widget_XXX' where XXX is the widget\n id. [vincent]\n\n\n0.1 (2011-02-25)\n----------------\n\n- Initial release.\n [vincent]", "description_content_type": null, "docs_url": null, "download_url": null, "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zestsoftware/collective.mcp", "keywords": "mac control panel", "license": "GPL", "maintainer": null, "maintainer_email": null, "name": "collective.mcp", "package_url": "https://pypi.org/project/collective.mcp/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/collective.mcp/", "project_urls": { "Homepage": "https://github.com/zestsoftware/collective.mcp" }, "release_url": "https://pypi.org/project/collective.mcp/0.5/", "requires_dist": null, "requires_python": null, "summary": "Macish Control Panel for Plone.", "version": "0.5" }, "last_serial": 1695469, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "183a85401d0ac2575906d8812280e3d4", "sha256": "7d8f187af1ef9873c0e870ce8da6be4aa22fd9b1867235376822bb7ea5244a91" }, "downloads": -1, "filename": "collective.mcp-0.1.zip", "has_sig": false, "md5_digest": "183a85401d0ac2575906d8812280e3d4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 190138, "upload_time": "2011-02-25T20:16:27", "url": "https://files.pythonhosted.org/packages/3d/bd/1cd02b1a65077fc99400b688b7be7b2d098ae50005d46bc989f21c515728/collective.mcp-0.1.zip" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "7d126d3bd9d46ddef35e88fa70bd0924", "sha256": "fc1f6680df74d8d7ad3638107f80474f7fd417e129651126a33d63180beb723a" }, "downloads": -1, "filename": "collective.mcp-0.2.zip", "has_sig": false, "md5_digest": "7d126d3bd9d46ddef35e88fa70bd0924", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 191557, "upload_time": "2011-12-15T13:18:21", "url": "https://files.pythonhosted.org/packages/38/5f/985760d7e9143b4fb78b150d9c10aa6dda391082e47aedb2c054e5af7a4d/collective.mcp-0.2.zip" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "209f97ca0207cba07a157b568b842a12", "sha256": "7487fec734eeb692dafb75ba7b6fbc118f7fbc37ece8c767a01bd0fa2bab8c6e" }, "downloads": -1, "filename": "collective.mcp-0.3.zip", "has_sig": false, "md5_digest": "209f97ca0207cba07a157b568b842a12", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 192020, "upload_time": "2012-10-30T10:21:49", "url": "https://files.pythonhosted.org/packages/1a/cf/c401e0997970162804a2415f1d5abd8ee06d3657c789fc2814d9c390a0de/collective.mcp-0.3.zip" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "a27811e8efbd5c6cd957b18059e0c8b6", "sha256": "09d88da1220e545de8b341da46b912da90e42dd373a543c0e99004a737d50160" }, "downloads": -1, "filename": "collective.mcp-0.4.zip", "has_sig": false, "md5_digest": "a27811e8efbd5c6cd957b18059e0c8b6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 192924, "upload_time": "2013-09-24T12:35:12", "url": "https://files.pythonhosted.org/packages/f2/17/cd3450bb8ba814372a9d4f83c0c62c7d98377e0609982f2a5f0b58525448/collective.mcp-0.4.zip" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "fb5fcf4fddd810f61bca319280e9a2ea", "sha256": "75e816786146fcf20e6ecdead51575bffe11c8a4e6a3e27ee0806ae0628a6124" }, "downloads": -1, "filename": "collective.mcp-0.5.tar.gz", "has_sig": false, "md5_digest": "fb5fcf4fddd810f61bca319280e9a2ea", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 177520, "upload_time": "2015-08-26T22:59:33", "url": "https://files.pythonhosted.org/packages/a5/62/3b088cd1138dd236a1f0abb422c2e8c6148940c311f1a6d2583cd51bdfb3/collective.mcp-0.5.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fb5fcf4fddd810f61bca319280e9a2ea", "sha256": "75e816786146fcf20e6ecdead51575bffe11c8a4e6a3e27ee0806ae0628a6124" }, "downloads": -1, "filename": "collective.mcp-0.5.tar.gz", "has_sig": false, "md5_digest": "fb5fcf4fddd810f61bca319280e9a2ea", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 177520, "upload_time": "2015-08-26T22:59:33", "url": "https://files.pythonhosted.org/packages/a5/62/3b088cd1138dd236a1f0abb422c2e8c6148940c311f1a6d2583cd51bdfb3/collective.mcp-0.5.tar.gz" } ] }