{ "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.multimode view is a Plone package to ease creation of views\n(or viewlets) which can be in several states, for example a page\ncontaining a form or a guide with several steps.\n\nThis products can not be used alone, you need to manually define the\npages has you would do usually when creating browser views.\n\nThis README will show three simple examples on how to use the\nproduct. All samples can be found in the sources in the samples\ndirectory.\n\n\nCompatibility\n=============\n\nThis has been tested with Plone 3.3.5.\n\n\nSamples of views\n================\n\nSample 1: a simple view with two states\n---------------------------------------\n\nLet's say you want to define a view that displays the conditions to\nuse the site or the engagments you are taking with the data provided\nby the user.\n\nFirst we need to define the Python view::\n\n from collective.multimodeview.browser import MultiModeView\n\n class Sample1View(MultiModeView):\n modes = ['conditions',\n 'data_use']\n default_mode = 'conditions'\n view_name = 'multimodeview_sample1'\n\n'modes' is the list of modes that the view can take. For simple cases, a\nlist is enough. The next samples will show the use of a dictionnary\nfor more complex cases.\n'default_mode' is, as you can guess, the mode that will be displayed\nby default for this page.\n'view_name' is the name of the view as defined in the zcml file (we'll\nsee it after). It is needed to be able to define the base url for the\npage or when using Ajax to fetch the content (mainly for viewlets).\n\nThe second step is to define a template for our page::\n\n \n \n
\n
\n

By using this site, you agree on the fact that you will\n not do stupid things.

\n\n

\n See how we use your data\n

\n
\n
\n

We will sell your email to all known spam database, we need money.

\n\n

\n See the conditions to use the site\n

\n
\n
\n \n \n\nWith this example, we can see two examples of auto-generated\nattributes for the multimodeviews.\n'is_conditions_mode': provides a boolean telling if the view is in the\n'conditions' mode. For each mode you defined, you can use this\nshortcut ('is_xxx_mode', where 'xxx' is the name defined for you\nmode).\n'conditions_link': provides a link to swtich the page in 'conditions'\nmode. This can be used for any mode, except if you have a mode called\n'make' (it conflicts with the 'make_link' method). If you have a\n'make' mode, then you'll have to manually use 'make_link' (that will\nbe described later).\n\nNow you can define your view in the zcml file::\n\n \n\nAnd that's all, you can now access this view and switch between the\ntwo modes.\n\nNow let's go for something a bit more interresting.\n\nSample 2: playing with forms\n----------------------------\n\nThe first sample was pretty basic and could have been simply done by\nusing two pages or browser views.\nThe second example will show how to manage some data with a view. We\nwill add some annotations on the portal object (basically a simple\nlist of string). The view will be able to list, add, edit and delete\nthose notes.\nWe consider we have a view called 'multimodeview_notes_sample', that\nprovides an API to list, add, edit and delete notes (see\nsamples/notes_view.py).\n\nAs usual, we first define the view::\n\n class Sample2View(MultiModeView):\n \"\"\" A view that adds annotations on the portal.\n \"\"\"\n modes = ['list',\n 'add',\n 'delete']\n default_mode = 'list'\n view_name = 'multimodeview_sample2'\n\n @property\n def notes_view(self):\n return self.context.restrictedTraverse('@@multimodeview_notes_sample')\n\n def _get_note_id(self):\n \"\"\" Extracts the note_id from the form, cast it\n\t to an int.\n\t Returns None if there is no corresponding note.\n\t \"\"\"\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\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\nLike for the previous example, we have defined our list of modes, the\ndefault mode and the name of the view.\nWe also defined some helpful functions (see the source for the\ncomplete code, I removed it from here to focus on the important part)\nto manage the notes.\n\nThe important functions are _check_xxx_form and _process_xxx_form.\n\nThe first one (_check_xxx_form) checks if the form submitted does not\ncontain errors. If an error is found, it is added to the 'errors'\ndictionnary of the class, as we can see in '_check_add_form' if the\ntitle is empty. \nThe method always returns True, except if something wrong hapenned to\nthe form (some fields have not been submitted, or a value that the\nuser can not change in normal use case is wrong). In this case, the\nmethod returns 'False' or None. A different message will be shown to\nthe user. We can see an example in '_check_delete_form', which only\nchecks that the note_id provided is correct.\n\nThe second one (_process_xxx_form) executes the code for the given\nmode. It is only called if the corresponding check method returned\nTrue and did not find any error.\nIf needed, it can return a 'mode' name so the view switch back to this\nmode once the form is proceeded. By default, it switches to the\ndefault mode.\n\nThe second step is to define the template for this view. We first\ncreate the div (or whatever else) that is shown by default::\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Notes\n
\n \n \n \"edit\"\n \n \n \n \"delete\"\n \n
\n\n

\n You do not have any notes for the moment.\n

\n\n \n Add a new note\n \n
\n
\n\nIn this short sample, we can see the use of the 'make_link' method. We\nuse it to create the link to edit or delete a note. We could not use\n'edit_link' or 'delete_link', as we also need to specify the note we\nwant to edit or delete.\nusing view.make_link('edit', {'note_id': note_id}) will generate a\nlink like this: http://..../multimodeview_sample2?mode=edit¬e_id=2.\n\nNow let's complete our template with the form to add a note::\n\n
\n
\n \n
\n \n
\n \n
\n\n \n\n \n \n \n \n
\n\nIn this code we can see a few usefull methods provided by\nmultimodeview:\n\n - 'view/get_for_action': provides the action that should be used for\n the form.\n\n - 'view.class_for_field(field)': this methods returns 'field' if\n there is no error found for this field, or 'field error' if an error\n was found. Those class names are the default ones provided by\n Archetype, so an error will appear in red with a default Plone theme.\n\n - 'view/make_form_extras': this method should be used in every form \n in multimode page. It adds some hidden fields such as the mode\n currently is use.\n\nWe can also see some specificities in the form:\n\n - the method should always be 'POST': if you do not use a 'POST'\n method, the form will not be processed.\n\n - the submit input to process the form is called 'form_submitted'.\n\n - the sumbit input to cancel is called 'form_cancelled'. If you use\n other names, the form will not be processed.\n\nWe can now complete the template to also be able to manage the 'edit'\nand 'delete' modes::\n\n \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\nNothing really new in this new code but at least we are now able to\nmanage the notes.\n\nNow that the system is complete, we can see some problems incoming:\n\n - there is some repetitions in the template code, mainly for the\n submit buttons. The one to cancel could be factorized but the one to\n process the form has a different name everytime.\n\n - the messages always say 'Your changes have been saved', whatever\n you do.\n\nLet's improve this quiclky.\n\nSample 2.1: using a dictionnary for modes\n-----------------------------------------\n\nThe two problems seen before can be quickly fixed when defining a list\nof modes with a dictionnary.\n\nLet's define the new view, inheriting from the prevous one::\n\n class Sample21View(Sample2View):\n \"\"\" A view that adds annotations on the portal.\n \"\"\"\n modes = {'list': {},\n '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\n view_name = 'multimodeview_sample21'\n\nAs you can see, for each mode, a dictionnary is provided with three\nvalues:\n\n - success_msg: the message displayed when the form is successfuly\n processed.\n\n - error_msg: the message shown when errors are found in the form.\n \n - submit_label: the title for the button to submit the form.\n\nNow we can also update our template. The part for listing the notes\ndoes not change, we only update the form::\n\n

\n \n
\n \n
\n \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\nAs we can see, this version is much shorter than the previous one. We\ncould even have factorized the input for the title, but this has\nnothing to see with multimodeview, it is normal Zope/Plone/TAL coding.\n\nThe question you may have now is \"Where are my input defined ?\". It is\nthe view/make_form_extras that creates them. If no label for the\nsubmit button is found, it will not show any button. If a label is\nfound, it automatically generates the two submit buttons.\n\nSample 3: Creating a multi-step form\n------------------------------------\n\nThis last example shows how to handle a form in multiple steps. The\nmethod used here is not the best one, as we pass the data from one\npage to the other using hidden input. It would be better to use\nsession, cookies or even local storage for HTML5 fans, but the goal\nhere is more to shown how to navigate from one mode to another.\n\nAs usual, we first define the view::\n\n class Sample3View(MultiModeView):\n modes = {'step1': {'submit_label': 'Go to step 2'},\n 'step2': {'submit_label': 'Go to step 3'},\n 'step3': {'submit_label': 'Go to step 4'},\n 'step4': {'submit_label': 'Go to step 5'},\n 'step5': {}}\n\n default_mode = 'step1'\n view_name = 'multimodeview_sample3'\n\n def check_form(self):\n return True\n\n def _process_step1_form(self):\n return 'step2'\n\n def _process_step2_form(self):\n return 'step3'\n\n def _process_step3_form(self):\n return 'step4'\n\n def _process_step4_form(self):\n return 'step5'\n\n def _process_step5_form(self):\n return 'step5'\n\n @property\n def cancel_mode(self):\n mapping = {'step1': 'step1',\n 'step2': 'step1',\n 'step3': 'step2',\n 'step4': 'step3',\n 'step5': 'step4'}\n return mapping.get(self.mode)\n\n\nWe have overriden the 'check_form' method so it always returns True\n(we do not really care about the values here).\nThe _process_xxx_form methods now returns the step to which the user\nis sent when completing the step. So once the 1st step is done, the\nsecond one is displayed and so on.\n\nThe 'cancel_mode' attribute has been defined has a property, so the\nvalue can change depending on the current mode used by the view. You\ncan also define it has a simple attribute, but in this case it will\nalways return to the same mode when cancelling.\n\nNow we can define a simple template for our view::\n\n

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

Yer answers to the questions were:

\n
    \n
  • What is your name?
  • \n
  • What is your quest?
  • \n
  • What is your favorite color?
  • \n
  • What is the air-speed velocity of an unladen swallow?
  • \n
\n
\n\n \n \n\nAs told previously, this code is far from perfect, but shows how easy\nit is to navigate from one form to the other by returning the next\nmode in '_process_xxx_form' and overriding the 'cancel_mode' property.\n\nBut let's make it cleaner (again).\n\nSample 3.1: Navigating between mode again\n-----------------------------------------\n\nWe'll use the same template than for the previous view, but update a\nfew things:\n\n - the cancel message wil differ in each mode.\n\n - the cancel mode will be defined in the 'modes' dictionnary\n\n - the next mode to use will also be defined there.\n\n\nAs previously, we override the 'check_form' to avoid having to define a\n_check_stepx_form method for each step. We define empty methods to\nprocess each step::\n\n class Sample31View(MultiModeView):\n modes = {'step1': {'submit_label': 'Go to step 2',\n 'cancel_label': 'Cancel',\n 'success_mode': 'step2',\n 'cancel_mode': 'step1',\n 'cancel_msg': 'You can not go back, mwahaha'}},\n 'step2': {'submit_label': 'Go to step 3',\n 'cancel_label': 'Back to step 1',\n 'success_mode': 'step3',\n 'cancel_mode': 'step1'},\n 'step3': {'submit_label': 'Go to step 4',\n 'cancel_label': 'Back to step 2',\n 'success_mode': 'step4',\n 'cancel_mode': 'step2'},\n 'step4': {'submit_label': 'Go to step 5',\n 'cancel_label': 'Back to step 3',\n 'success_mode': 'step5',\n 'cancel_mode': 'step3'},\n 'step5': {}}\n\n default_mode = 'step1'\n view_name = 'multimodeview_sample31'\n\n def check_form(self):\n return True\n\n def _process_step1_form(self):\n pass\n\n def _process_step2_form(self):\n pass\n\n def _process_step3_form(self):\n pass\n\n def _process_step4_form(self):\n pass\n\nYou might have seen that for step1, we also defined a\n'cancel_msg'. This has the same effect than 'success_msg' or\n'error_msg' shown in sample 2.1, except it is shown when the user cancels.\n\nSamples with viewlets\n=====================\n\nThere is currently no samples with the viewlets, for the good reason\nthat they work the exact same way than the views, except for two\npoints:\n\n - the class must inherit\n collective.multimodeview.browser.MultiModeViewlet instead of\n collective.multimodeview.browser.MultiModeView.\n - you must define a 'widget_id' attribute for the class, so there is\n no conflict when processing the form on page that have multiple\n viewlets defined.\n\nSamples will be added when the automated Ajax version for viewlets\nwill be integrated.\n\nChangelog\n=========\n\n\n0.3 (2015-08-27)\n----------------\n\n- Code cleanup.\n [maurits]\n\n\n0.2 (2013-09-24)\n----------------\n\n- the 'add_portal_message' now only displays a message if there is\n one. It allows for example to set an empty success message for a\n given mode. [vincent]\n\n- added auto_process to mode. When declaring this kind of mode, the\n form is automatically processed when switching to this\n mode. [vincent]\n\n- added possibility for modes to automatically redirect to another\n page. [vincent]\n\n\n0.1 (2011-02-25)\n----------------\n\n- added the possibility to define a custom label for the cancel button\n and a custom cancel message for each mode. [vincent]\n\n- you can now defined the modes to swtich to once form is processed or\n user cancelled in the 'modes' dictionnary. [vincent]\n\n- added samples + README. [vincent]\n\n- extracted code from Products.plonehrm. [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.multimodeview", "keywords": "multimode view", "license": "GPL", "maintainer": null, "maintainer_email": null, "name": "collective.multimodeview", "package_url": "https://pypi.org/project/collective.multimodeview/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/collective.multimodeview/", "project_urls": { "Homepage": "https://github.com/zestsoftware/collective.multimodeview" }, "release_url": "https://pypi.org/project/collective.multimodeview/0.3/", "requires_dist": null, "requires_python": null, "summary": "Simple package to manage views with multiple modes.", "version": "0.3" }, "last_serial": 1695472, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "810cb7fe0fb1731ee2d70205149cd1b7", "sha256": "f39cf9d6178338691bca54da3a9cbff018275b2b8e34a0fa7cf423f89971d9ae" }, "downloads": -1, "filename": "collective.multimodeview-0.1.zip", "has_sig": false, "md5_digest": "810cb7fe0fb1731ee2d70205149cd1b7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52311, "upload_time": "2011-02-25T20:18:37", "url": "https://files.pythonhosted.org/packages/34/0c/446d78584ebe2621106204211653ac4f866ad8b4b1d3259b7c59903a142e/collective.multimodeview-0.1.zip" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "f9ff5d04c696c597df6fc072cfcc6b0f", "sha256": "9e07318a63debed6491695be2d66d214e741604406cc662341f927f4b9865512" }, "downloads": -1, "filename": "collective.multimodeview-0.2.zip", "has_sig": false, "md5_digest": "f9ff5d04c696c597df6fc072cfcc6b0f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 56997, "upload_time": "2013-09-24T12:28:56", "url": "https://files.pythonhosted.org/packages/5b/c9/37c669c9c5d4855bb13b2ba58b3dd6a8bda1355ba2ae89b81b23028f74e2/collective.multimodeview-0.2.zip" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "dce136410c7f7a1f8007eea50aa977d6", "sha256": "319f8abf2d058ed108188501da87395cc2aaddab7912717b2f4419c451c870aa" }, "downloads": -1, "filename": "collective.multimodeview-0.3.tar.gz", "has_sig": false, "md5_digest": "dce136410c7f7a1f8007eea50aa977d6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35384, "upload_time": "2015-08-26T23:01:51", "url": "https://files.pythonhosted.org/packages/73/9e/ae019571baf614e8f45ed923af37c14d9111023d8497a76a1bd48f888218/collective.multimodeview-0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "dce136410c7f7a1f8007eea50aa977d6", "sha256": "319f8abf2d058ed108188501da87395cc2aaddab7912717b2f4419c451c870aa" }, "downloads": -1, "filename": "collective.multimodeview-0.3.tar.gz", "has_sig": false, "md5_digest": "dce136410c7f7a1f8007eea50aa977d6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35384, "upload_time": "2015-08-26T23:01:51", "url": "https://files.pythonhosted.org/packages/73/9e/ae019571baf614e8f45ed923af37c14d9111023d8497a76a1bd48f888218/collective.multimodeview-0.3.tar.gz" } ] }