From e70ac6f4cf94d8c71dd289248c25400e40fc351a Mon Sep 17 00:00:00 2001 From: Andrew Aikman Date: Fri, 2 Apr 2021 12:24:05 +0100 Subject: [PATCH 1/4] Ported the latet docs from develop --- docs/Makefile | 46 +- docs/conf.py | 22 +- docs/contributing/code.rst | 12 +- docs/contributing/code_of_conduct.rst | 6 +- docs/contributing/development-community.rst | 219 ++----- docs/contributing/development-policies.rst | 81 ++- docs/contributing/documentation.rst | 2 +- docs/contributing/management.rst | 21 +- docs/contributing/testing.rst | 16 +- docs/how_to/apphooks.rst | 219 ++++--- docs/how_to/caching.rst | 2 +- docs/how_to/contributing.rst | 4 +- docs/how_to/custom_plugins.rst | 234 +++---- docs/how_to/extending_page_title.rst | 30 +- docs/how_to/index.rst | 52 +- docs/how_to/install.rst | 54 +- docs/how_to/languages.rst | 28 +- docs/how_to/menus.rst | 4 +- docs/how_to/namespaced_apphooks.rst | 14 +- docs/how_to/testing.rst | 8 +- docs/how_to/toolbar.rst | 584 +++++++++++------- docs/index.rst | 68 +- docs/introduction/01-install.rst | 18 +- .../02-templates_placeholders.rst | 6 +- .../03-integrating_applications.rst | 8 +- docs/introduction/04-plugins.rst | 6 +- docs/introduction/05-apphooks.rst | 42 +- docs/introduction/06-toolbar.rst | 89 ++- docs/introduction/07-menu.rst | 4 +- docs/introduction/08-wizards.rst | 2 + docs/introduction/09-third_party.rst | 12 +- docs/introduction/index.rst | 13 +- docs/reference/api_references.rst | 21 +- docs/reference/configuration.rst | 97 ++- docs/reference/navigation.rst | 9 - docs/reference/plugins.rst | 49 +- docs/reference/toolbar.rst | 424 +++++++------ docs/requirements.txt | 6 +- docs/spelling_wordlist | 38 +- docs/topics/commonly_used_plugins.rst | 20 +- docs/topics/index.rst | 1 + docs/topics/menu_system.rst | 128 ++-- docs/topics/permissions.rst | 225 +++---- docs/topics/plugins.rst | 131 ++++ docs/upgrade/2.1.rst | 12 +- docs/upgrade/2.4.rst | 12 +- docs/upgrade/3.0.rst | 18 +- docs/upgrade/3.2.rst | 4 +- docs/upgrade/3.4.5.rst | 34 + docs/upgrade/3.4.6.rst | 27 + docs/upgrade/3.4.7.rst | 22 + docs/upgrade/3.5.3.rst | 34 + docs/upgrade/3.5.4.rst | 14 + docs/upgrade/3.6.1.rst | 14 + docs/upgrade/3.6.rst | 100 +++ docs/upgrade/3.7.1.rst | 40 ++ docs/upgrade/3.7.2.rst | 36 ++ docs/upgrade/3.7.3.rst | 16 + docs/upgrade/3.7.4.rst | 14 + docs/upgrade/3.7.rst | 90 +++ docs/upgrade/3.8.rst | 56 ++ docs/upgrade/index.rst | 13 + docs/user/index.rst | 11 +- docs/user/reference/index.rst | 11 +- .../tutorial/images/structure-content.png | Bin 2004 -> 0 bytes docs/user/tutorial/index.rst | 11 +- .../user/tutorial/structure-content-modes.rst | 4 - 67 files changed, 2276 insertions(+), 1392 deletions(-) create mode 100644 docs/topics/plugins.rst create mode 100644 docs/upgrade/3.4.5.rst create mode 100644 docs/upgrade/3.4.6.rst create mode 100644 docs/upgrade/3.4.7.rst create mode 100644 docs/upgrade/3.5.3.rst create mode 100644 docs/upgrade/3.5.4.rst create mode 100644 docs/upgrade/3.6.1.rst create mode 100644 docs/upgrade/3.6.rst create mode 100644 docs/upgrade/3.7.1.rst create mode 100644 docs/upgrade/3.7.2.rst create mode 100644 docs/upgrade/3.7.3.rst create mode 100644 docs/upgrade/3.7.4.rst create mode 100644 docs/upgrade/3.7.rst create mode 100644 docs/upgrade/3.8.rst delete mode 100644 docs/user/tutorial/images/structure-content.png diff --git a/docs/Makefile b/docs/Makefile index 34642f3919d..257a91f6002 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,18 +1,22 @@ # Makefile for Sphinx documentation -# - # You can set these variables from the command line. + SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = +VENV = env/bin/activate +PORT = 8001 +BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -n -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# list the targets that we don't want confused with files in the directory .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest +# "help" is first so that "make" without an argument acts like "make help". help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @@ -28,13 +32,29 @@ help: @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " spelling to check for typos in documentation" -clean: - -rm -rf build/* html: . $(VENV); $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html @echo @echo "Build finished. The HTML pages are in build/html." + sphinx-build -b html -n -d build/doctrees -D latex_paper_size=a4 -D latex_paper_size=letter build/html + +install: + @echo "... setting up virtualenv" + python3 -m venv env + . $(VENV); pip install -r requirements.txt + + @echo "\n" \ + "-------------------------------------------------------------------------------------------------- \n" \ + "* watch, build and serve the documentation: make run \n" \ + "* check spelling: make spelling \n" \ + "\n" \ + "enchant must be installed in order for pyenchant (and therefore spelling checks) to work. See \n" \ + "http://docs.django-cms.org/en/latest/contributing/documentation.html#install-the-spelling-software \n" \ + "-------------------------------------------------------------------------------------------------- \n" \ + +clean: + -rm -r $(BUILDDIR)/* dirhtml: . $(VENV); $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml @@ -95,23 +115,5 @@ spelling: @echo "Check finished. Wrong words can be found in " \ "build/spelling/output.txt." -################################################################################ -VENV = env/bin/activate -PORT = 8001 -# CORE COMMANDS -install: - @echo "... setting up virtualenv" - python3.6 -m venv env - . $(VENV); pip install -r requirements.txt - . $(VENV); $(SPHINXBUILD) . build/html - @echo "\n" \ - "-------------------------------------------------------------------------------------------------- \n" \ - "* watch, build and serve the documentation: make run \n" \ - "* check spelling: make spelling \n" \ - "\n" \ - "enchant must be installed in order for pyenchant (and therefore spelling checks) to work. See \n" \ - "http://docs.django-cms.org/en/latest/contributing/documentation.html#install-the-spelling-software \n" \ - "-------------------------------------------------------------------------------------------------- \n" \ - run: . $(VENV); sphinx-autobuild $(ALLSPHINXOPTS) build/html --host 0.0.0.0 --port $(PORT) diff --git a/docs/conf.py b/docs/conf.py index 103eabc6971..0b56e5737a9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # django cms documentation build configuration file, created by # sphinx-quickstart on Tue Sep 15 10:47:03 2009. @@ -12,6 +11,8 @@ # All configuration values have a default; values that are commented out serve # to show the default. +import cms +import datetime import os import sys @@ -39,7 +40,7 @@ ] intersphinx_mapping = { 'python': ('http://docs.python.org/3/', None), - 'django': ('https://docs.djangoproject.com/en/1.11/', 'https://docs.djangoproject.com/en/1.11/_objects/'), + 'django': ('https://docs.djangoproject.com/en/2.2/', 'https://docs.djangoproject.com/en/2.2/_objects/'), 'classytags': ('http://readthedocs.org/docs/django-classy-tags/en/latest/', None), 'sekizai': ('http://readthedocs.org/docs/django-sekizai/en/latest/', None), 'treebeard': ('http://django-treebeard.readthedocs.io/en/latest/', None), @@ -57,9 +58,10 @@ # The master toctree document. master_doc = 'index' +current_year = datetime.datetime.now().year # General information about the project. project = u'django cms' -copyright = u'2009-2017, Divio AG and contributors' +copyright = u'2009-{}, Divio AG and contributors'.format(current_year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -70,7 +72,6 @@ path = os.path.split(os.path.dirname(__file__))[0] path = os.path.split(path)[0] sys.path.insert(0, path) -import cms version = cms.__version__ # The full version, including alpha/beta/rc tags. @@ -130,9 +131,22 @@ import divio_docs_theme html_theme = 'divio_docs_theme' html_theme_path = [divio_docs_theme.get_html_theme_path()] + html_theme_options = { + 'show_cloud_banner': True, + 'cloud_banner_markup': """ +
+ The django CMS Association +

The django CMS Association is a non-profit organisation that funds and + steers the development of django CMS, and nurtures its world-wide + community of developers and users.

