diff --git a/Makefile b/Makefile index 7ec44279e..491842e38 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ # Makefile for Sphinx documentation -# +.DEFAULT_GOAL = help +SHELL = bash # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS ?= +VALEOPTS ?= + SPHINXBUILD = "$(realpath venv/bin/sphinx-build)" SPHINXAUTOBUILD = "$(realpath venv/bin/sphinx-autobuild)" -PAPER = -DOCS_DIR = ./docs/ -BUILDDIR = ../_build +PAPER = +DOCS_DIR = ./docs/ +BUILDDIR = ../_build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 @@ -16,7 +19,6 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . VALEFILES := $(shell find $(DOCS_DIR) -type f -name "*.md" -print) -VALEOPTS ?= # Add the following 'help' target to your Makefile # And add help text after each target name starting with '\#\#' @@ -48,7 +50,7 @@ html: venv/bin/python ## Build html .PHONY: livehtml livehtml: venv/bin/python ## Rebuild Sphinx documentation on changes, with live-reload in the browser - cd "$(DOCS_DIR)" && $(SPHINXAUTOBUILD) \ + cd "$(DOCS_DIR)" && ${SPHINXAUTOBUILD} \ --ignore "*.swp" \ -b html . "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) diff --git a/docs/index.md b/docs/index.md index 1c3c355ab..7d03f3009 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,6 @@ A collection of trainings developed and created by the Plone community. :hidden: true mastering-plone/index -mastering-plone-5/index volto-customization/index customizing-volto-light-theme/index voltohandson/index @@ -47,11 +46,6 @@ teaching/index : Best practices of Plone development for both the backend and frontend. -{ref}`mastering-plone5-label` - -: Mastering Plone 5 development training is the predecessor of the Plone 6 version. - It includes also Plone Classic UI topics including viewlets, views, portlets, and other customizations. - {doc}`testing/index` : Best practices for testing Plone add-ons. diff --git a/docs/mastering-plone-5/_static/add_ons_debug_toolbar.png b/docs/mastering-plone-5/_static/add_ons_debug_toolbar.png deleted file mode 100644 index 9cd041614..000000000 Binary files a/docs/mastering-plone-5/_static/add_ons_debug_toolbar.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/add_ons_easyform_1.png b/docs/mastering-plone-5/_static/add_ons_easyform_1.png deleted file mode 100644 index 2386bd688..000000000 Binary files a/docs/mastering-plone-5/_static/add_ons_easyform_1.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/add_ons_easyform_2.png b/docs/mastering-plone-5/_static/add_ons_easyform_2.png deleted file mode 100644 index bf7771727..000000000 Binary files a/docs/mastering-plone-5/_static/add_ons_easyform_2.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/configuring_customizing_logo.png b/docs/mastering-plone-5/_static/configuring_customizing_logo.png deleted file mode 100644 index 1a08d95be..000000000 Binary files a/docs/mastering-plone-5/_static/configuring_customizing_logo.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_3_sponsor_schema.png b/docs/mastering-plone-5/_static/dexterity_3_sponsor_schema.png deleted file mode 100644 index 036ec7e5d..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_3_sponsor_schema.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_choice_and_list_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_choice_and_list_fields.png deleted file mode 100644 index ec6f074ec..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_choice_and_list_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_edit.png b/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_edit.png deleted file mode 100644 index a55f3555d..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_edit.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_view.png b/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_view.png deleted file mode 100644 index 2e9570bea..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_datagridfield_view.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_datetime_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_datetime_fields.png deleted file mode 100644 index 12117ec07..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_datetime_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_default_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_default_fields.png deleted file mode 100644 index 806ebe0f9..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_default_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_file_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_file_fields.png deleted file mode 100644 index 6778f9d96..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_file_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_number_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_number_fields.png deleted file mode 100644 index 35b23d1b4..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_number_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_other_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_other_fields.png deleted file mode 100644 index 80a1fed57..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_other_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/dexterity_reference_relation_fields.png b/docs/mastering-plone-5/_static/dexterity_reference_relation_fields.png deleted file mode 100644 index 8ba06c8da..000000000 Binary files a/docs/mastering-plone-5/_static/dexterity_reference_relation_fields.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_add_rule_1.png b/docs/mastering-plone-5/_static/features_add_rule_1.png deleted file mode 100644 index d438db32f..000000000 Binary files a/docs/mastering-plone-5/_static/features_add_rule_1.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_add_rule_2.png b/docs/mastering-plone-5/_static/features_add_rule_2.png deleted file mode 100644 index 5ce014285..000000000 Binary files a/docs/mastering-plone-5/_static/features_add_rule_2.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_add_rule_3.png b/docs/mastering-plone-5/_static/features_add_rule_3.png deleted file mode 100644 index ff2632382..000000000 Binary files a/docs/mastering-plone-5/_static/features_add_rule_3.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_add_rule_4.png b/docs/mastering-plone-5/_static/features_add_rule_4.png deleted file mode 100644 index d66df2117..000000000 Binary files a/docs/mastering-plone-5/_static/features_add_rule_4.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_add_user_form.png b/docs/mastering-plone-5/_static/features_add_user_form.png deleted file mode 100644 index 1916a5404..000000000 Binary files a/docs/mastering-plone-5/_static/features_add_user_form.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_control_panel.png b/docs/mastering-plone-5/_static/features_control_panel.png deleted file mode 100644 index 147a08f1f..000000000 Binary files a/docs/mastering-plone-5/_static/features_control_panel.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_create_site_form.png b/docs/mastering-plone-5/_static/features_create_site_form.png deleted file mode 100644 index 254c79cb3..000000000 Binary files a/docs/mastering-plone-5/_static/features_create_site_form.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_new_navigation.png b/docs/mastering-plone-5/_static/features_new_navigation.png deleted file mode 100644 index a2675c794..000000000 Binary files a/docs/mastering-plone-5/_static/features_new_navigation.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_pending_collection.png b/docs/mastering-plone-5/_static/features_pending_collection.png deleted file mode 100644 index b581ec1ef..000000000 Binary files a/docs/mastering-plone-5/_static/features_pending_collection.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_plone_running.png b/docs/mastering-plone-5/_static/features_plone_running.png deleted file mode 100644 index eb10b204f..000000000 Binary files a/docs/mastering-plone-5/_static/features_plone_running.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/features_the_event_folder_content.png b/docs/mastering-plone-5/_static/features_the_event_folder_content.png deleted file mode 100644 index e9e2a6f68..000000000 Binary files a/docs/mastering-plone-5/_static/features_the_event_folder_content.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/instructions_plone_running.png b/docs/mastering-plone-5/_static/instructions_plone_running.png deleted file mode 100644 index eb10b204f..000000000 Binary files a/docs/mastering-plone-5/_static/instructions_plone_running.png and /dev/null differ diff --git a/docs/mastering-plone-5/_static/relations_with_selectwidget.png b/docs/mastering-plone-5/_static/relations_with_selectwidget.png deleted file mode 100644 index dd4e71b39..000000000 Binary files a/docs/mastering-plone-5/_static/relations_with_selectwidget.png and /dev/null differ diff --git a/docs/mastering-plone-5/about_mastering.md b/docs/mastering-plone-5/about_mastering.md deleted file mode 100644 index 58683ad90..000000000 --- a/docs/mastering-plone-5/about_mastering.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-about-mastering-label)= - -# About Mastering Plone - -This training was initially created by Philip Bauer and Patrick Gerken of [starzel.de](https://www.starzel.de) to create -a canonical training for future Plone developers. - -The aim is that anyone with the appropriate knowledge can give a training based on it and contribute to it. -It is published as Open Source on [GitHub](https://github.com/plone/training) and {doc}`index`. - -If you want to inquire the original authors about organizing a training please contact them at . - -(plone5-about-upcoming-label)= - -## Upcoming Trainings - -- [Plone Conference 2021 Online](https://2021.ploneconf.org/) - -If you want to have a training near you please ask for trainings on - -(plone5-about-previous-label)= - -## Previous Trainings - -The Mastering Plone Training was so far held publicly at the following occasions: - -- [Plone Conference 2020 Online](https://2020.ploneconf.org/) -- [Plone Conference 2019 in Ferrara](https://2019.ploneconf.org/) -- [Plone Conference 2018 in Tokyo](https://2018.ploneconf.org/) -- [August 2018, Munich](https://plone.org/news-and-events/events/community/mastering-plone-training-in-munich) -- [Plone Conference 2017 in Barcelona](https://2017.ploneconf.org/) -- [Plone Conference 2016 in Boston](https://2016.ploneconf.org/) -- [Plone Conference 2015, Bucharest](https://2015.ploneconf.org/) -- [March 2015, Munich](https://www.starzel.de/blog/mastering-plone-training-march-2015) -- Plone Conference 2014, Bristol -- [June 2014, Caracas](https://x.com/hellfish2/status/476906131970068480) -- [May 2014, Munich](https://www.starzel.de/blog/mastering-plone) -- [PythonBrasil/Plone Conference 2013, Brasilia](http://2013.pythonbrasil.org.br/) -- PyCon DE 2012, Leipzig -- Plone Conference 2012, Arnheim -- PyCon De 2011, Leipzig - -(plone5-about-trainers-label)= - -## Trainers - -The following trainers have given trainings based on Mastering Plone: - -Philip Bauer - -: Philip Bauer is a web developer from Munich who fell in love with Plone in 2005 and since then works almost exclusively with Plone. - A historian by education he drifted towards creating websites in the 90's and founded the company [Starzel.de](https://www.starzel.de/) in 2000. - He is a member of the Plone foundation, loves teaching and is dedicated to Open Source. - Among other Plone-related projects he started creating the Mastering Plone Training so that everyone can become a Plone-Developer. - -Patrick Gerken - -: Patrick Gerken works with Python since 2002. - He started working with pure Zope applications and now develops mainly with Plone, Pyramid and JavaScript as well as doing what is called DevOps. - He works at Zumtobel Group. - -Steve McMahon - -: Steve McMahon is a long-time Plone community member, contributor and trainer. - He is the creator of PloneFormGen and maintainer of the Unified installer. - Steve also wrote several chapters of Practical Plone and is an experienced speaker and instructor. - -Steffen Lindner - -: Steffen Lindner started developing Plone in 2006. - He worked on small Plone sites and also with huge intranet sites. - As Open Source / Free Software developer he joined the Plone core developer team 2011 and works at https://www.starzel.de/. - -Fulvio Casali - -: Fulvio Casali has been working almost exclusively with Plone since 2008. - He struggled for years to find his way around the source code of Plone when there was no documentation and no trainings, - and feels passionate about helping users and developers become proficient. - - He loves participating in Plone community events, and organized two strategic Plone sprints on the northwest coast - of the USA and helped galvanized the developer community there. - -Johannes Raggam - -: Johannes Raggam from Graz/Austria works most of the time with a technology stack based around Python, Plone, Pyramid and JavaScript. - As an active Open Source / Free Software developer he believes in the power of collaborative work. - - He is a BlueDynamics Alliance Partner and Plone Core Contributor since 2009, a member of the Plone Framework Team since 2012 and Plone Foundation member. - -Franco Pellegrini - -: Franco Pellegrini is a software developer from Cordoba, Argentina. - He started developing Plone in 2005 in a small software company, and as an independent contractor since 2011. - He believes in free software philosophy, and so, he has been a Plone core developer since 2010 and Framework Team member since 2012. - -Fred van Dijk - -: Fred, from Rotterdam the Netherlands, has been exposed to Plone early on as a user. - In 2007 he joined Zest Software to work on and with Plone and Python web apps full time. - - He can focus on the business side, helping users decide on which features are most valuable to develop or when to stick with standard functionality. He also gives training on using and administering the CMS. - On the IT side he has plenty technical knowledge to work on code, system administration and do project management in a team of developers. - -Leonardo Caballero - -: Leonardo J. Caballero G. of Maracaibo, Venezuela, is a Technical Director at Covantec R.L. and Conectivo C.A. - Leonardo maintains the Spanish translations of more than 49 Plone Add-ons as well as Spanish-language documentation for Plone itself. - - He has contributed several Plone Add-ons that are part of PloneGov. - Currently serving the Plone Board as a Plone Ambassador, Leonardo has also served as an Advisory Board member - and has spoken at or helped organize Plone and open-source events throughout South America. - - -(plone5-about-licence-label)= - -## License - -The Mastering Plone Training is licensed under a [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). - - -(plone5-about-use-label)= - -## Using the documentation for a training - -See the information for {doc}`teaching`. - - -## Contributing - -Everyone is **very welcome** to contribute. See the information for {doc}`contributing`. diff --git a/docs/mastering-plone-5/add-ons.md b/docs/mastering-plone-5/add-ons.md deleted file mode 100644 index f98c47c95..000000000 --- a/docs/mastering-plone-5/add-ons.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-add-ons-label)= - -# Extend Plone With Add-On Packages - -- There are more than 2,000 add-ons for Plone. We will cover only a handful today. -- Using them saves a lot of time -- The success of a project often depends on finding the right add-on -- Their use, usefulness, quality and complexity varies a lot - -(plone5-add-ons-notable-label)= - -## Some notable add-ons - -[collective.easyform](https://pypi.org/project/collective.easyform) - -: A form generator and the successor to [Products.PloneFormGen](https://5.docs.plone.org/develop/plone/forms/ploneformgen.html) - - ```{figure} _static/add_ons_easyform_1.png - :alt: A simple form created with collective.easyform. - :scale: 50% - - A simple form created with collective.easyform. - ``` - - ```{figure} _static/add_ons_easyform_2.png - :alt: Editing a form field through the web. - :scale: 50% - - Editing a form field through the web. - ``` - -[plone.app.mosaic](https://github.com/plone/plone.app.mosaic) - -: Layout solution to easily create complex layouts through the web. - -[collective.geo](https://collectivegeo.readthedocs.io/en/latest/) - -: Flexible bundle of add-ons to geo-reference content and display in maps - -[collective.mailchimp](https://pypi.org/project/collective.mailchimp) - -: Allows visitors to subscribe to mailchimp newsletters - -[eea.facetednavigation](https://pypi.org/project/eea.facetednavigation/) - -: Create faceted navigation and searches through the web. - -[collective.lineage](https://pypi.org/project/collective.lineage) - -: Microsites for Plone - makes subfolders appear to be autonomous Plone sites - -[collective.behavior.banner](https://github.com/collective/collective.behavior.banner) - -: Add decorative banners and sliders - -[Rapido](https://rapidoplone.readthedocs.io/en/latest/) - -: Allows developers with a little knowledge of HTML and a little knowledge of Python to implement custom elements and insert them anywhere they want. - -[Plomino](https://github.com/plomino/Plomino) - -: Powerful and flexible web-based application builder for Plone - -[collective.disqus](https://pypi.org/project/collective.disqus/) - -: Integrates the Disqus commenting platform API into Plone - -(plone5-add-ons-find-label)= - -## How to find add-ons - -It can be very hard to find the right add-on for your requirements. Here are some tips: - -- Make a list of required features. You'll almost never find an add-on that exactly fits your needs. - -- Either adapt your requirements to what is available, invest the time & money to modify an existing add-ons to fit your needs or create a new add-on that does exactly what you need. - -- Then search using the follwing links below. - - - - - >3400 Plone related packages - use the search form! - - >1500 repos - - >310 repos - - Google (e.g. [Plone+Slider](https://www.google.com/?q=plone+slider)) - -- Once you have a shortlist test these add-ons. Here are the main issues you need to test before you install a add-on on a production site: - - - Test all required features. Read but do not trust the documentation - - Check if the add-on runs on your required version and is currently maintained - - Does it have i18n-support, i.e. is the user-interface translated to your language? - - Does it uninstall cleanly? - A tough one. - See for the reason why. - - Check for unwanted dependecies - -Once you found an add-on you like you should ask the community if you made a good choice or if you missed something: - -- Message Board: -- Chat: - -There is also a talk that discusses in depth how to find the right add-on: - -(plone5-add-ons-installing-label)= - -## Installing Add-ons - -Installation is a two-step process. - -### Making the add-on packages available to Zope - -First, we must make the add-on packages available to Zope. This means that Zope can import the code. Buildout is responsible for this. - -Look at the {file}`buildout.cfg`. - -```{note} -If you're using our Vagrant kit, the Plone configuration is available in a folder that is shared between the host and guest operating systems. -Look in your Vagrant install directory for the {file}`buildout` folder. -You may edit configuration files using your favorite text editor in the host operating system, then switch into your virtual machine to run buildout on the guest operating system. -``` - -In the section `[instance]` there is a variable called `eggs`, which has a list of *eggs* as a value. For example: - -``` -eggs = - Plone - collective.easyform - plone.app.debugtoolbar -``` - -You add an egg by adding a new line containing the package name to the configuration. -You must write the egg name indented: this way, buildout understands that the current line is part of the last variable and not a new variable. - -If you add new add-ons here you will have to run buildout and restart the site: - -```shell -$ bin/buildout -$ bin/instance fg -``` - -Now the code is available from within Plone. - -### Installing add-ons in your Plone Site - -Your Plone site has not yet been told to use the add-on. For this, you have to activate the add-on in your Plone Site. - -```{note} -Why the extra step of activating the add-on package? You may have multiple Plone sites in a single Zope installation. It's common to want to activate some add-ons in one site, others in another. -``` - -In your browser, go to Site Setup (shortcut: add `/@@overview-controlpanel` to the Plone site URL), and open the `Add-ons` Panel. You will see that you can install the add-ons there. - -Install EasyForm (the human-readable name of {py:mod}`collective.easyform`) now. - -This is what happens: The GenericSetup profile of the product gets loaded. This does things like: - -- Configuring new actions -- Registering new contenttypes -- Registering css and js files -- Creating some content/configuration objects in your Plone site. - -Let's have a look at what we just installed. - -(plone5-add-ons-pfg-label)= - -## collective.easyform - -There are many ways to create forms in Plone: - -- Pure: html and python in a BrowserView -- Framework: {py:mod}`z3c.form` -- TTW: {py:mod}`Products.PloneFormGen` and {py:mod}`collective.easyform` - -The concept of {py:mod}`collective.easyform` is that you add a form, to which you add form fields as schema-fields exactly like the dexterity schema-editor. Fields are added, deleted, edited and moved just as with any other type of content. Form submissions may be automatically emailed and/or saved for download. - -Let's build a registration form: - -- Add an object of the new type 'EasyForm' in the site root. Call it "Registration" -- Save and view the result, a simple contact form that we may customize -- In the `Actions` Menu click on "Define form fields" -- Remove field "comments" -- Add fields for food preference (a choice field) and shirt size (also choice) -- In the `Actions` Menu click on "Define form actions" -- Add a new action and select "Save Data" as the type. This stores all entered data. -- Customize the mailer - -```{note} -Need CAPTCHAs? Read the [instructions how to add add Recapcha-field to easyform](https://github.com/collective/collective.easyform#recaptcha-support) -``` - -(plone5-add-ons-ptg-label)= - -## Add page layout management with {py:mod}`plone.app.mosaic` - -To make it possible for your site editors to drag and drop different blocks of content onto a page, you can use the add-on plone.app.mosaic - - -- Add `plone.app.mosaic` to the eggs section in the buildout -- Activate the Mosaic add-on -- Go to a page in your site and click on "Mosaic" in the `Display` menu in the toolbar -- Edit the page to select a Mosaic layout and try inserting some content blocks -- You can read more about the concepts and use of this add-on in the [Mosaic documentation](http://plone-app-mosaic.s3-website-us-east-1.amazonaws.com/latest/getting-started.html) - -```{note} -The Mosaic editor takes some time to load so please be patient until you see the full edit view -``` - -(plone5-add-ons-i18n-label)= - -## Internationalization - -Plone can run the same site in many different languages. - -We're not doing this with the conference site since the *lingua franca* of the Plone community is English. - -We would use the built-in add-on for this. - -Building a multi-lingual site requires activating {py:mod}`plone.app.multilingual`, but no add-on is necessary to build a site in only one language. Just select a different site language when creating a Plone site, and all text in the user-interface will be switched to that language. - -(plone5-add-ons-summary-label)= - -## Summary - -You are now able to customize and extend many parts of our website. You can even install extensions that add new functionality. - -But: - -- Can we submit talks now? -- Can we create lists with the most important properties of each talk? -- Can we allow a jury to vote on talks? - -We often have to work with structured data. -Up to a degree we can do all this TTW, but at some point we run into barriers. -In the next part of the training, we'll teach you how to break through these barriers. diff --git a/docs/mastering-plone-5/anatomy.md b/docs/mastering-plone-5/anatomy.md deleted file mode 100644 index 8bff3100e..000000000 --- a/docs/mastering-plone-5/anatomy.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -myst: - html_meta: - "description": "The Anatomy of Plone" - "property=og:description": "The Anatomy of Plone" - "property=og:title": "The Anatomy of Plone" - "keywords": "history, Plone, ZODB, CMF, Zope, Pyramid, Bluebream" ---- - -(plone5-anatomy-label)= - -# The Anatomy of Plone - -In this part you will: - -- Learn a bit about the history of Plone. - -Topics covered: - -- ZODB -- CMF -- Zope -- Pyramid -- Bluebream - -Plone started as an extension for CMF, which is a extension for Zope. Python, ZODB, Zope, CMF, Plone ... -- how does all that fit together? - -## Database - -- [ZODB](https://zodb.org/en/latest/): A native object database for Python - - - No separate language for database operations - - Very little impact on your code to make objects persistent - - Object database != ORM - - almost no seam between code and database. - -```python -import persistent - -class Account(persistent.Persistent): - - def __init__(self): - self.balance = 0.0 - - def deposit(self, amount): - self.balance += amount - - def cash(self, amount): - assert amount < self.balance - self.balance -= amount -``` - -- "NoSQL" -- [ZEO](https://github.com/zopefoundation/ZEO): Server + many clients -- [ZRS](https://github.com/zopefoundation/zc.zrs): DB-Replication -- [RelStorage](https://relstorage.readthedocs.io/en/latest/) (store pickles in a relational database) for Postgres, MySQL etc. -- blobstorage (binary large objects) in filesystem - -(plone5-anatomy-zope2-label)= - -## Zope - -- Zope is a web application framework that Plone runs on top of. -- The majority of Zope's code is written in Python, like everything else written on top of it. -- It serves applications that communicate with users via http. - -```{note} -**The great Version-Confusion** - -- Plone was always based on Zope 2.x. Starting with Plone 5.2+ it uses Zope 4.x -- Starting with Zope 4.0, the package is only called Zope (not Zope2 or Zope4) -- *Zope 3* is **not** a version of Zope but an ill-named rewrite of Zope 2 *(sigh)* -- 4.x is a major new release of Zope that supports Python 3 (among many other improvements) -``` - -````{only} not presentation -Before Zope, web applications were often realized using plain [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface). -An Apache web server would execute a script with the request data passed to it on standard input and as environment variables. -The script would then just print HTML to the standard output. -Apache returned that to the user. -Opening database connections, checking permission constraints, generating valid HTML, configuring caching, -interpreting form data and everything else: you had to do it on your own. - -When the second request comes in, you have to do everything again. - -Jim Fulton thought that this was slightly tedious. -So he wrote code to handle requests. -He believed that site content is object-oriented and that the URL should somehow point directly into the object hierarchy, -so he wrote an object-oriented database, called [ZODB](https://zodb.org/en/latest/). - -The ZODB is a fully [ACID](https://en.wikipedia.org/wiki/ACID) compliant database with automatic transactional integrity. -It automatically maps traversal in the object hierarchy to URL paths, there is no need to "wire" objects or database nodes to URLs. - -This gives Plone its easy SEO-friendly URLs. - -Traversal through the object database is security checked at every point via very fine grained access-control lists. - -```{note} -**Acquisition** - -One missing piece is important and complicated: `Acquisition`. - -Acquisition is a kind of magic. Imagine a programming system where you do not access the file system and where you do not need to import code. -You work with objects. -An object can be a folder that contains more objects, an HTML page, data, or another script. - -To access an object, you need to know where the object is. -Objects are found by paths that look like URLs, but without the domain name. -Now Acquisition allows you to write an incomplete path. - -An incomplete path is a relative path, it does not explicitly state that the path starts from the root, -it starts relative to where the content object is -- its context. - -If Zope cannot resolve the path to an object relative to your code, it tries the same path in the containing folder. -And then the folder containing the folder. - -This might sound weird, what do I gain with this? - -You can have different data or code depending on your {py:obj}`context`. -Imagine you want to have header images differing for each section of your page, sometimes even differing for a specific subsection of your site. - -You define a path `header_image` and put a header image at the root of your site. -If you want a folder with a different header image, you put the header image into this folder. - -Please take a minute to let this settle and think about what this allows you to do. - -- contact forms with different e-mail addresses per section -- different CSS styles for different parts of your site -- One site, multiple customers, everything looks different for each customer. - -As with all programming magic, acquisition exacts a price. -Zope code must be written carefully in order to avoid inheriting side effects via acquisition. - -The Zope community expresses this with the Python (Monty) maxim: Beware the `Spammish Acquisition`. -``` - -```{seealso} -- -- -``` -```` - -(plone5-anatomy-cmf-label)= - -## Content Management Framework - -- [CMF (Content Management Framework)](https://old.zope.dev/Products/CMF/index.html/) is add-on for Zope to build Content Management Systems (like Plone). - -```{only} not presentation -After many websites were successfully created using Zope, a number of recurring requirements emerged, -and some Zope developers started to write CMF, the Content Management Framework. - -The CMF offers many services that help you to write a CMS based on Zope. -Most objects you see in the ZMI are part of the CMF somehow. - -The developers behind CMF do not see CMF as a ready to use CMS. -They created a CMS Site which was usable out of the box, but made it deliberately ugly, because you have to customize it anyway. - -We are still in prehistoric times here. There were no eggs (Python packages), -Zope did not consist of 100 independent software components but was one big file set. - -Many parts of Plone are derived from the CMF, but it's a mixed heritage. -The CMF is an independent software project, and has often moved more slowly than Plone. - -Plone is gradually eliminating dependence on most parts of the CMF. -``` - -(plone5-anatomy-ztk-label)= - -## Zope Toolkit / Zope3 - -- Zope 3 was originally intended as a rewrite of Zope from the ground up. -- Plone uses parts of it provided by the [Zope Toolkit (ZTK)](https://zopetoolkit.readthedocs.io/en/latest/). -- The name was very unfortunate since it was in no way compatible with Zope 2 - -```{only} not presentation -Unfortunately, only few people started to use Zope 3, nobody migrated to Zope 3 because nobody knew how. - -But there were many useful things in Zope 3 that people wanted to use in Zope 2, -thus the Zope community adapted some parts so that they could use them in Zope 2. - -Sometimes, a wrapper of some sort was necessary, these usually are being provided by packages -from the {py:mod}`five` namespace. (Zope 2 + Zope 3 = "five") - -To make the history complete, since people stayed on Zope 2, the Zope community renamed Zope 3 to Bluebream, -so that people would not think that Zope 3 was the future. - -It wasn't anymore. -``` - -(plone5-anatomy-zca-label)= - -## Zope Component Architecture (ZCA) - -The [Zope Component Architecture](https://zopecomponent.readthedocs.io/en/latest/), which was developed as part of Zope 3, -is a system which allows for component pluggability and complex dispatching based on objects -which implement an interface (a description of a functionality). - -It is a subset of the ZTK but can be used standalone. -Plone makes extensive use of the ZCA in its codebase. - -(plone5-anatomy-pyramid-label)= - -## Pyramid - -- [Pyramid](https://trypyramid.com) is a Python web application development framework that is often seen as the successor to Zope. -- It does less than Zope, is very pluggable and [uses the Zope Component Architecture](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/zca.html) “under the hood” to perform view dispatching and other application configuration tasks. - -````{only} not presentation -You can use it with a relational Database instead of ZODB if you want, or you can use both databases or none of them. - -Apart from the fact that Pyramid was not forced to support all legacy functionality, -which can make things more complicated, the original developer had a very different stance on how software must be developed. -While both Zope and Pyramid have good test coverage, Pyramid has good documentation; something that was very neglected in Zope, -and at times in Plone too. - -Whether the component architecture is better in Pyramid or not we don't dare say, -but we like it more. But maybe it's just because it was documented. - -```{seealso} -- -``` -```` - -## Exercise - -Definition of the PYTHON_PATH makes up most of the `bin/instance` script's code. -Look at the package list (and maybe also the links provided in the respective sections of this chapter). -Try to identify 3 packages that belong to Zope 4, 3 packages from CMF, 3 Zope Toolkit packages and 3 packages from the ZCA. - -```{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -- Zope 4: Zope, ZODB, Acquisition, AccessControl, ... -- CMF: Products.CMFCore, Products.CMFUid, Products.CMFEditions, ... Products.DCWorkflow doesn't fit the pattern but is a very important part of the CMF -- ZTK: zope.browser, zope.container, zope.pagetemplate, ... You can find a complete list [here](https://dist.plone.org/versions/zopetoolkit-1-0-8-zopeapp-versions.cfg) -- ZCA: zope.component, zope.interface, zope.event -``` diff --git a/docs/mastering-plone-5/api.md b/docs/mastering-plone-5/api.md deleted file mode 100644 index 7a4d8a54e..000000000 --- a/docs/mastering-plone-5/api.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-api-label)= - -# Programming Plone - -In this part you will: - -- Learn about the right ways to do something in code in Plone. -- Learn to debug - -Topics covered: - -- plone.api -- Portal tools -- Debugging - -(plone5-api-api-label)= - -## plone.api - -The most important tool nowadays for plone developers is the add-on [plone.api](https://5.docs.plone.org/develop/plone.api/docs/index.html) that covers 20% of the tasks any Plone developer does 80% of the time. If you are not sure how to handle a certain task be sure to first check if plone.api has a solution for you. - -The API is divided in five sections. Here is one example from each: - -- `Content:` [Create content](https://5.docs.plone.org/develop/plone.api/docs/content.html#create-content) -- `Portal:` [Send E-Mail](https://5.docs.plone.org/develop/plone.api/docs/portal.html#send-e-mail) -- `Groups:` [Grant roles to group](https://5.docs.plone.org/develop/plone.api/docs/group.html#grant-roles-to-group) -- `Users:` [Get user roles](https://5.docs.plone.org/develop/plone.api/docs/user.html#get-user-roles) -- `Environment:` [Switch roles inside a block](https://5.docs.plone.org/develop/plone.api/docs/env.html#switch-roles-inside-a-block) - -{py:mod}`plone.api` is a great tool for integrators and developers that is included when you install Plone, though for technical reasons it is not used by the code of Plone itself. - -In existing code you'll often encounter methods that don't mean anything to you. You'll have to use the source to find out what they do. - -Some of these methods will be replaced by {py:mod}`plone.api`: - -- {py:meth}`Products.CMFCore.utils.getToolByName` -> {py:meth}`api.portal.get_tool` -- {py:meth}`zope.component.getMultiAdapter` -> {py:meth}`api.content.get_view` - -(plone5-api-portal-tools-label)= - -## portal-tools - -Some parts of Plone are very complex modules in themselves (e.g. the versioning machinery of {py:mod}`Products.CMFEditions`). -Most of them have an API of themselves that you will have to look up at when you need to implement a feature that is not covered by plone.api. - -Here are a few examples: - -portal_catalog - -: {py:meth}`unrestrictedSearchResults()` returns search results without checking if the current user has the permission to access the objects. - -{py:meth}`uniqueValuesFor()` returns all entries in an index - -portal_setup - -: {py:meth}`runAllExportSteps()` generates a tarball containing artifacts from all export steps. - -portal_quickinstaller - -: {py:meth}`isProductInstalled()` checks if a product is installed. - -Usually the best way to learn about the API of a tool is to look in the {file}`interfaces.py` in the respective package and read the docstrings. But sometimes the only way to figure out which features a tool offers is to read its code. - -To use a tool you usually first get the tool with {py:mod}`plone.api` and then invoke the method. - -Here is an example where we get the tool `portal_membership` and use one of its methods to logout a user: - -```python -mt = api.portal.get_tool('portal_membership') -mt.logoutUser(request) -``` - -```{note} -The code for {py:meth}`logoutUser()` is in {py:meth}`Products.PlonePAS.tools.membership.MembershipTool.logoutUser`. Many tools that are used in Plone are actually subclasses of tools from the package {py:mod}`Products.CMFCore`. For example `portal_membership` is subclassing and extending the same tool from {py:class}`Products.CMFCore.MembershipTool.MembershipTool`. That can make it hard to know which options a tool has. There is a ongoing effort by the Plone Community to consolidate tools to make it easier to work with them as a developer. -``` - -(plone5-api-debugging-label)= - -## Debugging - -Here are some tools and techniques we often use when developing and debugging. We use some of them in various situations during the training. - -tracebacks and the log - -: The log (and the console when running in foreground) collects all log messages Plone prints. When an exception occurs Plone throws a traceback. Most of the time the traceback is everything you need to find out what is going wrong. Also adding your own information to the log is very simple. - -pdb - -: The python debugger pdb is the single most important tool for us when programming. Just add `import pdb; pdb.set_trace()` in your code and debug away! - -Since Plone 5 you can even add it to templates: add `` to a template and you end up in a pdb shell on calling the template. Look at the variable {py:obj}`econtext` to see what might have gone wrong. - -pdbpp - -: A great drop-in replacement for pdb with tab completion, syntax highlighting, better tracebacks, introspection and more. And the best feature ever: The command {command}`ll` prints the whole current method. - -ipdb - -: Another enhanced pdb with the power of IPython, e.g. tab completion, syntax highlighting, better tracebacks and introspection. It also works nicely with {py:mod}`Products.PDBDebugMode`. Needs to be invoked with `import ipdb; ipdb.set_trace()`. - -Products.PDBDebugMode - -: An add-on that has two killer features. - -**Post-mortem debugging**: throws you in a pdb whenever an exception occurs. This way you can find out what is going wrong. - -**pdb view**: simply adding `/pdb` to a url drops you in a pdb session with the current context as {py:obj}`self.context`. From there you can do just about anything. - -Debug mode - -: When starting Plone using {command}`./bin/instance debug` you'll end up in an interactive debugger. - -plone.app.debugtoolbar - -: An add-on that allows you to inspect nearly everything. It even has an interactive console, a tester for TALES-expressions and includs a reload-feature like {py:mod}`plone.reload`. - -plone.reload - -: An add-on that allows to reload code that you changed without restarting the site. It is also used by {py:mod}`plone.app.debugtoolbar`. - -Products.PrintingMailHost - -: An add-on that prevents Plone from sending mails. Instead, they are logged. - -Products.enablesettrace or Products.Ienablesettrace - -: Add-on that allows to use pdb and ipdb in Python skin scripts. Very useful when debugging terrible legacy code. - -`verbose-security = on` - -: An option for the recipe {py:mod}`plone.recipe.zope2instance` that logs the detailed reasons why a user might not be authorized to see something. - -{command}`./bin/buildout annotate` - -: An option when running buildout that logs all the pulled packages and versions. - -Sentry - -: [Sentry](https://github.com/getsentry/sentry) is an error logging application you can host yourself. -It aggregates tracebacks from many sources and (here comes the killer feature) even the values of variables in the traceback. We use it in all our production sites. - -zopepy - -: Buildout can create a python shell for you that has all the packages from your Plone site in its python path. Add the part like this: - -``` -[zopepy] -recipe = zc.recipe.egg -eggs = ${instance:eggs} -interpreter = zopepy -``` - -```{seealso} -A video of the talk [Debug like a pro. How to become a better programmer through pdb-driven development](https://pyvideo.org/pycon-de-2016/debug-like-a-pro-how-to-become-a-better-programmer-through-pdb-driven-development.html) -``` - -## Exercise - -- Create a new BrowserView callable as `/@@demo_content` in a new file {file}`demo.py` -- The view should create 5 talks each time it is called -- Use the docs at to find out how to create new talks. -- Use `plone.api.content.transition` to publish all new talks. Find the docs for that method. -- Only managers should be able to use the view (the permission is called **cmf.ManagePortal**). -- Reload the frontpage after calling the view. -- Display a message about the results (). -- For extra credits use the library [requests](https://requests.readthedocs.io/en/latest/) and icndb .com/api/ to populate the talks with jokes. -- Use the utility methods `cropText` from `Producs.CMFPlone.browser.ploneview.Plone` to crop the title after 20 characters. - -```{note} -- Do not try everything at the same time, work in small iterations, use `plone.reload` to check your results frequently. -- Use `pdb` during development to experiment. -``` - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Add this to {file}`browser/configure.zcml`: - -```{code-block} xml -:linenos: - - -``` - -This is {file}`browser/demo.py`: - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from Products.Five import BrowserView -from plone import api -from plone.protect.interfaces import IDisableCSRFProtection -from zope.interface import alsoProvides - -import json -import logging -import requests - -logger = logging.getLogger(__name__) - - -class DemoContent(BrowserView): - - def __call__(self): - portal = api.portal.get() - self.create_talks(portal) - return self.request.response.redirect(portal.absolute_url()) - - def create_talks(self, container, amount=5): - """Create some talks""" - - alsoProvides(self.request, IDisableCSRFProtection) - plone_view = api.content.get_view('plone', self.context, self.request) - jokes = self.random_jokes(amount) - for data in jokes: - joke = data['joke'] - talk = api.content.create( - container=container, - type='talk', - title=plone_view.cropText(joke, length=20), - description=joke, - type_of_talk='Talk', - ) - api.content.transition(talk, to_state='published') - logger.info(u'Created talk {0}'.format(talk.absolute_url())) - api.portal.show_message( - u'Created {0} talks!'.format(amount), self.request) - - def random_jokes(self, amount): - jokes = requests.get( - 'http://api.icndb.com/jokes/random/{0}'.format(amount)) - return json.loads(jokes.text)['value'] -``` - -Some notes: - -- Since calling view is a GET and not a POST we need `alsoProvides(self.request, IDisableCSRFProtection)` to allow write-on-read without Plone complaining. - Alternatively we could create a simple form and create the content on submit. - -- . `transition` has two modes of operation: - The documented one is `api.content.transition(obj=foo, transition='bar')`. - That mode tries to execute that specific transition. - But sometimes it is better to use `to_state` which tries to to find a way to get from the current state to the target-state. - See for the docstring. - -- To use methods like `cropText` from another view, you can use the method already discussed in - -- Here the joke is added as the description. To add it as the text, you need to create an instance of `RichTextValue` and set that as an attribute: - - ```python - from plone.app.textfield.value import RichTextValue - talk.details = RichTextValue(joke, 'text/plain', 'text/html',) - ``` -```` diff --git a/docs/mastering-plone-5/behaviors_1.md b/docs/mastering-plone-5/behaviors_1.md deleted file mode 100644 index d658131c9..000000000 --- a/docs/mastering-plone-5/behaviors_1.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-behaviors1-label)= - -# Behaviors - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout testing -``` - -Code for the end of this chapter: - -```shell -git checkout behaviors_1 -``` - -{doc}`code` -```` - -In this part you will: - -- Add another field to talks by using a behavior - -Topics covered: - -- Behaviors - -```{only} not presentation -You can extend the functionality of your Dexterity object by writing an adapter that adapts your dexterity object to add another feature or aspect. - -But if you want to use this adapter, you must somehow know that an object implements that. -Also, adding more fields to an object would not be easy with such an approach. -``` - -(plone5-behaviors1-dexterity-label)= - -## Dexterity Approach - -```{only} not presentation -Dexterity has a solution for it, with special adapters that are called and registered by the name behavior. - -A behavior can be added to any content type through the web and at runtime. - -All default views (e.g. the add- and edit-forms) know about the concept of behaviors. -When rendering forms, the views also check whether there are behaviors referenced with the current context and if these behaviors have a schema of their own, these fields get shown in addition. -``` - -(plone5-behaviors1-names-label)= - -## Names and Theory - -```{only} not presentation -The name behavior is not a standard term in software development. -But it is a good idea to think of a behavior as an aspect. -You are adding an aspect to your content type and you want to write your aspect in such a way that it works independently of the content type on which the aspect is applied. -You should not have dependencies to specific fields of your object or to other behaviors. - -Such an object allows you to apply the [open/closed principle](https://en.wikipedia.org/wiki/Open/closed_principle) to your dexterity objects. -``` - -(plone5-behaviors1-example-label)= - -## Practical example - -```{only} not presentation -So, let us write our own small behavior. - -In the future, we want some talks, news items or other content be represented on the frontpage similar to what we did with the "hot news" field early on. - -So for now, our behavior just adds a new field to store this information. -``` - -We want to keep a clean structure, so we create a {file}`behaviors` directory first, and include it into the zcml declarations of our {file}`configure.zcml`. - -```xml - -``` - -Then, we add an empty {file}`behaviors/__init__.py` and a {file}`behaviors/configure.zcml` containing - -(plone5-social-behavior-zcml-label)= - -```{code-block} xml -:emphasize-lines: 6-10 -:linenos: - - - - - - -``` - -And a {file}`behaviors/featured.py` containing: - -(plone5-social-behavior-python-label)= - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone.autoform.interfaces import IFormFieldProvider -from plone.supermodel import directives -from plone.supermodel import model -from zope import schema -from zope.interface import provider - -@provider(IFormFieldProvider) -class IFeatured(model.Schema): - - directives.fieldset( - 'featured', - label=u'Featured', - fields=('featured',), - ) - - featured = schema.Bool( - title=u'Show this item on the frontpage', - required=False, - ) -``` - -```{only} not presentation -Let's go through this step by step. - -1. We register a behavior in {file}`behaviors/configure.zcml`. - We do not say for which content type this behavior is valid. - You do this through the web or in the GenericSetup profile. -2. We create a marker interface in {file}`behaviors/social.py` for our behavior. - We make it also a schema containing the fields we want to declare. - We could just define schema fields on a zope.interface class, but we use an extended form from {py:mod}`plone.supermodel`, else we could not use the fieldset features. -3. We mark our schema as a class that also provides the {py:mod}`IFormFieldProvider` interface using a decorator. - The schema class itself provides the interface, not its instance! -4. We also add a `fieldset` so that our fields are not mixed with the normal fields of the object. -5. We add a normal [Bool](https://zopeschema.readthedocs.io/en/latest/api.html#zope.schema.interfaces.IBool) schema field to control if a item should be displayed on the frontpage. -``` - -````{only} not presentation -```{note} - -It can be a bit confusing when to use factories or marker interfaces and when not to. - -If you do not define a factory, your attributes will be stored directly on the object. -This can result in clashes with other behaviors. - -You can avoid this by using the {py:class}`plone.behavior.AnnotationStorage` factory. -This stores your attributes in an [Annotation](https://5.docs.plone.org/develop/plone/misc/annotations.html). -But then you *must* use a marker interface if you want to have custom viewlets, browser views or portlets. - -Without it, you would have no interface against which you could register your views. -``` -```` - -(plone5-behaviors1-adding-label)= - -## Adding it to our talk - -```{only} not presentation -We could add this behavior now via the plone control panel. -But instead, we will do it directly and properly in our GenericSetup profile -``` - -We must add the behavior to {file}`profiles/default/types/talk.xml`: - -```{code-block} xml -:emphasize-lines: 8 -:linenos: - - - - ... - - - - - - ... - -``` - -[plone5_fieldset]: https://5.docs.plone.org/develop/addons/schema-driven-forms/customising-form-behaviour/fieldsets.html -[plone5_iformfieldprovider]: https://5.docs.plone.org/external/plone.app.dexterity/docs/advanced/custom-add-and-edit-forms.html?highlight=iformfieldprovider#edit-forms -[plone5_plone.supermodel]: https://5.docs.plone.org/external/plone.app.dexterity/docs/schema-driven-types.html#schema-interfaces-vs-other-interfaces diff --git a/docs/mastering-plone-5/behaviors_2.md b/docs/mastering-plone-5/behaviors_2.md deleted file mode 100644 index 3fcf0e072..000000000 --- a/docs/mastering-plone-5/behaviors_2.md +++ /dev/null @@ -1,522 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-behaviors2-label)= - -# More Complex Behaviors - -In this part you will: - -- Write an annotation - -Topics covered: - -- Annotation Marker Interfaces - -We are working in the newly created add-on `starzel.votable_behavior` we just created using mr.bob. - -(plone5-behaviors2-annotations-label)= - -## Using Annotations - -```{only} not presentation -We are going to store the information in an annotation. -Not because it is needed but because you will find code that uses annotations and need to understand the implications. - -[Annotations] in Zope/Plone mean that data won't be stored directly on an object but in an indirect way with namespaces so that multiple packages can store information under the same attribute, without colliding. - -So using annotations avoids namespace conflicts. -The cost is an indirection. -The dictionary is persistent so it has to be stored separately. -Also, one could give attributes a name containing a namespace prefix to avoid naming collisions. -``` - -```{only} presentation -- What are annotations -- When to use them -``` - -(plone5-behaviors2-schema-label)= - -## Using Schema - -```{only} not presentation -The attribute where we store our data will be declared as a schema field. -We mark the field as an omitted field (using schema directive similar to `read_permission` or `widget`), because we are not going to create {py:mod}`z3c.form` widgets for entering or displaying them. -We do provide a schema, because many other packages use the schema information to get knowledge of the relevant fields. - -For example, when files were migrated to blobs, new objects had to be created and every schema field was copied. -The code can not know about our field, except if we provide schema information. -``` - -```{only} presentation -- Why to use schemas always -``` - -(plone5-behaviors2-code-label)= - -## Writing Code - -To start, we create a directory {file}`behavior` with an empty {file}`behavior/__init__.py` file. - -Next we must, as always, register our ZCML. - -First, add the information that there will be another ZCML file in {file}`configure.zcml` - -```{code-block} xml -:linenos: - - - - ... - - ... - - -``` - -Next, create {file}`behavior/configure.zcml` - -```{code-block} xml -:linenos: - - - - - - -``` - -There are some important differences to our first behavior: - -> - There is a marker interface -> - There is a factory - -```{only} not presentation -The factory is a class that provides the behavior logic and gives access to the attributes we provide. -Factories in Plone/Zope land are retrieved by adapting an object to an interface and are following the adapter pattern. -If you want your behavior, you would write `voting = IVoting(object)`. - -But in order for this to work, your object may *not* be implementing the `IVoting` interface, because if it did, `IVoting(object)` would return the object itself! -If I need a marker interface for objects providing my behavior, I must provide one, for this we use the marker attribute. -My object implements `IVotable`. -Because of this, we can write views and viewlets just for this content type. -``` - -The interfaces need to be written, in our case into a file {file}`interfaces.py`: - -```{code-block} python -:linenos: - -# encoding=utf-8 -from plone import api -from plone.autoform import directives -from plone.autoform.interfaces import IFormFieldProvider -from plone.supermodel import model -from plone.supermodel.directives import fieldset -from zope import schema -from zope.interface import Interface -from zope.interface import provider - -class IVotableLayer(Interface): - """Marker interface for the Browserlayer - """ - -# Ivotable is the marker interface for contenttypes who support this behavior -class IVotable(Interface): - pass - -# This is the behaviors interface. When doing IVoting(object), you receive an -# adapter -@provider(IFormFieldProvider) -class IVoting(model.Schema): - if not api.env.debug_mode(): - directives.omitted("votes") - directives.omitted("voted") - - fieldset( - 'debug', - label=u'debug', - fields=('votes', 'voted'), - ) - - votes = schema.Dict(title=u"Vote info", - key_type=schema.TextLine(title=u"Voted number"), - value_type=schema.Int(title=u"Voted so often"), - required=False) - voted = schema.List(title=u"Vote hashes", - value_type=schema.TextLine(), - required=False) - - def vote(request): - """ - Store the vote information, store the request hash to ensure - that the user does not vote twice - """ - - def average_vote(): - """ - Return the average voting for an item - """ - - def has_votes(): - """ - Return whether anybody ever voted for this item - """ - - def already_voted(request): - """ - Return the information wether a person already voted. - This is not very high level and can be tricked out easily - """ - - def clear(): - """ - Clear the votes. Should only be called by admins - """ -``` - -```{only} not presentation -This is a lot of code. -The `IVotableLayer` we will need later for viewlets and browser views. -Let's add it right here. -The `IVotable` interface is the simple marker interface. -It will only be used to bind browser views and viewlets to contenttypes that provide our behavior, so no code needed. - -The `IVoting` class is more complex, as you can see. - -The `@provider` decorator above the class ensures that the schema fields are known to other packages. -Whenever some code wants all schemas from an object, it receives the schema defined directly on the object and the additional schemata. -Additional schemata are compiled by looking for behaviors and whether they provide the `IFormFieldProvider` functionality. -Only then the fields are used as form fields. - -While IVoting is just an interface, we use `plone.supermodel.model.Schema` for advanced dexterity features. -`zope.schema` provides no means for hiding fields. - -The directives `form.omitted` from `plone.autoform` allow us to annotate this additional information so that the autoform renderers for forms can use the additional information. -We make this omit conditional. -If we run Plone in debug mode, we will be able to see the internal data in the edit form. - -We create minimal schema fields for our internal data structures. -For a small test, I removed the form omitted directives and opened the edit view of a talk that uses the behavior. After seeing the ugliness, I decided that I should provide at least minimum of information. -`title` and `required` are purely optional, but very helpful if the fields won't be omitted, something that can be helpful when debugging the behavior. -Later, when we implement the behavior, the `votes` and `voted` attributes are implemented in such a way that you can't just modify these fields, they are read only. - -Then we define the API that we are going to use in browser views and viewlets. -``` - -Now the only thing that is missing is the behavior implementation, which we must put into {file}`behavior/voting.py` - -```{code-block} python -:linenos: - -# encoding=utf-8 -from .interfaces import IVoting -from hashlib import md5 -from persistent.dict import PersistentDict -from persistent.list import PersistentList -from zope.annotation.interfaces import IAnnotations -from zope.interface import implementer - -KEY = "starzel.votable_behavior.behavior.voting.Vote" - - -@implementer(IVoting) -class Vote(object): - def __init__(self, context): - self.context = context - annotations = IAnnotations(context) - if KEY not in annotations.keys(): - annotations[KEY] = PersistentDict({ - "voted": PersistentList(), - 'votes': PersistentDict() - }) - self.annotations = annotations[KEY] - - @property - def votes(self): - return self.annotations['votes'] - - @property - def voted(self): - return self.annotations['voted'] -``` - -````{only} not presentation -In our `__init__` method we get *annotations* from the object. -We look for data with a specific key. - -The key in this example is the same as what I would get with `__name__+Vote.__name__`. -But we won't create a dynamic name, this would be very clever and clever is bad. - -By declaring a static name, we won't run into problems if we restructure the code. - -You can see that we initialize the data if it doesn't exist. -We work with `PersistentDict` and `PersistentList`. -To understand why we do this, it is important to understand how the ZODB works. - -```{seealso} -The ZODB can store objects. -It has a special root object that you will never touch. -Whatever you store there, will be part of the root object, except if it is an object subclassing `persistent.Persistent`. Then it will be stored independently. - -Zope/ZODB persistent objects note when you change an attribute on it and mark itself as changed. -Changed objects will be saved to the database. -This happens automatically. -Each request begins a transaction and after our code runs and the Zope Server is preparing to send back the response we generated, the transaction will be committed and everything we changed will be saved. - -Now, if have a normal dictionary on a persistent object, and you will only change the dictionary, the persistent object has no way to know if the dictionary has been changed. -This happens from time to time. - -So one solution is to change the special attribute `_p_changed` to `True` (or any other value!) on the persistent object, or to use a `PersistentDict`. -Latter is what we are doing here. - -An important thing to note about `PersistentDict` and `PersistentList` is that they cannot handle write conflicts. -What happens if two users rate the same content independently at the same time? -In this case, a database conflict will occur because there is no way for Plone to know how to handle the concurrent write access. -Although this is rather unlikely during this training, it is a very common problem on high traffic websites. - -You can find more information in the documentation of the ZODB, in particular [Rules for Persistent Classes](https://zodb.org/en/latest/guide/writing-persistent-objects.html) -``` - -Next we provide the internal fields via properties. -Using this form of property makes them read only properties, as we did not define write handlers. -We don't need them so we won't add them. - -As you have seen in the Schema declaration, if you run your site in debug mode, you will see an edit field for these fields. -But trying to change these fields will throw an exception. -```` - -```{only} presentation -- Explain ZODB and Persistent Classes -``` - -Let's continue with the {file}`behavior/voting.py` file, inside the `Vote` class: - -```{code-block} python -:linenos: - - def _hash(self, request): - """ - This hash can be tricked out by changing IP addresses and might allow - only a single person of a big company to vote - """ - hash_ = md5() - hash_.update(request.getClientAddr()) - for key in ["User-Agent", "Accept-Language", "Accept-Encoding"]: - hash_.update(request.getHeader(key)) - return hash_.hexdigest() - - def vote(self, vote, request): - if self.already_voted(request): - raise KeyError("You may not vote twice") - vote = int(vote) - self.annotations['voted'].append(self._hash(request)) - votes = self.annotations['votes'] - if vote not in votes: - votes[vote] = 1 - else: - votes[vote] += 1 - - def average_vote(self): - if not has_votes(self): - return 0 - total_votes = sum(self.annotations['votes'].values()) - total_points = sum( - [vote * count for (vote, count) in self.annotations['votes'].items()]) - return float(total_points) / total_votes - - def has_votes(self): - return len(self.annotations.get('votes', [])) != 0 - - def already_voted(self, request): - return self._hash(request) in self.annotations['voted'] - - def clear(self): - annotations = IAnnotations(self.context) - annotations[KEY] = PersistentDict( - {'voted': PersistentList(), 'votes': PersistentDict()} - ) - self.annotations = annotations[KEY] -``` - -```{only} not presentation -We start with a little helper method which is not exposed via the interface. -We don't want people to vote twice. -There are many ways to ensure this and each one has flaws. - -We chose this way to show you how to access information from the request the browser of the user sent to us. -First, we get the IP address of the user, then we access a small set of headers from the user's browser and generate an md5 checksum of this. - -The vote method wants a vote and a request. We check the preconditions, then we convert the vote to an integer, store the request to `voted` and the votes into the `votes` dictionary. -We just count there how often any vote has been given. - -Everything else is just python. -``` - -### Exercises - -#### Exercise 1 - -Refactor the voting behavior so that it uses `BTrees` instead of `PersistentDict` and `PersistentList`. -Use `OOBTree` to replace `PersistentDict` and `OIBTree` to replace `PersistentList`. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -change {file}`behavior/voting.py` - -```{code-block} python -:emphasize-lines: 3,4,15-17,26-28,39-41 - -# encoding=utf-8 -from .interfaces import IVoting -from BTrees.OIBTree import OIBTree -from BTrees.OOBTree import OOBTree -from hashlib import md5 -from zope.annotation.interfaces import IAnnotations -from zope.interface import implementer - -KEY = "starzel.votable_behavior.behavior.voting.Vote" - -@implementer(IVoting) -class Vote(object): - def __init__(self, context): - self.context = context - annotations = IAnnotations(context) - if KEY not in annotations.keys(): - self.clear() - else: - self.annotations = annotations[KEY] - - ... - - def vote(self, vote, request): - if self.already_voted(request): - raise KeyError("You may not vote twice") - vote = int(vote) - self.annotations['voted'].insert( - self._hash(request), - len(self.annotations['voted'])) - votes = self.annotations['votes'] - if vote not in votes: - votes[vote] = 1 - else: - votes[vote] += 1 - - ... - - def clear(self): - annotations = IAnnotations(self.context) - annotations[KEY] = OOBTree() - annotations[KEY]['voted'] = OIBTree() - annotations[KEY]['votes'] = OOBTree() - self.annotations = annotations[KEY] -``` -```` - -#### Exercise 2 - -Write a unit test that simulates concurrent voting. -The test should raise a `ConflictError` on the original voting behavior implementation. -The solution from the first exercise should pass. -Look at the file `ZODB/ConflictResolution.txt` in the `ZODB3` egg for how to create a suitable test fixture for conflict testing. -Look at the test code in `zope.annotation` for how to create annotatable dummy content. -You will also have to write a 'request' dummy that mocks the `getClientAddr` and `getHeader` methods of Zope's HTTP request object to make the `_hash` method of the voting behavior work. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -There are no tests for `starzel.votablebehavior` at all at the moment. -But you can refer to {doc}`testing` for how to setup unit testing for a package. -Put the particular test for this exercise into a file named {file}`starzel.votable_behavior/starzel/votable_behavior/tests/test_voting`. -Remember you need an empty {file}`__init__.py` file in the {file}`tests` directory to make testing work. -You also need to add `starzel.votable_behavior` to `test-eggs` in {file}`buildout.cfg` and re-run buildout. - -```{code-block} python -:linenos: - -from persistent import Persistent -from zope.annotation.attribute import AttributeAnnotations -from zope.annotation.interfaces import IAttributeAnnotatable -from zope.interface import implementer - -import tempfile -import transaction -import unittest -import ZODB - -@implementer(IAttributeAnnotatable) -class Dummy(Persistent): - pass - - - -class RequestDummy(object): - - def __init__(self, ip, headers=None): - self.ip = ip - if headers is not None: - self.headers = headers - else: - self.headers = { - 'User-Agent': 'foo', - 'Accept-Language': 'bar', - 'Accept-Encoding': 'baz' - } - - def getClientAddr(self): - return self.ip - - def getHeader(self, key): - return self.headers[key] - - -class VotingTests(unittest.TestCase): - - def test_voting_conflict(self): - from starzel.votable_behavior.behavior.voting import Vote - dbname = tempfile.mktemp() - db = ZODB.DB(dbname) - tm_A = transaction.TransactionManager() - conn_A = db.open(transaction_manager=tm_A) - p_A = conn_A.root()['voting'] = Vote(AttributeAnnotations(Dummy())) - tm_A.commit() - # Now get another copy of 'p' so we can make a conflict. - # Think of `conn_A` (connection A) as one thread, and - # `conn_B` (connection B) as a concurrent thread. `p_A` - # is a view on the object in the first connection, and `p_B` - # is a view on *the same persistent object* in the second connection. - tm_B = transaction.TransactionManager() - conn_B = db.open(transaction_manager=tm_B) - p_B = conn_B.root()['voting'] - assert p_A.context.obj._p_oid == p_B.context.obj._p_oid - # Now we can make a conflict, and see it resolved (or not) - request_A = RequestDummy('192.168.0.1') - p_A.vote(1, request_A) - request_B = RequestDummy('192.168.0.5') - p_B.vote(2, request_B) - tm_B.commit() - tm_A.commit() -``` -```` - -[annotations]: https://5.docs.plone.org/develop/plone/misc/annotations.html -[plone5_happens]: https://github.com/plone/Products.CMFEditions/commit/5c07c72bc8701cf28c9cc68ad940186b9e296ddf diff --git a/docs/mastering-plone-5/buildout_1.md b/docs/mastering-plone-5/buildout_1.md deleted file mode 100644 index 4c34a9944..000000000 --- a/docs/mastering-plone-5/buildout_1.md +++ /dev/null @@ -1,398 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-buildout1-label)= - -# Buildout I - -In this part you will: - -- Learn about Buildout - -Topics covered: - -- Buildout -- Recipes -- Buildout Configuration -- mr.developer - -```{only} not presentation -[Buildout](https://pypi.org/project/zc.buildout) composes your application for you, according to your rules. - -To compose your application you must define the eggs you need, which version, what configuration files Buildout has to generate for you, what to download and compile, and so on. -Buildout downloads the eggs you requested and resolves all dependencies. You might need five different eggs, but in the end, Buildout has to install 300 eggs, all with the correct version in order to resolve all the dependencies. - -Buildout does this without touching your system Python or affecting any other package. The commands created by buildout bring all the required packages into the Python environment. Each command it creates may use different libraries or even different versions of the same library. - -Plone needs folders for logfiles, databases and configuration files. Buildout assembles all of this for you. - -You will need a lot of functionality that Buildout does not provide out of the box, so you'll need several extensions. -Some extensions provide new functionality, like mr.developer, the best way to manage your checked out sources. -``` - -## Minimal Example - -Here is a functioning minimal example from : - -```ini -[buildout] -parts = instance -extends = https://dist.plone.org/release/5.2-latest/versions.cfg - -[instance] -recipe = plone.recipe.zope2instance -eggs = - Plone -``` - -(plone5-buildout1-syntax-label)= - -## Syntax - -```{only} not presentation -The syntax of Buildout configuration files is similar to classic ini files. You write a parameter name, an equals sign and the value. If you enter another value in the next line and indent it, Buildout understands that both values belong to the parameter name, and the parameter stores all values as a list. - -A Buildout consists of multiple sections. Sections start with the section name in square brackets. Each section declares a different part of your application. As a rough analogy, your Buildout file is a cookbook with multiple recipes. - -There is a special section, called `[buildout]`. This section can change the behavior of Buildout itself. The variable {samp}`parts` defines which of the existing sections should actually be used. -``` - -(plone5-buildout1-recipes-label)= - -## Recipes - -Buildout itself has no idea how to install Zope. -Buildout is a plugin based system, it comes with a small set of plugins to create configuration files -and download eggs with their dependencies and the proper version. - -To install a Zope site, you need a third-party plugin. -The plugins provide new recipes that you have to declare and configure in their own respective sections. - -One example is the section - -```ini -[instance] -recipe = plone.recipe.zope2instance -user = admin:admin -``` - -This uses the python package [plone.recipe.zope2instance](https://pypi.org/project/plone.recipe.zope2instance) -to create and configure the Zope 2 instance which we use to run Plone. - -All the lines after {samp}`recipe = xyz` are the configuration of the specified recipe. - -```{note} -There are way to many buidout-recipes. See -``` - -(plone5-buildout1-references-label)= - -## References - -```{only} not presentation -Buildout allows you to use references in the configuration. A variable declaration may not only hold the variable value, but also a reference to where to look for the variable value. - -If you have a big setup with many Plone sites with minor changes between each configuration, you can generate a template configuration, and each site references everything from the template and overrides just what needs to be changed. - -Even in smaller buildouts this is a useful feature. We are using [collective.recipe.omelette](https://pypi.org/project/collective.recipe.omelette). A very practical recipe that creates a virtual directory that eases the navigation to the source code of each egg. - -The omelette recipe needs to know which eggs to reference. We want the same eggs that our instance uses, so we reference the eggs of the instance instead of repeating the whole list. - -Another example: Say you create configuration files for a webserver like nginx, you can define the target port for the reverse proxy by looking it up from the zope2instance recipe. - -Configuring complex systems always involves a lot of duplication of information. Using references in the buildout configuration allows you to minimize these duplications. -``` - -(plone5-buildout1-examples-label)= - -## A real life example - -Let us walk through the {file}`buildout.cfg` for the training and look at some important variables: - -```ini -[buildout] -extends = - http://dist.plone.org/release/5.2/versions.cfg - versions.cfg -extends-cache = extends-cache - -extensions = mr.developer -# Tell mr.developer to ask before updating a checkout. -always-checkout = true -show-picked-versions = true -sources = sources - -# The directory this buildout is in. Modified when using vagrant. -buildout_dir = ${buildout:directory} - -# We want to checkouts these eggs directly from github -auto-checkout = - ploneconf.site -# starzel.votable_behavior - -parts = - checkversions - instance - mrbob - packages - robot - test - zopepy - -eggs = - Plone - Pillow - -# development tools - plone.reload - Products.PDBDebugMode - plone.app.debugtoolbar - Products.PrintingMailHost - pdbpp - -# TTW Forms - collective.easyform - -# The add-on we develop in the training - ploneconf.site - -# Voting on content -# starzel.votable_behavior - -zcml = - -test-eggs += - ploneconf.site [test] - -[instance] -recipe = plone.recipe.zope2instance -user = admin:admin -http-address = 8080 -debug-mode = on -verbose-security = on -deprecation-warnings = on -eggs = ${buildout:eggs} -zcml = ${buildout:zcml} -file-storage = ${buildout:buildout_dir}/var/filestorage/Data.fs -blob-storage = ${buildout:buildout_dir}/var/blobstorage - -[test] -recipe = zc.recipe.testrunner -eggs = ${buildout:test-eggs} -defaults = ['--auto-color', '-vvv'] - -[robot] -recipe = zc.recipe.egg -eggs = - ${buildout:test-eggs} - Pillow - plone.app.robotframework[reload,debug] - -[packages] -recipe = collective.recipe.omelette -eggs = ${buildout:eggs} -location = ${buildout:buildout_dir}/packages - -[checkversions] -recipe = zc.recipe.egg -eggs = z3c.checkversions [buildout] - -[zopepy] -recipe = zc.recipe.egg -eggs = - ${buildout:eggs} -# need to explicitly mention plone.staticresources in order for plone-compile-resources to be found - plone.staticresources -interpreter = zopepy -scripts = - zopepy - plone-compile-resources - -[mrbob] -recipe = zc.recipe.egg -eggs = - mr.bob - bobtemplates.plone - -[sources] -ploneconf.site = git https://github.com/collective/ploneconf.site.git pushurl=git@github.com:collective/ploneconf.site.git -starzel.votable_behavior = git https://github.com/collective/starzel.votable_behavior.git pushurl=git://github.com/collective/starzel.votable_behavior.git -``` - -When you run {command}`./bin/buildout` without any arguments, Buildout will look for this file. - -```{note} -If you are using the vagrant installation, you will have to activate your `virtualenv` and run the command {command}`buildout` only. -In the vagrant setup `zc.buildout` and `setuptools` are installed in the virtualenv and therefore available without specifying the -preceding path. This is possible because in recent versions of `zc.buildout` the `bootstrap` step is no longer necessary. -``` - -```{only} not presentation -Let us look closer at some variables. -``` - -````{only} not presentation -```cfg -extends = - http://dist.plone.org/release/5.2/versions.cfg -``` - -This line tells Buildout to read another configuration file. You can refer to configuration files on your computer or to configuration files on the Internet, reachable via http. You can use multiple configuration files to share configurations between multiple Buildouts, or to separate different aspects of your configuration into different files. Typical examples are version specifications, or configurations that differ between different environments. - -```cfg -eggs = - Plone - -# development tools - z3c.jbot - plone.reload - Products.PDBDebugMode - plone.app.debugtoolbar - Products.PrintingMailHost - pdbpp - -# TTW Forms - collective.easyform - -# The add-on we develop in the training - ploneconf.site - -# Voting on content -# starzel.votable_behavior - -zcml = - -test-eggs += - ploneconf.site [test] -``` - -This is the list of eggs that we configure to be available for Zope. These eggs are put in the python path of the script {command}`bin/instance` with which we start and stop Plone. - -The egg `Plone` is a wrapper without code. Among its dependencies is {py:mod}`Products.CMFPlone` which is the egg that is at the center of Plone. - -The rest are add-ons we already used or will use later. The last eggs are commented out so they will not be installed by Buildout. - -The file {file}`versions.cfg` that is included by the {samp}`extends = ...` statement holds the version pins: - -```cfg -[versions] -# dev tools and their dependencies -pdbpp = 0.10.0 -fancycompleter = 0.8 -pyrepl = 0.9.0 - -# pins for Add-ons -collective.easyform = 2.1.0 -Products.validation = 2.1.1 - -# pins for mr.bob and bobtemplates.plone -bobtemplates.plone = 4.1.3 -case-conversion = 2.1.0 -mr.bob = 0.1.2 - -# Some other pins from coredev -argh = 0.26.2 -pathtools = 0.1.2 -prompt-toolkit = 1.0.16 -PyYAML = 5.1.2 -regex = 2019.8.19 -watchdog = 0.9.0 -wcwidth = 0.1.7 -wmctrl = 0.3 -``` - -This is another special section. By default buildout will look for version pins in a section called `[versions]`. This is why we included the file {file}`versions.cfg`. -```` - -(plone5-buildout1-mrdeveloper-label)= - -## Mr. Developer - -```{only} not presentation -There are many more important things to know, and we can't go through them all in detail but I want to focus on one specific feature: {py:mod}`mr.developer` - -With {py:mod}`mr.developer` you can declare which packages you want to check out from which version control system and which repository URL. You can check out sources from git, svn, bzr, hg and maybe more. Also, you can say that some sources are in your local file system. - -{py:mod}`mr.developer` comes with a command, {command}`./bin/develop`. You can use it to update your code, to check for changes and so on. You can activate and deactivate your source checkouts. If you develop your extensions in eggs with separate checkouts, which is a good practice, you can plan releases by having all source checkouts deactivated, and only activate them when you write changes that require a new release. You can activate and deactivate eggs via the {command}`develop` command or the Buildout configuration. You should always use the Buildout way. Your commit serves as documentation. -``` - -(plone5-buildout1-extensible-label)= - -## Extensible - -```{only} not presentation -You might have noticed that most if not all functionality is only available via plugins. -One of the things that Buildout excels at without any plugin is the dependency resolution. -You can help Plone in dependency resolution by declaring exactly which version of an egg you want. - -This is only one use case. -Another one is much more important: If you want to have a repeatable Buildout, one that works two months from now. - -Also, you *must* declare all your egg versions, else Buildout might install newer versions. -``` - -(plone5-buildout1-mcguyver-label)= - -## Be McGuyver - -````{only} not presentation -As you can see, you can build very complex systems with Buildout. It is time for some warnings. Be selective in your recipes. Supervisor is a program to manage running servers, and it's pretty good. There is a recipe for it. - -The configuration for this recipe is more complicated than the supervisor configuration itself! By using this recipe, you force others to understand the recipe's specific configuration syntax *and* the supervisor syntax. For such cases, [collective.recipe.template](https://pypi.org/project/collective.recipe.template) is a better match. - -Another problem is error handling. Buildout tries to install a weird dependency you do not actually want? Buildout will not tell you where it is coming from. - -If there is a problem, you can always run Buildout with {option}`-v` to get more verbose output, sometimes it helps. - -```shell -$ ./bin/buildout -v -``` - -If strange egg versions are requested, check the dependencies declaration of your eggs and your version pinnings. -Here is an invaluable shell command that allows you to find all packages that depend on a particular egg and version: - -```shell -$ grep your.egg.name.here /home/vagrant/buildout-cache/eggs/*.egg/EGG-INFO/requires.txt -``` - -Put the name of the egg with a version conflict as the first argument. Also, change the path to the buildout cache folder according to your installation (the vagrant buildout is assumed in the example). - -Some parts of Buildout interpret egg names case sensitively, others don't. This can result in funny problems. - -Always check out the ordering of your extends, always use the {samp}`annotate` command of Buildout to see if it interprets your configuration differently than you. Restrict yourself to simple Buildout files. You can reference variables from other sections, you can even use a whole section as a template. We learned that this does not work well with complex hierarchies and had to abandon that feature. - -In the chapter {doc}`deployment_sites` we will have a look at a production-ready buildout for Plone that has many useful features. -```` - -```{seealso} -Buildout-Documentation - -: - -Troubleshooting - -: - -A minimal buildout for Plone 5 - -: - -A minimal buildout for Plone 4 - -: - -The buildout of the unified installer has some valuable documentation as inline-comment -: - - - - - - -mr.developer - -: -``` diff --git a/docs/mastering-plone-5/case.md b/docs/mastering-plone-5/case.md deleted file mode 100644 index fdddb8612..000000000 --- a/docs/mastering-plone-5/case.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-case-label)= - -# The Case Study - -For this training we will build a website for a fictional Plone conference. - -(plone5-case-background-label)= - -## Background - -The Plone conference takes place every year and all Plone developers at least try to go there. - -(plone5-case-requirements-label)= - -## Requirements - -Here are some requirements that we want to meet when the site is done: - -- As a visitor I want to find current information on the conference. -- As a visitor I want to register for the conference. -- As a visitor I want to see the talks and sort them by my preferences. -- As a speaker I want to be able to submit talks. -- As a speaker I want to see and edit my submitted talks. -- As an organizer I want to see a list of all proposed talks. -- As an organizer I want to have an overview about how many people registered. -- As a jury member I want to vote on talks. -- As a jury member I want to decide which talks to accept, and which not. - -Note that all of our requirements connect roles with capabilities. -This is important because we'll want to limit the capabilities to those to whom we assign particular roles. diff --git a/docs/mastering-plone-5/code.md b/docs/mastering-plone-5/code.md deleted file mode 100644 index 131887741..000000000 --- a/docs/mastering-plone-5/code.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -# Using the code for the training - -You can get the complete code for this training from [GitHub](https://github.com/collective/ploneconf.site). - -## The code-package - -The package [ploneconf.site](https://github.com/collective/ploneconf.site) contains the complete code for this training excluding exercises. -It is automatically downloaded from GitHub when you run buildout. - -The master branch of that repository holds the code of the final chapter of this training. -Each chapter that adds code to the package has a tag that can be used to get the code for that chapter. - -## Getting the code for a certain chapter - -To use the code for a certain chapter you need to checkout the appropriate tag for the chapter. -The package will then contain the complete code for that chapter (excluding exercises). - -If you want to add the code for the chapter yourself you have to checkout the tag for the previous chapter. - -Here is an example: - -```shell -git checkout views_2 -``` - -The names of the tags are the same as the URL of the chapter. -The tag for the chapter https://training.plone.org/mastering-plone/registry.html is `registry`. -You can get it with {command}`git checkout registry`. - -## Moving from chapter to chapter - -To change the code to the state of the next chapter checkout the tag for the next chapter: - -```shell -git checkout views_3 -``` - -If you made any changes to the code you have to get them out of the way first. This inviolved two things - -```{warning} -Make sure you have no new files or changes in the folder structure of `ploneconf.site` that you want to keep because the following will delete them!!! -``` - -```shell -git clean -fd -git stash -``` - -This does two things: - -1. It deletes any files that you added and are not part of the package. -2. It will move away changes to files that are part of the package but not delete them. You can get them back later. You should learn about the command {command}`git stash` before you try reapply stashed changes. - -## Tags - -These are the tags for which there is code: - -| Chapter | Tag-Name | -| ------------------------------ | ------------------------ | -| {doc}`about_mastering` | | -| {doc}`intro` | | -| {doc}`installation` | | -| {doc}`case` | | -| {doc}`features` | | -| {doc}`anatomy` | | -| {doc}`plone5` | | -| {doc}`configuring_customizing` | | -| {doc}`theming` | | -| {doc}`extending` | | -| {doc}`add-ons` | | -| {doc}`dexterity` | | -| {doc}`buildout_1` | `buildout_1` | -| {doc}`eggs1` | `eggs1` | -| {doc}`export_code` | `export_code` | -| {doc}`views_1` | `views_1` | -| {doc}`zpt` | `zpt` | -| {doc}`zpt_2` | `zpt_2` | -| {doc}`views_2` | `views_2` | -| {doc}`views_3` | `views_3` | -| {doc}`testing` | `testing` | -| {doc}`behaviors_1` | `behaviors_1` | -| {doc}`viewlets_1` | `viewlets_1` | -| {doc}`api` | | -| {doc}`ide` | | -| {doc}`dexterity_2` | `dexterity_2` | -| {doc}`custom_search` | | -| {doc}`events` | `events` | -| {doc}`user_generated_content` | `user_generated_content` | -| {doc}`resources` | `resources` | -| {doc}`thirdparty_behaviors` | `thirdparty_behaviors` | -| {doc}`dexterity_3` | `dexterity_3` | -| {doc}`relations` | `relations` | -| {doc}`registry` | `registry` | -| {doc}`frontpage` | `frontpage` | -| {doc}`eggs2` | | -| {doc}`behaviors_2` | | -| {doc}`viewlets_2` | | -| {doc}`reusable` | | -| {doc}`embed` | | -| {doc}`deployment_code` | | -| {doc}`deployment_sites` | | - -## Updating the code-package - -This section is for trainers who want to update the code in {py:mod}`ploneconf.site` after changing something in the training documentation. - -The current model uses only one branch of commits and maintains the integrity through rebases. - -It goes like this: - -- Only one one branch (master) - -- Write the code for chapter 1 and commit - -- Write the code for chapter 2 and commit - -- Add the code for chapter 3 and commit - -- You realize that something or wrong in chapter 1 - -- You branch off at the commit id for chapter 1 - `git checkout -b temp 123456` - -- You cange the code and do a commit. - `git commit -am 'Changed foo to also do bar'` - -- Switch to master and rebase on the branch holding the fix which will inject the new commit into master at the right place: - `git checkout master` - `git rebase temp` - That inserts the changes into master in the right place. You only maintain a master branch that is a sequence of commits. - -- Then you need to update your chapter-docs to point to the corresponding commit ids: - - - chapter one: `git checkout 121431243` - - chapter two: `git checkout 498102980` - -Additionally you can - -- set tags on the respective commits and move these tags. This way the docs do not need to be changed when the code changes. -- squash the commits between the chapters to every chapter is one commit. - -To move tags after changes you do: - -- Move a to another commit: `git tag -a -f` -- Move the tag on the server `git push --tags -f` - -The final result should look like this: - -```{figure} ../_static/code_tree.png -:align: center -``` diff --git a/docs/mastering-plone-5/configuring_customizing.md b/docs/mastering-plone-5/configuring_customizing.md deleted file mode 100644 index 4058770d6..000000000 --- a/docs/mastering-plone-5/configuring_customizing.md +++ /dev/null @@ -1,274 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-customizing-label)= - -# Configuring and Customizing Plone "Through The Web" - -````{warning} -> This chapter has not yet been updated for Plone 5! - -```{eval-rst} -.. sectionauthor:: Philip Bauer -``` -```` - -(plone5-customizing-controlpanel-label)= - -## The Control Panel - -The most important parts of Plone can be configured in the control panel. - -- Click on the portrait/username in the toolbar -- Click {guilabel}`Site Setup` - -We'll explain every page and mention some of the actions you can perform here. - -### General - -01. Date and Time -02. Language -03. Mail -04. Navigation -05. Site -06. Add-ons -07. Search -08. Discussion -09. Theming -10. Social Media -11. Syndication -12. TinyMCE - -### Content - -1. Content Rules -2. Editing -3. Image Handling -4. Markup -5. Content Settings -6. Dexterity Content Types - -### Users - -1. Users and Groups - -### Security - -1. HTML Filtering -2. Security -3. Errors - -### Advanced - -1. Maintenance -2. Management Interface -3. Caching -4. Configuration Registry -5. Resource Registries - -Below the links you will find information on your Plone, Zope and Python Versions and an indicator as to whether you're running in production or development mode. - -#### Change the logo - -Let's change the logo. - -- Download a ploneconf logo: -- Go to -- Upload the Logo. - -```{figure} _static/configuring_customizing_logo.png -:alt: The view of the homepage with the customized logo. - -The view of the homepage with the customized logo. -``` - -```{seealso} - -``` - -(plone5-customizing-portlets-label)= - -## Portlets - -In the toolbar under the {guilabel}`Portlets` section, you can open the configuration for the different places where you can have portlets. - -- UI fit for smart content editors -- Various types -- Portlet configuration is inherited -- Managing -- Ordering/weighting -- The future: may be replaced by tiles -- `@@manage-portlets` - -Example: - -- Go to - -- Add a static portlet "Sponsors" on the right side. - -- Remove the news portlet and add a new one on the left side. - -- Go to the training folder: and click {guilabel}`Manage portlets` - -- Add a static portlet. "Featured training: Become a Plone-Rockstar at Mastering Plone!" - -- Use the toolbar to configure the portlets of the footer: - - - Hide the portlets "Footer" and "Colophon". - - Add a {guilabel}`Static text portlet` and enter "Copyright 2019 by Plone Community". - - Use {menuselection}`Insert --> Special Character` to add a real © sign. - - You could turn that into a link to a copyright page later. - -(plone5-customizing-viewlets-label)= - -## Viewlets - -Portlets save data, Viewlets usually don't. Viewlets are often used for UI-Elements and have no nice UI to customize them. - -- `@@manage-viewlets` -- Viewlets have no nice UI -- Not aimed at content editors -- Not locally addable, no configurable inheritance. -- Usually global (depends on code) -- Will be replaced by tiles? -- The code is much simpler (we'll create one tomorrow). -- Live in viewlet managers, can be nested (by adding a viewlet that contains a viewlet manager). -- TTW reordering only within the same viewlet manager. -- The code decides when it is shown and what it shows. - -(plone5-customizing-zmi-label)= - -## ZMI (Zope Management Interface) - -Go to - -Zope is the foundation of Plone. Here you can access the inner workings of Zope and Plone alike. - -```{note} -Here you can easily break your site so you should know what you are doing! -``` - -```{only} not presentation -We only cover three parts of customization in the ZMI now. -Later on when we added our own code we'll come back to the ZMI and will look for it. - -At some point you'll have to learn what all those objects are about. But not today. -``` - -### Actions (portal_actions) - -- Actions are mostly links. But **really flexible** links. -- Actions are configurable TTW (Through-The-Web) and through code. -- These actions are usually iterated over in viewlets and displayed. - -Examples: - -- Links in the Footer (`site_actions`) -- Actions Dropdown (`object_buttons`) - -Actions have properties like: - -- description -- url -- i18n-domain -- condition -- permissions - -#### `site_actions` - -These are the links at the bottom of the page: - -- {guilabel}`Site Map` -- {guilabel}`Accessibility` -- {guilabel}`Contact` -- {guilabel}`Site Setup` - -We want a new link to legal information, called "Imprint". - -- Go to `site_actions` (we know that because we checked in `@@manage-viewlets`) -- Add a CMF Action `imprint` -- Set URL to `string:${portal_url}/imprint` -- Leave *condition* empty -- Set permission to `View` -- Save - -```{only} not presentation -explain -``` - -- Check if the link is on the page -- Create new Document "Imprint" and publish - -```{seealso} - -``` - -#### Global navigation - -- The horizontal navigation is called `portal_tabs` -- Go to {menuselection}`portal_actions --> portal_tabs` [Link](http://localhost:8080/Plone/portal_actions/portal_tabs/manage_main) -- Edit `index_html` - -Where is the navigation? - -The navigation shows content-objects, which are in Plone's root. Plus all actions in `portal_tabs`. - -Explain & edit `index_html` - -Configuring the navigation itself is done elsewhere: - -If time explain: - -- user > login/logout - -### portal_view_customizations - -#### Change the footer - -- Go to `portal_view_customizations` - -- Search `plone.footer`, click and customize - -- Replace the content with the following - - ```html - - ``` - -```{seealso} - -``` - -### CSS Registry (`portal_css`) - -*deprecated* (See the chapter on theming) - -### Further tools in the ZMI - -There are many more notable items in the ZMI. We'll visit some of them later. - -- {guilabel}`acl_users` -- {guilabel}`error_log` -- {guilabel}`portal_properties` (deprecated) -- {guilabel}`portal_setup` -- {guilabel}`portal_workflow` -- {guilabel}`portal_catalog` - -(plone5-customizing-summary-label)= - -## Summary - -You can configure and customize a lot in Plone through the web. The most important options are accessible in the [Plone control panel](http://localhost:8080/Plone/@@overview-controlpanel) but some are hidden away in the [ZMI](http://localhost:8080/Plone/manage). The amount and presentation of information is overwhelming but you'll get the hang of it through a lot of practice. diff --git a/docs/mastering-plone-5/custom_search.md b/docs/mastering-plone-5/custom_search.md deleted file mode 100644 index 3df7e4ec4..000000000 --- a/docs/mastering-plone-5/custom_search.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-customsearch-label)= - -# Custom Search - -If the chapters about views seem complex, the custom search add-ons shown below might be a great alternative -until you feel comfortable writing views and templates. - -Here are two add-ons that allow you to add custom searches and content listings through the web ("TTW", requiring no programming: only the web browser) in Plone. - -(plone5-customsearch-eea-label)= - -## eea.facetednavigation - -eea.facetednavigation is a full-featured and a very powerful add-on to improve search within large collections of items. -No programming skills are required to configure it since the configuration is done TTW. - -It lets you gradually select and explore different facets (metadata/properties) of the site content and narrow down you search quickly -and dynamically. - -- Install [eea.facetednavigation](https://pypi.org/project/eea.facetednavigation/) - -- Enable it on a new folder "Discover talks" by clicking on {guilabel}`Actions > Enable faceted navigation`. - -- Click on the {guilabel}`Faceted > Configure` to configure it TTW. - - > - Select 'Talk' for *Portal type*, hide *Results per page* - > - Add a checkboxes widget to the left and use the catalog index *Audience* for it. - > - Add a select widget for speaker - > - Add a radio widget for type_of_talk - -Examples: - -- https://www.dipf.de/en/research/current-projects -- https://www.mountaineers.org/courses/courses-clinics-seminars -- https://www.dyna-jet.com/highpressurecleaner - -```{seealso} -We use the new catalog indexes to provide the data for the widgets and search the results. -For other use cases we could also use either the built-in vocabularies () or create custom vocabularies for this. - -- Custom vocabularies TTW using [Products.ATVocabularyManager](https://pypi.org/project/Products.ATVocabularyManager) -- Programming using Vocabularies: -``` - -## collective.collectionfilter - -A more lightweight solution for custom searches and faceted navigation is [collective.collectionfilter](https://pypi.org/project/collective.collectionfilter). -By default it allows you to search among the results of a collection and/or filter the results by keywords, author or type. - -It can also be extended quite easily to allow additional filters (like `audience`). diff --git a/docs/mastering-plone-5/deployment_code.md b/docs/mastering-plone-5/deployment_code.md deleted file mode 100644 index 486655af2..000000000 --- a/docs/mastering-plone-5/deployment_code.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -# Releasing Your Code - -> - zest.releaser -> - pypi-test egg deployment - -We finally have some working code! Depending on your policies, you need repeatable deployments and definitive versions of software. That means you don't just run your production site with your latest source code from your source repository. You want to work with eggs. - -Making eggs is easy, making them properly not so much. There are a number of good practices that you should ensure. -Let's see. You want to have a sensible version number. By looking at the version number alone one should get a good idea how many changes there are (semantic version number scheme). Of course you always document everything, but for upgrades it is even more important to have complete changelogs. - -Sometimes, you cannot upgrade to a newer version, but you need a hotfix or whatever. It is crucial that you are able to checkout the exact version you use for your egg. - -These are a lot of steps, and there are a lot of actions that can go wrong. Luckily, there is a way to automate it. zest.releaser provides scripts to release an egg, to check what has changed since the release and to check if the documentation has errors. - -There once was a book on python. Among other things, it had a chapter on releasing an egg with sample code. The sample code was about a printer of nested lists. This resulted in a lot of packages to print out nested lists on pypi. - -We will avoid this. Everybody, go to [test.pypi.org](https://test.pypi.org) and create an account now. - -Next, copy the pypirc_sample file to ~/.pypirc, modify it to contain your real username and password. - -Now that we are prepared, let's install zest.releaser. - -- lasttagdiff -- longtest -- prerelease -- release -- postrelease diff --git a/docs/mastering-plone-5/deployment_sites.md b/docs/mastering-plone-5/deployment_sites.md deleted file mode 100644 index b0ef430fa..000000000 --- a/docs/mastering-plone-5/deployment_sites.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-deployment-buildout-label)= - -# Buildout II: Getting Ready for Deployment - -(plone5-deployment-starzel-label)= - -## The Starzel buildout - -Have a look at the buildout some of the trainers use for their projects: - -It has some notable features: - -- It extends to config- and version-files on github shared by all projects that use the same version of Plone: - - ```cfg - [buildout] - extends = - https://raw.githubusercontent.com/starzel/buildout/5.1.2/linkto/base.cfg - ``` - -- It allows to update a project simply by changing the version it extends. - -- It allows to update all projects of one version by changing remote files (very useful for HotFixes). - -- It is minimal work to setup a new project. - -- It has presets for development, testing, staging and production. - -- It has all the nice development-helpers we use. - -Another noteable buildout to look for inspiration: - -- - -(plone5-deployment-setup-label)= - -## A deployment setup - -A 'normal' deployment setup could look like this: - -```text -ZEO-Server -> ZEO-Server (ZRS) - - / | \ - -ZEO Clients (as many as you want) - - \ | / - -Load balancer (nginx or haproxy) - - | - - Cache (varnish) - - | - -Webserver (nginx) -``` - -Deploying Plone and production-setups are outside the scope for this training. - -```{seealso} -- -- {doc}`../plone-deployment/index` -``` - -(plone5-deployment-tools-label)= - -## Other tools we use - -There are plenty of tools that make developing and managing sites much easier. Here are only some of the ones you might want to check out: - -- Fabric (managing sites) -- Sentry (error monitoring) -- Ansible (managing and provisioning machines) -- Greylog, ELK (logging) -- Nagios, Zabbix (server monitoring) -- jenkins, gitlab-ci, travis, [drone.io](https://www.drone.io/) (Continuous Integration) -- piwik (statistics) -- gitlab (code repository and code review) -- redmine, taiga, assembla (project-management and ticket-system) diff --git a/docs/mastering-plone-5/dexterity.md b/docs/mastering-plone-5/dexterity.md deleted file mode 100644 index 117de6ec4..000000000 --- a/docs/mastering-plone-5/dexterity.md +++ /dev/null @@ -1,331 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-dexterity1-label)= - -# Dexterity I: "Through The Web" - -In this part you will: - -- Create a new content type called _Talk_. - -Topics covered: - -- Content types -- Archetypes and Dexterity -- Fields -- Widgets - -(plone5-dexterity1-what-label)= - -## What is a content type? - -A content type is a kind of object that can store information and is editable by users. -We have different content types to reflect the different kinds of information about which we need to collect and display information. -Pages, folders, events, news items, files (binary) and images are all content types. - -It is common in developing a web site that you'll need customized versions of common content types, or perhaps even entirely new types. - -Remember the requirements for our project? We wanted to be able to solicit and edit conference talks. -We _could_ use the **Page** content type for that purpose. -But we need to make sure we collect certain bits of information about a talk and we couldn't be sure to get that information if we just asked potential presenters to create a page. -Also, we'll want to be able to display talks featuring that special information, and we'll want to be able to show collections of talks. -A custom content type will be ideal. - -(plone5-dexterity1-contains-label)= - -## The makings of a Plone content type - -Every Plone content type has the following parts: - -Schema - -: A definition of fields that comprise a content type; -properties of an object. - -FTI - -: The "Factory Type Information" configures the content type in Plone, assigns it a name, additional features and available views to it. - -Views - -: A view is a representation of the object and the content of its fields that may be rendered in response to a request. -You may have _one or more_ views for an object. -Some may be _visual_ — intended for display as web pages — others may be intended to satisfy AJAX requests and render content in formats like JSON or XML. - -(plone5-dexterity1-comparison-label)= - -## Dexterity and Archetypes - A Comparison - -There used to be two content frameworks in Plone: - -- _Dexterity_: new and default. -- _Archetypes_: the old default in Plone 4 and deprecated. Still used in some add-ons. -- Plone 4.x: Archetypes is the default, with Dexterity available. -- Plone 5.x: Dexterity is the default, with Archetypes available. In Plone 6 Archetypes will not be available any more. -- For both, add and edit forms are created automatically from a schema. - -What are the differences? - -- Dexterity: New, faster, modular, no dark magic for getters and setters. -- Archetypes had magic setter/getter - use {py:meth}`talk.getAudience()` for the field {py:attr}`audience`. -- Dexterity: fields are attributes: {py:attr}`talk.audience` instead of {py:meth}`talk.getAudience()`. - -"Through The Web" or TTW, i.e. in the browser, without programming: - -- Dexterity has a good TTW story. -- Archetypes has no TTW story. -- UML-modeling: [ArchGenXML](https://4.docs.plone.org/old-reference-manuals/archgenxml/index.html) for Archetypes, [agx](https://github.com/bluedynamics/agx.dev) for Dexterity - -Approaches for Developers: - -- Schema in Dexterity: TTW, XML, Python. Interface = schema, often no class needed. -- Schema in Archetypes: Schema only in Python. -- Dexterity: Easy permissions per field, easy custom forms. -- Archetypes: Permissions per field are hard, custom forms even harder. -- If you have to program for old Plone 4-based sites you still need to know Archetypes! -- If starting fresh always use Dexterity. - -Extending: - -- Dexterity has Behaviors: easily extendable. Just activate a behavior TTW and your content type is e.g. translatable ({py:mod}`plone.app.multilingual`). -- Archetypes has {py:mod}`archetypes.schemaextender`. Powerful but not as flexible. - -We have only used Dexterity for the last few years. -We teach Dexterity and not Archetypes because it's more accessible to beginners, has a great TTW story and is the future. - -Views: - -- Both Dexterity and Archetypes have a default view for content types. -- Browser Views provide custom views. -- You can generate views for content types in the browser without programming (using the {py:mod}`plone.app.mosaic` Add-on). -- Display Forms. - -(plone5-dexterity1-modify-label)= - -## Modifying existing types - -- Go to the control panel - -- Inspect some of the existing default types. - -- Select the type {guilabel}`News Item` and add a new field `Hot News` of type {guilabel}`Yes/No` - -- In another tab, add a _News Item_ and you'll see the new field. - -- Go back to the schema-editor and click on [Edit XML Field Model](http://localhost:8080/Plone/dexterity-types/News%20Item/@@modeleditor). - -- Note that the only field in the XML schema of the News Item is the one we just added. All others are provided by behaviors. - -- Edit the form-widget-type so it says: - - ```xml - - ``` - -- Edit the News Item again. The widget changed from a radio field to a check box. - -- The new field `Hot News` is not displayed when rendering the News Item. We'll take care of this later. - -```{seealso} - -``` - -(plone5-dexterity1-create-ttw-label)= - -## Creating content types TTW - -In this step we will create a content type called _Talk_ and try it out. When it's ready we will move the code from the web to the file system and into our own add-on. Later we will extend that type, add behaviors and a viewlet for Talks. - -- Add new content type "Talk" and some fields for it: - - - {guilabel}`Add new field` "Type of talk", type "Choice". Add options: talk, keynote, training. - - {guilabel}`Add new field` "Details", type "Rich Text" with a maximal length of 2000. - - {guilabel}`Add new field` "Audience", type "Multiple Choice". Add options: beginner, advanced, pro. - - Check the behaviors that are enabled: _Dublin Core metadata_, _Name from title_. Do we need them all? - -- Test the content type. - -- Return to the control panel - -- Extend the new type: add the following fields: - - - "Speaker", type: "Text line" - - "Email", type: "Email" - - "Image", type: "Image", not required - - "Speaker Biography", type: "Rich Text" - -- Test again. - -Here is the complete XML schema created by our actions: - -```{code-block} xml -:linenos: - - - - - - Type of talk - - Talk - Training - Keynote - - - - Add a short description of the talk (max. 2000 characters) - 2000 - Details - - - - Audience - - - Beginner - Advanced - Professionals - - - - - Name (or names) of the speaker - Speaker - - - Adress of the speaker - Email - - - - False - Image - - - - 1000 - False - Speaker Biography - - - -``` - -(plone5-dexterity1-ttw-to-code-label)= - -## Moving contenttypes into code - -It's awesome that we can do so much through the web. But it's also a dead end if we want to reuse this content type in other sites. - -Also, for professional development, we want to be able to use version control for our work, and we'll want to be able to add the kind of business logic that will require programming. - -So, we'll ultimately want to move our new content type into a Python package. We're missing some skills to do that, and we'll cover those in the next couple of chapters. - -```{seealso} -- [Dexterity Developer Manual](https://5.docs.plone.org/external/plone.app.dexterity/docs/index.html) -- [The standard behaviors](https://5.docs.plone.org/external/plone.app.dexterity/docs/reference/standard-behaviours.html) -``` - -(plone5-dexterity1-excercises-label)= - -## Exercises - -### Exercise 1 - -Modify Pages to allow uploading an image as decoration (like News Items do). - -```{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -- Go to the dexterity control panel () -- Click on *Page* () -- Select the tab *Behaviors* () -- Check the box next to {guilabel}`Lead Image` and save. - -The images are displayed above the title. -``` - -### Exercise 2 - -Create a new content type called _Speaker_ and export the schema to a XML File. -It should contain the following fields: - -- Title, type: "Text Line" -- Email, type: "Email" -- Homepage, type: "URL" (optional) -- Biography, type: "Rich Text" (optional) -- Company, type: "Text Line" (optional) -- Twitter Handle, type: "Text Line" (optional) -- IRC Handle, type: "Text Line" (optional) -- Image, type: "Image" (optional) - -Do not use the DublinCore or the Basic behavior since a speaker should not have a description (unselect it in the Behaviors tab). - -We could use this content type later to convert speakers into Plone users. We could then link them to their talks. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -The schema should look like this: - -```xml - - - - Name - - - Email - - - False - Homepage - - - False - Biography - - - False - Company - - - False - Twitter Handle - - - False - IRC Handle - - - False - Image - - - -``` -```` - -```{seealso} -- [Dexterity XML](https://5.docs.plone.org/external/plone.app.dexterity/docs/reference/dexterity-xml.html) -- [Model-driven types](https://5.docs.plone.org/external/plone.app.dexterity/docs/model-driven-types.html#model-driven-types) -``` diff --git a/docs/mastering-plone-5/dexterity_2.md b/docs/mastering-plone-5/dexterity_2.md deleted file mode 100644 index 127ebf532..000000000 --- a/docs/mastering-plone-5/dexterity_2.md +++ /dev/null @@ -1,709 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-dexterity2-label)= - -# Dexterity Types II: Growing Up - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout viewlets_1 -``` - -Code for the end of this chapter: - -```shell -git checkout dexterity_2 -``` - -{doc}`code` -```` - -The existing talks are still lacking some functionality we want to use. - -In this part we will: - -- add a marker interface to our talk type, -- create custom catalog indexes, -- query the catalog for them, -- enable some more default features for our type. - -(plone5-dexterity2-marker-label)= - -## Add a marker interface to the talk type - -### Marker Interfaces - -The content type `Talk` is not yet a _first class citizen_ because it does not implement its own interface. -Interfaces are like nametags, telling other elements who and what you are and what you can do. -A marker interface is like such a nametag. -The talks actually have an auto-generated marker interface `plone.dexterity.schema.generated.Plone_0_talk`. - -One problem is that the name of the Plone instance `Plone` is part of that interface name. -If you now moved these types to a site with another name the code that uses these interfaces would no longer find the objects in question. - -To create a real name tag we add a new {py:class}`Interface` to {file}`interfaces.py`: - -```{code-block} python -:emphasize-lines: 5,12-13 -:linenos: - -# -*- coding: utf-8 -*- -"""Module where all interfaces, events and exceptions live.""" - -from zope.publisher.interfaces.browser import IDefaultBrowserLayer -from zope.interface import Interface - - -class IPloneconfSiteLayer(IDefaultBrowserLayer): - """Marker interface that defines a browser layer.""" - - -class ITalk(Interface): - """Marker interface for Talks""" -``` - -{py:class}`ITalk` is a marker interface. -We can bind Views and Viewlets to content that provide these interfaces. -Lets see how we can provide this Interface. - -There are two solutions for this. - -1. Let them be instances of a class that implements this Interface. -2. Register this interface as a behavior and enable it on talks. - -The first option has an important drawback: only _new_ talks would be instances of the new class. -We would either have to migrate the existing talks or delete them. - -So let's register the interface as a behavior in {file}`behaviors/configure.zcml` - -```xml - -``` - -And enable it on the type in {file}`profiles/default/types/talk.xml` - -```{code-block} xml -:emphasize-lines: 5 -:linenos: - - - - - - - -``` - -Either reinstall the add-on, apply the behavior by hand or run an upgrade step (see below) and the interface will be there. - -Then we can safely bind the `talkview` to the new marker interface. - -```{code-block} xml -:emphasize-lines: 3 - - -``` - -Now the `/talkview` can only be used on objects that implement said interface. We can now also query the catalog for objects providing this interface {py:meth}`catalog(object_provides="ploneconf.site.interfaces.ITalk")`. -The `talklistview` and the `demoview` do not get this constraint since they are not only used on talks. - -````{note} -Just for completeness sake, this is what would have to happen for the first option (associating the {py:class}`ITalk` interface with a {py:class}`Talk` class): - -- Create a new class that inherits from {py:class}`plone.dexterity.content.Container` and implements the marker interface. - - ```python - from plone.dexterity.content import Container - from ploneconf.site.interfaces import ITalk - from zope.interface import implementer - - @implementer(ITalk) - class Talk(Container): - """Class for Talks""" - ``` - -- Modify the class for new talks in {file}`profiles/default/types/talk.xml` - - ```{code-block} xml - :emphasize-lines: 3 - :linenos: - - ... - cmf.AddPortalContent - ploneconf.site.content.talk.Talk - - ... - ``` - -- Create an upgrade step that changes the class of the existing talks. - A reuseable method to do such a thing is in [plone.app.contenttypes.migration.dxmigration.migrate_base_class_to_new_class](https://github.com/plone/plone.app.contenttypes/blob/ab8ea9f101ea10e1229b0ab1863ffda7c699d72c/plone/app/contenttypes/migration/dxmigration.py#L134). -```` - -(plone5-dexterity2-upgrades-label)= - -## Upgrade steps - -When projects evolve you sometimes want to modify various things while the site is already up and brimming with content and users. -Upgrade steps are pieces of code that run when upgrading from one version of an add-on to a newer one. -They can do just about anything. -We will use an upgrade step to enable the new behavior instead of reinstalling the add-on. - -We will create an upgrade step that: - -- runs the typeinfo step (i.e. loads the GenericSetup configuration stored in `profiles/default/types.xml` and `profiles/default/types/...` so we don't have to reinstall the add-on to have our changes from above take effect) and -- cleans up the talks that might be scattered around the site in the early stages of creating it. - We will move all talks to a folder `talks` (unless they already are there). - -Upgrade steps can be registered in their own ZCML file to prevent cluttering the main {file}`configure.zcml`. -The add-on you created already has a registration for the {file}`upgrades.zcml` in our {file}`configure.zcml`: - -```xml - -``` - -You register the first upgrade-step in {file}`upgrades.zcml`: - -```{code-block} xml -:linenos: - - - - - - -``` - -The upgrade step bumps the version number of the GenericSetup profile of {py:mod}`ploneconf.site` from 1000 to 1001. -The version is stored in {file}`profiles/default/metadata.xml`. -Change it to - -```xml -1001 -``` - -`GenericSetup` now expects the code as a method {py:meth}`upgrade_site` in the file {file}`upgrades.py`. -Let's create it. - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone import api - -import logging - -default_profile = 'profile-ploneconf.site:default' -logger = logging.getLogger(__name__) - - -def upgrade_site(setup): - setup.runImportStepFromProfile(default_profile, 'typeinfo') - portal = api.portal.get() - # Create a folder 'The event' if needed - if 'the-event' not in portal: - event_folder = api.content.create( - container=portal, - type='Folder', - id='the-event', - title=u'The event') - else: - event_folder = portal['the-event'] - - # Create folder 'Talks' inside 'The event' if needed - if 'talks' not in event_folder: - talks_folder = api.content.create( - container=event_folder, - type='Folder', - id='talks', - title=u'Talks') - else: - talks_folder = event_folder['talks'] - talks_url = talks_folder.absolute_url() - - # Find all talks - brains = api.content.find(portal_type='talk') - for brain in brains: - if talks_url in brain.getURL(): - # Skip if the talk is already somewhere inside the target folder - continue - obj = brain.getObject() - logger.info('Moving {} to {}'.format( - obj.absolute_url(), talks_folder.absolute_url())) - # Move talk to the folder '/the-event/talks' - api.content.move( - source=obj, - target=talks_folder, - safe_id=True) -``` - -Note: - -- Upgrade steps get the tool `portal_setup` passed as their argument. -- The `portal_setup` tool has a method {py:meth}`runImportStepFromProfile` -- We create the needed folder structure if it does not exists. - -After restarting the site we can run the step: - -- Go to the {guilabel}`Add-ons` control panel . - There should now be a new section **Upgrades** and a button to upgrade from 1000 to 1001. -- Run the upgrade step by clicking on it. - -On the console you should see logging messages like: - -``` -INFO ploneconf.site.upgrades Moving http://localhost:8080/Plone/old-talk1 to http://localhost:8080/Plone/the-event/talks -``` - -Alternatively you also select which upgrade steps to run like this: - -- In the ZMI go to _portal_setup_ -- Go to the tab {guilabel}`Upgrades` -- Select {guilabel}`ploneconf.site` from the dropdown and click {guilabel}`Choose profile` -- Run the upgrade step. - -```{seealso} - -``` - -```{note} -Upgrading from an older version of Plone to a newer one also runs upgrade steps from the package {py:mod}`plone.app.upgrade`. -You should be able to upgrade a clean site from 2.5 to 5.0 with one click. - -Find the upgrade steps in -``` - -(plone5-dexterity2-browserlayer-label)= - -## Add a browserlayer - -A browserlayer is another such marker interface, but this time on the request. -Browserlayers allow us to easily enable and disable views and other site functionality based on installed add-ons and themes. - -Since we want the features we write only to be available when {py:mod}`ploneconf.site` actually is installed we can bind them to a browserlayer. - -Our package already has a browserlayer (added by {py:mod}`bobtemplates.plone`). See {file}`interfaces.py`: - -```{code-block} python -:emphasize-lines: 4, 8-9 -:linenos: - -# -*- coding: utf-8 -*- -"""Module where all interfaces, events and exceptions live.""" - -from zope.publisher.interfaces.browser import IDefaultBrowserLayer -from zope.interface import Interface - - -class IPloneconfSiteLayer(IDefaultBrowserLayer): - """Marker interface that defines a browser layer.""" - - -class ITalk(Interface): - """Marker interface for Talks""" -``` - -It is enabled by GenericSetup when installing the package since it is registered in the {file}`profiles/default/browserlayer.xml` - -```xml - - - - -``` - -We should bind all views to it. -Here is an example using the `talklistview`. - -```{code-block} xml -:emphasize-lines: 4 - - -``` - -Note the relative Python path {py:class}`..interfaces.IPloneconfSiteLayer`. -It is equivalent to the absolute path {py:class}`ploneconf.site.interfaces.IPloneconfSiteLayer`. - -```{seealso} - -``` - -### Exercise - -Do you need to bind the viewlet `featured` from the chapter {doc}`viewlets_1` to this new browser layer? - -```{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -No, it would make no difference since the viewlet is already bound to the marker interface {py:class}`ploneconf.site.behaviors.social.ISocial`. -``` - -(plone5-dexterity2-catalogindex-label)= - -## Add catalog indexes - -In the `talklistview` we had to wake up all objects to access some of their attributes. -That is OK if we don't have many objects and they are light dexterity objects. -If we had thousands of objects this might not be a good idea. - -Instead of loading them all into memory we will use catalog indexes to get the data we want to display. - -Add a new file {file}`profiles/default/catalog.xml` - -```{code-block} xml -:linenos: - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -This adds new indexes for the three fields we want to show in the listing. Note that _audience_ is a {py:class}`KeywordIndex` because the field is multi-valued, but we want a separate index entry for every value in an object. - -The `column ..` entries allow us to display the values of these indexes in the tableview of collections. - -- Reinstall the add-on -- Go to to update the catalog -- Go to to inspect and manage the new indexes - -```{seealso} - -``` - -````{note} -The new indexes are still empty. -We'll have to reindex them. -To do so by hand go to , select the new indexes and click {guilabel}`Reindex`. -We could also rebuild the whole catalog by going to the {guilabel}`Advanced` tab and clicking {guilabel}`Clear and Rebuild`. -For large sites that can take a long time. - -We could also write an upgrade step to enable the catalog indexes and reindex all talks: - -```python -def add_some_indexes(setup): - setup.runImportStepFromProfile(default_profile, 'catalog') - for brain in api.content.find(portal_type='talk'): - obj = brain.getObject() - obj.reindexObject(idxs=[ - 'type_of_talk', - 'speaker', - 'audience', - 'room', - 'featured', - ]) -``` -```` - -(plone5-dexterity2-customindex-label)= - -## Query for custom indexes - -The new indexes behave like the ones that Plone has already built in: - -```pycon ->>> (Pdb) from Products.CMFCore.utils import getToolByName ->>> (Pdb) catalog = getToolByName(self.context, 'portal_catalog') ->>> (Pdb) catalog(type_of_talk='Keynote') -[, ] ->>> (Pdb) catalog(audience=('Advanced', 'Professionals')) -[, , ] ->>> (Pdb) brain = catalog(type_of_talk='Keynote')[0] ->>> (Pdb) brain.speaker -u'David Glick' -``` - -We now can use the new indexes to improve the `talklistview` so we don't have to _wake up_ the objects anymore. -Instead we use the brains' new attributes. - -```{code-block} python -:emphasize-lines: 13-16 -:linenos: - -class TalkListView(BrowserView): - """ A list of talks - """ - - def talks(self): - results = [] - brains = api.content.find(context=self.context, portal_type='talk') - for brain in brains: - results.append({ - 'title': brain.Title, - 'description': brain.Description, - 'url': brain.getURL(), - 'audience': ', '.join(brain.audience or []), - 'type_of_talk': brain.type_of_talk, - 'speaker': brain.speaker, - 'room': brain.room, - 'uuid': brain.UID, - }) - return results -``` - -The template does not need to be changed and the result in the browser did not change either. -But when listing a large number of objects the site will now be faster since all the data you use comes from the catalog and the objects do not have to be loaded into memory. - -(plone5-dexterity2-use-indexes-label)= - -## Exercise 1 - -In fact we could now simplify the view even further by only returning the brains. - -Modify {py:class}`TalkListView` to return only brains and adapt the template to these changes. Remember to move `', '.join(brain.audience or [])` into the template. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Here is the class: - -```{code-block} python -:linenos: - -class TalkListView(BrowserView): - """ A list of talks - """ - - def talks(self): - return api.content.find(context=self.context, portal_type='talk') -``` - -Here is the template: - -```{code-block} html -:linenos: - - - - - - - - - - - - - - - - - - - - - - - - - -
TitleSpeakerAudienceRoom
- - The 7 sins of Plone development - - - Philip Bauer - - Advanced - - Room 101 -
- No talks so far :-( -
- -
- - -``` -```` - -(plone5-dexterity2-collection-criteria-label)= - -## Add collection criteria - -To be able to search content in collections using these new indexes we would have to register them as criteria for the `querystring` widget that collections use. -As with all features make sure you only do this if you really need it! - -Add a new file {file}`profiles/default/registry.xml` - -```{code-block} xml -:linenos: - - - - Audience - A custom speaker index - True - False - - plone.app.querystring.operation.string.is - - Metadata - - - Type of Talk - A custom index - True - False - - plone.app.querystring.operation.string.is - - Metadata - - - Speaker - A custom index - True - False - - plone.app.querystring.operation.string.is - - Metadata - - -``` - -```{seealso} - -``` - -(plone5-dexterity2-gs-label)= - -## Add versioning through GenericSetup - -Configure the versioning policy and a diff view for talks through GenericSetup. - -Add new file {file}`profiles/default/repositorytool.xml` - -```{code-block} xml -:linenos: - - - - - - - - - - -``` - -Add new file {file}`profiles/default/diff_tool.xml` - -```{code-block} xml -:linenos: - - - - - - - - - -``` - -Finally you need to activate the versioning behavior on the content type. -Edit {file}`profiles/default/types/talk.xml`: - -```{code-block} xml -:emphasize-lines: 6 -:linenos: - - - - - - - - -``` - -```{note} -There is currently a bug that breaks showing diffs when multiple-choice fields were changed. -``` - -## Summary - -The talks are now grown up: - -- They provide a interface to which you can bind features like views -- Some fields are indexed in the catalog making the listing faster -- Talks are now versioned -- You wrote your first upgrade step to move the talks around: yipee! diff --git a/docs/mastering-plone-5/dexterity_3.md b/docs/mastering-plone-5/dexterity_3.md deleted file mode 100644 index 6e7893869..000000000 --- a/docs/mastering-plone-5/dexterity_3.md +++ /dev/null @@ -1,692 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-dexterity3-label)= - -# Dexterity Types III: Python - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout resources -``` - -Code for the end of this chapter: - -```shell -git checkout dexterity_3 -``` - -{doc}`code` -```` - -Without sponsors, a conference would be hard to finance! Plus it is a good opportunity for Plone companies to advertise their services. -But sponsors want to be displayed in a nice way according to the size of their sponsorship. - -In this part we will: - -- create the content type _sponsor_ that has a Python schema, -- create a viewlet that shows the sponsor logos sorted by sponsoring level. - -The topics we cover are: - -- Python schema for Dexterity -- schema hint and directives -- field permissions -- image scales -- caching - -## The Python schema - -First we create the schema for the new type. Instead of XML, we use Python this time. -In chapter {ref}`plone5-export-code-label` you already created a folder {file}`content` with an empty {file}`__init__.py` in it. -We don't need to register that folder in {file}`configure.zcml` since we don't need a {file}`content/configure.zcml` (at least not yet). - -Now add a new file {file}`content/sponsor.py`. - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone.app.textfield import RichText -from plone.autoform import directives -from plone.namedfile import field as namedfile -from plone.supermodel import model -from plone.supermodel.directives import fieldset -from ploneconf.site import _ -from z3c.form.browser.radio import RadioFieldWidget -from zope import schema -from zope.schema.vocabulary import SimpleTerm -from zope.schema.vocabulary import SimpleVocabulary - - -LevelVocabulary = SimpleVocabulary( - [SimpleTerm(value=u'platinum', title=_(u'Platinum Sponsor')), - SimpleTerm(value=u'gold', title=_(u'Gold Sponsor')), - SimpleTerm(value=u'silver', title=_(u'Silver Sponsor')), - SimpleTerm(value=u'bronze', title=_(u'Bronze Sponsor'))] - ) - - -class ISponsor(model.Schema): - """Dexterity Schema for Sponsors - """ - - directives.widget(level=RadioFieldWidget) - level = schema.Choice( - title=_(u'Sponsoring Level'), - vocabulary=LevelVocabulary, - required=True - ) - - text = RichText( - title=_(u'Text'), - required=False - ) - - url = schema.URI( - title=_(u'Link'), - required=False - ) - - fieldset('Images', fields=['logo', 'advertisement']) - logo = namedfile.NamedBlobImage( - title=_(u'Logo'), - required=False, - ) - - advertisement = namedfile.NamedBlobImage( - title=_(u'Advertisement (Gold-sponsors and above)'), - required=False, - ) - - directives.read_permission(notes='cmf.ManagePortal') - directives.write_permission(notes='cmf.ManagePortal') - notes = RichText( - title=_(u'Secret Notes (only for site-admins)'), - required=False - ) -``` - -Some things are notable here: - -- The fields in the schema are mostly from {py:mod}`zope.schema`. A reference of available fields is at -- In {samp}`directives.widget(level=RadioFieldWidget)` we change the default widget for a Choice field from a dropdown to radio-boxes. An incomplete reference of available widgets is at -- {py:class}`LevelVocabulary` is used to create the options used in the field `level`. This way we could easily translate the displayed value. -- {samp}`fieldset('Images', fields=['logo', 'advertisement'])` moves the two image fields to another tab. -- {samp}`directives.read_permission(...)` sets the read and write permission for the field `notes` to users who can add new members. Usually this permission is only granted to Site Administrators and Managers. We use it to store information that should not be publicly visible. Please note that {py:attr}`obj.notes` is still accessible in templates and Python. Only using the widget (like we do in the view later) checks for the permission. - -```{seealso} -See the chapter {ref}`plone5-dexterity-reference-label` for a reference of all field-types and directives you can use in dexterity. -``` - -## The Factory Type Information, or FTI - -Next, we create the factory type information ("FTI") for the new type in {file}`profiles/default/types/sponsor.xml` - -```{code-block} xml -:emphasize-lines: 26 -:linenos: - - - - Sponsor - - string:${portal_url}/document_icon.png - sponsor - string:${folder_url}/++add++sponsor - - view - True - True - - False - view - - - - False - cmf.AddPortalContent - plone.dexterity.content.Container - - - - - ploneconf.site.content.sponsor.ISponsor - - - dexterity - - - - - - - - - - - -``` - -Then we register the FTI in {file}`profiles/default/types.xml` - -```{code-block} xml -:emphasize-lines: 5 -:linenos: - - - - Controls the available contenttypes in your portal - - - - -``` - -After reinstalling our package we can create the new type. - -### Exercise 1 - -Sponsors are containers but they don't need to be. Turn them into items by changing their class to {py:class}`plone.dexterity.content.Item`. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Simply modify the property `klass` in the FTI and reinstall. - -```{code-block} xml -:linenos: - -plone.dexterity.content.Item -``` -```` - -## The view - -We use the default view provided by Dexterity for testing since we will only display the sponsors in a viewlet and not in their own page. - -````{note} -If we really want a custom view for sponsors it could look like this. - -```{code-block} xml -:linenos: - - - - -

- Level -

- -
- Text -
- -
- - - -
- -
- - Website - - - - -
- Notes -
- -
-
- - -``` - -Note how we handle the field with special permissions: {samp}`tal:condition="python: 'notes' in view.w"` checks if the convenience-dictionary {py:data}`w` (provided by the base class {py:class}`DefaultView`) holds the widget for the field `notes`. -If the current user does not have the permission `cmf.ManagePortal` it will be omitted from the dictionary and get an error since `notes` would not be a key in {py:data}`w`. By first checking if it's missing we work around that. -```` - -## The viewlet - -Instead of writing a view you will have to display the sponsors at the bottom of the website in a viewlet. - -Register the viewlet in {file}`browser/configure.zcml` - -```{code-block} xml -:linenos: - - -``` - -Add the viewlet class in {file}`browser/viewlets.py` - -```{code-block} python -:emphasize-lines: 2-3, 5, 7-9, 19-63 -:linenos: - -# -*- coding: utf-8 -*- -from collections import OrderedDict -from plone import api -from plone.app.layout.viewlets.common import ViewletBase -from plone.memoize import ram -from ploneconf.site.behaviors.featured import IFeatured -from ploneconf.site.content.sponsor import LevelVocabulary -from random import shuffle -from time import time - - -class FeaturedViewlet(ViewletBase): - - def is_featured(self): - adapted = IFeatured(self.context) - return adapted.featured - - -class SponsorsViewlet(ViewletBase): - - @ram.cache(lambda *args: time() // (60 * 60)) - def _sponsors(self): - results = [] - for brain in api.content.find(portal_type='sponsor'): - obj = brain.getObject() - scales = api.content.get_view( - name='images', - context=obj, - request=self.request) - scale = scales.scale( - 'logo', - width=200, - height=80, - direction='down') - tag = scale.tag() if scale else None - if not tag: - # only display sponsors with a logo - continue - results.append({ - 'title': obj.title, - 'description': obj.description, - 'tag': tag, - 'url': obj.url or obj.absolute_url(), - 'level': obj.level - }) - return results - - def sponsors(self): - sponsors = self._sponsors() - if not sponsors: - return - results = OrderedDict() - levels = [i.value for i in LevelVocabulary] - for level in levels: - level_sponsors = [] - for sponsor in sponsors: - if level == sponsor['level']: - level_sponsors.append(sponsor) - if not level_sponsors: - continue - shuffle(level_sponsors) - results[level] = level_sponsors - return results -``` - -- {py:meth}`_sponsors` returns a list of dictionaries containing all necessary info about sponsors. -- We create the complete `img` tag using a custom scale (200x80) using the view `images` from {py:mod}`plone.namedfile.` This actually scales the logos and saves them as new blobs. -- In {py:meth}`sponsors` we return an ordered dictionary of randomized lists of dicts (containing the information on sponsors). The order is by sponsor-level since we want the platinum sponsors on top and the bronze sponsors at the bottom. The randomization is for fairness among equal sponsors. - -{py:meth}`_sponsors` is cached for an hour using [plone.memoize](https://5.docs.plone.org/manage/deploying/performance/decorators.html#timeout-caches). This way we don't need to keep all sponsor objects in memory all the time. But we'd have to wait for up to an hour until changes will be visible. - -Instead we should cache until one of the sponsors is modified by using a callable {py:func}`_sponsors_cachekey` that returns a number that changes when a sponsor is modified. - -> ```python -> ... -> def _sponsors_cachekey(method, self): -> brains = api.content.find(portal_type='sponsor') -> cachekey = sum([int(i.modified) for i in brains]) -> return cachekey -> -> @ram.cache(_sponsors_cachekey) -> def _sponsors(self): -> catalog = api.portal.get_tool('portal_catalog') -> ... -> ``` - -```{seealso} -- [Guide to Caching](https://5.docs.plone.org/manage/deploying/caching/index.html) -- [Cache decorators](https://5.docs.plone.org/manage/deploying/performance/decorators.html) -- [Image Scaling](https://5.docs.plone.org/develop/plone/images/content.html#creating-scales) -``` - -## The template for the viewlet - -Add the template {file}`browser/templates/sponsors_viewlet.pt` - -```{code-block} xml -:linenos: - -
-
-
-

We ❤ our sponsors

-
-
-

- Gold -

- - - -
-
-
-``` - -You can now add some CSS in {file}`browser/static/ploneconf.css` to make it look OK. - -```css -.sponsor { - display: inline-block; - margin: 0 1em 1em 0; -} - -.sponsor:hover { - box-shadow: 0 0 8px #000; - -moz-box-shadow: 0 0 8px #000; - -webkit-box-shadow: 0 0 8px #000; -} -``` - -Result: - -```{figure} _static/dexterity_3_sponsor_schema.png -:alt: The result of the newly created content type. -:scale: 50% - -The result of the newly created content type. -``` - -### Exercise 2 - -Turn the content type Speaker from {ref}`Exercise 2 of the first chapter on Dexterity ` into a Python-based type. - -When we're done, it should have the following fields: - -- title -- email -- homepage -- biography -- company -- twitter_name -- irc_name -- image - -Do _not_ use the {py:class}`IBasic` or {py:class}`IDublinCore` behavior to add title and description. Instead add your own field `title` and give it the title _Name_. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone.app.textfield import RichText -from plone.app.vocabularies.catalog import CatalogSource -from plone.autoform import directives -from plone.namedfile import field as namedfile -from plone.supermodel import model -from ploneconf.site import _ -from z3c.relationfield.schema import RelationChoice -from z3c.relationfield.schema import RelationList -from zope import schema - - -class ISpeaker(model.Schema): - """Dexterity-Schema for Speaker - """ - - title = schema.TextLine( - title=_(u'Name'), - ) - - email = schema.TextLine( - title=_(u'E-Mail'), - required=False, - ) - - homepage = schema.URI( - title=_(u'Homepage'), - required=False, - ) - - biography = RichText( - title=_(u'Biography'), - required=False, - ) - - company = schema.TextLine( - title=_(u'Company'), - required=False, - ) - - twitter_name = schema.TextLine( - title=_(u'Twitter-Name'), - required=False, - ) - - irc_name = schema.TextLine( - title=_(u'IRC-Name'), - required=False, - ) - - image = namedfile.NamedBlobImage( - title=_(u'Image'), - required=False, - ) -``` - -Register the type in {file}`profiles/default/types.xml` - -```{code-block} xml -:emphasize-lines: 6 -:linenos: - - - - Controls the available contenttypes in your portal - - - - - -``` - -The FTI goes in {file}`profiles/default/types/speaker.xml`. Again we use {py:class}`Item` as the base-class: - -```{code-block} xml -:linenos: - - - - Speaker - - - speaker - string:${folder_url}/++add++speaker - - view - True - True - - False - view - - - - False - cmf.AddPortalContent - plone.dexterity.content.Item - ploneconf.site.content.speaker.ISpeaker - - - - - - dexterity - - - - - - - - - - - -``` - -After reinstalling the package the new type is usable. -```` - -### Exercise 3 - -This is more of a Python exercise. The gold and bronze sponsors should also have a bigger logo than the others. Scale the sponsors' logos to the following sizes without using CSS. - -- Platinum: 500x200 -- Gold: 350x150 -- Silver: 200x80 -- Bronze: 150x60 - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -```{code-block} python -:emphasize-lines: 10-15, 41, 44-45 -:linenos: - -# -*- coding: utf-8 -*- -from collections import OrderedDict -from plone import api -from plone.app.layout.viewlets.common import ViewletBase -from plone.memoize import ram -from ploneconf.site.behaviors.social import ISocial -from ploneconf.site.content.sponsor import LevelVocabulary -from random import shuffle - -LEVEL_SIZE_MAPPING = { - 'platinum': (500, 200), - 'gold': (350, 150), - 'silver': (200, 80), - 'bronze': (150, 60), -} - - -class SocialViewlet(ViewletBase): - - def lanyrd_link(self): - adapted = ISocial(self.context) - return adapted.lanyrd - - -class SponsorsViewlet(ViewletBase): - - def _sponsors_cachekey(method, self): - brains = api.content.find(portal_type='sponsor') - cachekey = sum([int(i.modified) for i in brains]) - return cachekey - - @ram.cache(_sponsors_cachekey) - def _sponsors(self): - results = [] - for brain in api.content.find(portal_type='sponsor'): - obj = brain.getObject() - scales = api.content.get_view( - name='images', - context=obj, - request=self.request) - width, height = LEVEL_SIZE_MAPPING[obj.level] - scale = scales.scale( - 'logo', - width=width, - height=height, - direction='down') - tag = scale.tag() if scale else None - if not tag: - # only display sponsors with a logo - continue - results.append({ - 'title': obj.title, - 'description': obj.description, - 'tag': tag, - 'url': obj.url or obj.absolute_url(), - 'level': obj.level - }) - return results - - def sponsors(self): - sponsors = self._sponsors() - if not sponsors: - return - results = OrderedDict() - levels = [i.value for i in LevelVocabulary] - for level in levels: - level_sponsors = [] - for sponsor in sponsors: - if level == sponsor['level']: - level_sponsors.append(sponsor) - if not level_sponsors: - continue - shuffle(level_sponsors) - results[level] = level_sponsors - return results -``` -```` diff --git a/docs/mastering-plone-5/dexterity_reference.md b/docs/mastering-plone-5/dexterity_reference.md deleted file mode 100644 index 27dfcf007..000000000 --- a/docs/mastering-plone-5/dexterity_reference.md +++ /dev/null @@ -1,717 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-dexterity-reference-label)= - -# Dexterity: Reference - -This chapter documents all types fields, widgets, directives that you can use with dexterity. - -## Fields included in Plone - -This is a schema with examples for all field-types that are shipped with Plone by default. They are arranged in fieldsets: - -Default - -: Textline, Text, Boolean, Richtext (html), Email - -Number fields - -: Integer, Float - -Date and time fields - -: Datetime, -Date, -Time, -Timedelta - -Choice and Multiple Choice fields - -: Choice, -Choice with radio widget, -Choice with Select2 widget, -Choice with named vocabulary, -List, -List with checkboxes, -List with Select2 widget, -List with values from named vocabulary but open to additions, -Tuple, -Set, -Set with checkboxes - -Relation fields - -: Relationchoice, Relationlist - -File fields - -: File, Image - -Other fields - -: Uri, Sourcetext, Ascii, Bytesline, Asciiline, Pythonidentifier, Dottedname, Dict, Dict with Choice - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone.app.multilingual.browser.interfaces import make_relation_root_path -from plone.app.textfield import RichText -from plone.app.z3cform.widget import AjaxSelectFieldWidget -from plone.app.z3cform.widget import RelatedItemsFieldWidget -from plone.app.z3cform.widget import SelectFieldWidget -from plone.autoform import directives -from plone.dexterity.content import Container -from plone.namedfile.field import NamedBlobFile -from plone.namedfile.field import NamedBlobImage -from plone.schema.email import Email -from plone.supermodel import model -from plone.supermodel.directives import fieldset -from plone.supermodel.directives import primary -from ploneconf.site import _ -from z3c.form.browser.checkbox import CheckBoxFieldWidget -from z3c.form.browser.radio import RadioFieldWidget -from z3c.relationfield.schema import Relation -from z3c.relationfield.schema import RelationChoice -from z3c.relationfield.schema import RelationList -from zope import schema -from zope.interface import implementer - - -class IExample(model.Schema): - """Dexterity-Schema with all field-types.""" - - # The most used fields - # textline, text, bool, richtext, email - - - fieldset( - 'numberfields', - label=u'Number fields', - fields=('int_field', 'float_field'), - ) - - fieldset( - 'datetimefields', - label=u'Date and time fields', - fields=('datetime_field', 'date_field', 'time_field', 'timedelta_field'), - ) - - fieldset( - 'choicefields', - label=u'Choice and Multiple Choice fields', - fields=( - 'choice_field', - 'choice_field_radio', - 'choice_field_select', - 'choice_field_voc', - 'list_field', - 'list_field_checkbox', - 'list_field_select', - 'list_field_voc_unconstrained', - 'tuple_field', - 'set_field', - 'set_field_checkbox', - ), - ) - - fieldset( - 'relationfields', - label=u'Relation fields', - fields=('relationchoice_field', 'relationlist_field'), - ) - - fieldset( - 'filefields', - label=u'File fields', - fields=('file_field', 'image_field'), - ) - - fieldset( - 'otherfields', - label=u'Other fields', - fields=( - 'uri_field', - 'sourcetext_field', - 'ascii_field', - 'bytesline_field', - 'asciiline_field', - 'pythonidentifier_field', - 'dottedname_field', - 'dict_field', - 'dict_field_with_choice', - ), - ) - - primary('title') - title = schema.TextLine( - title=u'Primary Field (Textline)', - required=True, - ) - - text_field = schema.Text( - title=u'Text Field', - required=False, - missing_value=u'', - ) - - textline_field = schema.TextLine( - title=u'Textline field', - description=u'A simple input field', - required=False, - ) - - bool_field = schema.Bool( - title=u'Boolean field', - required=False, - ) - - choice_field = schema.Choice( - title=u'Choice field', - values=[u'One', u'Two', u'Three'], - required=True, - ) - - directives.widget(choice_field_radio=RadioFieldWidget) - choice_field_radio = schema.Choice( - title=u'Choice field with radio boxes', - values=[u'One', u'Two', u'Three'], - required=True, - ) - - choice_field_voc = schema.Choice( - title=u'Choicefield with values from named vocabulary', - vocabulary='plone.app.vocabularies.PortalTypes', - required=False, - ) - - directives.widget(choice_field_select=SelectFieldWidget) - choice_field_select = schema.Choice( - title=u'Choicefield with select2 widget', - vocabulary='plone.app.vocabularies.PortalTypes', - required=False, - ) - - list_field = schema.List( - title=u'List field', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value=[], - ) - - directives.widget(list_field_checkbox=CheckBoxFieldWidget) - list_field_checkbox = schema.List( - title=u'List field with checkboxes', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value=[], - ) - - directives.widget(list_field_select=SelectFieldWidget) - list_field_select = schema.List( - title=u'List field with select widget', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value=[], - ) - - list_field_voc_unconstrained = schema.List( - title=u'List field with values from vocabulary but not constrained to them.', - value_type=schema.TextLine(), - required=False, - missing_value=[], - ) - directives.widget( - 'list_field_voc_unconstrained', - AjaxSelectFieldWidget, - vocabulary='plone.app.vocabularies.Users' - ) - - - tuple_field = schema.Tuple( - title=u'Tuple field', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value=(), - ) - - set_field = schema.Set( - title=u'Set field', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value={}, - ) - - directives.widget(set_field_checkbox=CheckBoxFieldWidget) - set_field_checkbox = schema.Set( - title=u'Set field with checkboxes', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value={}, - ) - - # File fields - image_field = NamedBlobImage( - title=u'Image field', - description=u'A upload field for images', - required=False, - ) - - file_field = NamedBlobFile( - title=u'File field', - description=u'A upload field for files', - required=False, - ) - - # Date and Time fields - datetime_field = schema.Datetime( - title=u'Datetime field', - description=u'Uses a date and time picker', - required=False, - ) - - date_field = schema.Date( - title=u'Date field', - description=u'Uses a date picker', - required=False, - ) - - time_field = schema.Time( - title=u'Time field', - required=False, - ) - - timedelta_field = schema.Timedelta( - title=u'Timedelta field', - required=False, - ) - - # Relation Fields - relationchoice_field = RelationChoice( - title=u"Relationchoice field", - vocabulary='plone.app.vocabularies.Catalog', - required=False, - ) - directives.widget( - "relationchoice_field", - RelatedItemsFieldWidget, - pattern_options={ - "selectableTypes": ["Document"], - "basePath": make_relation_root_path, - }, - ) - - relationlist_field = RelationList( - title=u"Relationlist Field", - default=[], - value_type=RelationChoice(vocabulary='plone.app.vocabularies.Catalog'), - required=False, - missing_value=[], - ) - directives.widget( - "relationlist_field", - RelatedItemsFieldWidget, - vocabulary='plone.app.vocabularies.Catalog', - pattern_options={ - "selectableTypes": ["Document"], - "basePath": make_relation_root_path, - }, - ) - - # Number fields - int_field = schema.Int( - title=u"Integer Field (e.g. 12)", - description=u"Allocated (maximum) number of objects", - required=False, - ) - - float_field = schema.Float( - title=u"Float field (e.g. 12.2)", - required=False, - ) - - # Text fields - email_field = Email( - title=u'Email field', - description=u'A simple input field for a email', - required=False, - ) - - uri_field = schema.URI( - title=u'URI field', - description=u'A simple input field for a URLs', - required=False, - ) - - richtext_field = RichText( - title=u'RichText field', - description=u'This uses a richtext editor.', - max_length=2000, - required=False, - ) - - sourcetext_field = schema.SourceText( - title=u'SourceText field', - required=False, - ) - - ascii_field = schema.ASCII( - title=u'ASCII field', - required=False, - ) - - bytesline_field = schema.BytesLine( - title=u'BytesLine field', - required=False, - ) - - asciiline_field = schema.ASCIILine( - title=u'ASCIILine field', - required=False, - ) - - pythonidentifier_field = schema.PythonIdentifier( - title=u'PythonIdentifier field', - required=False, - ) - - dottedname_field = schema.DottedName( - title=u'DottedName field', - required=False, - ) - - dict_field = schema.Dict( - title=u'Dict field', - required=False, - key_type = schema.TextLine( - title=u'Key', - required=False, - ), - value_type = schema.TextLine( - title=u'Value', - required=False, - ), - ) - - dict_field_with_choice = schema.Dict( - title=u'Dict field with key and value as choice', - required=False, - key_type = schema.Choice( - title=u'Key', - values=[u'One', u'Two', u'Three'], - required=False, - ), - value_type = schema.Set( - title=u'Value', - value_type=schema.Choice( - values=[u'Beginner', u'Advanced', u'Professional'], - ), - required=False, - missing_value={}, - ), - ) - -@implementer(IExample) -class Example(Container): - """Example instance class""" -``` - -## How fields look like - -This is how these fields look like when editing content: - -```{figure} _static/dexterity_reference_default_fields.png -:alt: Default fields - -Default fields -``` - -```{figure} _static/dexterity_reference_number_fields.png -:alt: Number fields - -Number fields -``` - -```{figure} _static/dexterity_reference_datetime_fields.png -:alt: Date and time fields - -Date and time fields -``` - -```{figure} _static/dexterity_reference_choice_and_list_fields.png -:alt: Choice and multiple choice fields - -Choice and multiple choice fields -``` - -```{figure} _static/dexterity_reference_file_fields.png -:alt: File fields - -File fields -``` - -```{figure} _static/dexterity_reference_relation_fields.png -:alt: Reference fields - -Reference fields -``` - -```{figure} _static/dexterity_reference_other_fields.png -:alt: Other fields including the dict field - -Other fields including the dict field -``` - -## 3rd party fields - -- To control the avilable values of other fields or hide/show them based on user input use the [Masterselect Field](https://pypi.org/project/plone.formwidget.masterselect/). -- For spam-protection use [collective.z3cform.norobots](https://pypi.org/project/collective.z3cform.norobots/). -- Color-Picker [collective.z3cform.colorpicker](https://github.com/collective/collective.z3cform.colorpicker) -- There is no Computedfield but most use-cases can be achieved with a readonly-field and a property. See the [discussion](https://community.plone.org/t/computed-field-for-dexterity/11405) - -## Datagrid Field - -The [Datagridfield](https://pypi.org/project/collective.z3cform.datagridfield/) allows you to enter multiple values at once as rows in a table. Each row is a sub form defined in a separate schema. - -Here is an example: - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from collective.z3cform.datagridfield import DataGridFieldFactory -from collective.z3cform.datagridfield import DictRow -from plone.app.z3cform.widget import SelectFieldWidget -from plone.autoform import directives -from plone.supermodel import model -from zope import schema -from zope.interface import Interface - - -class IMyRowSchema(Interface): - - choice_field = schema.Choice( - title=u'Choice Field', - vocabulary='plone.app.vocabularies.PortalTypes', - required=False, - ) - directives.widget('objective', SelectFieldWidget) - - textline_field = schema.TextLine( - title=u'Textline field', - required=False, - ) - - bool_field = schema.Bool( - title=u'Boolean field', - required=False, - ) - - -class IExampleWithDatagrid(model.Schema): - - title = schema.TextLine(title=u'Title', required=True) - - datagrid_field = schema.List( - title=u'Datagrid field', - value_type=DictRow(title=u'Table', schema=IMyRowSchema), - default=[], - required=False, - ) - directives.widget('datagrid_field', DataGridFieldFactory) -``` - -The edit-form looks like this: - -```{figure} _static/dexterity_reference_datagridfield_edit.png - -``` - -The output looks like this: - -```{figure} _static/dexterity_reference_datagridfield_view.png - -``` - -```{seealso} -- [All available Fields](https://5.docs.plone.org/external/plone.app.dexterity/docs/reference/fields.html#field-types) -- [Schema-driven types with Dexterity](https://5.docs.plone.org/external/plone.app.dexterity/docs/schema-driven-types.html#schema-driven-types) -``` - -## Widgets - -```{eval-rst} -.. todo:: - - Document all available widgets - -``` - -## Directives - -Directives can be placed anywhere in the class body (annotations are made directly on the class). By convention they are kept next to the fields they apply to. - -For example, here is a schema that omits a field: - -```python -from plone.autoform import directives -from plone.supermodel import model -from zope import schema - - -class ISampleSchema(model.Schema): - - title = schema.TextLine(title=u'Title') - - directives.omitted('additionalInfo') - additionalInfo = schema.Bytes() -``` - -You can also handle multiple fields with one directive: - -```python -directives.omitted('field_1', 'field_2') -``` - -With the directive "mode" you can set fields to 'input', 'display' or 'hidden'. - -```python -directives.mode(additionalInfo='hidden') -``` - -You can apply directives to certain forms only. Here we drop a field from the add-form, it will still show up in the edit-form. - -```python -from z3c.form.interfaces import IAddForm - -class ITask(model.Schema): - - title = schema.TextLine(title=u'Title') - - directives.omitted(IAddForm, 'done') - done = schema.Bool( - title=_(u'Done'), - required=False, - ) -``` - -The same works for custom forms. - -With the directive {py:meth}`widget` you can not only change the widget used for a field. With {py:data}`pattern_options` you can pass additional parameters to the widget. Here, we configure the datetime widget powered by the JavaScript library [pickadate](https://amsul.ca/pickadate.js/) by adding options that are used by it. Plone passes the options to the library. - -```python -class IMeeting(model.Schema): - - meeting_date = schema.Datetime( - title=_(default=u'Date and Time'), - required=False, - ) - directives.widget( - 'meeting_date', - DatetimeFieldWidget, - pattern_options={ - 'time': {'interval': 60, 'min': [7, 0], 'max': [19, 0]}}, - ) -``` - -### Validation and default values - -In the following example we add a validator and a default value. - -```python -from zope.interface import Invalid -import datetime - - -def future_date(value): - if value and not value.date() >= datetime.date.today(): - raise Invalid(_(u"Meeting date can not be before today.")) - return True - -def meeting_date_default_value(): - return datetime.datetime.today() + datetime.timedelta(7) - - -class IMeeting(model.Schema): - - meeting_date = schema.Datetime( - title=_(default=u'Date and Time'), - required=False, - constraint=future_date, - defaultFactory=meeting_date_default_value, - ) -``` - -Validators and defaults can be also be made aware of the context (i.e. to check against the values of other fields). - -For context aware defaults you need to use a {py:class}`IContextAwareDefaultFactory`. It will be passed the container for which the add form is being displayed: - -```python -from zope.interface import provider -from zope.schema.interfaces import IContextAwareDefaultFactory - -@provider(IContextAwareDefaultFactory) -def get_container_id(context): - return context.id.upper() - -class IMySchema(model.Schema): - - parent_id = schema.TextLine( - title=_(u'Parent ID'), - required=False, - defaultFactory=get_container_id, - ) -``` - -For context-aware validators you need to use {py:meth}`invariant`: - -```python -from zope.interface import Invalid -from zope.interface import invariant -from zope.schema.interfaces import IContextAwareDefaultFactory - - -class IMyEvent(model.Schema): - - start = schema.Datetime( - title=_(u'Start date'), - required=False) - - end = schema.Datetime( - title=_(u"End date"), - required=False) - - @invariant - def validate_start_end(data): - if data.start is not None and data.end is not None: - if data.start > data.end: - raise Invalid(_('Start must be before the end.')) -``` - -```{seealso} -To learn more about directives, validators and default values, refer to the following: - -- [Form schema hints and directives](https://5.docs.plone.org/external/plone.app.dexterity/docs/reference/form-schema-hints.html) -- [Validation](https://5.docs.plone.org/develop/addons/schema-driven-forms/customising-form-behaviour/validation.html) (this documentation unfortunately still uses the obsolete grok technology) -- [z3c.form documentation](https://z3cform.readthedocs.io/en/latest/advanced/validator.html) -- [Default values for fields on add forms](https://5.docs.plone.org/external/plone.app.dexterity/docs/advanced/defaults.html) -``` diff --git a/docs/mastering-plone-5/eggs1.md b/docs/mastering-plone-5/eggs1.md deleted file mode 100644 index dc5417007..000000000 --- a/docs/mastering-plone-5/eggs1.md +++ /dev/null @@ -1,275 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-eggs1-label)= - -# Write Your Own Add-Ons to Customize Plone - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout buildout_1 -``` - -Code for the end of this chapter: - -```shell -git checkout eggs1 -``` - -{doc}`code` -```` - -(plone5-eggs1-create-label)= - -In this part you will: - -- Create a custom Python package {py:mod}`ploneconf.site` to hold all the code -- Modify buildout to install that package - -Topics covered: - -- {py:mod}`mr.bob` and {py:mod}`bobtemplates.plone` -- the structure of python packages - -## Creating the package - -Your own code has to be organized as a [Python package](https://docs.python.org/2/tutorial/modules.html#packages). A python package is directory that follows certain conventions to hold python modules. - -We are going to use [bobtemplates.plone](https://pypi.org/project/bobtemplates.plone) to create a skeleton package. You only need to fill in the blanks. - -{py:mod}`bobtemplates.plone` offers several Plone-specific templates for {py:mod}`mr.bob`, a project template builder similar to {py:mod}`cookiecutter`. - -Enter the {file}`src` directory (*src* is short for *sources*) and call a script called {command}`mrbob` from our buildout's {file}`bin` directory: - -```shell -$ cd src -$ ../bin/mrbob -O ploneconf.site bobtemplates.plone:addon -``` - -````{warning} -Before version 2.0.0 of {py:mod}`bobtemplates.plone` the command to create a add-on was different: - -```shell -$ ../bin/mrbob -O ploneconf.site bobtemplates:plone_addon -``` -```` - -You have to answer some questions about the add-on. Press {kbd}`Enter` (i.e. choosing the default value) for most questions except where indicated (enter your GitHub username if you have one, do not initialize a GIT repository, Use Plone 5.2 and python 3.7): - -``` ---> Author's name [Philip Bauer]: - ---> Author's email [bauer@starzel.de]: - ---> Author's GitHub username: pbauer - ---> Package description [An add-on for Plone]: - ---> Do you want me to initialize a GIT repository in your new package? (y/n) [y]: n - ---> Plone version [5.1]: 5.2 - ---> Python version for virtualenv [python2.7]: python3.7 - -git init is disabled! -Generated file structure at /Users/pbauer/workspace/training_buildout/src/ploneconf.site -``` - -```{only} not presentation -If this is your first python package, this is a very special moment. - -You generated a package with a lot files. It might look like too much boilerplate but all files in this package serve a clear purpose and it will take some time to learn about the meaning of each of them. -``` - -## Eggs - -When a python package is production-ready you can choose to distribute it as an egg over the python package index, [pypi](https://pypi.org). This allows everyone to install and use your package without having to download the code from github. The over 270 python packages that are used by your current Plone instance are also distributed as eggs. - -(plone5-eggs1-inspect-label)= - -## Inspecting the package - -In {file}`src` there is now a new folder {file}`ploneconf.site` and in there is the new package. Let's have a look at some of the files: - -{file}`buildout.cfg`, {file}`.travis.yml`, {file}`.coveragerc`, {file}`requirements.txt`, {file}`MANIFEST.in`, {file}`.gitignores`, {file}`.gitattributes`, - -: You can ignore these files for now. They are here to create a buildout only for this package to make distributing and testing it easier. - -{file}`README.rst`, {file}`CHANGES.rst`, {file}`CONTRIBUTORS.rst`, {file}`DEVELOP.rst`, {file}`docs/` - -: The documentation of your package goes in here. - -{file}`setup.py` - -: This file configures the package, its name, dependencies and some metadata like the author's name and email address. The dependencies listed here are automatically downloaded when running buildout. - -{file}`src/ploneconf/site/` - -: The python code of your package itself lives inside a special folder structure. - That seems confusing but is necessary for good testability. - Our package contains a [namespace package](https://peps.python.org/pep-0420/) called *ploneconf.site* and because of this there is a folder {file}`ploneconf` with a {file}`__init__.py` and in there another folder {file}`site` and in there finally is our code. - From the buildout's perspective your code is in {file}`{your buildout directory}/src/ploneconf.site/src/ploneconf/site/{real code}` - -```{note} -Unless discussing the buildout we will from now on silently omit these folders when describing files and assume that {file}`{your buildout directory}/src/ploneconf.site/src/ploneconf/site/` is the root of our package! -``` - -{file}`configure.zcml` ({file}`src/ploneconf/site/configure.zcml`) - -: The phone book of the distribution. By reading it you can find out which functionality is registered using the component architecture. There are more registrations in other zcml-files in this add-ons (e.g. {file}`browser/configure.zcml`, {file}`upgrades.zcml` and {file}`permissions.zcml`) that are included in your main {file}`configure.zcml` - -{file}`setuphandlers.py` ({file}`src/ploneconf/site/setuphandlers.py`) - -: This holds code that is automatically run when installing and uninstalling our add-on. - -{file}`interfaces.py` ({file}`src/ploneconf/site/interfaces.py`) - -: Here a browserlayer is defined in a straightforward python class. We will need it later. - -{file}`testing.py` - -: This holds the setup for running tests. - -{file}`tests/` - -: This holds the tests. - -{file}`browser/` - -: This directory is a python package (because it has a {file}`__init__.py`) and will by convention hold most things that are visible in the browser. - -{file}`browser/configure.zcml` - -: The phonebook of the browser package. Here views, resources and overrides are registered. - -{file}`browser/overrides/` - -: This folder is here to allow overriding existing default Plone templates. - -{file}`browser/static/` - -: A directory that holds static resources (images/css/js). The files in here will be accessible through URLs like `++resource++ploneconf.site/myawesome.css` - -{file}`locales/` - -: This directory can hold translations of text used in the package to allow for multiple languages of your user-interface. - -{file}`profiles/default/` - -: This folder contains the GenericSetup profile. During the training we will put some XML files here that hold configuration for the site. - -{file}`profiles/default/metadata.xml` - -: Version number and dependencies that are auto-installed when installing our add-on. - -% profiles/uninstall/ -% This folder holds another GenericSetup profile. The steps in here are executed on uninstalling. - -(plone5-eggs1-include-label)= - -## Including the package in Plone - -Before we can use our new package we have to tell Plone about it. Look at {file}`buildout.cfg` and see how `ploneconf.site` is included in `auto-checkout`, `eggs` and `test`: - -```{code-block} cfg -:emphasize-lines: 2, 30, 38 - -auto-checkout += - ploneconf.site -# starzel.votable_behavior - -parts = - checkversions - instance - mrbob - packages - robot - test - zopepy - -eggs = - Plone - Pillow - -# development tools - plone.api - plone.reload - Products.PDBDebugMode - plone.app.debugtoolbar - Products.PrintingMailHost - pdbpp - -# TTW Forms - collective.easyform - -# The add-on we develop in the training - ploneconf.site - -# Voting on content -# starzel.votable_behavior - -zcml = - -test-eggs += - ploneconf.site [test] -``` - -This tells Buildout to add the egg {py:mod}`ploneconf.site`. The sources for this eggs are defined in the section `[sources]` at the bottom of {file}`buildout.cfg`. - -```{code-block} cfg -:emphasize-lines: 2 - -[sources] -ploneconf.site = git https://github.com/collective/ploneconf.site.git pushurl=git@github.com:collective/ploneconf.site.git -starzel.votable_behavior = git https://github.com/collective/starzel.votable_behavior.git pushurl=git://github.com/collective/starzel.votable_behavior.git -``` - -This tells buildout to not download it from pypi but to do a checkout from GitHub put the code in {file}`src/ploneconf.site`. - -```{note} -The package {py:mod}`ploneconf.site` is now downloaded from GitHub and automatically in the branch master. {py:mod}`ploneconf.site` can be called an egg even though it has not been released on pypi. Plone can use it like it uses an egg. -``` - -````{note} -If you do **not** want to use the prepared package for ploneconf.site from GitHub but write it yourself (we suggest you try that) then add the following instead: - -```{code-block} cfg -:emphasize-lines: 2 - -[sources] -ploneconf.site = fs ploneconf.site path=src -starzel.votable_behavior = git https://github.com/collective/starzel.votable_behavior.git pushurl=git://github.com/collective/starzel.votable_behavior.git -``` - -This tells buildout to expect `ploneconf.site` in {file}`src/ploneconf.site`. -The directive `fs` allows you to add eggs on the filesystem without a version control system. -```` - -Now run buildout to reconfigure Plone with the updated configuration: - -```shell -$ ./bin/buildout -``` - -After restarting Plone with {command}`./bin/instance fg` the new add-on {py:mod}`ploneconf.site` is available for install like EasyForm or Plone True Gallery. - -We will not install it now since we did not add any of our own code or configuration yet. Let's do that next. - -## Exercises - -1. Create a new package called {py:mod}`collective.behavior.myfeature`. Inspect the directory structure of this package. Delete it after you are done. Many packages that are part of Plone and some add-ons use a *nested namespace* such as {py:mod}`plone.app.contenttypes`. -2. Open and read about the templates and subtemplates it provides. - -## Summary - -- You created the package {py:mod}`ploneconf.site` to hold your code. -- You added the new package to buildout so that Plone can use it. diff --git a/docs/mastering-plone-5/eggs2.md b/docs/mastering-plone-5/eggs2.md deleted file mode 100644 index bba9b661c..000000000 --- a/docs/mastering-plone-5/eggs2.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-eggs2-label)= - -# Creating Reusable Packages - -We already created the package {py:mod}`ploneconf.site` much earlier. - -In this part you will: - -- Build your own standalone egg. - -Topics covered: - -- {py:mod}`mr.bob` - -Now you are going to create a feature that is independent of the ploneconf site and can be reused in other packages. - -To make the distinction clear, this is not a package from the namespace {samp}`ploneconf` but from {samp}`starzel`. - -We are going to add a voting behavior. - -For this we need: - -> - A behavior that stores its data in annotations -> - A viewlet to present the votes -> - A bit of JavaScript -> - A bit of CSS -> - Some helper views so that the JavaScript code can communicate with Plone - -We move to the {file}`src` directory and again use a script called {file}`mrbob` from our project's {file}`bin` directory -and the template from `bobtemplates.plone` to create the package. - -If the {file}`src` directory does not exist yet, create it: - -```shell -mkdir src -``` - -Go inside the {file}`src` folder and create the package: - -```shell -cd src -$ ../bin/mrbob -O starzel.votable_behavior bobtemplates.plone:addon -``` - -We press {kbd}`Enter` to all questions *except* our personal data and the Plone version. -Here we enter {kbd}`5.2`. diff --git a/docs/mastering-plone-5/embed.md b/docs/mastering-plone-5/embed.md deleted file mode 100644 index 1400fb0aa..000000000 --- a/docs/mastering-plone-5/embed.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-embed-label)= - -# Using starzel.votable_behavior in ploneconf.site - -In this part you will: - -- Integrate your own third party package into your site. - -Topics covered: - -- Permissions -- Workflows - -````{sidebar} Get the code! -Get the code for this chapter ({doc}`More info `) using this command in the buildout directory: - -```shell -TODO -``` -```` - -```{only} not presentation -- We want to use the votable behavior, so that our reviewers can vote. -- To show how to use events, we are going to auto-publish talks that have reached a certain rating. -- We are not going to let everybody vote everything. -``` - -First, we must add our package as a dependency to ploneconf.site. - -```{only} not presentation -We do this in two locations. The egg description {file}`setup.py` needs {samp}`starzel.votable_behavior` as a dependency. -Else no source code will be available. - -The persistent configuration needs to be installed when we install our site. This is configured in GenericSetup. -``` - -We start by editing {file}`setup.py` - -```{code-block} python -:emphasize-lines: 8 -:linenos: - -... -zip_safe=False, -install_requires=[ - 'setuptools', - 'plone.app.dexterity [relations]', - 'plone.app.relationfield', - 'plone.namedfile [blobs]', - 'starzel.votable_behavior', - # -*- Extra requirements: -*- -], -... -``` - -Next up we modify {file}`profiles/default/metadata.xml` - -```{code-block} xml -:emphasize-lines: 4 -:linenos: - - - 1002 - - profile-starzel.votable_behavior:default - - -``` - -... only:: not presentation - -> What a weird name. profile- is a prefix you will always need nowadays. Then comes the egg name, and the part after the colon is the name of the profile. The name of the profile is defined in zcml. So far I've stumbled over only one package where the profile directory name was different than the GenericSetup Profile name. -> -> Now the package is there, but nothing is votable. That is because no content type declares to use this behavior. We can add this behavior via the control panel, export the settings and store it in our egg. Let's just add it by hand now. - -To add the behavior to talks, we do this in {file}`profiles/default/types/talk.xml` - -```{note} -After changing the {file}`metadata.xml` you have to restart your site since unlike other GenericSetup XML files that file is cached. - -Managing dependencies in {file}`metadata.xml` is good practice. We can't rely on remembering what we'd have to do by hand. -In Plone 4 we should also have added {samp}`profile-plone.app.contenttypes:plone-content` like the [documentation for plone.app.contenttypes](https://5.docs.plone.org/external/plone.app.contenttypes/docs/README.html#installation-as-a-dependency-from-another-product) recommends. - -Read more: -``` - -```{code-block} xml -:emphasize-lines: 4 -:linenos: - - - - - - -``` - -... only:: not presentation - -> Now you can reinstall your Plone site. -> -> Everybody can now vote on talks. That's not what we wanted. We only want reviewers to vote on _pending_ Talks. -> This means the permission has to change depending on the workflow state. Luckily, workflows can be configured to do just that. -> Since Talks already have their own workflow we also won't interfere with other content. -> -> First, we have to tell the workflow that it will be managing more permissions. Next, for each state we have to configure which role has the two new permissions. -> -> That is a very verbose configuration, maybe you want to do it in the web interface and export the settings. -> Whichever way you choose, it is easy to make a simple mistake. I will just present the XML way here. - -The config for the Workflow is in {file}`profiles/default/workflows/talks_workflow.xml` - -```{code-block} xml -:emphasize-lines: 7-8, 12-21, 27-34, 40-45 -:linenos: - - - - Access contents information - Change portal events - Modify portal content - View - starzel.votable_behavior: View Vote - starzel.votable_behavior: Do Vote - - Waiting to be reviewed, not editable by the owner. - ... - - Site Administrator - Manager - Reviewer - - - Site Administrator - Manager - Reviewer - - ... - - - Can only be seen and edited by the owner. - ... - - Site Administrator - Manager - - - Site Administrator - Manager - - ... - - - Visible to everyone, editable by the owner. - ... - - Site Administrator - Manager - - - - ... - - ... - -``` - -```{only} not presentation -We have to reinstall our product again. - -But this time, this is not enough. Permissions get updated on workflow changes. As long as a workflow change didn't happen, the talks have the same permissions as ever. - -Luckily, there is a button for that in the ZMI Workflow view {guilabel}`Update security settings`. - -After clicking on this, only managers and Reviewers can see the Voting functionality. - -Lastly, we add our silly function to auto-approve talks. - -You quickly end up writing many event handlers, so we put everything into a directory for eventhandlers. -``` - -For the events we need an {file}`events` directory. - -Create the {file}`events` directory and add an empty {file}`events/__init__.py` file. - -Next, register the events directory in {file}`configure.zcml` - -```{code-block} xml -:linenos: - - -``` - -Now write the ZCML configuration for the events into {file}`events/configure.zcml` - -```{code-block} xml -:linenos: - - - - - - -``` - -```{only} not presentation -This looks like a MultiAdapter. We want to get notified when an IVotable object gets modified. Our method will receive the votable object and the event itself. -``` - -And finally, our event handler in {file}`events/votable.py` - -```{code-block} python -:linenos: - -from plone.api.content import transition -from plone.api.content import get_state -from starzel.votable_behavior.interfaces import IVoting - - -def votable_update(votable_object, event): - votable = IVoting(votable_object) - if get_state(votable_object) == 'pending': - if votable.average_vote() > 0.5: - transition(votable_object, transition='publish') -``` - -```{only} not presentation -We are using a lot of plone api here. Plone API makes the code a breeze. Also, there is nothing really interesting. -We will only do something if the workflow state is pending and the average vote is above 0.5. -As you can see, the {samp}`transition` Method does not want the target state, but the transition to move the state to the target state. - -There is nothing special going on. -``` diff --git a/docs/mastering-plone-5/events.md b/docs/mastering-plone-5/events.md deleted file mode 100644 index 933d4c0dd..000000000 --- a/docs/mastering-plone-5/events.md +++ /dev/null @@ -1,312 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-events-label)= - -# Turning Talks into Events - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout dexterity_2 -``` - -Code for the end of this chapter: - -```shell -git checkout events -``` - -{doc}`code` -```` - -We forgot something: a list of talks is great, especially if you can sort it according to your preferences. But if a visitor decides she wants to actually go to see a talk she needs to know when it will take place. - -We need a schedule and for this we need to store the information when a talk will happen. - -Luckily the default type _Event_ is based on reusable behaviors from the package {py:mod}`plone.app.event` that we can reuse. - -In this chapter you will - -- enable this behavior for talks -- display the date in the talkview and talklistview - -First enable the behavior {py:class}`IEventBasic` for talks in {file}`profiles/default/types/talk.xml` - -```{code-block} xml -:emphasize-lines: 6 -:linenos: - - - - - - - - -``` - -After you activate the behavior by hand or you reinstalled the add-on you will now have some additional fields for `start` and `end`. - -To display the new field we reuse a default event summary view as documented in - -Edit {file}`browser/templates/talkview.pt` - -```{code-block} html -:emphasize-lines: 7 -:linenos: - - - - - - - -

- - Talk - - suitable for - - Audience - -

- -
- Details -
- -
- -
- -
- -
- Biography -
-
- -
- - -``` - -Similar to the field `room`, the problem now appears that speakers submitting their talks should not be able to set a time and day for their talks. -Sadly it is not easy to modify permissions of fields provided by behaviors (unless you write the behavior yourself). -At least in this case we can take the easy way out since the field does not contain secret information: we will simply hide the fields from contributors using css and show them for reviewers. We will do so in chapter {ref}`plone5-resources-label` when we add some CSS files. - -Modify {file}`browser/static/ploneconf.css` and add: - -```css -body.userrole-contributor #formfield-form-widgets-IEventBasic-start, -body.userrole-contributor #formfield-form-widgets-IEventBasic-end > *, -body.userrole-contributor #formfield-form-widgets-IEventBasic-whole_day, -body.userrole-contributor #formfield-form-widgets-IEventBasic-open_end { - display: none; -} - -body.userrole-reviewer #formfield-form-widgets-IEventBasic-start, -body.userrole-reviewer #formfield-form-widgets-IEventBasic-end > *, -body.userrole-reviewer #formfield-form-widgets-IEventBasic-whole_day, -body.userrole-reviewer #formfield-form-widgets-IEventBasic-open_end { - display: block; -} -``` - -You can now display the start date of a talk in the talklist. -Modify the class {py:class}`TalkListView` and the template {file}`browser/templates/talklistview.pt` to show the new info: - -```{code-block} python -:emphasize-lines: 17 -:linenos: - -class TalkListView(BrowserView): - """ A list of talks - """ - - def talks(self): - results = [] - brains = api.content.find(context=self.context, portal_type='talk') - for brain in brains: - results.append({ - 'title': brain.Title, - 'description': brain.Description, - 'url': brain.getURL(), - 'audience': ', '.join(brain.audience or []), - 'type_of_talk': brain.type_of_talk, - 'speaker': brain.speaker, - 'room': brain.room, - 'start': brain.start, - }) - return results -``` - -```{code-block} html -:emphasize-lines: 5-9 -:linenos: - -[...] - - Advanced - - - Time - - - 101 - -[...] -``` - -```{note} -If you changed the view {py:class}`TalkListView` to only return brains as described in {ref}`plone5-dexterity2-use-indexes-label` you can save yourself a lot of work and simply use the existing index `start` (generously provided by {py:mod}`plone.app.event`) in the template as `python:brain.start`. -``` - -## Exercise 1 - -Find out where `event_summary` comes from and describe how you could override it. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Use your editor or grep to search all ZCML files in the folder {file}`packages` for the string `name="event_summary"` - -```shell -$ grep -siRn --include \*.zcml 'name="event_summary"' ./packages -./packages/plone/app/event/browser/configure.zcml:66: name="event_summary" -./packages/plone/app/event/browser/configure.zcml:75: name="event_summary" -``` - -The relevant registration is: - -```xml - -``` - -So there is a class {py:class}`plone.app.event.browser.event_summary.EventSummaryView` and a template {file}`event_summary.pt` that could be overridden with {py:mod}`z3c.jbot` by copying it as {file}`plone.app.event.browser.event_summary.pt` in {file}`browser/overrides`. -```` - -## Exercise 2 - -Find out where the event behavior is defined and which fields it offers. - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -The id with which the behavior is registered in {file}`Talk.xml` is a Python path. So {py:class}`plone.app.event.dx.behaviors.IEventBasic` can be found in {file}`packages/plone.app.event/plone/app/event/dx/behaviors.py` - -```python -class IEventBasic(model.Schema, IDXEvent): - - """ Basic event schema. - """ - start = schema.Datetime( - title=_( - u'label_event_start', - default=u'Event Starts' - ), - description=_( - u'help_event_start', - default=u'Date and Time, when the event begins.' - ), - required=True, - defaultFactory=default_start - ) - directives.widget( - 'start', - DatetimeFieldWidget, - default_timezone=default_timezone, - klass=u'event_start' - ) - - end = schema.Datetime( - title=_( - u'label_event_end', - default=u'Event Ends' - ), - description=_( - u'help_event_end', - default=u'Date and Time, when the event ends.' - ), - required=True, - defaultFactory=default_end - ) - directives.widget( - 'end', - DatetimeFieldWidget, - default_timezone=default_timezone, - klass=u'event_end' - ) - - whole_day = schema.Bool( - title=_( - u'label_event_whole_day', - default=u'Whole Day' - ), - description=_( - u'help_event_whole_day', - default=u'Event lasts whole day.' - ), - required=False, - default=False - ) - directives.widget( - 'whole_day', - SingleCheckBoxFieldWidget, - klass=u'event_whole_day' - ) - - open_end = schema.Bool( - title=_( - u'label_event_open_end', - default=u'Open End' - ), - description=_( - u'help_event_open_end', - default=u"This event is open ended." - ), - required=False, - default=False - ) - directives.widget( - 'open_end', - SingleCheckBoxFieldWidget, - klass=u'event_open_end' - ) -``` - -Note how it uses `defaultFactory` to set an initial value. -```` - -### Summary - -- You reused a existing behavior to add new fields -- You reused existing indexes to display the time of a talk -- You did not have to write your own datetime fields and indexers o/ diff --git a/docs/mastering-plone-5/export_code.md b/docs/mastering-plone-5/export_code.md deleted file mode 100644 index cf15c0941..000000000 --- a/docs/mastering-plone-5/export_code.md +++ /dev/null @@ -1,438 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-export-code-label)= - -# Return to Dexterity: Moving contenttypes into Code - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout eggs1 -``` - -Code for the end of this chapter: - -```shell -git checkout export_code -``` - -{doc}`code` -```` - -In this part you will: - -- Move the _Talk_ type into {py:mod}`ploneconf.site` -- Improve the schema and the FTI - -Topics covered: - -- Content type definitions with generic setup -- FTI -- XML schema -- more widgets - -Remember the _Talk_ content type that we created through-the-web with Dexterity? Let's move that new content type into our add-on package so that it may be installed in other sites without TTW manipulation. - -Steps: - -- Return to the Dexterity control panel -- Export the _Talk_ Type Profile and save the file -- Delete the _Talk_ from the site before installing it from the file system -- Extract the files from the exported tar file and add them to our add-on package in {file}`profiles/default/` - -```{note} -From the buildout directory perspective that is {file}`src/ploneconf.site/src/ploneconf/site/profiles/default/` -``` - -The file {file}`profiles/default/types.xml` tells Plone that there is a new content type defined in file {file}`talk.xml`. - -```xml - - - Controls the available contenttypes in your portal - - - -``` - -Upon installing, Plone reads the file {file}`profiles/default/types/talk.xml` and registers a new type in `portal_types` (you can find and inspect this tool in the ZMI!) with the information taken from that file. - -```xml - - - Talk - None - string:${portal_url}/document_icon.png - talk - string:${folder_url}/++add++talk - - view - True - True - - False - view - - - - False - cmf.AddPortalContent - plone.dexterity.content.Container - - - - - - <?xml version='1.0' encoding='utf8'?> -<model xmlns:lingua="http://namespaces.plone.org/supermodel/lingua" xmlns:users="http://namespaces.plone.org/supermodel/users" xmlns:form="http://namespaces.plone.org/supermodel/form" xmlns:i18n="http://xml.zope.org/namespaces/i18n" xmlns:security="http://namespaces.plone.org/supermodel/security" xmlns:marshal="http://namespaces.plone.org/supermodel/marshal" xmlns="http://namespaces.plone.org/supermodel/schema"> - <schema> - <field name="type_of_talk" type="zope.schema.Choice"> - <description/> - <title>Type of talk</title> - <values> - <element>Talk</element> - <element>Training</element> - <element>Keynote</element> - </values> - </field> - <field name="details" type="plone.app.textfield.RichText"> - <description>Add a short description of the talk (max. 2000 characters)</description> - <max_length>2000</max_length> - <title>Details</title> - </field> - <field name="audience" type="zope.schema.Set"> - <description/> - <title>Audience</title> - <value_type type="zope.schema.Choice"> - <values> - <element>Beginner</element> - <element>Advanced</element> - <element>Professionals</element> - </values> - </value_type> - </field> - <field name="speaker" type="zope.schema.TextLine"> - <description>Name (or names) of the speaker</description> - <title>Speaker</title> - </field> - <field name="email" type="plone.schema.email.Email"> - <description>Adress of the speaker</description> - <title>Email</title> - </field> - <field name="image" type="plone.namedfile.field.NamedBlobImage"> - <description/> - <required>False</required> - <title>Image</title> - </field> - <field name="speaker_biography" type="plone.app.textfield.RichText"> - <description/> - <max_length>1000</max_length> - <required>False</required> - <title>Speaker Biography</title> - </field> - </schema> - </model> - - dexterity - - - - - - - - - - - -``` - -Now our package has new configuration for Generic Setup. Generic Setup store the configiuration for the site in the folder {file}`profiles/`. This configuration is applied to your site upon installing the package. So, we'll need to reinstall it (if installed before). - -- Restart Plone. -- Re-install ploneconf.site (deactivate and activate). -- Test the type by adding an object or editing one of the old ones. -- Look at how the talks are presented in the browser. - -The escaped inline xml is simply too ugly to look at. You should move it to a separate file! - -Create a new folder {file}`content` in the main directory (from the buildout directory perspective that is {file}`src/ploneconf.site/src/ploneconf/site/content/`). Inside add an empty file {file}`__init__.py` and a file {file}`talk.xml` that contains the real XML (copied from and beautified with some online XML formatter ()) - -```{code-block} xml -:linenos: - - - - - - - Type of Talk - - Talk - Training - Keynote - - - - Add a short description of the talk (max. 2000 characters)/> - 2000 - Details - - - - Audience - - - Beginner - Advanced - Professional - - - - - Name (or names) of the speaker/> - Speaker - - - Adress of the speaker/> - Email - - - - False - Image - - - - 1000 - False - Speaker Biography - - - -``` - -Now remove the ugly model_source and instead point to the new XML file in the FTI by using the property `model_file`: - -```xml - -ploneconf.site.content:talk.xml -``` - -`ploneconf.site.content:talk.xml` points to a file {file}`talk.xml` to be found in the Python path `ploneconf.site.content`. The {file}`__init__.py` is needed to turn the folder {file}`content` into a Python package. It is best-practice to add schemas in this folder, and in later chapters you will add new types with pythons-schemata in the same folder. - -```{note} -The default types of Plone 5 also have an xml schema like this since that allows the fields of the types to be editable trough the web! Fields for types with a python schema are not editable ttw. -``` - -## Changing a widget - -[Dexterity XML](https://5.docs.plone.org/external/plone.app.dexterity/docs/reference/dexterity-xml.html) is very powerful. By editing it (not all features have a UI) you should be able to do everything you can do with a Python schema. -Sadly not every feature also is exposed in the UI of the dexterity schema editor. For example you cannot yet change the widgets or permissions for fields in the UI. We need to do this in the xml- or python-schema. - -Our talks use a dropdown for {guilabel}`type_of_talk` and a multiselect for {guilabel}`audience`. Radio-buttons and checkboxes would be the better choice here. Modify the XML to make that change happen: - -```{code-block} xml -:emphasize-lines: 11, 26 -:linenos: - - - - - - - Type of talk - - Talk - Training - Keynote - - - - Add a short description of the talk (max. 2000 characters) - 2000 - Details - - - - Audience - - - Beginner - Advanced - Professionals - - - - - Name (or names) of the speaker - Speaker - - - Adress of the speaker - Email - - - - False - Image - - - - 1000 - False - Speaker Biography - - - -``` - -## Protect fields with permissions - -We also want to have a add a new field `room` to show where a talk will take place. -Our case-study says the speakers will submit the talks online. -How should they know in which room the talk will take place (if it got accepted at all)? -So we need to hide this field from them by requiring a permission that they do not have. - -Let's assume the prospective speakers will not have the permission to review content (i.e. edit submitted content and publish it) but the organizing commitee has. -You can then protect the field using the permission `Review portal content` in this case the name of the permission-utility for this permission: `cmf.ReviewPortalContent`. - -We only want to prevent writing, not reading, so we'll only manage the `write-permission`: - -```{code-block} xml -:emphasize-lines: 38-50 -:linenos: - - - - - - - Type of talk - - Talk - Training - Keynote - - - - Add a short description of the talk (max. 2000 characters) - 2000 - Details - - - - Audience - - - Beginner - Advanced - Professionals - - - - - - False - Room - - 101 - 201 - Auditorium - - - - Name (or names) of the speaker - Speaker - - - Adress of the speaker - Email - - - - False - Image - - - - 1000 - False - Speaker Biography - - - -``` - -```{seealso} -- -- -``` - -### Exercise 1 - -Go to the ZMI and look for the definition of the new `Talk` content type in `portal_types`. Now deactivate {guilabel}`Implicitly addable?` and save. Go back to the site. Can you identify what this change has caused? And why is that useful? - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Go to - -When disabling *Implicitly addable* you can no longer add Talks any more unless you change some container like the type *Folder*: Enable *Filter contenttypes?* for it and add *Talk* to the items that are allowed. - -With this method you can prevent content that only makes sense inside some defined structure to show up in places where they do not belong. - -The equivalent setting for disabling {guilabel}`Implicitly addable` in {file}`Talk.xml` is: - -```xml -False -``` -```` - -## Summary - -- You can now create new content-types and store them in a reproduceable way -- You installed the package to apply the Generic Setup configuration -- You learned how to read and modify the content type schema in xml diff --git a/docs/mastering-plone-5/extending.md b/docs/mastering-plone-5/extending.md deleted file mode 100644 index 901598d3f..000000000 --- a/docs/mastering-plone-5/extending.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-extending-label)= - -# Extending Plone - -In this part you will: - -- Get an overview over the technologies used to extend Plone - -Topics covered: - -- Component Architecture -- ZCML -- GenericSetup - -As a developer you want to go further than simply configuring Plone, you want to extend and customize it. -Plone is built to be extended. -Extendability is not an afterthought but is the core of Plone and the systems it is based on. -Plone started as an extension for CMF, which is an extension for Zope. - -(plone5-extending-technologies-label)= - -## Extension technologies - -How do you extend Plone? - -This depends on what type of extension you want to create. - -```{only} not presentation -- You can create extensions with new types of objects to add to your Plone site. Usually these are contenttypes. -- You can create an extension that changes or extends functionality. For example to change the way Plone displays search results, or to make pictures searchable by adding a converter from jpg to text. -``` - -For most projects you mix all kinds of methods to extend Plone. - -### Component Architecture - -```{only} presentation -- State of the art -- verbose -- cryptic -- Powerful and flexible -``` - -```{only} not presentation -The best way to extend Plone is via *Components*. - -A bit of history is in order. - -When Zope started, object-oriented design was **the** silver bullet. - -Object-oriented design is good at modeling inheritance, but breaks down when an object has multiple aspects that are part of multiple taxonomies. - -Some object-oriented programming languages like Python handle this through multiple inheritance. But it's not a good way to do it. Zope objects have more than 10 base classes. Too many namespaces makes code that's hard to maintain. Where did that method/attribute come from? - -After a while, XML and Components became the next silver bullet (Does anybody remember J2EE?). - -Based on their experiences with Zope in the past, Zope developers thought that a component system configured via XML might be the way to go to keep the code more maintainable. - -Before Zope Components functionality was often extended by a practice called Monkey Patching: Changing code in other modules by importing and then modifying it at runtime. - -Monkey Patching, like subclassing via multiple inheritance, does not scale. Multiple plugins might overwrite each other, you would explain to people that they have to reorder the imports, and then, suddenly, you will be forced to import feature A before B, B before C and C before A, or else your application won't work. - -As the new concepts were radically different from the old Zope concepts, the Zope developers renamed the new project to Zope 3. -But it did not gain traction, was eventually renamed to Bluebream and then died out. - -But the component architecture itself is quite successful and the Zope developers extracted it into the Zope Toolkit. The Zope toolkit is part of Zope, and Plone developers use it extensively. - -This is what you want to use. -``` - -(plone5-extending-components-label)= - -### Configuring Zope Components with ZCML - -```{only} presentation -- zcml (Zope Component Markup Language) is used to register components -- components are distingushed by interfaces (contracts) that they require or provide -``` - -```{only} not presentation -ZCML, the Zope Configuration Mark-up Language is an XML based language used to configure Zope Components. With ZCML you declare utilities, adapters and browser views. - -Components are distinguished from one another by the interfaces (formal definitions of functionality) that they require or provide. - -During startup, Zope reads all these ZCML statements, validates that there are not two declarations trying to register the same components and registers everything. All components are registered by interfaces required and provided. Components with the same interfaces may optionally also be named. - -It may seem a little cumbersome that you have to register all components. But thanks to ZCML, you hardly ever have a hard time to find what and where extensions or customizations are defined. ZCML files are like a phone book. -``` - -```{eval-rst} -.. epigraph:: - - Explicit is better than implicit - - -- The Zen of Python - -``` - -### GenericSetup - -```{only} presentation -- Old style -- Does not cover 100% of use cases -``` - -```{only} not presentation -The next thing is {py:mod}`Products.GenericSetup`. - -*GenericSetup* lets you define persistent configuration in XML files. *GenericSetup* parses the XML files and updates the persistent configuration according to the configuration. This is a step you have to run on your own! - -You will see many objects in Zope or the ZMI that you can customize through the web. If they are well behaving, they can export their configuration via *GenericSetup* and import it again. - -Typically you use *GenericSetup* to change workflows or add new content type definitions. - -GenericSetup profiles may also be built into Python packages. Every package that is listed on the add-on package list inside a Plone installation has a GS profile that details how it fits into Plone. Packages that are part of Plone itself may have GS profiles, but are excluded from the active/inactive listing. -``` - -Example: - -{file}`metadata.xml`: - -```xml - - - 1000 - - profile-pas.plugins.ldap:default - profile-collective.folderishtypes.dx:default - profile-collective.geolocationbehavior:default - profile-collective.behavior.banner:default - - -``` - -Most settings are stored in a tool called `portal_registry`. Since it has great import/export handlers for GenericSetup it can be configures with {file}`registry.xml`: - -{file}`registry.xml`: - -```xml - - - - Mastering Plone Development - - -``` diff --git a/docs/mastering-plone-5/features.md b/docs/mastering-plone-5/features.md deleted file mode 100644 index 5c7fd34b5..000000000 --- a/docs/mastering-plone-5/features.md +++ /dev/null @@ -1,496 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-features-label)= - -# The Features of Plone - -In-depth user-manual: - -See also: - -(plone5-features-start-stop-label)= - -## Starting and Stopping Plone - -We control Plone with a small script called "instance": - -``` -$ ./bin/instance fg -``` - -This starts Plone in foreground mode so that we can see what it is doing by monitoring console messages. -This is an important development method. -Note that when Plone is started in foreground mode, -it is also automatically in development mode. -Development mode gives better feedback, but is much slower, particularly on Windows. - -You can stop it by pressing {kbd}`ctrl + c`. - -Apart from the `fg` command the {program}`instance` script offers several more commands. -`./bin/instance help` shows the list of available commands, `bin/instance help ` will give a short help for each command. -Some commands you will use rather often are: - -``` -$ ./bin/instance fg -$ ./bin/instance start -$ ./bin/instance stop -$ ./bin/instance debug -$ ./bin/instance run myscript.py -$ ./bin/instance adduser name password -``` - -````{only} not presentation -Depending on your computer, it might take up to a minute until Zope will tell you that it's ready to serve requests. -On a decent laptop it should be running in under 15 seconds. - -A standard installation listens on port 8080, so lets have a look at our Zope site by visiting - -```{figure} _static/features_plone_running.png -``` - -As you can see, there is no Plone site yet! - -We have a running Zope with a database but no content. -But luckily there is a button to create a Plone site. -Click on that button (login: admin, password: admin). -This opens a form to create a Plone site. -Use {samp}`Plone` as the site id. - -```{figure} _static/features_create_site_form.png -``` - -You will be automatically redirected to the new site. -```` - -```{only} presentation -- By default Plone listens on port 8080. Look at -- No Plone site yet! Create a new Plone site. -- Use {samp}`Plone` (the default) as the site id. -``` - -```{note} -Plone has many message boxes. -They contain important information. -Read them and make sure you understand them! -``` - -### Exercises - -#### Exercise 1 - -Open the `bin/instance` script in your favorite editor. Now let's say you want Plone to listen on port 9080 instead of the default 8080. Looking at the script, how could you do this? - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -At the end of the `bin/instance` script, you'll see the following code: - -```python - -``` - -if \_\_name\_\_ == '\_\_main\_\_': -: sys.exit(plone.recipe.zope2instance.ctl.main( - - : \['-C', '/Users/pbauer/workspace/training_buildout/parts/instance/etc/zope.conf', '-p', '/Users/pbauer/workspace/training_buildout/parts/instance/bin/interpreter', '--wsgi'\] - \+ sys.argv\[1:\])) - -The second to last line points to the configuration file your Plone instance is using. An absolute path is used so it might differ depending on the installation method. Open the `wsgi.ini` that lives in the same folder in your editor and look for the section: - -```ini -[server:main] -use = egg:waitress#main -listen = 0.0.0.0:8080 -threads = 4 -``` - -Change the address to 0.0.0.0:9080 and restart your instance. -```` - -#### Exercise 2 - -Knowing that `bin/instance debug` basically offers you a Python prompt, how would you start to explore Plone? - -```{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -Use `locals()` or `locals().keys()` to see Python objects available in Plone -``` - -#### Exercise 3 - -The `app` object you encountered in the previous exercise can be seen as the root of Plone. Once again using Python, can you find your newly created Plone site? - -`````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -`app.__dict__.keys()` will show `app`'s attribute names - there is one called `Plone`, this is your Plone site object. Use `app.Plone` to access and further explore it. - -````{note} -Plone and its objects are stored in an object database, the ZODB. You can use `bin/instance debug` as a database client (in the same way e.g. `psql` is a client for PostgreSQL). Instead -of a special query language (like SQL) you simply use Python to access and manipulate ZODB objects. Don't worry if you accidentally change objects in `bin/instance debug` - you would have to commit -your changes explicitly to make them permanent. The Python code to do so is: - -```pycon ->>> import transaction ->>> transaction.commit() -``` - -You have been warned. -```` -````` - -(plone5-features-walkthrough-label)= - -## Walkthrough of the UI - -Let's see what is there... - -- {guilabel}`header`: - - - {guilabel}`logo`: with a link to the front page - - {guilabel}`searchbox`: search (with live-search) - -- {guilabel}`navigation`: The global navigation - -- {guilabel}`banner`: A banner. Only visible on the front page. - -- {guilabel}`portal-columns`: a container holding: - - - {guilabel}`portal-column-one`: portlets (configurable boxes with tools like navigation, news etc.) - - {guilabel}`portal-column-content`: the content and the editor - - {guilabel}`portal-column-two`: portlets - -- {guilabel}`portal-footer`: portlets for the footer, site actions, and colophon - -- {guilabel}`edit-zone`: a vertical bar on the left side of the browser window with editing options for the content - -```{only} not presentation -These are also the CSS classes of the respective divs. -If you want to do theming, you'll need them. -``` - -On the edit bar, we find options affecting the current context... - -- {guilabel}`folder contents` -- {guilabel}`edit` -- {guilabel}`view` -- {guilabel}`add` -- {guilabel}`state` -- {guilabel}`actions` -- {guilabel}`display` -- {guilabel}`manage portlets` -- {guilabel}`history` -- {guilabel}`sharing` -- {guilabel}`rules` -- {guilabel}`user actions` - -Some edit bar options only show when appropriate; -for example, {guilabel}`folder contents` and {guilabel}`add` are only shown for Folders. -{guilabel}`rules` is currently invisible because we have no content rules available. - -(plone5-features-users-label)= - -## Users - -````{only} not presentation -Let's create our first users within Plone. -So far we used the admin user (admin:admin) configured in the buildout. -This user is often called "Zope root" and is not managed in Plone but only by Zope. -Therefore the user is missing some features like email and full name and won't be able to use some of Plone's features. -But the user has all possible permissions. -As with the root user of a server, it's bad practice to make unnecessary use of Zope root. -Use it to create Plone sites and their initial users, but not much else. - -You can also add Zope users via the terminal by entering: - -``` -$ ./bin/instance adduser -``` - -That way you can access databases you get from customers where you have no Plone user. - -To add a new user in Plone, click on the user icon at the bottom of the left vertical bar and then on {guilabel}`Site setup`. -This is Plone's control panel. -You can also access it by browsing to - -```{figure} _static/features_control_panel.png -``` - -Click on {guilabel}`Users and Groups` and add a user. -If we had configured a mail server, Plone could send you a mail with a link to a form where you can choose a password. -(Or, if you have Products.PrintingMailHost in your buildout, you can see the email scrolling by in the console, just the way it would be sent out.) -We set a password here because we haven't yet configured a mail server. - -Make this user with your name an administrator. - -```{figure} _static/features_add_user_form.png -``` - -Then create another user called `testuser`. -Make this one a normal user. -You can use this user to see how Plone looks and behaves to users that have no admin permissions. - -Now let's see the site in 3 different browsers with three different roles: - -> - as anonymous -> - as editor -> - as admin -```` - -```{only} presentation -Create some Plone users: - -1. {guilabel}`admin` > {guilabel}`Site setup` > {guilabel}`Users and Groups` -2. Add user \ (groups: Administrators) -3. Add another user "tester" (groups: None) -4. Add another user "editor" (groups: None) -5. Add another user "reviewer" (groups: Reviewers) -6. Add another user "jurymember" (groups: None) - -Logout as admin by clicking 'Logout' and following the instructions. - -Login to the site with your user now. -``` - -(plone5-features-mailserver-label)= - -## Configure a Mailserver - -```{only} not presentation -We have to configure a mailserver since later we will create some content rules that send emails when new content is put on our site. -``` - -- Server: {samp}`localhost` -- Username: leave blank -- Password: leave blank -- Site 'From' name: Your name -- Site 'From' address: Your email address - -```{only} not presentation -Click on `Save and send test e-mail`. Since we have configured PrintingMailHost, you will see the mail content in the console output of your instance. Plone will not -actually send the email to the receivers address. -``` - -(plone5-features-content-types-label)= - -## Content-Types - -Edit a page: - -- {guilabel}`Edit front-page` -- {guilabel}`Title` {samp}`Plone Conference 2019, Ferrara, Italy` -- {guilabel}`Summary` {samp}`Tutorial` -- {guilabel}`Text` {samp}`...` - -Create a site structure: - -- Add a folder "The Event" and in it add: - - - Folder "Talks" - - Folder "Training" - - Folder "Sprint" - - ```{figure} _static/features_the_event_folder_content.png - :alt: The view of the newly created site structure. - - The view of the newly created site structure. - ``` - -- In `/news`: Add a News Item "Conference Website online!" with some image - -- In `/news`: Add a News Item "Submit your talks!" - -- In `/events`: Add an Event "Deadline for talk submission" Date: 2019/08/10 - -- Add a Folder "Register" - -- Delete the Folder "Users" - -- Add a Folder "Intranet" - -```{figure} _static/features_new_navigation.png -:alt: The view of the extended navigation bar. - -The view of the extended navigation bar. -``` - -The default Plone content types are: - -- Collection -- Event -- File -- Folder -- Image -- Link -- News Item -- Page - -```{note} -Please keep in mind that we use [plone.app.contenttypes](https://5.docs.plone.org/external/plone.app.contenttypes/docs/README.html) for the training, which are the default in Plone 5. Therefore the types are based on Dexterity and slightly different from the types that you will find in a default Plone 4.3.x site. -``` - -(plone5-features-folders-label)= - -## Folders - -- Go to 'the-event' -- explain the difference between title, ID, and URL -- explain /folder_contents -- change the order of items -- explain bulk actions -- dropdown "display" -- default pages -- Add a page to 'the-event': "The Event" and make it the default page - -(plone5-features-collections-label)= - -## Collections - -- add a new collection: "all content that has `pending` as wf_state". - -```{figure} _static/features_pending_collection.png -:alt: Add a collection through the web. - -Add a collection through the web. -``` - -- explain the default collection for events at -- explain Topics -- mention collection portlets -- multi-path queries -- constraints, e.g. `/Plone/folder::1` - -(plone5-features-content-rules-label)= - -## Content Rules - -- Create new rule "a new talk is in town"! -- New content in folder "Talks" -> Send Mail to reviewers. - -```{figure} _static/features_add_rule_1.png -:alt: Add a rule through the web. - -Add a rule through the web. -``` - -```{figure} _static/features_add_rule_2.png -:alt: Add an action to the rule. - -Add an action to the rule. -``` - -```{figure} _static/features_add_rule_3.png -:alt: Add mail action. - -Add mail action. -``` - -```{figure} _static/features_add_rule_4.png -:alt: Assign the newly created rule. - -Assign the newly created rule. -``` - -(plone5-features-history-label)= - -## History - -Show and explain; mention versioning and its relation to types. - -(plone5-features-manage-members-label)= - -## Manage members and groups - -- add/edit/delete Users - -- roles - -- groups - - - Add group "Editors" and add the user 'editor' to it - - Add group: `orga` - - Add group: `jury` and add user 'jurymember' to it. - -(plone5-features-workflows-label)= - -## Workflows - -Take a look at the {guilabel}`state` drop down on the edit bar on the homepage. -Now, navigate to one of the folders just added. -The homepage has the status `published` and the new content is `private`. - -Let's look at the state transitions available for each type. -We can make a published item private and a private item published. -We can also submit an item for review. - -Each of these states connects roles to permissions. - -- In `published` state, the content is available to anonymous visitors; -- In `private` state, the content is only viewable by the author (owner) and users who have the `can view` role for the content. - -A *workflow state* is an association between a role and one or more permissions. -Moving from one state to another is a `transition`. -Transitions (like `submit for review`) may have actions — such as the execution of a content rule or script — associated with them. - -A complete set of workflow states and transitions makes up a *workflow*. -Plone allows you to select among several pre-configured workflows that are appropriate for different types of sites. -Individual content types may have their own workflow. -Or, and this is particularly interesting, they may have no workflow. -In that case, which initially applies to file and image uploads, the content object inherits the workflow state of its container. - -```{note} -An oddity in all of the standard Plone workflows: a content item may be viewable even if its container is not. -Making a container private does **not** automatically make its contents private. -``` - -Read more at: - -(plone5-features-wc-label)= - -## Working copy - -Published content, even in an intranet setting, can pose a special problem for editing. -It may need to be reviewed before changes are made available. -In fact, the original author may not even have permission to change the document without review. -Or, you may need to make a partial edit. -In either case, it may be undesirable for changes to be immediately visible. - -Plone's working copy support solves this problem by adding a check-out/check-in function for content — available on the actions menu. -A content item may be checked out, worked on, then checked back in. -Or it may be abandoned if the changes weren't acceptable. -Not until check in is the new content visible. - -While it's shipped with Plone, working copy support is not a common need. -So, if you need it, you need to activate it via the add-on packages configuration page. -Unless activated, check-in/check-out options are not visible. - -```{Note} -Working Copy Support has limited support for Dexterity content types. The limitation is that there are some outstanding issues with folderish items that contain many items. -See: [plone/Products.CMFPlone#665](https://github.com/plone/Products.CMFPlone/issues/665) -``` - -(plone5-features-placeful-wf-label)= - -## Placeful workflows - -You may need to have different workflows in different parts of a site. -For example, we created an intranet folder. -Since this is intended for use by our conference organizers — but not the public — the simple workflow we wish to use for the rest of the site will not be desirable. - -Plone's `Workflow Policy Support` package gives you the ability to set different workflows in different sections of a site. -Typically, you use it to set a special workflow in a folder that will govern everything under that folder. -Since it has effect in a "place" in a site, this mechanism is often called "Placeful Workflow". - -As with working-copy support, Placeful Workflow ships with Plone but needs to be activated via the add-on configuration page. -Once it's added, a {guilabel}`Policy` option will appear on the state menu to allow setting a placeful workflow policy. diff --git a/docs/mastering-plone-5/frontpage.md b/docs/mastering-plone-5/frontpage.md deleted file mode 100644 index ecf751ea5..000000000 --- a/docs/mastering-plone-5/frontpage.md +++ /dev/null @@ -1,311 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-frontpage-label)= - -# Creating a Dynamic Front Page - -````{sidebar} Get the code! - -Code for the beginning of this chapter: - -```shell -git checkout registry -``` - -Code for the end of this chapter: - -```shell -git checkout frontpage -``` - -{doc}`code` -```` - -In this chapter we will: - -- Create a standalone view used for the front page -- Show dynamic content -- Use ajax to load content -- Embed tweets about ploneconf - -The topics we cover are: - -- Standalone views -- Querying the catalog by date -- DRY ("Don't Repeat Yourself") -- macros -- patterns - -## The Front Page - -Register the view in `browser/configure.zcml`: - -```xml - -``` - -Add the view to a file `browser/frontpage.py`. We want a list of all talks that happen today. - -```{code-block} python -:linenos: - -# -*- coding: utf-8 -*- -from plone import api -from Products.Five.browser import BrowserView - -import datetime - - -class FrontpageView(BrowserView): - """The view of the conference frontpage - """ - - def talks(self): - """Get today's talks""" - results = [] - today = datetime.date.today() - brains = api.content.find( - portal_type='talk', - sort_on='start', - sort_order='descending', - ) - for brain in brains: - if brain.start.date() == today: - results.append({ - 'title': brain.Title, - 'description': brain.Description, - 'url': brain.getURL(), - 'audience': ', '.join(brain.audience or []), - 'type_of_talk': brain.type_of_talk, - 'speaker': brain.speaker, - 'room': brain.room, - 'start': brain.start, - }) - return results -``` - -- We do not constrain the search to a certain folder to also find the party and the sprints. - -- With `if brain.start.date() == today:` we test if the talk is today. - -- It would be more effective to query the catalog for events that happen in the daterange between today and tomorrow: - - ```{code-block} python - :emphasize-lines: 2, 3, 6 - :linenos: - - today = datetime.date.today() - tomorrow = today + datetime.timedelta(days=1) - date_range_query = {'query': (today, tomorrow), 'range': 'min:max'} - brains = api.content.find( - portal_type='talk', - start=date_range_query, - sort_on='start', - sort_order='ascending' - ) - ``` - -- The `sort_on='start'` sorts the results returned by the catalog by start-date. - -- By removing the `portal_type='talk'` from the query you could include other events in the schedule (like the party or sightseeing-tours). But you'd have to take care to not create AttributeErrors by accessing fields that are specific to talk. To work around that use `speaker = getattr(brain, 'speaker', None)` and testing with `if speaker is not None:` - -- The rest is identical to what the talklistview does. - -## The template - -Create the template `browser/templates/frontpageview.pt` (for now without talks). Display the rich text field to allow the frontpage to be edited. - -```{code-block} html -:linenos: - - - - - - -
- - - - - -``` - -Now you could add the whole code that we used for the talklistview again. But instead we go D.R.Y. and reuse the talklistview by turning it into a macro. - -Edit `browser/templates/talklistview.pt` and wrap the list in a macro definition: - -```{code-block} html -:emphasize-lines: 7, 55 -:linenos: - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TitleSpeakerAudienceTimeRoom
- - The 7 sins of plone-development - - - Philip Bauer - - Advanced - - Time - - 101 -
- No talks so far :-( -
-
-
- -
- - -``` - -Now use that macro in `browser/templates/frontpageview.pt` - -```{code-block} html -:linenos: - -
-

Todays Talks

-
- Instead of this the content of the macro will appear... -
-
-``` - -Calling that macro in Python looks like this `metal:use-macro="python: context.restrictedTraverse('talklistview')['talklist']"` - -```{note} -In {file}`talklistview.pt` the call {samp}`view/talks"` calls the method {py:meth}`talks` from the browser view {py:class}`TalkListView` to get the talks. Reused as a macro on the frontpage it now uses the method {py:meth}`talks` by the `frontpageView` to get a different list! -It is not always smart to do that since you might want to display other data. E.g. for a list of todays talks you don't want show the date but only the time using `data-pat-moment="format:LT"` -Also this frontpage will probably not win a beauty-contest. But that's not the task of this training. -``` - -### Exercise 1 - -Change the link to open the talk-info in a [modal](https://plone.github.io/mockup/dev/#pattern/modal). - -````{dropdown} Solution -:animate: fade-in-slide-down -:icon: question - -```{code-block} html -:emphasize-lines: 2 - - - The 7 sins of plone development - -``` -```` - -## Twitter - -You might also want to embed a twitter feed into the page. Luckily twitter makes it easy to do that. -When you browse to the [publish.twitter.com](https://publish.twitter.com/) and have them create a snippet for @ploneconf and paste it in the template wrapped in a `
...
` to have the talklist next to the feeds: - -```{code-block} html -:emphasize-lines: 19-22 - - - - - - -
- -
-

Todays Talks

-
- Instead of this the content of the macro will appear... -
-
- - - - - - - -``` - -## Activating the view - -The view is meant to be used with documents (or any other type that has a rich text field 'text'). The easiest way to use it is setting it as the default view for the Document that is currently the default page for the portal. By default that document has the id `front-page`. - -You can either access it directly at or by disabling the default page for the portal and it should show up in the navigation. Try out the new view like this: . - -To set that view by hand as the default view for `front-page` in the ZMI: . Add a new property `layout` and set it to `frontpageview`. - -Done. This way you can still use the button _Edit_ to edit the frontpage. - -```{seealso} -- Querying by date: -``` diff --git a/docs/mastering-plone-5/future_of_plone.md b/docs/mastering-plone-5/future_of_plone.md deleted file mode 100644 index e5fcf1b00..000000000 --- a/docs/mastering-plone-5/future_of_plone.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-future-label)= - -# The Future of Plone - -- The Plone process, the various teams and and the Plone Community -- Plips: -- Plone 5.x -- Plone 6 -- Plone 7 and beyond... -- Plone Roadmap: diff --git a/docs/mastering-plone-5/ide.md b/docs/mastering-plone-5/ide.md deleted file mode 100644 index d9d30daff..000000000 --- a/docs/mastering-plone-5/ide.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -% ide-label: - -# IDEs and Editors - -In this part you will: - -- Learn about editors - -Topics covered: - -- Many editors - -Plone consists of more than 20.000 files! You need a tool to manage that. No development environment is complete without a good editor. - -People pick editors themselves. Use whatever you are comfortable and productive with. Here are some of the most used editors in the Plone community: - -- [Visual Studio Code](https://code.visualstudio.com/) -- [Sublime](https://www.sublimetext.com/) -- [PyCharm](https://www.jetbrains.com/pycharm/) -- [Vim](https://www.vim.org/) -- [Emacs](https://www.gnu.org/software/emacs/) - -Some features that most editors have in one form or another are essential when developing with Plone. - -- **Find in project** (SublimeText 3: `cmd + shift + f`) -- **Find files in Project** (SublimeText 3: `cmd + t`) -- **Find methods and classes in Project** (SublimeText 3: `cmd + shift + r`) -- **Goto Definition** (SublimeText3 with codeintel: `alt + click`) -- **Powerful search & replace** - -The capability of performing a *full text search* through the complete Plone code is invaluable. Thanks to omelette, an SSD and plenty of RAM you can search through the complete Plone code base in 3 seconds. - -```{note} -Some editors and IDEs have to be extended to be fully featured. Here are some packages we recommend when using Sublime Text 3: - -- BracketHighlighter -- GitGutter -- FileDiffs -- SublimeLinter with SublimeLinter-flake8 ... -- INI (syntax for ini-Files) -- SideBarEnhancements -- SyncedSideBar -``` - -```{note} -This list of extensions gets out of date quickly, especially for VS Code. -``` diff --git a/docs/mastering-plone-5/index.md b/docs/mastering-plone-5/index.md deleted file mode 100644 index 13aa20a21..000000000 --- a/docs/mastering-plone-5/index.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -myst: - html_meta: - "description": "Documentation for the 'Mastering Plone 5' training" - "property=og:description": "Documentation for the 'Mastering Plone 5' training" - "property=og:title": "Mastering Plone 5 development" - "keywords": "Plone" ---- - -(mastering-plone5-label)= - -# Mastering Plone 5 development - -This is the documentation for the "Mastering Plone 5" training. - -Mastering Plone is intended as a week-long training for people who are new to Plone or want to learn about the current best practices of Plone development. It can be split in two trainings: - -- A beginner training (2 to 3 days) that covers chapters 1-18. -- An advanced training (3 to 5 days) that covers the rest. - -At conferences a shortened 2-day version of the advanced training with a slightly modified order is held. - -```{toctree} -:caption: Mastering Plone 5 Development -:name: toc-mastering5 -:maxdepth: 3 -:numbered: 2 - -about_mastering -intro -what_is_plone -installation -instructions_plone5/index -case -features -anatomy -plone5 -configuring_customizing -theming -extending -add-ons -dexterity -buildout_1 -eggs1 -export_code -views_1 -zpt -zpt_2 -views_2 -views_3 -testing -behaviors_1 -viewlets_1 -api -ide -dexterity_2 -custom_search -events -user_generated_content -resources -thirdparty_behaviors -dexterity_3 -dexterity_reference -relations -registry -frontpage -eggs2 -behaviors_2 -viewlets_2 -reusable -embed -deployment_code -deployment_sites -restapi -future_of_plone -optional -code -``` - -Please note that this document is *not complete* without the spoken word of a trainer. - -Even though we attempt to include the most important parts of what we teach in the narrative but -reading it here can in no way be considered equal to attending a training. - diff --git a/docs/mastering-plone-5/installation.md b/docs/mastering-plone-5/installation.md deleted file mode 100644 index 5d46e4d85..000000000 --- a/docs/mastering-plone-5/installation.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-installation-label)= - -# Installation & Setup - -(plone5-installation-plone-label)= - -## Installing Plone - -The following table shows the Python versions required by Plone from version 3.x to 5.2.x: - -| Plone | Python | -| ----- | -------------- | -| 3.x | 2.4 | -| 4.0.x | 2.6 | -| 4.1.x | 2.6 | -| 4.2.x | 2.6 or 2.7 | -| 4.3.x | 2.7 | -| 5.0.x | 2.7 | -| 5.1.x | 2.7.9 | -| 5.2.x | 2.7.14 or 3.6+ | - -(Hopefully you won't have to deal with any Plone sites older than version 4.3.x) - -Plone 5.x requires a working Python 2.7 (or Python 3.6+ for Plone 5.2.x) and other system tools that not every OS provides. -The installation of Plone is different on every system. -Here are some ways that Python can be installed: - -- use a Python that comes pre-installed in your operating system (most Linux Distributions and macOS have one) -- use the [python buildout](https://github.com/collective/buildout.python) -- building Linux packages -- [Homebrew](https://brew.sh) (macOS) -- PyWin32 (Windows) - -Most developers use their primary system to develop Plone. -For complex setups they often use Linux virtual machines. - -- macOS: Use the system python and [Homebrew](https://brew.sh) for some missing Linux tools. -- Linux: Depending on your Linux flavor you might have to install Python 3.7 yourself and install some tools. -- Windows: Alan Runyan (one of Plone's founders) uses it. A downside: Plone seems to be running slower on Windows. - -Plone offers multiple options for being installed: - -1. Unified installers (all 'nix, including macOS) -2. A Vagrant/VirtualBox install kit (all platforms) -3. A VirtualBox Appliance -4. A [Windows installer](https://github.com/plone/WinPloneInstaller) -5. Use your own Buildout - -Visit the [download page](https://plone.org/download) to see all the options. - -```{only} not presentation -For the training you will use option 2 or 5 to install and run Plone. -We will create our own Buildout and extend it as we wish. -If you choose to do so you will run it in a Vagrant machine. - -For your own first experiments we recommend option 1 or 2 (if you have a Windows laptop or encounter problems). -Later on you should be able to use your own Buildout (we will cover that later on). -``` - -```{only} presentation -For the training we will use option 2 or 5 to install and run Plone. -``` - -```{seealso} -- -``` - -(plone5-installation-hosting-label)= - -## Hosting Plone - -```{only} not presentation -If you want to host a real live Plone site yourself then running it from your laptop is not a viable option. -``` - -You can host Plone... - -- with one of many professional [hosting providers](https://plone.org/providers) -- on a virtual private server -- on dedicated servers - -See all the ways you can [set up Plone](https://plone.org/download) -```{seealso} -- Plone Installation Requirements: -``` - -(plone5-installation-prod-deploy-label)= - -## Production Deployment - -The way we are setting up a Plone site during this class may be adequate for a small site -— or even a large one that's not very busy — but you are likely to want to do much more if you are using Plone for anything demanding. - -- Using a production web server like Apache or Nginx for URL rewriting, SSL and combining multiple, best-of-breed solutions into a single web site. -- Reverse proxy caching with a tool like Varnish to improve site performance. -- Load balancing to make best use of multiple core CPUs and even multiple servers. -- Optimizing cache headers and Plone's internal caching schemes with plone.app.caching. - -And, you will need to learn strategies for efficient backup and log file rotation. - -All these topics are introduced in [Guide to deploying and installing Plone in production](https://5.docs.plone.org/manage/deploying/index.html). diff --git a/docs/mastering-plone-5/instructions_plone5/_static/instructions_plone_running.png b/docs/mastering-plone-5/instructions_plone5/_static/instructions_plone_running.png deleted file mode 100644 index eb10b204f..000000000 Binary files a/docs/mastering-plone-5/instructions_plone5/_static/instructions_plone_running.png and /dev/null differ diff --git a/docs/mastering-plone-5/instructions_plone5/index.md b/docs/mastering-plone-5/instructions_plone5/index.md deleted file mode 100644 index fc3bf22c9..000000000 --- a/docs/mastering-plone-5/instructions_plone5/index.md +++ /dev/null @@ -1,382 +0,0 @@ ---- -myst: - html_meta: - "description": "Set up environment for development of Plone 5" - "property=og:description": "Set up environment for development of Plone 5" - "property=og:title": "Installing Plone 5 for the Training" - "keywords": "Plone 5" ---- - -(plone5-instructions-label)= - -# Installing Plone for the Training - -Keep in mind that you need a fast Internet connection during installation since you'll have to download a lot of data! - -```{warning} -If you feel the desire to try out both methods below (with Vagrant and without), -make sure you use different {file}`training` directories! - -The two installations do not coexist well. -``` - -(plone5-instructions-no-vagrant-label)= - -## Installing Plone without vagrant - -```{warning} -If you are new to running Plone on your laptop you could skip this part and continue with {ref}`plone5-install-virtualbox`. -``` - -If you **are** experienced with running Plone on your own laptop, we encourage you to do so because you will have certain benefits: - -- You can use the editor you are used to. -- You can use *omelette* to have all the code of Plone at your fingertips. -- You don't have to switch between different operating systems during the training. - -If you feel comfortable, please work on your own machine with your own Python. - -**Please** make sure that you have a system that will work, since we don't want you to lose valuable time! - -```{note} -If you also want to follow the JavaScript training and install the JavaScript development tools, -you need [Node.js](https://nodejs.org/en/download/package-manager) installed on your development computer. -``` - -```{note} -Please make sure you have your system properly prepared and installed all necessary prerequisites. -``` - -The following instructions are based on Ubuntu and macOS. -If you use a different operating system (OS), please adjust them to fit your OS. - -On Ubuntu/Debian, you need to make sure you system is up-to-date: - -```shell -sudo apt-get update -sudo apt-get -y upgrade -``` - -Then, you need to install the following packages: - -```shell -sudo apt-get install python3.9-dev python3.9-tk python3.9-venv build-essential libssl-dev libxml2-dev libxslt1-dev libbz2-dev libjpeg62-dev -sudo apt-get install libreadline-dev wv poppler-utils -sudo apt-get install git -``` - -On macOS you at least need to install some dependencies with [Homebrew](https://brew.sh/). - -```shell -brew install zlib git readline jpeg libpng libyaml -``` - -For more information or in case of problems see the [official installation instructions](https://5.docs.plone.org/manage/installing/installation.html). - -Set up Plone for the training like this if you use your own OS (Linux or Mac): - -```shell -mkdir training -cd training -git clone https://github.com/collective/training_buildout.git buildout -cd buildout -python3.9 -m venv . -./bin/pip install -r requirements.txt -``` - -This creates a Python virtual environment with Python 3.9 in the folder {file}`buildout` and installs some requirements in it. - -Now you can run the buildout for the first time: - -```shell -./bin/buildout -``` - -This will take **a long time** (~10 minutes on the least powerful Linode) and will produce a lot of output because it downloads and configures more than 260 Python packages. -Once it is done, you can start your Plone instance with the following command. - -```shell -./bin/instance fg -``` - -The output should be similar to: - -```{code-block} console -:emphasize-lines: 40 - -pbauer@bullet:/workspace/training_buildout$ ./bin/instance fg -2019-09-05 20:11:03,708 WARNING [Init:89][MainThread] Class Products.CMFFormController.ControllerPythonScript.ControllerPythonScript has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs' -2019-09-05 20:11:03,715 WARNING [Init:89][MainThread] Class Products.CMFFormController.ControllerValidator.ControllerValidator has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs' -2019-09-05 20:11:03,776 WARNING [Products.PDBDebugMode:31][MainThread] - -****************************************************************************** - -Debug-Mode enabled! - -This will result in a pdb when a exception happens. -Turn off debug mode or remove Products.PDBDebugMode to disable. - -See https://pypi.python.org/pypi/Products.PDBDebugMode - -****************************************************************************** - -2019-09-05 20:11:04,858 INFO [chameleon.config:38][MainThread] directory cache: /Users/pbauer/workspace/training_buildout/var/cache. -2019-09-05 20:11:07,151 WARNING [plone.behavior:172][MainThread] Specifying 'for' in behavior 'Tiles' if no 'factory' is given has no effect and is superfluous. -2019-09-05 20:11:08,353 WARNING [PrintingMailHost:30][MainThread] Hold on to your hats folks, I'm a-patchin' -2019-09-05 20:11:08,353 WARNING [PrintingMailHost:124][MainThread] - -****************************************************************************** - -Monkey patching MailHosts to print e-mails to the terminal. - -This is instead of sending them. - -NO MAIL WILL BE SENT FROM ZOPE AT ALL! - -Turn off debug mode or remove Products.PrintingMailHost from the eggs -or remove ENABLE_PRINTING_MAILHOST from the environment variables to -return to normal e-mail sending. - -See https://pypi.python.org/pypi/Products.PrintingMailHost - -****************************************************************************** - -2019-09-05 20:11:08,390 INFO [Zope:45][MainThread] Ready to handle requests -Starting server in PID 30620. -Serving on http://0.0.0.0:8080 -``` - -If the output says `Serving on http://0.0.0.0:8080` then you are in business. - -If you point your browser at you see that Plone is running. - -```{figure} _static/instructions_plone_running.png -:alt: A running Plone instance. -:scale: 50 % - -A running plone instance. -``` - -There is no Plone site yet - we will create one in {doc}`/mastering-plone-5/features`. - -Now you have a working Plone site up and running and can continue with the next chapter. - -You can stop the running instance anytime using {kbd}`ctrl + c`. - -```{warning} -If there is an error message you should either try to fix it or use Vagrant and continue in this chapter. -``` - -(plone5-instructions-vagrant-label)= - -## Installing Plone with Vagrant - -We use a virtual machine (Ubuntu 18.04) to run Plone during the training. - -We rely on [Vagrant](https://www.vagrantup.com) and [VirtualBox](https://www.virtualbox.org) to give the same development environment to everyone. - -[Vagrant](https://www.vagrantup.com) is a tool for building complete development environments. - -We use it together with Oracle’s [VirtualBox](https://www.virtualbox.org) to create and manage a virtual environment. - -(plone5-install-virtualbox)= - -### Install VirtualBox - -Vagrant uses Oracle’s VirtualBox to create virtual environments. - -Here is a link directly to the download page: . - -We use VirtualBox 6.0.x. - -(plone5-instructions-configure-vagrant-label)= - -### Install and configure Vagrant - -Get the latest version from for your operating system and install it. - -Now your system has a command {command}`vagrant` that you can run in the terminal. - -First, create a directory in which you want to do the training. - -```{warning} -If you already have a {file}`training` directory because you followed the {ref}`plone5-instructions-no-vagrant-label` instructions above, -you should either delete it, rename it, or use a different name below. -``` - -```shell -mkdir training -cd training -``` - -Setup Vagrant to automatically install the current guest additions. -You can choose to skip this step if you encounter any problems with it. - -```shell -vagrant plugin install vagrant-vbguest -``` - -Now download {download}`plone_training_config.zip <../../_static/plone_training_config.zip>` and copy its contents into your training directory. - -```shell -wget https://github.com/plone/training/raw/refs/heads/main/docs/_static/plone_training_config.zip -unzip plone_training_config.zip -``` - -The training directory should now hold the file {file}`Vagrantfile` and the directory {file}`manifests` which again contains several files. - -Now start setting up the virtual machine (VM) that is configured in {file}`Vagrantfile`: - -```shell -vagrant up -``` - -This takes a **veeeeery loooong time** (between 10 minutes and 1 hour depending on your Internet connection and system speed) since it does all the following steps: - -- downloads a virtual machine (Official Ubuntu Server 18.04 LTS, also called "Bionic Beaver") -- sets up the VM -- updates the VM -- installs various system-packages needed for Plone development -- clones the training buildout into `/vagrant/buildout` -- builds Plone annd installs all dependencies - -````{note} -Sometimes this stops with the message: - -```shell -Skipping because of failed dependencies -``` -```` - -If this happens or you have the feeling that something has gone wrong and the installation has not finished correctly for some reason -you need to run the following command to repeat the process. - -This will only repeat steps that have not finished correctly. - -```shell -vagrant provision -``` - -You can do this multiple times to fix problems, for example, if your network connection was down and steps could not finish because of this. - -````{note} -If while bringing vagrant up you get an error similar to: - -```shell -ssh_exchange_identification: read: Connection reset by peer -``` -```` - -The configuration may have stalled out because your computer's BIOS requires virtualization to be enabled. -Check with your computer's manufacturer on how to properly enable virtualization. - -See: - -Once Vagrant finishes the provisioning process, you can login to the now running virtual machine. - -```shell -vagrant ssh -``` - -```{note} -If you use Windows you'll have to login with [putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html). -Connect to at port 2222. User **and** password are `vagrant`. -``` - -You are now logged in as the user vagrant in {file}`/home/vagrant`. - -We'll do all steps of the training as this user. - -Instead we use our own Plone instance during the training. -It is in {file}`/vagrant/buildout/`. Start it in foreground with {command}`./bin/instance fg`. - -```console -vagrant@training:~$ cd /vagrant/buildout/ -vagrant@training:/vagrant/buildout$ ./bin/instance fg -2019-03-07 10:38:17,666 WARNI [Init:88][MainThread] Class Products.CMFFormController.ControllerPythonScript.ControllerPythonScript has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs' -2019-03-07 10:38:17,670 WARNI [Init:88][MainThread] Class Products.CMFFormController.ControllerValidator.ControllerValidator has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs' -2019-03-07 10:38:21,160 WARNI [plone.behavior:172][MainThread] Specifying 'for' in behavior 'Tiles' if no 'factory' is given has no effect and is superfluous. -2019-03-07 10:38:22,473 WARNI [PrintingMailHost:30][MainThread] Hold on to your hats folks, I'm a-patchin' -2019-03-07 10:38:22,474 WARNI [PrintingMailHost:124][MainThread] - -****************************************************************************** - -Monkey patching MailHosts to print e-mails to the terminal. - -This is instead of sending them. - -NO MAIL WILL BE SENT FROM ZOPE AT ALL! - -Turn off debug mode or remove Products.PrintingMailHost from the eggs -or remove ENABLE_PRINTING_MAILHOST from the environment variables to -return to normal e-mail sending. - -See https://pypi.python.org/pypi/Products.PrintingMailHost - -****************************************************************************** - -2019-03-07 10:38:22,510 INFO [Zope:44][MainThread] Ready to handle requests -Starting server in PID 25230. -Serving on http://0.0.0.0:8080 -``` - -````{note} -In rare cases when you are using macOS with an UTF-8 character set starting Plone might fail with the following error: - -```text -ValueError: unknown locale: UTF-8 -``` -```` - -In that case you have to put the localized keyboard and language settings in the {file}`.bash_profile` -of the Vagrant user to your locale (like `en_US.UTF-8` or `de_DE.UTF-8`). - -```shell -export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 -``` - -Now the Zope instance we're using is running. -You can stop the running instance anytime using {kbd}`ctrl + c`. - -If it doesn't, don't worry, your shell isn't blocked. - -Type {kbd}`reset` (even if you can't see the prompt) and press {kbd}`return`, and it should become visible again. - -If you point your local browser at you see that Plone is running in Vagrant. - -This works because VirtualBox forwards the port 8080 from the guest system (the Vagrant Ubuntu) to the host system (your normal operating system). - -There is no Plone site yet - we will create one in {doc}`/mastering-plone-5/features`. - -The Buildout for this Plone is in a shared folder. -This means we run it in the Vagrant box from {file}`/vagrant/buildout` but we can also access it in our own operating system and use our favorite editor. - -You will find the directory {file}`buildout` in the directory {file}`training` that you created in the beginning -next to {file}`Vagrantfile` and {file}`manifests`. - -```{note} -The database and the Python packages are not accessible in your own system since large files cannot make use of symlinks in shared folders. -The database lies in `/home/vagrant/var`, the Python packages are in `/home/vagrant/packages`. -``` - -If you have any problems or questions please mail us at or create a ticket at . - -(plone5-instructions-vagrant-does-label)= - -### What Vagrant does - -Installation is done automatically by Vagrant and Puppet. -If you want to know which steps are actually done please see the chapter {doc}`what_vagrant_does`. - -(plone5-instructions-vagrant-care-handling-label)= - -```{note} -**Vagrant Care and Handling** - -Keep in mind the following recommendations for using your Vagrant VirtualBoxes: - -- Use the {command}`vagrant suspend` or {command}`vagrant halt` commands to put the VirtualBox to "sleep" or to "power it off" before attempting to start another Plone instance anywhere else on your machine, if it uses the same port. That's because vagrant "reserves" port 8080, and even if you stopped Plone in vagrant, that port is still in use by the guest OS. -- If you are done with a vagrant box, and want to delete it, always remember to run {command}`vagrant destroy` on it before actually deleting the directory containing it. Otherwise you'll leave its "ghost" in the list of boxes managed by Vagrant and possibly taking up disk space on your machine. -- See {command}`vagrant help` for all available commands, including {command}`suspend`, {command}`halt`, {command}`destroy`, {command}`up`, {command}`ssh` and {command}`resume`. -``` diff --git a/docs/mastering-plone-5/instructions_plone5/plone_training_config/Vagrantfile b/docs/mastering-plone-5/instructions_plone5/plone_training_config/Vagrantfile deleted file mode 100644 index 751983756..000000000 --- a/docs/mastering-plone-5/instructions_plone5/plone_training_config/Vagrantfile +++ /dev/null @@ -1,55 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "ubuntu/bionic64" - - config.vm.hostname = "training.plone.org" - config.vm.network :forwarded_port, guest: 8080, host: 8080 - - # Allow the creation of symbolic links in the shared folder. - # This is needed for some builds with cmmi and for omelette to work. - # 'v-root' is the default-name for the primary volume. - config.vm.provider :virtualbox do |vb| - vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"] - vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] - vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"] - vb.memory = "1536" - end - - # A workaround for missing symbolic links on windows: - # config.vm.synced_folder "eggs/", "/home/vagrant/buildout-cache/eggs/" - - # Read initial package index information + upgrade - config.vm.provision :shell, inline: <<-SHELL - apt-get update - apt-get -y upgrade - SHELL - - # We need to install puppet on the guest before we can use it - config.vm.provision :shell, inline: <<-SHELL - apt-get install -y puppet - SHELL - - # ensure we have the packages we need - config.vm.provision :puppet do |puppet| - puppet.manifests_path = "manifests" - puppet.manifest_file = "packages.pp" - end - - # Create a Putty-style keyfile for Windows users - config.vm.provision :shell do |shell| - shell.path = "manifests/host_setup.sh" - shell.args = RUBY_PLATFORM - end - - # install plone - config.vm.provision :puppet do |puppet| - puppet.manifests_path = "manifests" - puppet.manifest_file = "plone.pp" - end - - -end diff --git a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/host_setup.sh b/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/host_setup.sh deleted file mode 100755 index a43e84370..000000000 --- a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/host_setup.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -AS_VAGRANT="sudo -u vagrant" -SHARED_DIR="/vagrant" - -# RUBY_PLATFORM=$1 -echo $1 | if grep -q mingw; then - # Host environment is mingw, aka Windows. - # Let's set up support for using Putty - - echo Host environment appears to be Windows. - echo Setting up support for using Putty for ssh. - - if [ ! -f .ssh/id_rsa ]; then - echo Creating an ssh key, authorizing it, and putting a - echo Putty-compatible key file in shared directory. - cd .ssh - # generate openssh-style key pair - /usr/bin/ssh-keygen -q -t rsa -N "" -f id_rsa - # append public key to authorized_keys - cat id_rsa.pub >> authorized_keys - # create a putty-compatible key file - /usr/bin/puttygen id_rsa -O private -o id_rsa.ppk - # copy putty keyfile into shared directory - cp id_rsa.ppk ${SHARED_DIR}/insecure_putty_key.ppk - chmod 400 ${SHARED_DIR}/insecure_putty_key.ppk - cd ~ - fi - - for script in ${SHARED_DIR}/manifests/windows_host_scripts/* - do - target=`basename $script` - if [ ! -f ${SHARED_DIR}/$target ]; then - echo Copying `basename $script` ... - $AS_VAGRANT cp $script ${SHARED_DIR} - fi - done -else - # Host environment is probably something *nix - for script in ${SHARED_DIR}/manifests/host_scripts/* - do - target=`basename $script` - if [ ! -f ${SHARED_DIR}/$target ]; then - echo Copying `basename $script` ... - $AS_VAGRANT cp $script ${SHARED_DIR} - chmod 755 ${SHARED_DIR}/*.sh - fi - done -fi - diff --git a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/packages.pp b/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/packages.pp deleted file mode 100644 index 1667f9c88..000000000 --- a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/packages.pp +++ /dev/null @@ -1,35 +0,0 @@ -class packages { - - package { "build-essential": ensure => present, } - package { "curl": ensure => present, } - package { "elinks": ensure => present, } - package { "gettext": ensure => present, } - package { "git": ensure => present, } - package { "libedit-dev": ensure => present, } - package { "libjpeg-dev": ensure => present, } - package { "libpcre3-dev": ensure => present, } - package { "libssl-dev": ensure => present, } - package { "libxml2-dev": ensure => present, } - package { "libxslt-dev": ensure => present, } - package { "libyaml-dev": ensure => present, } - package { "libz-dev": ensure => present, } - package { "nodejs": ensure => present, } - package { "npm": ensure => present, } - package { "python3.7-dev": ensure => present, } - package { "python3.7-tk": ensure => present, } - package { "python3.7-venv": ensure => present, } - package { "subversion": ensure => present, } - package { "unzip": ensure => present, } - package { "vim": ensure => present, } - package { "wget": ensure => present, } - - # Optional packages to enable indexing of office/pdf docs - # package { "wv": ensure => present, } - # package { "poppler-utils": ensure => present, } - - # used for creating a PuTTy-compatible key file - package { "putty-tools": ensure => present, } - -} - -include packages diff --git a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/plone.pp b/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/plone.pp deleted file mode 100644 index 6b7e71ed6..000000000 --- a/docs/mastering-plone-5/instructions_plone5/plone_training_config/manifests/plone.pp +++ /dev/null @@ -1,103 +0,0 @@ -class plone { - - $plone_version = "5.2" - $buildout_branch = "plone52" - - file { ['/home/vagrant/tmp', - '/home/vagrant/.buildout', - '/home/vagrant/buildout-cache', - '/home/vagrant/buildout-cache/eggs', - '/home/vagrant/buildout-cache/downloads', - '/home/vagrant/buildout-cache/extends', - ]: - ensure => directory, - owner => 'vagrant', - group => 'vagrant', - mode => '0755', - } - - file { '/home/vagrant/.buildout/default.cfg': - ensure => present, - content => inline_template('[buildout] -eggs-directory = /home/vagrant/buildout-cache/eggs -download-cache = /home/vagrant/buildout-cache/downloads -extends-cache = /home/vagrant/buildout-cache/extends'), - owner => 'vagrant', - group => 'vagrant', - mode => '0664', - } - - Exec { - path => [ - '/usr/local/bin', - '/opt/local/bin', - '/usr/bin', - '/usr/sbin', - '/bin', - '/sbin'], - logoutput => true, - } - - # Create virtualenv - exec {'python3.7 -m venv py37': - alias => "virtualenv", - creates => '/home/vagrant/py37', - user => 'vagrant', - cwd => '/home/vagrant', - before => Exec["install_buildout_setuptools"], - timeout => 300, - } - - # Install zc.buildout, setuptools - exec {"/home/vagrant/py37/bin/pip install -r http://dist.plone.org/release/${plone_version}/requirements.txt": - alias => "install_buildout_setuptools", - creates => '/home/vagrant/py37/bin/buildout', - user => 'vagrant', - cwd => '/home/vagrant', - before => Exec["checkout_training"], - timeout => 0, - } - - # Unpack the buildout-cache to /home/vagrant/buildout-cache/ - exec {"tar xjf /home/vagrant/buildout-cache.tar.bz2": - alias => "unpack_buildout_cache", - creates => "/home/vagrant/buildout-cache/eggs/Products.CMFPlone-${plone_version}-py3.7.egg/", - user => 'vagrant', - cwd => '/home/vagrant', - before => Exec["checkout_training"], - timeout => 0, - onlyif => "test -f /home/vagrant/buildout-cache.tar.bz2" # managed to dowload the tarball - } - - # get training buildout - exec {'git clone https://github.com/collective/training_buildout.git buildout': - alias => "checkout_training", - creates => '/vagrant/buildout', - user => 'vagrant', - cwd => '/vagrant', - before => Exec["branch_training"], - timeout => 0, - } - - # select branch of training buildout - exec {'git checkout ${buildout_branch}': - alias => "branch_training", - user => 'vagrant', - cwd => '/vagrant/buildout', - before => Exec["buildout_training"], - timeout => 0, - } - - # run training buildout - exec {'/home/vagrant/py37/bin/buildout -c vagrant_provisioning.cfg': - alias => "buildout_training", - creates => '/vagrant/buildout/bin/instance', - user => 'vagrant', - cwd => '/vagrant/buildout', - # before => Exec["buildout_final"], - timeout => 0, - } - -} - -include plone diff --git a/docs/mastering-plone-5/instructions_plone5/what_vagrant_does.md b/docs/mastering-plone-5/instructions_plone5/what_vagrant_does.md deleted file mode 100644 index 28dd18f6e..000000000 --- a/docs/mastering-plone-5/instructions_plone5/what_vagrant_does.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" -orphan: ---- - -# What Vagrant is and does - -```{note} -These steps are automatically done by Vagrant and Puppet for you. They are only interesting if you want to know what goes on under the hood for preparing your virtual training environment. -``` - -Vagrant is an automation tool for developers to script the configuration and starting/stopping of virtual machines using applications like VirtualBox or Vmware Fusion/Workstation. The beauty of Vagrant is that it is largely platform independent for Linux, Windows and Apple, so with one 'Vagrantfile' per project you describe a base installation virtual image and all kinds of virtual machine settings you would otherwise have to click and type together in Virtual machine application. - -What Vagrant for example does is install a port forward so that `http://localhost:8080` on your physical computer is automatically forwarded to the port Plone will be listening on in the guest virtual machine. After Vagrant has done its thing to set up your virtual machine we are not finished though. Although Vagrant has the option to prebuild specific images it would be a lot of work and waste of bandwidth to redownload a machine images (300-600Mb) each time we would like to change small things in our virtual training environment. - -Puppet is a configuration management tool (others you might have heard of are Chef, Ansible and SaltStack) and helps system admnistrators to automatically manage servers (real and virtual). We won't get into Puppet in detail, but it builds on top of our base Vagrant image to further set up our environment. - -Vagrant detects when you set up a new machine and runs Puppet or other Provisioners by default only once, although it also can be used to keep machines up to date, which is a bit harder. See the {file}`Vagrantfile` and [Vagrant Documentation](https://developer.hashicorp.com/vagrant/docs), especially the *Provisioning* chapter. - -This is basically what Puppet does if we were to configure our system by hand: - -First we update Ubuntu and install some packages. - -```shell -$ sudo aptitude update --quiet --assume-yes -$ sudo aptitude upgrade --quiet --assume-yes -$ sudo apt-get install build-essential -$ sudo apt-get install curl -$ sudo apt-get install elinks -$ sudo apt-get install gettext -$ sudo apt-get install git -$ sudo apt-get install libedit-dev -$ sudo apt-get install libjpeg-dev -$ sudo apt-get install libpcre3-dev -$ sudo apt-get install libssl-dev -$ sudo apt-get install libxml2-dev -$ sudo apt-get install libxslt-dev -$ sudo apt-get install libyaml-dev -$ sudo apt-get install libz-dev -$ sudo apt-get install nodejs -$ sudo apt-get install npm -$ sudo apt-get install python3.7-dev -$ sudo apt-get install python3.7-tk -$ sudo apt-get install python3.7-venv -$ sudo apt-get install subversion -$ sudo apt-get install unzip -$ sudo apt-get install vim -$ sudo apt-get install wget -$ sudo apt-get install wv -$ sudo apt-get install poppler-utils -$ sudo apt-get install putty-tools -``` - -Then we create a virtual python environment using virtualenv. This is always a good practice since that way we get a clean isolated copy of our system python, so that we do not break the system python by installing eggs that might collide with other eggs. Python is nowadays used a lot by your operating system as well for all kinds of system tools and scripting. - -```shell -$ python3.7 -m venv /home/vagrant/py37 -``` - -Install zc.buildout, setuptools and other dependencies for the current version into the new virtualenv. - -```shell -$ /home/vagrant/py37/bin/pip install -r http://dist.plone.org/release/5.2/requirements.txt -``` - -Now we download and unpack a buildout-cache that holds all the python packages that make up Plone. This is an optimisation: We could skip this step and have buildout download all packages individually from the [python packaging index PyPi](https://pypi.org) but that takes much longer on a first install. - -```shell -$ wget http://dist.plone.org/release/5.2/buildout-cache.tar.bz2 -$ tar xjf buildout-cache.tar.bz2 -``` - -Then we check out our tutorial buildout from and build it. - -```shell -$ cd /vagrant -$ git clone https://github.com/collective/training_buildout.git buildout -$ cd buildout -``` - -Then we run buildout: - -```shell -$ /home/vagrant/py37/bin/buildout -c vagrant_provisioning.cfg -``` - -This will download many additional eggs that are not yet part of the buildout-cache and configure Plone to be ready to run. - -At this point Vagrant and Puppet have finished their job to set up your virtual training environment on your local machine. - -You can now connect to the machine and start Plone. - -```shell -$ vagrant ssh -$ cd /vagrant/buildout -$ ./bin/instance fg -``` - -Now we have a fresh Buildout-based Zope application server, ready to add a Plone site. Go to and create a Plone site. diff --git a/docs/mastering-plone-5/intro.md b/docs/mastering-plone-5/intro.md deleted file mode 100644 index 8710e663b..000000000 --- a/docs/mastering-plone-5/intro.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-intro-label)= - -# Introduction - -(plone5-intro-who-are-you-label)= - -## Who are you? - -Tell us about yourselves: - -- Name, company, country... -- What is your Plone experience? -- What is your web development experience? -- What are your expectations for this tutorial? -- What is your favorite text editor? -- If this training will include the development chapters: - - Do you know the HTML of the output of this? - - ```html -
- This is some weird stuff! -
- ``` - - ````{only} not presentation - The answer is: - - ``` - 1 6 - ``` - ```` - - - Do you know what the following would return?: - - ``` - [(i.Title, i.getURL()) for i in context.getFolderContents()] - ``` - -(plone5-intro-what-happens-label)= - -## What will we do? - -Some technologies and tools we use during the training: - -- For the beginning training: - - > - [Virtualbox](https://www.virtualbox.org/) - > - [Vagrant](https://www.vagrantup.com/) - > - [Ubuntu linux](https://ubuntu.com/) - > - Through-the-web (TTW) - > - [Buildout](http://www.buildout.org/en/latest/) - > - A little XML - > - Python 3.7 - -- For the advanced chapters: - - > - [Git](https://git-scm.com/) - > - [GitHub](https://github.com) - > - [Try Git (Nice introduction to git and github)](https://docs.github.com/en/get-started/quickstart/set-up-git) - > - TAL - > - METAL - > - ZCML - > - [Python](https://www.python.org) - > - Dexterity - > - Viewlets - > - [JQuery](https://jquery.com/) - > - [Testing](https://5.docs.plone.org/external/plone.testing/docs/index.html) - > - [References/Relations](https://5.docs.plone.org/external/plone.app.dexterity/docs/advanced/references.html) - -(plone5-intro-what-wont-happen-label)= - -## What will we not do? - -We will not cover the following topics: - -- [Archetypes](https://4.docs.plone.org/develop/plone/content/archetypes/index.html) -- [Portlets](https://5.docs.plone.org/develop/plone/functionality/portlets.html) -- [z3c.forms](https://5.docs.plone.org/develop/plone/forms/z3c.form.html) -- [Theming](https://5.docs.plone.org/adapt-and-extend/theming/index.html) -- [i18n and locales](https://5.docs.plone.org/develop/plone/i18n/index.html) -- [Deployment, Hosting and Caching](https://5.docs.plone.org/manage/deploying/index.html) -- Grok - -Other topics are only covered lightly: - -- [Zope Component Architecture](https://5.docs.plone.org/develop/addons/components/index.html) -- [GenericSetup](https://5.docs.plone.org/develop/addons/components/genericsetup.html) -- [ZODB](https://5.docs.plone.org/develop/plone/persistency/index.html) -- [Security](https://5.docs.plone.org/develop/plone/security/index.html) -- [Permissions](https://5.docs.plone.org/develop/plone/security/permissions.html) -- [Performance and Tuning](https://5.docs.plone.org/manage/deploying/performance/index.html) - -(plone5-intro-expect-label)= - -## What to expect - -At the end of the first two days of training, you'll know many of the tools required for Plone installation, -integration and configuration. - -You'll be able to install add-on packages and will know something about the technologies underlying Plone and their histories. - -At the end of the second two days, you won't be a complete professional Plone-programmer, -but you will know some of the more powerful features of Plone and should be able to construct a more complex website with custom themes and packages. - -You should also be able to find out where to look for instructions to do tasks we did not cover. -You will know most of the core technologies involved in Plone programming. - -If you want to become a professional Plone developer or a highly sophisticated Plone integrator you should -definitely read [Martin Aspeli's book](https://www.packtpub.com/product/professional-plone-4-development/9781849514422) -and then re-read it again while actually doing a complex project. - -(plone5-intro-classroom-protocol)= - -## Classroom Protocol - -````{only} not presentation -```{note} -- Stop us and ask questions when you have them! -- Tell us if we speak too fast, too slow or not loud enough. -- One of us is always there to help you if you are stuck. Please give us a sign if you are stuck. -- We'll take some breaks, the first one will be at XX. -- Where is food, restrooms -- Someone please record the time we take for each chapter (incl. title) -- Someone please write down errors -- Contact us after the training: -``` -```` - -**Questions to ask:** - -> - What did you just say? -> - Please explain what we just did again? -> - How did that work? -> - Why didn't that work for me? -> - Is that a typo? - -**Questions \_\_not\_\_ to ask:** - -> - **Hypotheticals**: What happens if I do X? -> - **Research**: Can Plone do Y? -> - **Syllabus**: Are we going to cover Z in class? -> - **Marketing questions**: please just don't. -> - **Performance questions**: Is Plone fast enough? -> - **Unpythonic**: Why doesn't Plone do it some other way? -> - **Show off**: Look what I just did! - -(plone5-intro-docs-label)= - -## Documentation - -Follow the training at - -```{note} -You can use this presentation to copy & paste the code but you will memorize more if you type yourself. -``` - -(plone5-intro-further-reading-label)= - -## Further Reading - -- [Martin Aspeli: Professional Plone4 Development](https://www.packtpub.com/product/professional-plone-4-development/9781849514422) -- [Practical Plone](https://www.packtpub.com/product/practical-plone-3-a-beginner-s-guide-to-building-powerful-websites/9781847191786) -- [Zope Page Templates Reference](https://zope.readthedocs.io/en/latest/zopebook/AppendixC.html) diff --git a/docs/mastering-plone-5/optional.md b/docs/mastering-plone-5/optional.md deleted file mode 100644 index 39403c08c..000000000 --- a/docs/mastering-plone-5/optional.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -myst: - html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" ---- - -(plone5-optional-label)= - -# Optional - -- zc3.form -- Portlets -- ZCA in depth -- ZODB -- RelStorage -- More and more complex fields -- Custom edit forms -- Users, authentication, member profiles, LDAP -- Caching (plone.app.caching) -- Migrations -- Asynchronous processing -- Talking with external APIs -- {doc}`deployment_code` -- Professional Deployment diff --git a/docs/mastering-plone-5/plone5.md b/docs/mastering-plone-5/plone5.md deleted file mode 100644 index 88c849ea6..000000000 --- a/docs/mastering-plone-5/plone5.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -myst: - html_meta: - "description": "What's New in Plone 5, 5.1 and Plone 5.2" - "property=og:description": "What's New in Plone 5, 5.1 and Plone 5.2" - "property=og:title": "What's New in Plone 5, 5.1 and Plone 5.2" - "keywords": "New, Plone, 5, 5.1, 5.2, release, notes, changes" ---- - -(plone5-plone5-label)= - -# What's New in Plone 5, 5.1 and Plone 5.2 - -Plone 5.0 was released in September 2015. Plone 5 was a major release, that changed the content type framework, the user interface, and the default design. - -Plone 5.1 was released in October 2017 and holds a couple of smaller improvements. - -Plone 5.2 was released in March 2019. Plone 5.2 is the first version that supports Python 3. It also has some improvements like a new drop-down navigation and built-in url-management. - -If you are already familiar with Plone 5.0, 5.1 and 5.2 you can skip this section. - -(plone5-plone5-theme-label)= - -## Default Theme - -The default theme of Plone 5.x is called [Barceloneta](https://github.com/plone/plonetheme.barceloneta/) - -It is a Diazo theme, meaning it uses {py:mod}`plone.app.theming` to insert the output of Plone into static HTML/CSS. - -It uses HTML5, so it uses `
`, `