+ Join us +
+ """, + } except: html_theme = 'default' + show_cloud_banner = True # The theme to use for HTML and HTML Help pages. Major themes that come with diff --git a/docs/contributing/code.rst b/docs/contributing/code.rst index 0b881eb65cd..75859ccab7b 100644 --- a/docs/contributing/code.rst +++ b/docs/contributing/code.rst @@ -14,7 +14,7 @@ In a nutshell Here's what the contribution process looks like in brief: -#. Fork our `GitHub`_ repository, https://github.com/divio/django-cms +#. Fork our `GitHub`_ repository, https://github.com/django-cms/django-cms #. Work locally and push your changes to your repository. #. When you feel your code is good enough for inclusion, send us a pull request. @@ -26,7 +26,7 @@ Basic requirements and standards ******************************** If you're interested in developing a new feature for the CMS, it is recommended -that you first discuss it on the `django-cms-developers`_ mailing list so as +that you first discuss it on the `Discourse forum `_ so as not to do any work that will not get merged in anyway. - Code will be reviewed and tested by at least one core developer, preferably @@ -105,11 +105,9 @@ coverage will only be accepted with a very good reason; bug-fixing patches **must** demonstrate the bug with a test to avoid regressions and to check that the fix works. -We have an IRC channel, our `django-cms-developers`_ email list, -and of course the code reviews mechanism on GitHub - do use them. +We have `a Slack group `_, a `Discourse forum +`_, and of course the code reviews mechanism on GitHub - do use them. -If you don't have an IRC client, you can `join our IRC channel using the KiwiIRC web client -`_, which works pretty well. .. _contributing_frontend: @@ -194,7 +192,7 @@ CMS in external applications, you can only use bundles distributed by CMS, not the source modules. -.. _fork: https://github.com/divio/django-cms +.. _fork: https://github.com/django-cms/django-cms .. _PEP8: http://www.python.org/dev/peps/pep-0008/ .. _Aldryn Boilerplate: https://aldryn-boilerplate-bootstrap3.readthedocs.io/en/latest/guidelines/index.html .. _django-cms-developers: https://groups.google.com/group/django-cms-developers diff --git a/docs/contributing/code_of_conduct.rst b/docs/contributing/code_of_conduct.rst index c5e9f36712d..22c2ab4799d 100644 --- a/docs/contributing/code_of_conduct.rst +++ b/docs/contributing/code_of_conduct.rst @@ -13,9 +13,9 @@ We will not tolerate abusive behaviour or language or any form of harassment. Individuals whose behaviour is a cause for concern will be give a warning, and if necessary will be excluded from participation in official django CMS -channels (email lists, IRC channels, etc) and events. The `Django Software -Foundation `_ will also be informed of -the issue. +channels (Slack group, Discourse forum, email lists, IRC channels, etc) and +events. The `Django Software Foundation +`_ will also be informed of the issue. ***************** Raising a concern diff --git a/docs/contributing/development-community.rst b/docs/contributing/development-community.rst index 390fec13209..b5d8905de89 100644 --- a/docs/contributing/development-community.rst +++ b/docs/contributing/development-community.rst @@ -6,19 +6,15 @@ django CMS's development community You can join us online: -* in our IRC channel, #django-cms, on ``irc.freenode.net``. If you don't have an IRC client, you can - `join our IRC channel using the KiwiIRC web client - `_, which works pretty well. -* on our `django CMS users email list `_ for - **general** django CMS questions and discussion -* on our `django CMS developers email list - `_ for discussions about the - **development of django CMS** +* in our `django CMS Slack channel `_ +* on our `Discourse forum `_ +* on `StackOverflow `_ You can also follow: -* the `Travis Continuous Integration build reports `_ -* the `@djangocms`_ Twitter account for general announcements +* the `Travis Continuous Integration build reports `_ +* the `@djangocms `_ Twitter account for general announcements +* the `django CMS Association LinkedIn `_ account You don't need to be an expert developer to make a valuable contribution - all you need is a little knowledge of the system, and a willingness to follow the @@ -32,177 +28,72 @@ guidance of a **technical board**. All activity in the community is governed by our :doc:`code_of_conduct`. +********************** +Django CMS Association +********************** -******** -Divio AG -******** +django CMS was released under a BSD licence in 2009. It was created at `Divio AG `_ +of Zürich, Switzerland, by `Patrick Lauber `_, who led its development for several +years. -django CMS was released under a BSD licence in 2009. It was created at `Divio -AG `_ of Zürich, Switzerland, by -`Patrick Lauber `_, who led its development for -several years. +In July 2020 Divio handed over the banner to the newly founded +`django CMS Association `_ (dCA). Its +goal is to drive the success of django CMS, by increasing customer happiness, +market share and open-source-contributions. Divio remains thoroughly committed +to django CMS as the host of the `django CMS project website `_ +and as one of the founding members of the dCA, next to `What `_. and +`Eliga Services `_. -django CMS represents Divio's vision for a general-purpose CMS platform able to meet its needs as a -web agency with a large portfolio of different clients. This vision continues to guide the -development of django CMS. +The dCA’s role in steering the project’s development is formalised in the +`django CMS technical comittee `_, +whose members are drawn from the django CMS community and the dCA. -Divio's role in steering the project's development is formalised in the -:ref:`django CMS technical board `, whose members are drawn -both from key staff at Divio and other members of the django CMS community. +The dCA maintains overall control of the `django CMS repository `_. +As the chief backer of django CMS, and in order to ensure a consistent and +long-term approach to the project, the dCA reserves the right of final say in +any decisions concerning its development. -Divio hosts the `django CMS project website `_ and maintains overall control -of the `django CMS repository `_. As the chief backer of -django CMS, and in order to ensure a consistent and long-term approach to the project, Divio -reserves the right of final say in any decisions concerning its development. +As a non-profit organization the django CMS Association is dependent on +donations to fulfill its mission, which is based on the following three statements: -Divio remains thoroughly committed to django CMS both as a high-quality technical -product and as a healthy open source project. +* Innovate and lead +* Foster contribution +* Drive adoption +The best way to donate is to become a member of the association and pay +membership fees. The funding is funneled back into core development and +community projects. -.. _core_developers: - -*************** -Core developers -*************** - -Leading this process is a small team of core developers - people who have made -and continue to make a significant contribution to the project, and have a good -understanding not only of the code powering django CMS, but also the -longer-term aims and directions of the project. - -All core developers are volunteers. - -Core developers have commit authority to django CMS's repository on GitHub. -It's up to a core developer to say when a particular pull request should be -committed to the repository. - -Core developers also keep an eye on the ``#django-cms`` IRC channel on the -`Freenode network `_, and the `django CMS users -`_ and `django CMS -developers `_ -email lists. - -In addition to leading the development of the project, the core developers have -an important role in fostering the community of developers who work with django -CMS, and who create the numerous applications, plugins and other software that -integrates with it. - -Finally, the core developers are responsible for setting the tone of the -community and helping ensure that it continues to be friendly and welcoming to -all who wish to participate. The values and standards of the community are set -out in its Code of Conduct. - - -Current core developers -======================= - -* Angelo Dini https://github.com/finalangel -* Daniele Procida https://github.com/evildmp -* Iacopo Spalletti https://github.com/yakky -* Jonas Obrist https://github.com/ojii -* Martin Koistinen https://github.com/mkoistinen -* Patrick Lauber https://github.com/digi604 -* Paulo Alvarado https://github.com/czpython -* Stefan Foulis https://github.com/stefanfoulis -* Vadim Sikora https://github.com/vxsx - - -Core designers --------------- - -django CMS also receives important contributions from *core designers*, responsible for key aspects of its visual -design: - -* Christian Bertschy -* Matthias Nüesch - -Retired core developers -======================= - -* Chris Glass https://github.com/chrisglass -* Øyvind Saltvik https://github.com/fivethreeo -* Benjamin Wohlwend https://github.com/piquadrat - -Following a year or so of inactivity, a core developer will be moved to the -"Retired" list, with the understanding that they can rejoin the project in the -future whenever they choose. - - -Becoming a core developer -========================= - -Anyone can become a core developer. You don't need to be an expert developer, or -know more than anyone else about the internals of the CMS. You just have to be a -regular contributor, who makes a sustained and valuable contribution to the -project. - -This contribution can take many forms - not just commits to our codebase. For -example, documentation is a valuable contribution, and so is simply being a -member of the community who spends time assisting others. - -Any member of the core team can nominate a new person for membership. The -nomination will be discussed by the technical board, and assuming there are no -objections raised, approved. +* `Sign up for more information about becoming a member of the dCA `_ +.. _core_developers: -.. _technical_board: +********************** +The dCA Tech Committee +********************** -*************** -Technical board -*************** +Mission +======= -Historically, django CMS's development has been led by members of staff from -Divio. It has been (and will continue to be) a requirement of the CMS that it -meet Divio's needs. +It prepares and updates the technical roadmap for approval by the Executive +Board and/or the General Assembly, manages incoming feature requests and +proposals and takes decisions on awarding credits for work submitted by members. -However, as the software has matured and its user-base has dramatically -expanded, it has become increasingly important also to reflect a wider range of -perspectives in the development process. The technical board exists to help -guarantee this. +* `Find out more about the mission `_ -Role +Team ==== -The role of the board is to maintain oversight of the work of the core team, to -set key goals for the project and to make important decisions about the -development of the software. - -In the vast majority of cases, the team of core developers will be able to -resolve questions and make decisions without the formal input of the technical -board; where a disagreement with no clear consensus exists however, the board -will make the necessary definitive decision. - -The board is also responsible for making final decisions on the election of new -core developers to the team, and - should it be necessary - the removal of -developers who have retired, or for other reasons. - -Composition of the board -======================== - -The the technical board will include key developers from Divio and others in the -django CMS development community - developers who work *with* django CMS, as -well as developers *of* django CMS - in order to help ensure that all -perspectives are represented in important decisions about the software and the -project. - -The board may also include representatives of the django CMS community who are -not developers but who have a valuable expertise in key fields (user -experience, design, content management, etc). - -The current members of the technical board are: +* `Overview of the team `_ -* Angelo Dini -* Christian Bertschy -* Daniele Procida (Chair) -* Iacopo Spalletti -* Jonas Obrist -* Martin Koistinen -* Matteo Larghi +Tasks +===== -The board will co-opt new members as appropriate. +* `Tasks & Decisions Log `_ +* `Kanban Board `_ +Processes +========= -.. _security@django-cms.org: mailto:security@django-cms.org -.. _django-cms-developers: https://groups.google.com/group/django-cms-developers -.. _freenode: http://freenode.net/ -.. _@djangocms: https://twitter.com/djangocms +* `Become a core contributor `_ +* `Become a member of the Tech Committee `_ diff --git a/docs/contributing/development-policies.rst b/docs/contributing/development-policies.rst index ec240d78da0..3b0aa86c511 100644 --- a/docs/contributing/development-policies.rst +++ b/docs/contributing/development-policies.rst @@ -13,26 +13,22 @@ Reporting security issues .. ATTENTION:: If you think you have discovered a security issue in our code, please report - it **privately**, by emailing us at `security@django-cms.org`_. + it **privately**, by emailing us at `security@divio.com `_. - Please **do not** raise it on: - - * IRC - * GitHub - * either of our email lists - - or in any other public forum until we have had a chance to deal with it. + Please **do not** raise it in any public forum until we have had a + chance to deal with it. ****** Review ****** -All patches should be made as pull requests to `the GitHub repository `_. Patches -should never be pushed directly. +All patches should be made as pull requests **against develop** to +`the GitHub repository `_. Patches should +never be pushed directly. -**Nothing** may enter the code-base, *including the documentation*, without proper review and formal approval from the -core team. +**Nothing** may enter the code-base, *including the documentation*, without +proper review and formal approval from the core team. Reviews are welcomed by all members of the community. You don't need to be a core developer, or even an experienced programmer, to contribute usefully to code review. Even noting that you don't understand something in a pull request @@ -42,19 +38,16 @@ is valuable feedback and will be taken seriously. Formal approval =============== -Formal approval means "OK to merge" comments, following review, from at least two different members of the core team -who have expertise in the relevant areas, and excluding the author of the pull request. - -The exceptions to this are frontend code and documentation, where one "OK to merge" comment will suffice, at least -until the team has more expert developers in those areas. +Formal approval means "OK to merge" comments, following review, from at least +one member of the core team who has expertise in the relevant areas, and excluding +the author of the pull request. ********************************************** Proposal and discussion of significant changes ********************************************** -New features and backward-incompatible changes should be proposed using the `django CMS developers email list -`_. Discussion should take place there before any pull requests +New features and backward-incompatible changes should be proposed using the `Discourse forum `_. Discussion should take place there before any pull requests are made. This is in the interests of openness and transparency, and to give the community a chance to participate in and @@ -65,16 +58,22 @@ understand the decisions taken by the project. Release schedule **************** +.. versionchanged:: 3.7 + + django CMS 3.7 is the new active long term release. + The `roadmap `_ can be found on our website. We are planning releases according to **key principles and aims**. Issues within milestones are therefore subject to change. -The `django CMS developers email list `_ serves as gathering +The `django CMS Discourse forum `_ serves as gathering point for developers. We submit ideas and proposals prior to the roadmap goals. -django CMS 3.4 will be the first "LTS" ("Long-Term Support") release of the application. *Long-term support* means that -this version will continue to receive security and other critical updates for 24 months after its first release. +django CMS 3.4, surpassed by 3.7, was the first "LTS" ("Long-Term Support") +release of the application. *Long-term support* means that this version will +continue to receive security and other critical updates for 24 months after its +first release. Any updates it does receive will be backward-compatible and will not alter functional behaviour. This means that users can deploy this version confident that keeping it up-to-date requires only easily-applied security and other critical @@ -92,31 +91,32 @@ Branches Previously, we maintained a ``master`` branch (now deleted), and a set of ``support`` branches (now pruned, and renamed ``release``). -We maintain a number of branches on `our GitHub repository `_. +.. versionchanged:: 3.7 -the latest (highest-numbered) ``release/x.y.z`` - This is the branch that will become the next release on PyPI. + Simplified the description of the release branches and added additional + information for ``releases`` and ``release/4.0.x``. In general open PRs + against ``develop``. - **Fixes and backwards-compatible improvements** (i.e. most pull requests) will be made against - this branch. +We maintain a number of branches on +`our GitHub repository `_: ``develop`` - This is the branch that will become the next release that increments the ``x`` or ``y`` of the latest - ``release/x.y.z``. - - This branch is for **new features and backwards-incompatible changes**. By their nature, these will require more - substantial team co-ordination. + The default target branch for on-going development and new pull requests. -Older ``release/x.y.z`` branches - These represent the final point of development (the highest ``y`` of older versions). Releases in the full set of - older versions have been tagged (use Git Tags to retrieve them). +``release/x.y.z`` are the latest released versions of django CMS. Commits + are cherry-picked from ``develop`` and merged into ``release/x.y.z`` + when suitable. We **officially support** the latest, highest released version + and the latest LTS (currently 3.7). - These branches will only rarely be patched, with security fixes representing the main reason for a patch. +``release/4.0.x`` is an experimental branch and should not be considered + as the highest released version. -Commits in ``release/x.y.z`` will be merged forward into ``develop`` periodically by the core developers. +``releases`` hosts the `releases.json` file to indicate the availability of new + django CMS versions when using `djangocms-admin-style `_. -If in doubt about which branch to work from, ask on the #django-cms IRC channel on `freenode`_ or the -`django-cms-developers`_ email list! +Please always open PR's against develop and indicate that they should be +backported to the latest LTS release when necessary. Older branches are not +supported any longer. .. _commit_policy: @@ -198,7 +198,7 @@ Changelog .. versionadded:: 3.3 **Every new feature, bugfix or other change of substance** must be represented in the `CHANGELOG -`_. This includes documentation, but **doesn't** extend +`_. This includes documentation, but **doesn't** extend to things like reformatting code, tidying-up, correcting typos and so on. Each line in the changelog should begin with a verb in the past tense, for example:: @@ -214,5 +214,4 @@ New lines should be added to the top of the list. .. _security@django-cms.org: mailto:security@django-cms.org -.. _django-cms-developers: https://groups.google.com/group/django-cms-developers .. _freenode: http://freenode.net/ diff --git a/docs/contributing/documentation.rst b/docs/contributing/documentation.rst index c89dda18988..6e38de6b79b 100644 --- a/docs/contributing/documentation.rst +++ b/docs/contributing/documentation.rst @@ -138,7 +138,7 @@ caught by the spelling checker. You may well find that some words that pass the spelling test on one system but not on another. Dictionaries on different systems contain different words and even behave differently. The important thing is that the spelling tests pass on `Travis - `_ when you submit a pull request. + `_ when you submit a pull request. ********************* diff --git a/docs/contributing/management.rst b/docs/contributing/management.rst index a93a7c6937f..4f71c03f357 100644 --- a/docs/contributing/management.rst +++ b/docs/contributing/management.rst @@ -4,7 +4,7 @@ Code and project management ########################### -We use our `GitHub project `_ for managing both django CMS code +We use our `GitHub project `_ for managing both django CMS code and development activity. This document describes how we manage tickets on GitHub. By "tickets", we mean GitHub issues and @@ -20,23 +20,20 @@ Raising an issue .. ATTENTION:: If you think you have discovered a security issue in our code, please report - it **privately**, by emailing us at `security@django-cms.org`_. + it **privately**, by emailing us at `security@divio.com `_. - Please **do not** raise it on: + Please **do not** raise it in any public forum until we have had a + chance to deal with it. - * IRC - * GitHub - * either of our email lists - or in any other public forum until we have had a chance to deal with it. - -Except in the case of security matters, of course, you're welcome to raise issues in any way that -suits you - :ref:`on one of our email lists, or the IRC channel ` or in person -if you happen to meet another django CMS developer. +Except in the case of security matters, of course, you're welcome to raise +issues in any way that suits you - :ref:`using Discourse, or the Slack group +` or in person if you happen to meet another django CMS +developer. It's very helpful though if you don't just raise an issue by mentioning it to people, but actually file it too, and that means creating a `new issue on GitHub -`_. +`_. There's an art to creating a good issue report. diff --git a/docs/contributing/testing.rst b/docs/contributing/testing.rst index 0cbb5672b81..caef2a5e8ce 100644 --- a/docs/contributing/testing.rst +++ b/docs/contributing/testing.rst @@ -39,7 +39,7 @@ There's more than one way to do this, but here's one to help you get started:: source bin/activate # get django CMS from GitHub - git clone git@github.com:divio/django-cms.git + git clone https://github.com/django-cms/django-cms.git # install the dependencies for testing # note that requirements files for other Django versions are also provided @@ -65,9 +65,7 @@ We are working to improve the performance and reliability of our test suite. We' problems, but need feedback from people using a wide range of systems and configurations in order to benefit from their experience. -Please use the open issue `#3684 Test suite is error-prone -`_ on our GitHub repository to report such -problems. +Please report any issues on our `GitHub repository `_. If you can help *improve* the test suite, your input will be especially valuable. @@ -96,6 +94,14 @@ when the entire suite is run. To work around this you can invoke the test class and it should then run without errors. +``ERROR: zlib is required unless explicitly disabled using --disable-zlib, aborting`` +------------------------------------------------------------------------------------------ + +If you run into that issue, make sure to install zlib using Homebrew:: + + brew install libjpeg zlib && brew link --force zlib + + Advanced testing options ======================== @@ -169,7 +175,7 @@ Integration tests ================= In order to run integration tests you'll have to install at least the version -of django CMS from the current directory and djangocms-helper into into your virtualenv. +of django CMS from the current directory and django-app-helper into into your virtualenv. All commands should be run from the root of the repository. If you do not have virtualenv yet, create and activate it first:: diff --git a/docs/how_to/apphooks.rst b/docs/how_to/apphooks.rst index f967956ba35..df9fbd85c66 100644 --- a/docs/how_to/apphooks.rst +++ b/docs/how_to/apphooks.rst @@ -12,8 +12,8 @@ will be delivered at the page's URL. All URLs in that URL path will be passed to the attached application's URL configs. -The :ref:`Tutorials ` section contains a basic guide to :ref:`getting started with apphooks -`. This document assumes more familiarity with the CMS generally. +The :ref:`Tutorials ` section contains a basic guide to :ref:`getting started with +apphooks `. This document assumes more familiarity with the CMS generally. ****************************** @@ -26,67 +26,78 @@ The file needs to contain a :class:`CMSApp ` sub-class. For from cms.app_base import CMSApp from cms.apphook_pool import apphook_pool - from django.utils.translation import ugettext_lazy as _ @apphook_pool.register class MyApphook(CMSApp): - name = _("My Apphook") + app_name = "myapp" # must match the application namespace + name = "My Apphook" def get_urls(self, page=None, language=None, **kwargs): - return ["myapp.urls"] # replace this with the path to your application's URLs module + return ["myapp.urls"] # replace this with the path to your application's URLs module .. versionchanged:: 3.3 ``CMSApp.get_urls()`` replaces ``CMSApp.urls``. ``urls`` was removed in version 3.5. -Apphook URLS -============ +Apphooks for namespaced applications +==================================== -Instead of defining the URL patterns in another file ``myapp/urls.py``, it also is possible -to return them directly, for instance as: +Your application should use :ref:`namespaced URLs `. -.. code-block:: python +In the example above, the application uses the ``myapp`` namespace. Your ``CMSApp`` +sub-class **must reflect the application's namespace** in the ``app_name`` attribute. - from django.conf.urls import url - from myapp.views import SomeListView, SomeDetailView +The application may specify a namespace by supplying an ``app_name`` in its ``urls.py``, or its +documentation might advise that you when include its URLs, you do it thus: - class MyApphook(CMSApp): - # ... - def get_urls(self, page=None, language=None, **kwargs): - return [ - url(r'^$', SomeListView.as_view()), - url(r'^(?P[\w-]+)/?$', SomeDetailView.as_view()), - ] +.. code-block:: python -However, it's neater to keep them in the application's ``urls.py``, where they can easily be reused. + re_path(r'^myapp/', include('myapp.urls', app_name='myapp')) +If you fail to do this, then any templates in the application that invoke URLs using the form ``{% url +'myapp:index' %}`` or views that call (for example) ``reverse('myapp:index')`` will throw a +``NoReverseMatch`` error. -Apphooks for namespaced applications -==================================== -Does your application use :ref:`namespaced URLs `? This is good practice, -so it should! +Apphooks for non-namespaced applications +---------------------------------------- -In that case you will need to ensure that your apphooks include its URLs in the right namespace. Add an ``app_name`` -attribute to the class that reflects the way you'd include the applications' URLs into your project. +If you are writing apphooks for third-party applications, you may find one that in fact does +not have an application namespace for its URLs. Such an application is liable to tun into namespace +conflicts, and doesn't represent good practice. -For example, if your application requires that your project's URLs do:: +However if you *do* encounter such an application, your own apphook for it will need in turn to forgo the +``app_name`` attribute. - url(r'^myapp/', include('myapp.urls', app_name='myapp')), +Note that unlike apphooks without ``app_name`` attributes can be attached only to one page at a +time; attempting to apply them a second time will cause an error. Only one instance of these +apphooks can exist. -then your ``MyApphook`` class should include:: +See :ref:`multi_apphook` for more on having multiple apphook instances. - app_name = "myapp" -If you fail to this, then any templates in the application that invoke URLs using the form ``{% url 'myapp:index' %}`` -or views that call (for example) ``reverse('myapp:index')`` will throw a ``NoReverseMatch`` error. +Returning apphook URLs manually +=============================== -If you had already assigned a page to your aplication prior to setting the ``app_name`` attribute, you'll also need to edit its *Advanced settings* and specify your ``app_name`` in the *Application instance name* field that now appears, to avoid the ``NoReverseMatch`` error (the instance name is filled automatically in new pages). +Instead of defining the URL patterns in another file ``myapp/urls.py``, it also is possible +to return them manually, for example if you need to override the set provided. An example: -*Unless* the class that defines the apphook specifies an ``app_name``, it can be attached only to one page at a time. -Attempting to apply it a second time will cause an error. See :ref:`multi_apphook` for more on having multiple apphook -instances. +.. code-block:: python + + from django.urls import re_path + from myapp.views import SomeListView, SomeDetailView + + class MyApphook(CMSApp): + # ... + def get_urls(self, page=None, language=None, **kwargs): + return [ + re_path(r'^$', SomeListView.as_view()), + re_path(r'^(?P[\w-]+)/?$', SomeDetailView.as_view()), + ] + +However, it's much neater to keep them in the application's ``urls.py``, where they can easily be +reused. .. _reloading_apphooks: @@ -99,43 +110,43 @@ Certain apphook-related changes require server restarts in order to be loaded. Whenever you: * add or remove an apphook -* change the slug of a page containing an apphook or the slug of a page which has a descendant with an apphook +* change the slug of a page containing an apphook or the slug of a page which has a descendant with + an apphook the URL caches must be reloaded. -If you have the :ref:`ApphookReloadMiddleware` installed, which is recommended, the server will do it for your by -re-initialising the URL patterns automatically. +If you have the :ref:`ApphookReloadMiddleware` installed, which is recommended, the server will do +it for you by re-initialising the URL patterns automatically. -Otherwise, you will need to restart it manually. +Otherwise, you will need to restart the server manually. **************** Using an apphook **************** -Once your apphook has been set up and loaded, you'll now be able to select the *Application* that's hooked into that page from its *Advanced settings*. +Once your apphook has been set up and loaded, you'll now be able to select the *Application* that's +hooked into that page from its *Advanced settings*. .. note:: - An apphook won't actually do anything until the page it belongs to is published. Take note that this also - means all parent pages must also be published. - -The apphook attaches all of the apphooked application's URLs to the page; its root URL will be the page's own URL, and -any lower-level URLs will be on the same URL path. + An apphook won't actually do anything until the page it belongs to is published. Take note that + this also means all parent pages must also be published. -So, given an application with the ``urls.py``:: +The apphook attaches all of the apphooked application's URLs to the page; its root URL will be the +page's own URL, and any lower-level URLs will be on the same URL path. - from django.conf.urls import * +So, given an application with the ``urls.py`` for the views ``index_view`` and ``archive_view``:: - urlpatterns = patterns('sampleapp.views', - url(r'^$', 'main_view', name='app_main'), - url(r'^sublevel/$', 'sample_view', name='app_sublevel'), - ) + urlpatterns = [ + re_path(r'^$', index_view), + re_path(r'^archive/$', archive_view), + ] -attached to a page whose URL path is ``/hello/world/``, its views will be exposed as follows: +attached to a page whose URL path is ``/hello/world/``, the views will be exposed as follows: -* ``main_view`` at ``/hello/world/`` -* ``sample_view`` at ``/hello/world/sublevel/`` +* ``index_view`` at ``/hello/world/`` +* ``archive_view`` at ``/hello/world/archive/`` Sub-pages of an apphooked page @@ -145,32 +156,33 @@ Sub-pages of an apphooked page Don't add child pages to a page with an apphook. -The apphook "swallows" all URLs below that of the page, handing them over to the attached -application. If you have any child pages of the apphooked page, django CMS will not be -able to serve them reliably. + The apphook "swallows" all URLs below that of the page, handing them over to the attached + application. If you have any child pages of the apphooked page, django CMS will not be + able to serve them reliably. -****************** -Apphook management -****************** +***************** +Managing apphooks +***************** Uninstalling an apphook with applied instances ============================================== -If you remove an apphook class (in effect uninstalling it) from your system that still has instances applied to pages, -django CMS tries to handle this as gracefully as possible: +If you remove an apphook class from your system (in effect uninstalling it) that still has +instances applied to pages, django CMS tries to handle this as gracefully as possible: -* Affected Pages still maintain a record of the applied apphook; if the apphook class is reinstated, it will work as - before. +* Affected pages still maintain a record of the applied apphook; if the apphook class is + subsequently reinstated, it will work as before. * The page list will show apphook indicators where appropriate. -* The page will otherwise behave like a normal django CMS page, and display its placeholders in the usual way. -* If you save the page's Advanced settings, the apphook will be removed. +* The page will otherwise behave like a normal django CMS page, and display its placeholders in the + usual way. +* If you save the page's *Advanced settings*, the apphook will be removed. Management commands =================== -You can clear uninstalled apphook instances using a CMS management command ``uninstall apphooks``; for example:: +You can clear uninstalled apphook instances using the CMS management command ``uninstall apphooks``. For example:: manage.py cms uninstall apphooks MyApphook MyOtherApphook @@ -180,20 +192,22 @@ You can get a list of installed apphooks using the :ref:`cms-list-command`; in t See the :ref:`Management commands reference ` for more information. + .. _apphook_menus: -************* -Apphook menus -************* +************************ +Adding menus to apphooks +************************ -Generally, it is recommended to allow the user to control whether a menu is attached to a page. However, an apphook can -be made to do this automatically if required. It will behave just as if it were attached the page using its *Advanced -settings*). +Generally, it is recommended to allow the user to control whether a menu is attached to a page (See +:ref:`integration_attach_menus` for more on these menus). However, an apphook can be made to do +this automatically if required. It will behave just as if the menu had been attached to the page +using its *Advanced settings*). Menus can be added to an apphook using the ``get_menus()`` method. On the basis of the example above:: # [...] - from myapp.menu import MyAppMenu + from myapp.cms_menus import MyAppMenu class MyApphook(CMSApp): # [...] @@ -201,12 +215,13 @@ Menus can be added to an apphook using the ``get_menus()`` method. On the basis return [MyAppMenu] .. versionchanged:: 3.3 - ``CMSApp.get_menus()`` replaces ``CMSApp.menus``. The ``menus`` attribute is now deprecated and will be - removed in version 3.5. + ``CMSApp.get_menus()`` replaces ``CMSApp.menus``. The ``menus`` attribute is now deprecated and + has been removed in version 3.5. -The menus returned in the ``get_menus()`` method need to return a list of nodes, in their ``get_nodes()`` methods. See -:ref:`integration_attach_menus` for more on creating menu classes that generate nodes. +The menus returned in the ``get_menus()`` method need to return a list of nodes, in their +``get_nodes()`` methods. :ref:`integration_attach_menus` has more information on creating menu +classes that generate nodes. You can return multiple menu classes; all will be attached to the same page:: @@ -216,19 +231,18 @@ You can return multiple menu classes; all will be attached to the same page:: .. _apphook_permissions: -******************* -Apphook permissions -******************* +******************************** +Managing permissions on apphooks +******************************** -By default the content represented by an apphook has the same permissions set as the page it is assigned to. So if for -example a page requires the user to be logged in, then the attached apphook and all its URLs will have the same -requirements. +By default the content represented by an apphook has the same permissions set as the page it is +assigned to. So if for example a page requires the user to be logged in, then the attached apphook +and all its URLs will have the same requirements. To disable this behaviour set ``permissions = False`` on your apphook:: - class SampleApp(CMSApp): - name = _("Sample App") - _urls = ["project.sampleapp.urls"] + class MyApphook(CMSApp): + [...] permissions = False If you still want some of your views to use the CMS's permission checks you can enable them via a decorator, ``cms.utils.decorators.cms_perms`` @@ -241,29 +255,14 @@ Here is a simple example:: def my_view(request, **kw): ... -If you have your own permission checks in your application, then use ``exclude_permissions`` property of the apphook:: +If you make your own permission checks in your application, then use the ``exclude_permissions`` property of the apphook:: - class SampleApp(CMSApp): - name = _("Sample App") + class MyApphook(CMSApp): + [...] permissions = True exclude_permissions = ["some_nested_app"] - def get_urls(self, page=None, language=None, **kwargs): - return ["project.sampleapp.urls"] - -For example, django-oscar_ apphook integration needs to be used with ``exclude_permissions`` of the -dashboard app, because it uses the `customisable access function`__. So, your apphook in this case -will look like this:: - - class OscarApp(CMSApp): - name = _("Oscar") - exclude_permissions = ['dashboard'] - - def get_urls(self, page=None, language=None, **kwargs): - return application.urls[0] - -.. _django-oscar: https://github.com/tangentlabs/django-oscar -.. __: https://github.com/tangentlabs/django-oscar/blob/0.7.2/oscar/apps/dashboard/nav.py#L57 +where you provide the name of the application in question *********************************************** @@ -287,10 +286,8 @@ the signal ``cms.signals.urls_need_reloading``. Django applications, there is no way we can provide a generic solution for this problem that will always work. -.. warning:: - - The signal is fired **after** a request. If you change something via an API - you'll need a request for the signal to fire. + The signal is fired **after** a request - for example, upon saving a page's settings. If you + change and apphook's setting via an API the signal won't fire until a subsequent request. ************************************** diff --git a/docs/how_to/caching.rst b/docs/how_to/caching.rst index f85321f48f6..550b3be3c38 100644 --- a/docs/how_to/caching.rst +++ b/docs/how_to/caching.rst @@ -14,7 +14,7 @@ Details for caching can be found here: https://docs.djangoproject.com/en/dev/top In your middleware settings be sure to add ``django.middleware.cache.UpdateCacheMiddleware`` at the first and ``django.middleware.cache.FetchFromCacheMiddleware`` at the last position:: - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.cache.UpdateCacheMiddleware', ... 'cms.middleware.language.LanguageCookieMiddleware', diff --git a/docs/how_to/contributing.rst b/docs/how_to/contributing.rst index 7244bb0c880..28455d26dce 100644 --- a/docs/how_to/contributing.rst +++ b/docs/how_to/contributing.rst @@ -28,7 +28,7 @@ The basics The basic workflow for a code contribution will typically run as follows: -#. Fork the `django CMS project `_ GitHub repository to your +#. Fork the `django CMS project `_ GitHub repository to your own GitHub account #. Clone your fork locally:: @@ -87,7 +87,7 @@ Let's say you want to test the behaviour of the ``CMSPluginBase.render`` method: .. code-block:: python - class CMSPluginBase(six.with_metaclass(CMSPluginBaseMetaclass, admin.ModelAdmin)): + class CMSPluginBase(admin.ModelAdmin, metaclass=CMSPluginBaseMetaclass): ... diff --git a/docs/how_to/custom_plugins.rst b/docs/how_to/custom_plugins.rst index 633bf3bb36a..0f9e30606f2 100644 --- a/docs/how_to/custom_plugins.rst +++ b/docs/how_to/custom_plugins.rst @@ -1,151 +1,25 @@ .. _custom-plugins: -############################ -How to create custom Plugins -############################ - -CMS Plugins are reusable content publishers that can be inserted into django -CMS pages (or indeed into any content that uses django CMS placeholders). They -enable the publishing of information automatically, without further -intervention. - -This means that your published web content, whatever it is, is kept -up-to-date at all times. - -It's like magic, but quicker. - -Unless you're lucky enough to discover that your needs can be met by the -built-in plugins, or by the many available third-party plugins, you'll have to -write your own custom CMS Plugin. Don't worry though - writing a CMS Plugin is -very straightforward. - - -************************************* -Why would you need to write a plugin? -************************************* - -A plugin is the most convenient way to integrate content from another Django -app into a django CMS page. - -For example, suppose you're developing a site for a record company in django -CMS. You might like to have a "Latest releases" box on your site's home page. - -Of course, you could every so often edit that page and update the information. -However, a sensible record company will manage its catalogue in Django too, -which means Django already knows what this week's new releases are. - -This is an excellent opportunity to make use of that information to make your -life easier - all you need to do is create a django CMS plugin that you can -insert into your home page, and leave it to do the work of publishing information -about the latest releases for you. - -Plugins are **reusable**. Perhaps your record company is producing a series of -reissues of seminal Swiss punk records; on your site's page about the series, -you could insert the same plugin, configured a little differently, that will -publish information about recent new releases in that series. - -******** -Overview -******** - -A django CMS plugin is fundamentally composed of three things. - -* a plugin **editor**, to configure a plugin each time it is deployed -* a plugin **publisher**, to do the automated work of deciding what to publish -* a plugin **template**, to render the information into a web page - -These correspond to the familiar Model-View-Template scheme: - -* the plugin **model** to store its configuration -* the plugin **view** that works out what needs to be displayed -* the plugin **template** to render the information - -And so to build your plugin, you'll make it from: - -* a sub-class of :class:`cms.models.pluginmodel.CMSPlugin` to - **store the configuration** for your plugin instances -* a sub-class of :class:`cms.plugin_base.CMSPluginBase` that **defines - the operating logic** of your plugin -* a template that **renders your plugin** - -A note about :class:`cms.plugin_base.CMSPluginBase` -=================================================== - -:class:`cms.plugin_base.CMSPluginBase` is actually a sub-class of -:class:`django:django.contrib.admin.ModelAdmin`. - -Because :class:`~cms.plugin_base.CMSPluginBase` sub-classes ``ModelAdmin`` several important -``ModelAdmin`` options are also available to CMS plugin developers. These -options are often used: - -* ``exclude`` -* ``fields`` -* ``fieldsets`` -* ``form`` -* ``formfield_overrides`` -* ``inlines`` -* ``radio_fields`` -* ``raw_id_fields`` -* ``readonly_fields`` - -Please note, however, that not all ``ModelAdmin`` options are effective in a CMS -plugin. In particular, any options that are used exclusively by the -``ModelAdmin``'s ``changelist`` will have no effect. These and other notable options -that are ignored by the CMS are: - -* ``actions`` -* ``actions_on_top`` -* ``actions_on_bottom`` -* ``actions_selection_counter`` -* ``date_hierarchy`` -* ``list_display`` -* ``list_display_links`` -* ``list_editable`` -* ``list_filter`` -* ``list_max_show_all`` -* ``list_per_page`` -* ``ordering`` -* ``paginator`` -* ``preserve_fields`` -* ``save_as`` -* ``save_on_top`` -* ``search_fields`` -* ``show_full_result_count`` -* ``view_on_site`` - - -An aside on models and configuration -==================================== - -The plugin **model**, the sub-class of :class:`cms.models.pluginmodel.CMSPlugin`, -is actually optional. - -You could have a plugin that doesn't need to be configured, because it only -ever does one thing. - -For example, you could have a plugin that only publishes information -about the top-selling record of the past seven days. Obviously, this wouldn't -be very flexible - you wouldn't be able to use the same plugin for the -best-selling release of the last *month* instead. - -Usually, you find that it is useful to be able to configure your plugin, and this -will require a model. - +##################### +How to create Plugins +##################### ******************* The simplest plugin ******************* -You may use ``python manage.py startapp`` to set up the basic layout for you +We'll start with an example of a very simple plugin. + +You may use ``python manage.py startapp`` to set up the basic layout for your plugin app (remember to add your plugin to ``INSTALLED_APPS``). Alternatively, just add a file called ``cms_plugins.py`` to an existing Django application. -In ``cms_plugins.py``, you place your plugins. For our example, include the following code:: +Place your plugins in ``cms_plugins.py``. For our example, include the following code:: from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool from cms.models.pluginmodel import CMSPlugin - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ @plugin_pool.register_plugin class HelloPlugin(CMSPluginBase): @@ -180,7 +54,7 @@ There are two required attributes on those classes: but a plugin is not registered in that way. * ``name``: The name of your plugin as displayed in the admin. It is generally good practice to mark this string as translatable using - :func:`django.utils.translation.ugettext_lazy`, however this is optional. By + :func:`django.utils.translation.gettext_lazy`, however this is optional. By default the name is a nicer version of the class name. And one of the following **must** be defined if ``render_plugin`` attribute @@ -254,7 +128,7 @@ Now we need to change our plugin definition to use this model, so our new from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from .models import Hello @@ -266,7 +140,7 @@ Now we need to change our plugin definition to use this model, so our new cache = False def render(self, context, instance, placeholder): - context = super(HelloPlugin, self).render(context, instance, placeholder) + context = super().render(context, instance, placeholder) return context We changed the ``model`` attribute to point to our newly created ``Hello`` @@ -389,7 +263,7 @@ it becomes:: sections = models.ManyToManyField(Section) def copy_relations(self, oldinstance): - self.sections = oldinstance.sections.all() + self.sections.set(oldinstance.sections.all()) If your plugins have relational fields of both kinds, you may of course need to use *both* the copying techniques described above. @@ -400,7 +274,7 @@ Relations *between* plugins It is much harder to manage the copying of relations when they are from one plugin to another. See the GitHub issue `copy_relations() does not work for relations between cmsplugins #4143 -`_ for more details. +`_ for more details. ******** Advanced @@ -424,7 +298,7 @@ admin form for your foreign key references:: inlines = (ItemInlineAdmin,) def render(self, context, instance, placeholder): - context = super(ArticlePlugin, self).render(context, instance, placeholder) + context = super().render(context, instance, placeholder) items = instance.associated_item.all() context.update({ 'items': items, @@ -523,6 +397,27 @@ A **bad** example: {% endaddtoblock %} +.. note:: + If the Plugin requires javascript code to be rendered properly, + the class ``'cms-execute-js-to-render'`` can be added to the script tag. + This will download and execute all scripts with this class, which weren't present before, + when the plugin is first added to the page. + If the javascript code is protected from prematurely executing by + the EventListener for the event ``'load'`` and/or ``'DOMContentLoaded'``, + the following classes can be added to the script tag: + + =========================================== ======================================================== + Classname Corresponding javascript code + =========================================== ======================================================== + cms-trigger-event-document-DOMContentLoaded ``document.dispatchEvent(new Event('DOMContentLoaded')`` + cms-trigger-event-window-DOMContentLoaded ``window.dispatchEvent(new Event('DOMContentLoaded')`` + cms-trigger-event-window-load ``window.dispatchEvent(new Event('load')`` + =========================================== ======================================================== + + + The events will be triggered once after all scripts are successfully injected into the DOM. + + .. _plugin-context-processors: @@ -663,7 +558,7 @@ achieve this functionality: # child_classes = ['ChildCMSPlugin'] def render(self, context, instance, placeholder): - context = super(ParentCMSPlugin, self).render(context, instance, placeholder) + context = super().render(context, instance, placeholder) return context @@ -704,6 +599,23 @@ achieve this functionality: +If you have attributes of the parent plugin which you need to access in the +child you can access the parent instance using ``get_bound_plugin``: + +.. code-block:: django + + class ChildPluginForm(forms.ModelForm): + + class Meta: + model = ChildPlugin + exclude = () + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance: + parent, parent_cls = self.instance.parent.get_bound_plugin() + + .. _extending_context_menus: Extending context menus of placeholders or plugins @@ -731,10 +643,11 @@ Example:: render_template = "cms/plugins/alias.html" def render(self, context, instance, placeholder): - context = super(AliasPlugin, self).render(context, instance, placeholder) + context = super().render(context, instance, placeholder) if instance.plugin_id: - plugins = instance.plugin.get_descendants(include_self=True).order_by('placeholder', 'tree_id', 'level', - 'position') + plugins = instance.plugin.get_descendants( + include_self=True + ).order_by('placeholder', 'tree_id', 'level', 'position') plugins = downcast_plugins(plugins) plugins[0].parent_id = None plugins = build_plugin_tree(plugins) @@ -750,7 +663,10 @@ Example:: PluginMenuItem( _("Create Alias"), reverse("admin:cms_create_alias"), - data={'plugin_id': plugin.pk, 'csrfmiddlewaretoken': get_token(request)}, + data={ + 'plugin_id': plugin.pk, + 'csrfmiddlewaretoken': get_token(request) + }, ) ] @@ -759,13 +675,16 @@ Example:: PluginMenuItem( _("Create Alias"), reverse("admin:cms_create_alias"), - data={'placeholder_id': placeholder.pk, 'csrfmiddlewaretoken': get_token(request)}, + data={ + 'placeholder_id': placeholder.pk, + 'csrfmiddlewaretoken': get_token(request) + }, ) ] def get_plugin_urls(self): urlpatterns = [ - url(r'^create_alias/$', self.create_alias, name='cms_create_alias'), + re_path(r'^create_alias/$', self.create_alias, name='cms_create_alias'), ] return urlpatterns @@ -773,7 +692,9 @@ Example:: if not request.user.is_staff: return HttpResponseForbidden("not enough privileges") if not 'plugin_id' in request.POST and not 'placeholder_id' in request.POST: - return HttpResponseBadRequest("plugin_id or placeholder_id POST parameter missing.") + return HttpResponseBadRequest( + "plugin_id or placeholder_id POST parameter missing." + ) plugin = None placeholder = None if 'plugin_id' in request.POST: @@ -781,21 +702,30 @@ Example:: try: plugin = CMSPlugin.objects.get(pk=pk) except CMSPlugin.DoesNotExist: - return HttpResponseBadRequest("plugin with id %s not found." % pk) + return HttpResponseBadRequest( + "plugin with id %s not found." % pk + ) if 'placeholder_id' in request.POST: pk = request.POST['placeholder_id'] try: placeholder = Placeholder.objects.get(pk=pk) except Placeholder.DoesNotExist: - return HttpResponseBadRequest("placeholder with id %s not found." % pk) + return HttpResponseBadRequest( + "placeholder with id %s not found." % pk + ) if not placeholder.has_change_permission(request): - return HttpResponseBadRequest("You do not have enough permission to alias this placeholder.") + return HttpResponseBadRequest( + "You do not have enough permission to alias this placeholder." + ) clipboard = request.toolbar.clipboard clipboard.cmsplugin_set.all().delete() language = request.LANGUAGE_CODE if plugin: language = plugin.language - alias = AliasPluginModel(language=language, placeholder=clipboard, plugin_type="AliasPlugin") + alias = AliasPluginModel( + language=language, placeholder=clipboard, + plugin_type="AliasPlugin" + ) if plugin: alias.plugin = plugin if placeholder: diff --git a/docs/how_to/extending_page_title.rst b/docs/how_to/extending_page_title.rst index 39fa0034e37..324c8007bbc 100644 --- a/docs/how_to/extending_page_title.rst +++ b/docs/how_to/extending_page_title.rst @@ -75,7 +75,7 @@ permissions. .. note:: If you want to use your own admin class, make sure to exclude the live versions of the - extensions by using ``filter(extended_page__publisher_is_draft=True)`` on the queryset. + extensions by using ``filter(extended_object__publisher_is_draft=True)`` on the queryset. Continuing with the example model above, here's a simple corresponding ``PageExtensionAdmin`` class:: @@ -119,7 +119,7 @@ selected, it will open a modal dialog in which the *Page icon* field can be edit from cms.toolbar_pool import toolbar_pool from cms.extensions.toolbar import ExtensionToolbar - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from .models import IconExtension @@ -197,7 +197,7 @@ In this example, we need to loop over the titles for the page, and populate the from cms.toolbar_pool import toolbar_pool from cms.extensions.toolbar import ExtensionToolbar - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from .models import RatingExtension from cms.utils import get_language_list # needed to get the page's languages @toolbar_pool.register @@ -223,7 +223,7 @@ In this example, we need to loop over the titles for the page, and populate the # we now also need to get the titleset (i.e. different language titles) # for this page page = self._get_page() - titleset = page.title_set.filter(language__in=get_language_list(page.site_id)) + titleset = page.title_set.filter(language__in=get_language_list(page.node.site_id)) # create a 3-tuple of (title_extension, url, title) nodes = [(title_extension, url, title.title) for ( @@ -261,7 +261,7 @@ page extension model will be available on ``page.iconextension``. From there you can access the extra fields you defined in your extension, so you can use something like:: - {% load staticfiles %} + {% load static %} {# rest of template omitted ... #} @@ -352,7 +352,7 @@ low-level API to edit the toolbar according to your needs:: from cms.utils import get_cms_setting from cms.utils.page_permissions import user_can_change_page from django.urls import reverse, NoReverseMatch - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from .models import IconExtension @@ -401,15 +401,9 @@ Simplified Toolbar API The simplified Toolbar API works by deriving your toolbar class from ``ExtensionToolbar`` which provides the following API: - -* :meth:`cms.extensions.toolbar.ExtensionToolbar._setup_extension_toolbar`: this must be called first to setup - the environment and do the permission checking; -* :py:meth:`~cms.extensions.toolbar.ExtensionToolbar.get_page_extension_admin()`: for page extensions, retrieves the - correct admin URL for the related toolbar item; returns the extension instance (or `None` if not exists) - and the admin URL for the toolbar item; -* :py:meth:`~cms.extensions.toolbar.ExtensionToolbar.get_title_extension_admin()`: for title extensions, retrieves the - correct admin URL for the related toolbar item; returns a list of the extension instances - (or `None` if not exists) and the admin urls for each title of the current page; - -* :meth:`cms.toolbar.toolbar.CMSToolbar.get_or_create_menu` -* :meth:`cms.extensions.toolbar.ExtensionToolbar._setup_extension_toolbar` +* ``ExtensionToolbar.get_page_extension_admin()``: for page extensions, retrieves the correct admin + URL for the related toolbar item; returns the extension instance (or ``None`` if none exists) and + the admin URL for the toolbar item +* ``ExtensionToolbar.get_title_extension_admin()``: for title extensions, retrieves the correct + admin URL for the related toolbar item; returns a list of the extension instances (or ``None`` if + none exists) and the admin URLs for each title of the current page diff --git a/docs/how_to/index.rst b/docs/how_to/index.rst index 3bfe813976e..2c41573f226 100644 --- a/docs/how_to/index.rst +++ b/docs/how_to/index.rst @@ -7,23 +7,55 @@ How-to guides These guides presuppose some familiarity with django CMS. They cover some of the same territory as the :doc:`/introduction/index`, but in more detail. + +****** +Set-up +****** + .. toctree:: :maxdepth: 1 - + Install django CMS by hand - Create custom plugins - Customise menus - Create apphooks - Manage complex apphook configuration + + +************************ +Using core functionality +************************ + +.. toctree:: + :maxdepth: 1 + + Use placeholders outside the CMS Serve multiple languages Work with templates - Extend Page & Title models - Extend the Toolbar - Test your extensions - Use placeholders outside the CMS Manage caching Enable frontend editing for Page and Django models Create sitemaps Manage Page Types - Implement content creation wizards + + +************************** +Creating new functionality +************************** + +.. toctree:: + :maxdepth: 1 + + Create plugins + Create apphooks + Manage complex apphook configuration + Extend the Toolbar + Customise navigation menus + Create content creation wizards + Extend Page & Title models + Test your extensions + + +************ +Contributing +************ + +.. toctree:: + :maxdepth: 1 + Contribute a patch diff --git a/docs/how_to/install.rst b/docs/how_to/install.rst index 0ddabfc1949..83adb944b1e 100644 --- a/docs/how_to/install.rst +++ b/docs/how_to/install.rst @@ -60,7 +60,7 @@ Create a new project Create a new project:: - django-admin.py startproject myproject + django-admin startproject myproject If this is new to you, you ought to read the `official Django tutorial `_, which covers starting a new project. @@ -99,8 +99,10 @@ You will need to add the following to its list of ``INSTALLED_APPS``:: * `django-treebeard `_ is used to manage django CMS's page and plugin tree structures. -django CMS installs `django CMS admin style `_. This provides some styling that helps make django CMS administration components easier to work with. Technically it's an optional -component and does not need to be enabled in your project, but it's strongly recommended. +django CMS installs `django CMS admin style `_. +This provides some styling that helps make django CMS administration components easier to work with. +Technically it's an optional component and does not need to be enabled in your project, +but it's strongly recommended. In the ``INSTALLED_APPS``, **before** ``django.contrib.admin``, add:: @@ -174,7 +176,8 @@ Create an admin superuser:: Using ``cms check`` for configuration ************************************* -Once you have completed the minimum required set-up described above, you can use django CMS's built-in ``cms check`` command to help you identify and install other components. Run:: +Once you have completed the minimum required set-up described above, you can use django CMS's built-in ``cms check`` +command to help you identify and install other components. Run:: python manage.py cms check @@ -221,7 +224,7 @@ in the ``TEMPLATES['OPTIONS']['context_processors']``: Middleware ========== -in your :setting:`django:MIDDLEWARE_CLASSES` you'll need :class:`django:django.middleware.locale.LocaleMiddleware` - +in your :setting:`django:MIDDLEWARE` you'll need :class:`django:django.middleware.locale.LocaleMiddleware` - it's **not** installed in Django projects by default. Also add:: @@ -236,12 +239,17 @@ to the list. You can also add ``'cms.middleware.utils.ApphookReloadMiddleware'``. It's not absolutely necessary, but it's :ref:`useful `. If included, should be at the start of the list. +add the following configuration to your ``settings.py``:: + + X_FRAME_OPTIONS = 'SAMEORIGIN' Context processors ================== Add ``'cms.context_processors.cms_settings'`` to ``TEMPLATES['OPTIONS']['context_processors']``. +Also add ``'django.template.context_processors.i18n'`` if it's not already present. + ``cms check`` should now be unable to identify any further issues with your project. Some additional configuration is required however. @@ -254,18 +262,19 @@ URLs ==== In the project's ``urls.py``, add ``url(r'^', include('cms.urls'))`` to the ``urlpatterns`` list. It should come after -other patterns, so that specific URLs for other applications can be detected first. +other patterns, so that specific URLs for other applications can be detected first. Note: when using Django 2.0 or +later the syntax is ``re_path(r'^', include('cms.urls'))`` -You'll also need to have an import for ``django.conf.urls.include``. For example: +You'll also need to have an import for ``django.urls.include``. For example: .. code-block:: python :emphasize-lines: 1,5 - from django.conf.urls import url, include + from django.urls import re_path, include urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^', include('cms.urls')), + re_path(r'^admin/', admin.site.urls), + re_path(r'^', include('cms.urls')), ] The django CMS project will now run, as you'll see if you launch it with ``python manage.py runserver``. You'll be able @@ -278,8 +287,8 @@ do anything very useful with it though. Templates ========= -django CMS requires at least one template for its pages. The first template in the :setting:`CMS_TEMPLATES` list will -be the project's default template. +django CMS requires at least one template for its pages, so you'll need to add :setting:`CMS_TEMPLATES` to your +settings. The first template in the :setting:`CMS_TEMPLATES` list will be the project's default template. :: @@ -445,7 +454,7 @@ Django CMS CKEditor `Django CMS CKEditor`_ is the default text editor for django CMS. -.. _Django CMS CKEditor: https://github.com/divio/djangocms-text-ckeditor +.. _Django CMS CKEditor: https://github.com/django-cms/djangocms-text-ckeditor Install: ``pip install djangocms-text-ckeditor``. @@ -462,18 +471,18 @@ Miscellaneous plugins There are plugins for django CMS that cover a vast range of functionality. To get started, it's useful to be able to rely on a set of well-maintained plugins that cover some general content management needs. -* `djangocms-link `_ -* `djangocms-file `_ -* `djangocms-picture `_ -* `djangocms-video `_ -* `djangocms-googlemap `_ -* `djangocms-snippet `_ -* `djangocms-style `_ -* `djangocms-column `_ +* `djangocms-link `_ +* `djangocms-file `_ +* `djangocms-picture `_ +* `djangocms-video `_ +* `djangocms-googlemap `_ +* `djangocms-snippet `_ +* `djangocms-style `_ To install:: - pip install djangocms-link djangocms-file djangocms-picture djangocms-video djangocms-googlemap djangocms-snippet djangocms-style djangocms-column + pip install djangocms-link djangocms-file djangocms-picture djangocms-video djangocms-googlemap djangocms-snippet + djangocms-style and add:: @@ -484,7 +493,6 @@ and add:: 'djangocms_googlemap', 'djangocms_snippet', 'djangocms_style', - 'djangocms_column', to ``INSTALLED_APPS``. diff --git a/docs/how_to/languages.rst b/docs/how_to/languages.rst index 7a02307eeb3..de640193e26 100644 --- a/docs/how_to/languages.rst +++ b/docs/how_to/languages.rst @@ -24,24 +24,24 @@ on the subject. Here's a full example of ``urls.py``:: - from django.conf import settings - from django.conf.urls import include, url + from django.conf.urls.i18n import i18n_patterns from django.contrib import admin - from django.conf.urls.i18n import i18n_patterns, JavascriptCatalog from django.contrib.staticfiles.urls import staticfiles_urlpatterns + from django.urls import include, re_path + from django.views.i18n import JavaScriptCatalog - admin.autodiscover() - urlpatterns = [ - url(r'^jsi18n/(?P\S+?)/$', JavascriptCatalog.as_view()), - ] + admin.autodiscover() + urlpatterns = i18n_patterns( + re_path(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'), + ) urlpatterns += staticfiles_urlpatterns() # note the django CMS URLs included via i18n_patterns - urlpatterns += i18n_patterns('', - url(r'^admin/', admin.site.urls), - url(r'^', include('cms.urls')), + urlpatterns += i18n_patterns( + re_path(r'^admin/', include(admin.site.urls)), + re_path(r'^', include('cms.urls')), ) @@ -51,8 +51,8 @@ Monolingual URLs Of course, if you want only monolingual URLs, without a language code, simply don't use :func:`~django.conf.urls.i18n.i18n_patterns`:: urlpatterns += [ - url(r'^admin/', admin.site.urls), - url(r'^', include('cms.urls')), + re_path(r'^admin/', admin.site.urls), + re_path(r'^', include('cms.urls')), ] @@ -62,7 +62,7 @@ Store the user's language preference The user's preferred language is maintained through a browsing session. So that django CMS remembers the user's preference in subsequent sessions, it must be stored in a cookie. To enable this, ``cms.middleware.language.LanguageCookieMiddleware`` must -be added to the project's ``MIDDLEWARE_CLASSES`` setting. +be added to the project's ``MIDDLEWARE`` setting. See :ref:`determining_language_preference` for more information about how this works. @@ -99,7 +99,7 @@ Example: self.object = self.get_object() if hasattr(self.request, 'toolbar'): self.request.toolbar.set_object(self.object) - response = super(AnswerView, self).get(*args, **kwargs) + response = super().get(*args, **kwargs) return response diff --git a/docs/how_to/menus.rst b/docs/how_to/menus.rst index 23a0889efb1..0da8544992e 100644 --- a/docs/how_to/menus.rst +++ b/docs/how_to/menus.rst @@ -21,7 +21,7 @@ Create a ``cms_menus.py`` in your application, with the following:: from menus.base import Menu, NavigationNode from menus.menu_pool import menu_pool - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class TestMenu(Menu): @@ -115,7 +115,7 @@ We will do that with the example from above:: from menus.base import NavigationNode from menus.menu_pool import menu_pool - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from cms.menu_bases import CMSAttachMenu class TestMenu(CMSAttachMenu): diff --git a/docs/how_to/namespaced_apphooks.rst b/docs/how_to/namespaced_apphooks.rst index 3d3b5b53985..9bce387804d 100644 --- a/docs/how_to/namespaced_apphooks.rst +++ b/docs/how_to/namespaced_apphooks.rst @@ -180,7 +180,7 @@ In a new file ``cms_appconfig.py`` in the FAQ application: from app_data import AppDataForm from django.db import models from django import forms - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ class FaqConfig(AppHookConfig): @@ -254,17 +254,19 @@ Now let's create the apphook, and set it up with support for multiple instances. from aldryn_apphooks_config.app_base import CMSConfigApp from cms.apphook_pool import apphook_pool - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from .cms_appconfig import FaqConfig @apphook_pool.register class FaqApp(CMSConfigApp): name = _("Faq App") - urls = ["faq.urls"] app_name = "faq" app_config = FaqConfig + def get_urls(self, page=None, language=None, **kwargs): + return ["faq.urls"] + Define a list view for FAQ entries ---------------------------------- @@ -284,7 +286,7 @@ that only displays entries for the currently used namespace. In ``views.py``: template_name = 'faq/index.html' def get_queryset(self): - qs = super(IndexView, self).get_queryset() + qs = super().get_queryset() return qs.namespace(self.namespace) def get_paginate_by(self, queryset): @@ -359,12 +361,12 @@ URLconf .. code-block:: python - from django.conf.urls import url + from django.urls import re_path from . import views urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), + re_path(r'^$', views.IndexView.as_view(), name='index'), ] diff --git a/docs/how_to/testing.rst b/docs/how_to/testing.rst index d86e4dedcae..01c555bb5ed 100644 --- a/docs/how_to/testing.rst +++ b/docs/how_to/testing.rst @@ -18,12 +18,12 @@ test version of ``urls.py`` and tell your tests to use that. So you could create ``myapp/tests/urls.py`` with the following code:: from django.contrib import admin - from django.conf.urls import url, include + from django.urls import re_path, include urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^myapp/', include('myapp.urls')), - url(r'', include('cms.urls')), + re_path(r'^admin/', admin.site.urls), + re_path(r'^myapp/', include('myapp.urls')), + re_path(r'', include('cms.urls')), ] And then in your tests you can plug this in with the diff --git a/docs/how_to/toolbar.rst b/docs/how_to/toolbar.rst index 288aa48ed1a..0c4ce191605 100644 --- a/docs/how_to/toolbar.rst +++ b/docs/how_to/toolbar.rst @@ -4,321 +4,437 @@ How to extend the Toolbar ######################### -You can add and remove toolbar items. This allows you to integrate django CMS's frontend editing -mode into your application, and provide your users with a streamlined editing experience. +The django CMS toolbar provides an API that allows you to add, remove and manipulate toolbar items +in your own code. It helps you to integrate django CMS's frontend editing mode into your +application, and provide your users with a streamlined editing experience. -For the toolbar API reference, please refer to :ref:`toolbar-api-reference`. +.. seealso:: -.. important:: **Overlay** and **sideframe** + * :ref:`Extending the Toolbar ` in the tutorial + * :ref:`Toolbar API reference ` - Then django CMS *sideframe* has been replaced with an *overlay* mechanism. The API still refers - to the ``sideframe``, because it is invoked in the same way, and what has changed is merely the - behaviour in the user's browser. - In other words, *sideframe* and the *overlay* refer to different versions of the same thing. +********************************* +Create a ``cms_toolbars.py`` file +********************************* +In order to interact with the toolbar API, you need to create a +:class:`~cms.toolbar_base.CMSToolbar` sub-class in your own code, and register it. -*********** -Registering -*********** +This class should be created in your application's ``cms_toolbars.py`` file, where it will be +discovered automatically when the Django runserver starts. -There are two ways to control what gets shown in the toolbar. +You can also use the :setting:`CMS_TOOLBARS` to control which toolbar classes are loaded. -One is the :setting:`CMS_TOOLBARS`. This gives you full control over which -classes are loaded, but requires that you specify them all manually. +.. admonition:: Use the high-level toolbar APIs -The other is to provide ``cms_toolbars.py`` files in your apps, which will be -automatically loaded as long :setting:`CMS_TOOLBARS` is not set (or is set to -``None``). + You will find a ``toolbar`` object in the request in your views, and you may be tempted to + do things with it, like: -If you use the automated way, your ``cms_toolbars.py`` file should contain -classes that extend ``cms.toolbar_base.CMSToolbar`` and are registered using :meth:`~cms.toolbar_pool.ToolbarPool.register()`. -The register function can be used as a decorator. + .. code-block:: python -These classes have four attributes: + toolbar = request.toolbar + toolbar.add_modal_button('Do not touch', dangerous_button_url) -* ``toolbar`` (the toolbar object) -* ``request`` (the current request) -* ``is_current_app`` (a flag indicating whether the current request is handled by the same app as the function is in) -* ``app_path`` (the name of the app used for the current request) + \- but you should not, in the same way that it is not recommended to poke tweezers into + electrical sockets just because you can. -These classes must implement a ``populate`` or ``post_template_populate`` function. An optional -``request_hook`` function is available for you to overwrite as well. + Instead, you should **only** interact with the toolbar using a ``CMSToolbar`` class, and the + :ref:`documented APIs for managing it `. -* The populate functions will only be called if the current user is a staff user. -* The ``populate`` function will be called before the template and plugins are rendered. -* The ``post_template_populate`` function will be called after the template is rendered. -* The ``request_hook`` function is called before the view and may return a response. This way you - can issue redirects from a toolbar if needed + Similarly, although a generic :meth:`~cms.toolbar.items.ToolbarAPIMixin.add_item` method is + available, we provide higher-level methods for handling specific item types, and it is always + recommended that you use these instead. -These classes can define an optional ``supported_apps`` attribute, specifying which applications -the toolbar will work with. This is useful when the toolbar is defined in a different application -from the views it's related to. -``supported_apps`` is a tuple of application dotted paths (e.g: ``supported_apps = -('whatever.path.app', 'another.path.app')``. +********************************************** +Define and register a ``CMSToolbar`` sub-class +********************************************** -A simple example, registering a class that does nothing:: +.. code-block:: python - from cms.toolbar_pool import toolbar_pool from cms.toolbar_base import CMSToolbar + from cms.toolbar_pool import toolbar_pool + + class MyToolbarClass(CMSToolbar): + [...] + + toolbar_pool.register(MyToolbarClass) + +The ``cms.toolbar_pool.ToolbarPool.register`` method can also be used as a decorator: + +.. code-block:: python + :emphasize-lines: 1 @toolbar_pool.register - class NoopModifier(CMSToolbar): + class MyToolbarClass(CMSToolbar): + [...] - def populate(self): - pass - def post_template_populate(self): - pass +******************** +Populate the toolbar +******************** - def request_hook(self): - pass +Two methods are available to control what will appear in the django CMS toolbar: +* ``populate()``, which is called *before* the rest of the page is rendered +* ``post_template_populate()``, which is called *after* the page's template is rendered -.. warning:: +The latter method allows you to manage the toolbar based on the contents of the page, such as the +state of plugins or placeholders, but unless you need to do this, you should opt for the more +simple ``populate()`` method. - As the toolbar passed to ``post_template_populate`` has been already populated with items from - other applications, it might contain different items when processed by ``populate``. +.. code-block:: python + :emphasize-lines: 3-5 -.. tip:: + class MyToolbar(CMSToolbar): - You can change the toolbar or add items inside a plugin render method - (``context['request'].toolbar``) or inside a view (``request.toolbar``) + def populate(self): + # add items to the toolbar -************ -Adding items -************ +Now you have to decide exactly what items will appear in your toolbar. These can include: -Items can be added through the various :ref:`APIs ` -exposed by the toolbar and its items. +* :ref:`menus ` +* :ref:`buttons ` and button lists +* various other toolbar items -To add a :class:`cms.toolbar.items.Menu` to the toolbar, use -:meth:`cms.toolbar.toolbar.CMSToolbar.get_or_create_menu`. -Then, to add a link to your changelist that will open in the sideframe, use the -:meth:`cms.toolbar.items.ToolbarMixin.add_sideframe_item` method on the menu -object returned. +Add links and buttons to the toolbar +==================================== -When adding items, all arguments other than the name or identifier should be -given as **keyword arguments**. This will help ensure that your custom toolbar -items survive upgrades. +You can add links and buttons as entries to a menu instance, using the various +``add_`` methods. -Following our example in the :ref:`toolbar tutorial `, let's add the poll app -to the toolbar:: +====================== ============================================================= =========================================================== +Action Text link variant Button variant +====================== ============================================================= =========================================================== +Open link :meth:`~cms.toolbar.items.ToolbarAPIMixin.add_link_item` :meth:`~cms.toolbar.toolbar.CMSToolbar.add_button` +Open link in sideframe :meth:`~cms.toolbar.items.ToolbarAPIMixin.add_sideframe_item` :meth:`~cms.toolbar.toolbar.CMSToolbar.add_sideframe_button` +Open link in modal :meth:`~cms.toolbar.items.ToolbarAPIMixin.add_modal_item` :meth:`~cms.toolbar.toolbar.CMSToolbar.add_modal_button` +POST action :meth:`~cms.toolbar.items.ToolbarAPIMixin.add_ajax_item` +====================== ============================================================= =========================================================== - from django.urls import reverse - from django.utils.translation import ugettext_lazy as _ - from cms.toolbar_pool import toolbar_pool - from cms.toolbar_base import CMSToolbar +The basic form for using any of these is: - @toolbar_pool.register - class PollToolbar(CMSToolbar): +.. code-block:: python - def populate(self): - if self.is_current_app: - menu = self.toolbar.get_or_create_menu('poll-app', _('Polls')) - url = reverse('admin:polls_poll_changelist') - menu.add_sideframe_item(_('Poll overview'), url=url) + def populate(self): + self.toolbar.add_link_item( # or add_button(), add_modal_item(), etc + name='A link', + url=url + ) -However, there's already a menu added by the CMS which provides access to -various admin views, so you might want to add your menu as a sub menu there. -To do this, you can use positional insertion coupled with the fact that -:meth:`cms.toolbar.toolbar.CMSToolbar.get_or_create_menu` will return already existing -menus:: +Note that although these toolbar items may take various positional arguments in their methods, **we +strongly recommend using named arguments**, as above. This will help ensure that your own toolbar +classes and methods survive upgrades. See the reference documentation linked to in the table above +for details of the signature of each method. - from django.urls import reverse - from django.utils.translation import ugettext_lazy as _ - from cms.toolbar_pool import toolbar_pool - from cms.toolbar.items import Break - from cms.cms_toolbars import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK - from cms.toolbar_base import CMSToolbar +Opening a URL in an iframe +-------------------------- + +A common case is to provide a URL that opens in a sideframe or modal dialog on the same page. +*Administration...* in the site menu, that opens the Django admin in a sideframe, is a good +example of this. Both the sideframe and modal are HTML iframes. + +A typical use for a sideframe is to display an admin list (similar to that used in the +:ref:`tutorial example `): + +.. code-block:: python + :emphasize-lines: 1, 8-11 + + from cms.utils.urlutils import admin_reverse + [...] - @toolbar_pool.register class PollToolbar(CMSToolbar): def populate(self): - admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, _('Site')) - position = admin_menu.find_first(Break, identifier=ADMINISTRATION_BREAK) - menu = admin_menu.get_or_create_menu('poll-menu', _('Polls'), position=position) - url = reverse('admin:polls_poll_changelist') - menu.add_sideframe_item(_('Poll overview'), url=url) - admin_menu.add_break('poll-break', position=menu) + self.toolbar.add_sideframe_item( + name='Poll list', + url=admin_reverse('polls_poll_changelist') + ) -If you wish to simply detect the presence of a menu without actually creating -it, you can use :meth:`~cms.toolbar.toolbar.CMSToolbar.get_menu()`, which will -return the menu if it is present, or, if not, will return ``None``. +A typical use for a modal item is to display the admin for a model instance: +.. code-block:: python -***************************** -Modifying an existing toolbar -***************************** + self.toolbar.add_modal_item(name='Add new poll', url=admin_reverse('polls_poll_add')) -If you need to modify an existing toolbar (say to change the ``supported_apps`` attribute) you can -do this by extending the original one, and modifying the appropriate attribute. +However, you are not restricted to these examples, and you may open any suitable resource inside +the modal or sideframe. Note that protocols may need to match and the requested resource must allow +it. -If :setting:`CMS_TOOLBARS` is used to register the toolbars, add your own toolbar instead of the -original one, otherwise unregister the original and register your own:: +.. _create-toolbar-button: - from cms.toolbar_pool import toolbar_pool - from third.party.app.cms.toolbar_base import FooToolbar +Adding buttons to the toolbar +----------------------------- - @toolbar_pool.register - class BarToolbar(FooToolbar): - supported_apps = ('third.party.app', 'your.app') +A button is a sub-class of :class:`cms.toolbar.items.Button` - toolbar_pool.unregister(FooToolbar) +Buttons can also be added in a list - a :class:`~cms.toolbar.items.ButtonList` is a group of +visually-linked buttons. -=========================== -Adding Items Alphabetically -=========================== +.. code-block:: python + :emphasize-lines: 3-5 -Sometimes it is desirable to add sub-menus from different applications -alphabetically. This can be challenging due to the non-obvious manner in which -your apps will be loaded into Django and is further complicated when you add new -applications over time. + def populate(self): -To aid developers, django-cms exposes a :meth:`cms.toolbar.items.ToolbarMixin.get_alphabetical_insert_position` -method, which, if used consistently, can produce alphabetised sub-menus, even -when they come from multiple applications. + button_list = self.toolbar.add_button_list() + button_list.add_button(name='Button 1', url=url_1) + button_list.add_button(name='Button 2', url=url_2) -An example is shown here for an 'Offices' app, which allows handy access to -certain admin functions for managing office locations in a project:: - from django.urls import reverse - from django.utils.translation import ugettext_lazy as _ - from cms.toolbar_base import CMSToolbar - from cms.toolbar_pool import toolbar_pool - from cms.toolbar.items import Break, SubMenu - from cms.cms_toolbars import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK +.. _create-toolbar-menu: - @toolbar_pool.register - class OfficesToolbar(CMSToolbar): +Create a toolbar menu +===================== - def populate(self): - # - # 'Apps' is the spot on the existing djang-cms toolbar admin_menu - # 'where we'll insert all of our applications' menus. - # - admin_menu = self.toolbar.get_or_create_menu( - ADMIN_MENU_IDENTIFIER, _('Apps') - ) +The text link items described above can also be added as nodes to menus in the toolbar. - # - # Let's check to see where we would insert an 'Offices' menu in the - # admin_menu. - # - position = admin_menu.get_alphabetical_insert_position( - _('Offices'), - SubMenu +A menu is an instance of :class:`cms.toolbar.items.Menu`. In your ``CMSToolbar`` sub-class, you can +either create a menu, or identify one that already exists (in order to add new items to it, for +example), in the ``populate()`` or ``post_template_populate()`` methods, using +:meth:`~cms.toolbar.toolbar.CMSToolbar.get_or_create_menu`. + +.. code-block:: python + + def populate(self): + menu = self.toolbar.get_or_create_menu( + key='polls_cms_integration', + verbose_name='Polls' ) - # - # If zero was returned, then we know we're the first of our - # applications' menus to be inserted into the admin_menu, so, here - # we'll compute that we need to go after the first - # ADMINISTRATION_BREAK and, we'll insert our own break after our - # section. - # - if not position: - # OK, use the ADMINISTRATION_BREAK location + 1 - position = admin_menu.find_first( - Break, - identifier=ADMINISTRATION_BREAK - ) + 1 - # Insert our own menu-break, at this new position. We'll insert - # all subsequent menus before this, so it will ultimately come - # after all of our applications' menus. - admin_menu.add_break('custom-break', position=position) - - # OK, create our office menu here. - office_menu = admin_menu.get_or_create_menu( - 'offices-menu', - _('Offices ...'), - position=position +The ``key`` is unique menu identifier; ``verbose_name`` is what will be displayed in the menu. If +you know a menu already exists, you can obtain it with +:meth:`~cms.toolbar.toolbar.CMSToolbar.get_menu`. + +.. note:: + + It's recommended to namespace your ``key`` with the application name. Otherwise, another + application could unexpectedly interfere with your menu. + +Once you have your menu, you can add items to it in much the same way that you add them to the +toolbar. For example: + +.. code-block:: python + :emphasize-lines: 4-7 + + def populate(self): + menu = [...] + + menu.add_sideframe_item( + name='Poll list', + url=admin_reverse('polls_poll_changelist') + ) + + +To add a menu divider +--------------------- + +:meth:`~cms.toolbar.items.SubMenu.add_break` will place a +:class:`~cms.toolbar.items.Break`, a visual divider, in a menu list, to allow grouping of items. +For example: + +.. code-block:: python + + menu.add_break(identifier='settings_section') + + +To add a sub-menu +----------------- + +A sub-menu is a menu that belongs to another ``Menu``: + +.. code-block:: python + :emphasize-lines: 4-7 + + def populate(self): + menu = [...] + + submenu = menu.get_or_create_menu( + key='sub_menu_key', + verbose_name='My sub-menu' ) - # Let's add some sub-menus to our office menu that help our users - # manage office-related things. +You can then add items to the sub-menu in the same way as in the examples above. Note that a +sub-menu is an instance of :class:`~cms.toolbar.items.SubMenu`, and may not itself have further +sub-menus. - # Take the user to the admin-listing for offices... - url = reverse('admin:offices_office_changelist') - office_menu.add_sideframe_item(_('Offices List'), url=url) - # Display a modal dialogue for creating a new office... - url = reverse('admin:offices_office_add') - office_menu.add_modal_item(_('Add New Office'), url=url) +.. _finding_toolbar_items: - # Add a break in the sub-menus - office_menu.add_break() +****************************** +Finding existing toolbar items +****************************** - # More sub-menus... - url = reverse('admin:offices_state_changelist') - office_menu.add_sideframe_item(_('States List'), url=url) +``get_or_create_menu()`` and ``get_menu()`` +=========================================== - url = reverse('admin:offices_state_add') - office_menu.add_modal_item(_('Add New State'), url=url) +A number of methods and useful constants exist to get hold of and manipulate existing toolbar +items. For example, to find (using ``get_menu()``) and rename the *Site* menu: -Here is the resulting toolbar (with a few other menus sorted alphabetically -beside it) +.. code-block:: python -|alphabetized-toolbar-app-menus| + from cms.cms_toolbars import ADMIN_MENU_IDENTIFIER -.. |alphabetized-toolbar-app-menus| image:: ../images/alphabetized-toolbar-app-menus.png + class ManipulativeToolbar(CMSToolbar): -========================== -Adding items through views -========================== -Another way to add items to the toolbar is through our own views (``polls/views.py``). -This method can be useful if you need to access certain variables, in our case e.g. the -selected poll and its sub-methods:: + def populate(self): - from django.urls import reverse - from django.shortcuts import get_object_or_404, render - from django.utils.translation import ugettext_lazy as _ + admin_menu = self.toolbar.get_menu(ADMIN_MENU_IDENTIFIER) - from polls.models import Poll + admin_menu.name = "Site" +``get_or_create_menu()`` will equally well find the same menu, and also has the advantages that: - def detail(request, poll_id): - poll = get_object_or_404(Poll, pk=poll_id) - menu = request.toolbar.get_or_create_menu('polls-app', _('Polls')) - menu.add_modal_item(_('Change this Poll'), url=reverse('admin:polls_poll_change', args=[poll_id])) - menu.add_sideframe_item(_('Show History of this Poll'), url=reverse('admin:polls_poll_history', args=[poll_id])) - menu.add_sideframe_item(_('Delete this Poll'), url=reverse('admin:polls_poll_delete', args=[poll_id])) +* it can update the item's attributes itself + (``self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, 'Site')``) +* if the item doesn't exist, it will create it rather than raising an error. - return render(request, 'polls/detail.html', {'poll': poll}) -.. _url_changes: +``find_items()`` and ``find_first()`` +===================================== ---------------------- -Detecting URL changes ---------------------- +Search for items by their type: -Sometimes toolbar entries allow you to change the URL of the current object displayed in the -website. +.. code-block:: python -For example, suppose you are viewing a blog entry, and the toolbar allows the blog slug or URL to -be edited. The toolbar will watch the ``django.contrib.admin.models.LogEntry`` model and detect if -you create or edit an object in the admin via modal or sideframe view. After the modal or sideframe -closes it will redirect to the new URL of the object. + def populate(self): -To set this behaviour manually you can set the ``request.toolbar.set_object()`` function on which you can set the current object. + self.toolbar.find_items(item_type=LinkItem) -Example:: +will find all ``LinkItem``\s in the toolbar (but not for example in the menus in the toolbar - it +doesn't search *other* items in the toolbar for items of their own). - def detail(request, poll_id): - poll = get_object_or_404(Poll, pk=poll_id) - if hasattr(request, 'toolbar'): - request.toolbar.set_object(poll) - return render(request, 'polls/detail.html', {'poll': poll}) +:meth:`~cms.toolbar.items.ToolbarAPIMixin.find_items` returns a list of +:class:`~cms.toolbar.items.ItemSearchResult` objects; +:meth:`~cms.toolbar.items.ToolbarAPIMixin.find_first` returns the first object in that list. They +share similar behaviour so the examples here will use ``find_items()`` only. +The ``item_type`` argument is always required, but you can refine the search by using their other +attributes, for example:: + self.toolbar.find_items(Menu, disabled=True)) + +Note that you can use these two methods to search ``Menu`` and ``SubMenu`` classes for items too. + + +.. _toolbar_control_item_position: + +******************************************** +Control the position of items in the toolbar +******************************************** + +Methods to add menu items to the toolbar take an optional :option:`position` argument, that can be +used to control where the item will be inserted. + +By default (``position=None``) the item will be inserted after existing items in the same level of +the hierarchy (a new sub-menu will become the last sub-menu of the menu, a new menu will be become +the last menu in the toolbar, and so on). + +A position of ``0`` will insert the item before all the others. + +If you already have an object, you can use that as a reference too. For example: + +.. code-block:: python + + def populate(self): + + link = self.toolbar.add_link_item('Link', url=link_url) + self.toolbar.add_button('Button', url=button_url, position=link) + +will add the new button before the link item. + +Finally, you can use a :class:`~cms.toolbar.items.ItemSearchResult` as a position: + +.. code-block:: python + + def populate(self): + + self.toolbar.add_link_item('Link', url=link_url) + + link = self.toolbar.find_first(LinkItem) + + self.toolbar.add_button('Button', url=button_url, position=link) + +and since the ``ItemSearchResult`` can be cast to an integer, you could even do: + + self.toolbar.add_button('Button', url=button_url, position=link+1) + + +**************************************** +Control how and when the toolbar appears +**************************************** + +By default, your :class:`~cms.toolbar_base.CMSToolbar` sub-class will be active (i.e. its +``populate`` methods will be called) in the toolbar on every page, when the user ``is_staff``. +Sometimes however a ``CMSToolbar`` sub-class should only populate the toolbar when visiting pages +associated with a particular application. + +A ``CMSToolbar`` sub-class has a useful attribute that can help determine whether a toolbar should +be activated. ``is_current_app`` is ``True`` when the application containing the toolbar class +matches the application handling the request. + +This allows you to activate it selectively, for example: + +.. code-block:: python + :emphasize-lines: 3-4 + + def populate(self): + + if not self.is_current_app: + return + + [...] + +If your toolbar class is in another application than the one you want it to be active for, +you can list any applications it should support when you create the class: + +.. code-block:: python + + supported_apps = ['some_app'] + +``supported_apps`` is a tuple of application dotted paths (e.g: ``supported_apps = +('whatever.path.app', 'another.path.app')``. + +The attribute ``app_path`` will contain the name of the application handling the current request +- if ``app_path`` is in ``supported_apps``, then ``is_current_app`` will be ``True``. + + +***************************** +Modifying an existing toolbar +***************************** + +If you need to modify an existing toolbar (say to change an attribute or the behaviour of a method) +you can do this by creating a sub-class of it that implements the required changes, and registering +that instead of the original. + +The original can be unregistered using ``toolbar_pool.unregister()``, as in the example below. +Alternatively if you originally invoked the toolbar class using :setting:`CMS_TOOLBARS`, you will +need to modify that to refer to the new one instead. + +An example, in which we unregister the original and register our own:: + + + from cms.toolbar_pool import toolbar_pool + from third_party_app.cms_toolbar import ThirdPartyToolbar + + @toolbar_pool.register + class MyBarToolbar(ThirdPartyToolbar): + [...] + + toolbar_pool.unregister(ThirdPartyToolbar) + + +.. _url_changes: + +********************************** +Detecting URL changes to an object +********************************** If you want to watch for object creation or editing of models and redirect after they have been added or changed add a ``watch_models`` attribute to your toolbar. @@ -334,21 +450,27 @@ Example:: After you add this every change to an instance of ``Poll`` via sideframe or modal window will trigger a redirect to the URL of the poll instance that was edited, according to the toolbar -status: if in *draft* mode the ``get_draft_url()`` is returned (or ``get_absolute_url()`` if the -former does not exists), if in *live* mode and the method exists ``get_public_url()`` is returned. +status: + +* in *draft* mode the ``get_draft_url()`` is returned (or ``get_absolute_url()`` if the former + does not exist) +* in *live* mode, and the method exists, ``get_public_url()`` is returned. ******** Frontend ******** -The toolbar adds a class ``cms-ready`` to the **html** tag when ready. Additionally we add -``cms-toolbar-expanded`` when the toolbar is fully expanded. We also add -``cms-toolbar-expanding`` and ``cms-toolbar-collapsing`` classes while toolbar -is animating. +If you need to interact with the toolbar, or otherwise account for it in your site's frontend code, +it provides CSS and JavaScript hooks for you to use. -The toolbar also fires a JavaScript event called **cms-ready** on the document. +It will add various classes to the page's ```` element: + +* ``cms-ready``, when the toolbar is ready +* ``cms-toolbar-expanded``, when the toolbar is fully expanded +* ``cms-toolbar-expanding`` and ``cms-toolbar-collapsing`` during toolbar animation. + +The toolbar also fires a JavaScript event called ``cms-ready`` on the document. You can listen to this event using jQuery:: CMS.$(document).on('cms-ready', function () { ... }); - diff --git a/docs/index.rst b/docs/index.rst index 4ee97aea7fe..6395effb8c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,23 +83,36 @@ Technical reference material, for classes, methods, APIs, commands. Join us online ************** -django CMS is supported by a friendly and very knowledgeable community. +The `django CMS Association `_ is a non-profit +organisation that exists to support the development of django CMS and its community. + .. rst-class:: column column3 -Our IRC channel, #django-cms, is on ``irc.freenode.net``. If you don't have an IRC client, you can -`join our IRC channel using the KiwiIRC web client -`_, which works pretty well. +Slack +===== + +Join `our friendly Slack group `_ for +**support** and to **share ideas** and **discuss technical questions** with +other members of the community. + .. rst-class:: column column3 -Our `django CMS users email list `_ is for **general** django CMS questions and discussion +Discourse +========= + +Our `Discourse forum `_ is also used for +discussion of django CMS, particularly to manage its technical development process. + .. rst-class:: column column3 -Our `django CMS developers email list -`_ is for discussions about the -**development of django CMS** +StackOverflow +============= + +`StackOverflow `_ is also a good place +for questions around django CMS and its plugin ecosystem. *************** @@ -138,19 +151,30 @@ This document refers to version |release|. Django/Python compatibility table ================================= -=========== ========= ========= === === ==== ==== === === === === === === -django CMS Django Python ------------ ------------------------------------------ ---------------------------- -\ 1.4 & 1.5 1.6 & 1.7 1.8 1.9 1.10 1.11 2.6 2.7 3.3 3.4 3.5 3.6 -=========== ========= ========= === === ==== ==== === === === === === === -3.0.18 ✓ ✓ ⨯ ⨯ ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ ⨯ -3.1.7 ⨯ ✓ ✓ ⨯ ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ ⨯ -3.2.0 ⨯ ✓ ✓ ⨯ ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ ⨯ -3.2.1-3.2.5 ⨯ ✓ ✓ ✓ ⨯ ⨯ ✓ ✓ ✓ ✓ ✓ ⨯ -3.3.0-3.4.1 ⨯ ⨯ ✓ ✓ ⨯ ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ -3.4.2-3.4.4 ⨯ ⨯ ✓ ✓ ✓ ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ -3.4.5-3.5.x ⨯ ⨯ ✓ ✓ ✓ ✓ ⨯ ✓ ✓ ✓ ✓ ✓ -=========== ========= ========= === === ==== ==== === === === === === === +*LTS* in the table indicates a combination of Django and django CMS *both* covered +by a long-term support policy. + +*✓* indicates that the version has been tested and works. *×* indicates that it has not been tested, or +is known to be incompatible. + +=========== === === === === === === === === === === === === ==== ==== === === === === +django CMS Python Django +----------- ------------------------------- ----------------------------------------- +\ 3.8 3.7 3.6 3.5 3.4 3.3 2.7 2.6 3.0 2.2 2.1 2.0 1.11 1.10 1.9 1.8 1.6 1.4 +=========== === === === === === === === === === === === === ==== ==== === === === === +3.7.x ✓ ✓ ✓ ✓ ✓ ✓ ✓ × ✓ LTS ✓ ✓ LTS × × × × × +3.6.x × ✓ ✓ ✓ ✓ ✓ ✓ × x ✓ ✓ ✓ ✓ × × × × × +3.5.x × ✓ ✓ ✓ ✓ ✓ ✓ × × × × × ✓ ✓ ✓ ✓ × × +3.4.5 × × ✓ ✓ ✓ ✓ ✓ × × × × × LTS ✓ ✓ LTS × × +3.4.2 × × × ✓ ✓ ✓ ✓ × × × × × × ✓ ✓ ✓ × × +3.4.1 × × × ✓ ✓ ✓ ✓ × × × × × × × ✓ ✓ × × +3.3.x × × × ✓ ✓ ✓ ✓ × × × × × × × ✓ ✓ × × +3.2.1 × × × ✓ ✓ ✓ ✓ ✓ × × × × × × ✓ ✓ ✓ × +3.2.0 × × × × ✓ ✓ ✓ ✓ × × × × × × × ✓ ✓ × +3.1.7 × × × × ✓ ✓ ✓ ✓ × × × × × × × ✓ ✓ × +3.0.18 × × × × ✓ ✓ ✓ ✓ × × × × × × × × ✓ ✓ +=========== === === === === === === === === === === === === ==== ==== === === === === + .. _Python: https://www.python.org @@ -170,8 +194,8 @@ project. introduction/index how_to/index - topics/index reference/index + topics/index contributing/index upgrade/index user/index diff --git a/docs/introduction/01-install.rst b/docs/introduction/01-install.rst index 3df9c5e5409..3173e79177a 100644 --- a/docs/introduction/01-install.rst +++ b/docs/introduction/01-install.rst @@ -1,3 +1,5 @@ +:sequential_nav: next + .. _install-django-cms-tutorial: ##################### @@ -10,7 +12,7 @@ We'll get started by setting up our environment. Requirements ************ -django CMS requires Django 1.8 or newer, and Python 2.7 or 3.3 or newer. This tutorial assumes +django CMS requires Django 1.11 or newer, and Python 2.7 or 3.3 or newer. This tutorial assumes you are using Python 3. ************************ @@ -45,6 +47,7 @@ Update pip inside the virtual environment Use the django CMS installer ============================ + The `django CMS installer `_ is a helpful script that takes care of setting up a new project. @@ -61,25 +64,16 @@ Create a new directory to work in, and ``cd`` into it:: Run it to create a new Django project called ``mysite``:: - djangocms -f -p . mysite + djangocms mysite This means: * run the django CMS installer -* install Django Filer too (``-f``) - **required for this tutorial** -* use the current directory as the parent of the new project directory (``-p .``) * call the new project directory ``mysite`` -.. note:: **About Django Filer** - - Django Filer, a useful application for managing files and processing images. Although it's not - required for django CMS itself, a vast number of django CMS addons use it, and nearly all django - CMS projects have it installed. If you know you won't need it, omit the flag. See the `django - CMS installer documentation for more information `_. - .. warning:: - djangocms-installer expects directory ``.`` to be empty at this stage, and will check for this, + djangocms-installer expects current directory to be empty at this stage, and will check for this, and will warn if it's not. You can get it to skip the check and go ahead anyway using the ``-s`` flag; **note that this may overwrite existing files**. diff --git a/docs/introduction/02-templates_placeholders.rst b/docs/introduction/02-templates_placeholders.rst index 6bf5fd99b7c..24e92ce019c 100644 --- a/docs/introduction/02-templates_placeholders.rst +++ b/docs/introduction/02-templates_placeholders.rst @@ -1,3 +1,5 @@ +:sequential_nav: both + ######################## Templates & Placeholders ######################## @@ -136,8 +138,8 @@ The menu we use in ``mysite/templates/base.html`` is: .. code-block:: html+django -