`` to the DOM. It is suggested to enable the ``margin-top`` and ``margin-bottom`` CSS styles, so that the ruler can be positioned appropriately. HeadingPlugin ============= This plugins adds a text heading ``
``...```` to the DOM. Although simple headings can be
achieved with the **TextPlugin**, there they can't be styled using special CSS classes or styles.
Here the **HeadingPlugin** can be used, since any allowed CSS class or style can be added.
CustomSnippetPlugin
===================
Not every collection of DOM elements can be composed using the Cascade plugin system. Sometimes one
might want to add a simple HTML snippet. Altough it is quite simple to create a customized plugin
yourself, an easier approach to just render an arbitrary HTML snippet, is to use the
**CustomSnippetPlugin**. This can be achieved by adding the customized template to the project's
``settings.py``:
.. code-block:: python
CMSPLUGIN_CASCADE = {
# other settings
'plugins_with_extra_render_templates': {
'CustomSnippetPlugin': [
('myproject/snippets/custom-template.html', "Custom Template Identifier"),
# other tuples
],
},
}
Now, when editing the page, a plugin named **Custom Snippet** appears in the *Generic* section in
the plugin's dropdown menu. This plugin then offers a select element, where the site editor then can
chose between the configured templates.
Adding children to a CustomSnippetPlugin
----------------------------------------
It is even possible to add children to the **CustomSnippetPlugin**. Simple add these templatetag_s
to the customized template, and all plugins which are children of the **CustomSnippetPlugin** will
be rendered as well.
.. code-block:: django
{% load cms_tags %}
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
.. _templatetag: https://docs.djangoproject.com/en/stable/ref/templates/language/#tags
================================================
FILE: docs/source/hide-plugins.rst
================================================
==============================
Conditionally hide some plugin
==============================
Sometimes a placholder contains some plugins, which temporarily should not show up while rendering.
If this feature is enabled, then instead of deleting them, it is possible to hide them.
Enable the meachanism
=====================
In the projects ``settings.py``, add:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'allow_plugin_hiding': True,
...
}
By default, this feature is disabled. If enabled, **djangocms-cascade** adds a checkbox to every
plugin editor. This checkbox is labeled *Hide plugin*. If checked, the plugin and all of it's
children are not rendered in the current tree. To easily distinguish hidden plugins in structure
mode, they are rendered using a shaded background.
================================================
FILE: docs/source/icon-fonts.rst
================================================
======================
Using Fonts with Icons
======================
Introduction
============
Sometimes we want to enrich our web pages with vectorized symbols. A lot of them can be found in
various font libraries, such as `Font Awesome`_, `Material Icons`_, `Streamline Icons`_ and many
more. A typical approach would be to upload the chosen SVG symbol, and use it as image. This
process however is time consuming and error-prone to organize. Therefore, **djangocms-cascade**
offers an optional submodule, so that we can work with externally packed icon fonts.
In order to use such a font, currently we must use Fontello_, an external service for icon font
packaging. In the future, this service might be integrated into **djangocms-cascade** itself.
This submodule, if enabled adds three additional plugins: **Icon with frame**, **Simple icon** and
**Icon in text**. Additionally it allows to decorate buttons with an icon on the left or right side
of its main content.
Configuration
-------------
To enable this service in **djangocms-cascade**, in ``settings.py`` add:
.. code-block:: python
INSTALLED_APPS = [
…
'cmsplugin_cascade',
'cmsplugin_cascade.icon',
…
]
CMSPLUGIN_CASCADE_PLUGINS = [
…
'cmsplugin_cascade.icon',
…
]
This submodule, can of course be combined with all other submodules available for the Cascade
ecosystem.
If ``CMS_PLACEHOLDER_CONF`` is used to configure available plugins for each placeholder, assure
that the ``TextIconPlugin`` is added to the list of ``text_only_plugins``.
Since the CKEditor widget must load the font stylesheets for it's own WYSIWIG mode, we have to add
this special setting to our configuration:
.. code-block:: python
from django.core.urlresolvers import reverse_lazy
from django.utils.text import format_lazy
CKEDITOR_SETTINGS = {
…
'stylesSet': format_lazy(reverse_lazy('admin:cascade_texteditor_config')),
}
Uploading the Font
==================
In order to start with an external font icon, choose one or more icons and/or whole font families
from the Fontello_ website and download the generated webfont zip-file to a local folder on your
computer.
In Django's admin backend, change into ``Start › django CMS Cascade › Uploaded Icon Fonts`` and
add an Icon Font object. Choose an appropriate name and upload the just downloaded webfont file,
without unzipping it. After the upload completed, all the imported icons appear grouped by their
font family name. They now are ready for being used by the Icon plugin.
.. attention::
The icon fonts generated by Fontello_, offer a generated ``….css`` file containing a mapping of
private UTF-8 characters onto their font symbol. This means that the genarated font files may
have an overlapping encoding. Therefore each uploaded font requires a unique CSS prefix,
otherwise it wouldn't be possible to use more than one icon font per page. This prefix must be
set under Fontello's settings, located left of the **Download webfont** button.
Attempting to upload an icon fonts with a CSS prefix, which is already used, will be rejected.
.. warning::
Depending on your settings, Safari auto-unzips that file and hence makes it unusable for
re-upload. Either change your settings in Safari (Preferences > General > Open "safe" files),
or use another browser.
.. note::
During the 0.17-series of **djangocms-cascade**, an icon font had to be selected per page,
rather than per element. This feature turned out to be impractical and has been reverted to
the pre-0.17 behaviour.
Using the Icon Plugin
=====================
In **djangocms-cascade**, currently four plugins make use of the icon font sublibrary. These
are the **Simple Icon**, the **Icon with frame**, the **Button** and the **Icon in Text** plugin.
The latter is available only as subplugin of the **Text Editor** plugin.
In their respective editors, the user may select one of the uploaded icon fonts. Each time one
of them is selected, the table of symbols is rerendered. Use the search field on the top of the
table to restrict the list of icons, in case there are too many.
Choose the desired symbol, its size and color. Optionally choose a background color, a border with
width, color and style, and the relative position in respect of its wrapping element. After saving
the form, that element should appear inside the chosen container.
Shared Settings
---------------
By default, the **IconPlugin** is configured to allow to share the following styling attributes:
* Icon size
* Icon color
* Background color, or without background
* Text alignment
* Border width, color and style
* Border radius
By storing these attributes under a common name, one can reuse them across various icons, without
having to set them for each one, separately. Additionally, each of the shared styling attributes
can be changed globally in Django's admin backend at
``Start › django CMS Cascade › Shared between Plugins``. For details please refer to the section
about :ref:`sharable-fields`.
Using the Icon Plugin in plain text
===================================
If **django-CMS** is configured to use the `CKEditor for django-CMS`_, then you may use the
**Icon Plugin** inside plain text. Place the cursor at the desired location in text and select
**Icon** from the pull down menu **CMS Plugins**. This opens a popup where you may select the
font family and the symbol. All other attributes described above, are not available with this
type of plugin.
.. _Font Awesome: http://fontawesome.io/
.. _Material Icons: https://design.google.com/icons/
.. _Streamline Icons: https://streamlineicons.com/
.. _Fontello: http://fontello.com/
.. _CKEditor for django-CMS: https://pypi.org/project/djangocms-text-ckeditor/
================================================
FILE: docs/source/impatient.rst
================================================
=================
For the Impatient
=================
This HowTo gives you a quick instruction on how to get a demo of **djangocms-cascade** up and
running. It also is a good starting point to ask questions or report bugs, since its backend is
used as a fully functional reference implementation, used by the unit tests of project.
Create a Python Virtual Environment
===================================
To keep environments separate, create a virtual environment and install external dependencies.
Missing packages with JavaScript files and Style Sheets, which are not available via pip must be
installed via npm:
Dependency packaging to made easy with Pipenv or Poetry.
.. code-block:: bash
$ git clone --depth=1 https://github.com/jrief/djangocms-cascade.git
$ cd djangocms-cascade/examples/bs4demo
$ python -m venv .venv
$ poetry shell
$ poetry update
Initialize the database, create a superuser and start the development server:
.. code-block:: bash
$ cd djangocms-cascade/examples/bs4demo
$ npm install
$ ./manage.py migrate
$ ./manage.py createsuperuser
$ ./manage.py runserver
Point a browser to http://localhost:8000/?edit and log in as the super user you just
created. Hit "next" and fill out the form to create your first page. Afterwards, click **Structure**
on the top of the page. A heading named **Main Content** appears, it symbolizes our main
**django-CMS** Placeholder.
Locate the plus sign right to the heading and click on it. From its context menu select
**Container** located in the section **Bootstrap**:
|add-container|
.. |add-container| image:: _static/bootstrap3/add-container.png
This brings you into the editor mode for a Bootstrap container. To this container you may add one or
more Bootstrap **Rows**. Inside these rows you may organize the layout using some Bootstrap
**Columns**.
Please proceed with the detailed explanation on how to use the
:ref:`Bootstrap's grid ` system within **djangocms-cascade**.
================================================
FILE: docs/source/index.rst
================================================
============================================
Welcome to DjangoCMS-Cascade's documentation
============================================
Project's home
==============
Check for the latest release of this project on GitHub_.
Please report bugs or ask questions using the `Issue Tracker`_.
Project's goals
===============
#. Create a modular system, which allows programmers to add simple widget code, without having to
implement an extra djangoCMS_ plugins for each of them.
#. Make available a meaningful subset of widgets as available for the most common CSS frameworks,
such as `Twitter Bootstrap`_. With these special plugins, in many configurations, **djangoCMS**
can be operated using one single template, containing one generic placeholder.
#. Extend this **djangoCMS** plugin, to be used with other CSS frameworks such as `Foundation 5`_,
Unsemantic_ and others.
#. Use the base functionality of **djangoCMS-Cascade** to easily add special plugins. For instance,
djangoSHOP_ implements all its cart and checkout specific forms this way.
Contents:
=========
.. toctree::
:maxdepth: 2
:numbered:
impatient
introduction
installation
link-plugin
bootstrap3/index
icon-fonts
leaflet
client-side
section
segmentation
sharable-fields
customize-styles
render-template
hide-plugins
clipboard
strides
sphinx
customized-plugins
generic-plugins
changelog
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _Github: https://github.com/jrief/djangocms-cascade
.. _Issue Tracker: https://github.com/jrief/djangocms-cascade/issues
.. _djangoCMS: https://www.django-cms.org/
.. _djangoSHOP: https://www.django-shop.org/
.. _Twitter Bootstrap: http://getbootstrap.com/
.. _Foundation 5: http://foundation.zurb.com/
.. _Grid System 960: http://960.gs/
.. _Unsemantic: http://unsemantic.com/
================================================
FILE: docs/source/installation.rst
================================================
============
Installation
============
Install the latest stable release
.. code-block:: bash
$ pip install djangocms-cascade
or the current development release from github
.. code-block:: bash
$ pip install -e git+https://github.com/jrief/djangocms-cascade.git#egg=djangocms-cascade
Python Package Dependencies
===========================
Due to some incompatibilities in the API of Django, django-CMS and djangocms-text-ckeditor, please
only use these combinations of Python package dependencies:
djangocms-cascade-0.11.x
------------------------
* Django_ >=1.8, <=1.9
* Django-CMS_ >=3.2, <=3.3
* djangocms-text-ckeditor_ == 3.0
djangocms-cascade-0.12.x
------------------------
* Django_ >=1.9, <1.11
* Django-CMS_ >=3.4.3
* djangocms-text-ckeditor_ >= 3.3
djangocms-cascade-0.13.x
------------------------
* Django_ >=1.9, <1.11
* Django-CMS_ >=3.4.3
* djangocms-text-ckeditor_ >= 3.4
djangocms-cascade-0.14.x
------------------------
* Django_ >=1.9, <1.11
* Django-CMS_ >=3.4.4
* djangocms-text-ckeditor_ >= 3.4
* django-filer_ >= 1.2.8
djangocms-cascade-0.17.x - 0.19.x
---------------------------------
* Django_ >=1.10, <2.0
* Django-CMS_ >=3.4.4, <=3.6
* djangocms-text-ckeditor_ >= 3.4
djangocms-cascade-1.0.x
-----------------------
* Django_ >=1.11, <=2.1
* Django-CMS_ >=3.5.3, <=3.6.x
* djangocms-text-ckeditor_ >= 3.7
other combinations might work, but have not been tested.
Optional packages
-----------------
If you intend to use Image, Picture, Jumbotron, or FontIcons you will have to install django-filer
in addition:
.. code-block:: bash
$ pip install django-filer
For a full list of working requirements see the `requirements folder`_ in the sources.
.. _requirements folder: https://github.com/jrief/djangocms-cascade/tree/master/requirements
Create a database schema
========================
.. code-block:: bash
./manage.py migrate cmsplugin_cascade
Install Dependencies not handled by PIP
=======================================
Since the Bootstrap CSS and other JavaScript files are part of their own repositories, they are
not shipped within this package. Furthermore, as they are not part of the PyPI network, they have
to be installed through the `Node Package Manager`_, ``npm``.
In your Django projects it is good practice to keep a reference onto external node modules using
the file ``packages.json`` added to its own version control repository, rather than adding the
complete node package.
.. code-block:: bash
cd my-project-dir
npm init
npm install bootstrap@3 bootstrap-sass@3 jquery@3 leaflet@1 leaflet-easybutton@2.2 picturefill select2@4 --save
If the Django project contains already a file named ``package.json``, then skip the ``npm init``
in the above command.
The node packages ``leaflet`` and ``leaflet-easybutton`` are only required if the Leaflet plugin
is activated.
The node packages ``picturefill`` is a shim to support the ``srcset`` and ``sizes`` attributes on
``
`` elements. Please check `browser support`_ if that feature is required in your
project.
The node packages ``select2`` is required for autofilling the select box in Link plugins. It is
optional, but strongly suggested.
Remember to commit the changes in ``package.json`` into the projects version control repository.
Since these Javascript and Stylesheet files are located outside of the project's ``static`` folder,
we must add them explicitly to our lookup path, using ``STATICFILES_DIRS`` in ``settings.py``:
.. code-block:: python
STATICFILES_DIRS = [
...
('node_modules', os.path.join(MY_PROJECT_DIR, 'node_modules')),
]
Using AngularJS instead of jQuery
---------------------------------
If you prefer AngularJS over jQuery, then replace the above install command with:
.. code-block:: bash
npm install bootstrap@3 bootstrap-sass@3 angular@1.5 angular-animate@1.5 angular-sanitize@1.5 angular-ui-bootstrap@0.14 leaflet@1 leaflet-easybutton@2.2 picturefill select2@4 --save
Remember to point to the prepared AngularJS templates using this setting:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'bootstrap3': {
'template_basedir': 'angular-ui',
},
...
}
Configuration
=============
Add ``'cmsplugin_cascade'`` to the list of ``INSTALLED_APPS`` in the project’s ``settings.py``
file. Optionally add 'cmsplugin_cascade.extra_fields' and/or 'cmsplugin_cascade.sharable' to
the list of ``INSTALLED_APPS``. Make sure that these entries are located before the entry ``cms``.
Configure the CMS plugin
------------------------
.. code-block:: python
INSTALLED_APPS = (
...
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard', # optional
'cmsplugin_cascade.extra_fields', # optional
'cmsplugin_cascade.sharable', # optional
'cmsplugin_cascade.segmentation', # optional
'cms',
...
)
Activate the plugins
--------------------
By default, no **djangocms-cascade** plugins is activated. Activate them in the project’s
``settings.py`` with the directive ``CMSPLUGIN_CASCADE_PLUGINS``.
To activate all available Bootstrap plugins, use:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS = ['cmsplugin_cascade.bootstrap3']
If for some reason, only a subset of the available Bootstrap plugins shall be activated, name each
of them. If for example, only the grid system shall be used but no other Bootstrap plugins, then
configure:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS = ['cmsplugin_cascade.bootstrap3.container']
A very useful plugin is the **LinkPlugin**. It superseds the djangocms-link_-plugin, normally used
together with the CMS.
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.link')
If this plugin is enabled ensure, that the node package ``select2`` has been installed and findable
by the static files finder using these directives in ``settings.py``:
.. code-block:: python
SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'
SELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'
:ref:`generic-plugins` which are not opinionated towards a specific CSS framework, are kept in a
separate folder. It is strongly suggested to always activate them:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.generic')
Sometimes it is useful to do a :ref:`segmentation`. Activate this by adding its plugin:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.segmentation')
When :ref:`icon-fonts`: on your site, add ``'cmsplugin_cascade.icon'`` to ``INSTALLED_APPS``
and add it to the configured Cascade plugins:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.icon')
Special settings when using the TextPlugin
------------------------------------------
Since it is possible to add plugins from the Cascade ecosystem as children to the
`djangocms-text-ckeditor`_, we must add a special configuration:
.. code-block:: python
from django.core.urlresolvers import reverse_lazy
from django.utils.text import format_lazy
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'skin': 'moono-lisa',
'toolbar': 'CMS',
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),
}
The last line in this configuration invokes a special function, which adds special configuration settings to the
CKTextEditor plugin.
.. note:: The skin ``moono-lisa`` has been introduced in Django CKEditor version 3.5, so if you upgrade from an earlier
version, please adopt this in your settings.
Restrict plugins to a particular placeholder
--------------------------------------------
.. warning:: You **must** set ``parent_classes`` for your placeholder, else you
won't be able to add a container to your placeholder. This means that as an
absolute minimum, you must add this to your settings:
.. code-block:: python
CMS_PLACEHOLDER_CONF = {
...
'content': {
'parent_classes': {'BootstrapContainerPlugin': None,},
},
...
}
Unfortunately **django-CMS** does not allow to declare dynamically which plugins are eligible to be
added as children of other plugins. This is determined while bootstrapping the Django project and
thus remains static. We therefore must somehow trick the CMS to behave as we want.
Say, our Placeholder named "Main Content" shall accept the **BootstrapContainerPlugin** as its only
child, we then must use this CMS settings directive:
.. code-block:: python
CMS_PLACEHOLDER_CONF = {
...
'Main Content Placeholder': {
'plugins': ['BootstrapContainerPlugin'],
'text_only_plugins': ['TextLinkPlugin'],
'parent_classes': {'BootstrapContainerPlugin': None},
'glossary': {
'breakpoints': ['xs', 'sm', 'md', 'lg'],
'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},
'fluid': False,
'media_queries': {
'xs': ['(max-width: 768px)'],
'sm': ['(min-width: 768px)', '(max-width: 992px)'],
'md': ['(min-width: 992px)', '(max-width: 1200px)'],
'lg': ['(min-width: 1200px)'],
},
},
},
...
}
Here we add the **BootstrapContainerPlugin** to ``plugins`` and ``parent_classes``. This is because
the Container plugin normally is the root plugin in a placeholder. If this plugin would not restrict
its parent plugin classes, we would be allowed to use it as a child of any plugin. This could
destroy the page's grid.
Furthermore, in the above example we must add the **TextLinkPlugin** to ``text_only_plugins``.
This is because the **TextPlugin** is not part of the Cascade ecosystem and hence does not know
which plugins are allowed as its children.
The dictionary named ``glossary`` sets the initial parameters of the :ref:`bootstrap3/grid`.
Define the leaf plugins
-----------------------
Leaf plugins are those, which contain real data, say text or images. Hence the default setting
is to allow the **TextPlugin** and the **FilerImagePlugin** as leafs. This can be overridden using
the configuration directive
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'alien_plugins': ['TextPlugin', 'FilerImagePlugin', 'OtherLeafPlugin'],
...
}
Bootstrap 3 with AngularJS
--------------------------
Some Bootstrap3 plugins can be rendered using templates which are suitable for the very popular
`Angular UI Bootstrap`_ framework. This can be done during runtime; when editing the plugin a
select box appears which allows to chose an alternative template for rendering.
Template Customization
======================
Make sure that the style sheets are referenced correctly by the used templates. **Django-CMS**
requires django-sekizai_ to organize these includes, so a strong recommendation is to use that
Django app.
The templates used for a **django-CMS** project shall include a header, footer, the menu bar and
optionally a breadcrumb, but should leave out an empty working area. When using HTML5, wrap this
area into an ```` or ```` element or just use it unwrapped.
This placeholder then shall be named using a generic identifier, for instance "Main Content" or
similar:
.. code-block:: html
{% load cms_tags sekizai_tags %}
...
{% render_block "css" postprocessor "cmsplugin_cascade.sekizai_processors.compress" %}
...
{% placeholder "Main Content" %}
{% render_block "js" postprocessor "cmsplugin_cascade.sekizai_processors.compress" %}
From now on, the page layout can be adopted inside this placeholder, without having to fiddle with
template coding anymore.
Note the two templatetags ``render_block``. The upper one collects all the CSS files referenced by
``{% addtoblock "css" ... %}``. The lower one collects all the JS files referenced by
``{% addtoblock "js" ... %}``. They then are rendered alltogether instead of beeing distributed all
across the page. If django-compressor_ is installed and enabled, then add the special compressor
``"cmsplugin_cascade.sekizai_processors.compress"`` to the templatetag. It can handle files outside
the ``STATIC_ROOT``directory.
.. _Django: http://djangoproject.com/
.. _Django-CMS: https://www.django-cms.org/
.. _Angular UI Bootstrap: http://angular-ui.github.io/bootstrap/
.. _pip: http://pypi.python.org/pypi/pip
.. _django-sekizai: http://django-sekizai.readthedocs.org/en/latest/
.. _django-compressor: http://django-compressor.readthedocs.org/en/latest/
.. _djangocms-link: https://github.com/divio/djangocms-link
.. _djangocms-text-ckeditor: https://github.com/divio/djangocms-text-ckeditor
.. _django-filer: https://github.com/divio/django-filer
.. _Node Package Manager: https://nodejs.org/en/download/
.. _browser support: https://caniuse.com/#search=srcset
================================================
FILE: docs/source/introduction.rst
================================================
============
Introduction
============
**DjangoCMS-Cascade** is a collection of plugins for Django-CMS_ >=3.3 to add various HTML elements
from CSS frameworks, such as `Twitter Bootstrap`_ to the Django templatetag_ placeholder_. This
Django App makes it very easy to add other CSS frameworks, or to extend an existing collection with
additional elements.
**DjangoCMS-Cascade** allows web editors to layout their pages, without having to create different
`Django templates`_ for each layout modification. In most cases, one template with one single
placeholder is enough. The editor then can subdivide that placeholder into rows and columns, and
add additional DOM_ elements such as buttons, rulers, or even the Bootstrap Carousel. Some basic
understanding on how the DOM works is required though.
**Twitter Bootstrap** is a well documented CSS framework which gives web designers lots of
possibilities to add a consistent structure to their pages. This collection of `Django-CMS plugins`_
offers a subset of these predefined elements to web designers.
Extensibility
=============
This module requires one database table with one column to store all data in a JSON object. All
**DjangoCMS-Cascade** plugins share this same model, therefore they can be easily extended, because
new data structures are added to that JSON object without requiring a database migration.
Another three database tables are required for additional optional features.
Naming Conflicts
----------------
Some **djangoCMS** plugins may use the same name as plugins from **djangocms-cascade**. To prevent
confusion, since version 0.7.2, all Cascade plugins as prefixed with a Ϟ (koppa) symbol. This can
be deactivated or changed by setting ``CMSPLUGIN_CASCADE['plugin_prefix']`` to ``False`` or any
other symbol.
.. _Django-CMS: https://github.com/divio/django-cms/
.. _Twitter Bootstrap: http://getbootstrap.com/
.. _Django templates: https://docs.djangoproject.com/en/dev/topics/templates/
.. _templatetag: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/
.. _placeholder: https://django-cms.readthedocs.org/en/latest/advanced/templatetags.html#placeholder
.. _DOM: http://www.w3.org/DOM/
.. _Django-CMS plugins: https://django-cms.readthedocs.org/en/latest/getting_started/plugin_reference.html
================================================
FILE: docs/source/leaflet.rst
================================================
=====================================
Map Plugin using the Leaflet frontend
=====================================
If you want to add a interactive maps to a **Django-CMS** placeholder, the **Cascade Leaflet Map
Plugin** may be your best choice. It is not activated by default, because it requires a special
JavaScript library, an active Internet connection (in order to load the map tiles), and a license
key (this depends on the chosen tiles layer). By default the **Cascade Leaflet Map Plugin** uses
the `Open Street Map`_ tile layer, but this can be changed to Mapbox_, `Google Maps`_ or another
provider.
This plugin uses third party packages, based on the `Leaflet JavaScript`_ library for mobile-friendly
interactive maps.
.. _Open Street Map: http://www.openstreetmap.org/
.. _Mapbox: https://www.mapbox.com/
.. _Google Maps: https://developers.google.com/maps/
.. _Leaflet JavaScript: http://leafletjs.com/
Installation
============
The required JavaScript dependencies are not shipped with **djangocms-cascade**. They must be
installed separately from the `Node JS repository`_.
.. code-block:: shell
npm install leaflet
npm install leaflet-easybutton
.. note:: Leaflet Easybutton is only required for the administration backend.
.. _Node JS repository: https://www.npmjs.com/
Configuration
=============
The default Cascade settings must be active in order to use the **Leaflet Map Plugin**. Additionally
add to the project's settings:
.. code-block:: python
CMSPLUGIN_CASCADE_PLUGINS = [
...
'cmsplugin_cascade.leaflet',
...
]
By modifying the dictionary ``CMSPLUGIN_CASCADE['leaflet']`` you may override Leaflet specific
settings. Change ``CMSPLUGIN_CASCADE['leaflet']['tilesURL']`` to the `titles layer`_ of your choice.
All other attributes of that dictionary are passed as options to the Leaflet ``tileLayer``
constructor. For details, please refer to the Leaflet specific documentation.
.. _titles layer: http://leafletjs.com/reference-1.0.3.html#tilelayer
Usage
=====
Add a **Map Plugin** to any **django-CMS** placeholder. Here you may adjust the width and height of
the map.
The map can be repositioned at any time. Use the *Center* button on the top left corner to reset the
position to the coordinates and zoom level, it was saved the last time.
Adding a marker to the map
--------------------------
First click on *Add another Marker* and enter a title of your choice. Afterwards go to the map and
place the marker. After saving the map, this new marker will be persisted.
Additionally, one may choose a customized marker icon: Click on *Use customized marker icon* and
choose an image from your media files. It is recommended to use PNG images with a transparent layer
as marker icons.
Adjust the icon's size by setting the marker width. The height is computed in order to keep the same
aspect ratio.
.. note:: Customized marker icons are only displayed in the frontend. The backend always uses the
default pin symbol.
By settings the marker's anchor, the icon can be positioned exactly.
Markers can be repositioned at any time and the new coordinates are saved together with the map.
Alternative Tiles
=================
By default, **djangocms-cascade** is shipped using tiles from the `Open Street Map`_ project.
This is mainly because these tiles can be used without requiring a license key. However, they load
slowly and their appearance might not be what your customers expect.
Mapbox
------
A good alternative are tiles from Mapbox_. Please refer to their terms and conditions for details.
There you can also apply for an access token, they offer free plans for low traffic sites.
Then add to the project's ``settings.py``:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'leaflet': {
'tilesURL': 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
'accessToken': YOUR-MAPBOX-ACCESS-TOKEN,
...
}
...
}
Google Maps
-----------
The problem with Google is that its Terms of Use forbid any means of tile access other than through
the Google Maps API. Therefore in the frontend, Google Maps are rendered using a different template,
which is not based on the LeafletJS library. This means that you must edit your maps using Mapbox or
OpenStreetMap titles, whereas Google Maps is only rendered in the frontend.
To start with, apply for a `Google Maps API key`_ and add it to the project's ``settings.py``:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'leaflet': {
...
'apiKey': YOUR-GOOGLE-MAPS-API-KEY,
...
}
...
}
When editing a **Map** plugin, choose *Google Map* from the select field named *Render template*.
If want to render Google Maps exclusively in the frontend, change this in your project's
``settings.py``:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_extra_render_templates': {
'LeafletPlugin': [
('cascade/plugins/googlemap.html', "Google Map"),
],
}
...
}
.. _Google Maps API key: https://developers.google.com/maps/documentation/javascript/get-api-key
Default Starting Position
=========================
Depending of the region you normally create maps, you can specify the default starting position. If for instance
your main area of interest is Germany, than these coordinates are a good setting:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'leaflet': {
...
'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6},
}
...
}
Default Marker Icon
===================
In case you don't like the default marker icon, you can replace it with your own one.
Simply add to the configuration
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'leaflet': {
...
'defaultMarkerIcon': {
'iconUrl': STATIC_URL + 'my_project/icons/marker.svg',
'iconSize': (25, 41),
'iconAnchor': (13, 41),
...
},
}
...
}
For details about all the possible options for a marker icon, refer to the `Leaflet documentation`_.
.. _Leaflet documentation: https://leafletjs.com/reference.html#icon
Address Lookup
==============
Since version 2.3 it is possible to search for a location, using the `OSM Nominatim`_ lookup
service.
.. _OSM Nominatim: https://nominatim.org/
When adding or editing a marker, there is a field named **Address lookup**. Entering an address
into that field and pressing the "Enter" key, generates a list of possible locations. Choose one
from that list and the marker will be placed at the specified coordinates.
In case you want to override the address lookup service with one compatible the Nominatim's API,
change the URL in the Django settings:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'leaflet': {
...
'addressLookupURL': 'https://my-nominatim.example.org/search',
}
...
}
================================================
FILE: docs/source/link-plugin.rst
================================================
.. _link-plugin:
===========
Link Plugin
===========
**djangocms-cascade** ships with its own link plugin. This is because other plugins from the
Cascade eco-system, such as the **BootstrapButtonPlugin**, the **BootstrapImagePlugin** or the
**BootstrapPicturePlugin** also require a functionality in order to set links to internal- and
external URLs. Since we do not want to duplicate the linking functionality for each of those
plugins, it has been moved into its own mixin-classes. Therefore we will use the terminology
**TextLinkPlugin** when referring to text-based links.
The de-facto plugin for links, djangocms-link_ can't be used as a base class for these plugins,
hence an alternative implementation has been created within the Cascade framework. The link related
data is stored in a various fields in our main JSON field (named ``glossary``).
Prerequisites
=============
Before using this plugin, assure that ``'cmsplugin_cascade.link'`` is member of the list or
tuple ``CMSPLUGIN_CASCADE_PLUGINS`` in the project's ``settings.py``.
|simple-link-element|
.. |simple-link-element| image:: _static/simple-link-element.png
The behavior of this Plugin is what you expect from a Link editor. The field **Link Content** is the
text displayed between the opening and closing ```` tag. If used in combination with
djangocms-text-ckeditor_ the field automatically is filled out.
By changing the **Link type**, the user can choose between different types of Links:
* Internal Links pointing to another page inside the CMS.
* External Links pointing to a valid Internet URL.
* Files from **django-filer** to download.
* Links pointing to a valid e-mail address.
* Optionally any other linkable object, if another Django application extends the Link-Plugin (see
below for details).
The optional field **Title** can be used to add a ``title="some value"`` attribute to the
```` element.
With **Link Target**, the user can specify, whether the linked content shall open in the current
window or if the browser shall open a new window.
Link Plugin with Sharable Fields
================================
If your web-site contains many links pointing onto a few external URLs, you might want to refer to
them by a symbolic name, rather than having to reenter the URL repeatedly. With
**djangocms-cascade** this can be achieved easily by declaring some of the plugin's fields as
*sharable*.
Assure that ``INSTALLED_APPS`` contains ``'cmsplugin_cascade.sharable'``, then redefine the
**TextLinkPlugin** to have sharable fields in ``settings.py``:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_sharables':
…
'TextLinkPlugin': ['link_type', 'ext_url'],
…
},
...
}
This will change the Link Plugin's editor slightly. Note the extra field added to the bottom of the
form.
|sharable-link-element|
.. |sharable-link-element| image:: _static/sharable-link-element.png
The URL for this link entity now is stored in a central entity. This feature is useful, if for
instance the URL of an external web page may change in the future. Then the administrator can change
that link in the administration area once, rather than having to go through all the pages and check
if that link was used.
To retain the Link settings, click onto the checkbox *Remember these settings as: ...* and give it
a name of your choice. The next time your create a Shared Link element, you may select a previously
named settings from the select field *Shared Settings*. Since these settings can be shared among
other plugins, these input fields are disabled and can't be changed anymore.
Changing shared settings
------------------------
The settings of a shared plugin can be changed globally, for all plugins using them. To edit such a
shared setting, in the Django Admin, go into the list view for
**Home › django CMS Cascade › Shared between Plugins** and choose the named shared settings.
Please note, that each plugin type can specify which fields shall be sharable between plugins of
the same type. In this example, only the Link itself is shared, but one could configure
**djangocms-cascade** to also share the link's ``title``, the ``target``, and other tags.
Then only these fields are editable in the detail view **Shared between Plugins**. The interface
for other shared plugin may vary substantially, depending of their type definition.
Extending the Link Plugin
=========================
While programming third party modules for Django, one might have to access a model instance through
a URL and thus add the method get_absolute_url_ to that Django model. Since such a URL is neither a
CMS page, nor a URL to an external web page, it would be convenient to access that model using a
special Link type.
For example, in django-shop_ we can allow to link directly to a product, sold by the shop.
This is achieved by reconfiguring the Link Plugin inside Cascade with:
.. code-block:: python
CMSPLUGIN_CASCADE = {
…
'link_plugin_classes': (
'shop.cascade.plugin_base.CatalogLinkPluginBase',
'shop.cascade.plugin_base.CatalogLinkForm',
),
…
}
The tuple specified through ``link_plugin_classes`` replaces the base class for the **LinkPlugin**
class and the form class used by its editor.
Here we replace the two built-in classes :class:`cmsplugin_cascade.link.plugin_base.DefaultLinkPluginBase`
and :class:`cmsplugin_cascade.link.forms.LinkForm` by alternative implementations.
.. code-block:: python
:caption: shop/cascade/plugin_base.py
from entangled.forms import get_related_object
from cmsplugin_cascade.link.plugin_base import LinkPluginBase
class CatalogLinkPluginBase(LinkPluginBase):
@classmethod
def get_link(cls, obj):
link_type = obj.glossary.get('link_type')
if link_type == 'product':
relobj = get_related_object(obj.glossary, 'product')
if relobj:
return relobj.get_absolute_url()
else:
return super().get_link(obj) or link_type
This class handles links of type "Product" and creates a URL pointing onto a Django model implementing
the method ``get_absolute_url``.
Additionally, we have to override the form class used by the Link plugin editor:
.. code-block:: python
:caption: shop/cascade/plugin_base.py
from cms.plugin_pool import plugin_pool
from django.forms import models
from shop.models.product import ProductModel
class CatalogLinkForm(LinkForm):
LINK_TYPE_CHOICES = [
('cmspage', _("CMS Page")),
('product', _("Product")),
('download', _("Download File")),
('exturl', _("External URL")),
('email', _("Mail To")),
]
product = models.ModelChoiceField(
label=_("Product"),
queryset=ProductModel.objects.all(),
required=False,
help_text=_("An internal link onto a product from the catalog"),
)
class Meta:
entangled_fields = {'glossary': ['product']}
Now the select box for **Link type** will offer one additional option named "Product". When this is
selected, the page administrator can select one product in the shop and the link will point onto
its proper detail page.
Using Links in your own Plugins
===============================
Many HTML components allow to link onto other resources, for instance images, the button element,
icons, etc. Since we don't want the reimplement the linking functionality for each of them,
**djangocms-cascade** offers a few base classes, which can be used by those plugin. As an example,
let's implement a simple button plugin.
.. code-block:: python
:caption: myproject/cascade/button.py
from django.forms import models
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin
from cmsplugin_cascade.link.plugin_base import LinkElementMixin
class ButtonForm(LinkFormMixin):
require_link = False
button_content = models.CharField(
label=_("Button Content"),
)
class Meta:
entangled_fields = {'glossary': ['link_content']}
class ButtonPlugin(LinkPluginBase):
name = _("Button")
model_mixins = (LinkElementMixin,)
form = ButtonForm
render_template = 'myproject/button.html'
allow_children = False
plugin_pool.register_plugin(ButtonPlugin)
What we see here is, that our ``ButtonForm``, which is used by our ``ButtonPlugin`` inherits from
a base form offering all the fields required to link somewhere. Sine the button may just display
some content, but without linking anywhere, we make that optional by setting ``require_link`` to
``False``. The box for selecting the "Link Type" then adds "No Link" to its set of options.
We don't even have to bother, whether our custom button can point onto links types specified by yet
another third party app, and not handled by **djangocms-cascade** – All these additional link types
are handled automatically by the configuration setting ``CMSPLUGIN_CASCADE['link_plugin_classes']``
as explained in the previous section.
.. _djangocms-link: https://github.com/divio/djangocms-link
.. _djangocms-text-ckeditor: https://github.com/divio/djangocms-text-ckeditor
.. _get_absolute_url: https://docs.djangoproject.com/en/stable/ref/models/instances/#get-absolute-url
.. _django-shop: https://github.com/awesto/django-shop
================================================
FILE: docs/source/release-notes-1.rst
================================================
# Release Notes for version 1.0
Apart from dropping support for Python-2.7, **djangocms-cascade** version 1.0 internally changes a lot.
Until version 0.19 it used a special widget :class:`cmsplugin_cascade.widgets.JSONMultiWidget` which
took care of converting the so named "glossary fields" into an editor, used to change the properties
of all Cascade plugins.
While this editor was able to handle all kinds of primitive data types, such as strings, numeric input,
simple- and multiple choices, it failed to handle references onto foreign keys and other data inputs,
requiring input validation and rectification. Therefore many Cascade plugins turned into kind of hybrids,
using a mixture of classic Django form fields plus one special "glossary" field, using the ``JSONMultiWidget``
mentioned before.
This approach turned out to be impracticable, because input widgets rendered by the form fields could not
be mixed with fields rendered by the ``JSONMultiWidget``. It also was complicated from a point of understanding
and other programmers had difficulties to implement their own plugins.
Therefore in version 1.0, the list of "glossary fields" will be replaced against a slightly modified Django
``ModelForm``. This form then reads and writes its data from the Django model field
:class:`cmsplugin_cascade.models.CascadeElement.glossary`, just as it always did. This means that we still
have the advantage of using a JSON field to store arbitrary data, preventing us from having to create a Django
model for each plugin in our database.
The code for reading and writing JSON data from and to this special Django model field (ie. ``glossary``),
has been moved out of **dangocms-cascade** and into a new Django app named
[django-entangled](https://github.com/jrief/django-entangled). The reason for this code separation is greater
reusability.
================================================
FILE: docs/source/render-template.rst
================================================
========================================
Choose an alternative rendering template
========================================
Sometimes you must render a plugin with a slightly different template, other than the given default.
A possible solution is to create a new plugin, inheriting from the given one and overriding
the ``render_template`` attribute with a customized template. This however adds another plugin to
the list of registered CMS plugins.
A simpler solution to solve this problem, is to allow a plugin to be rendered with a customized
template out of a set of alternatives.
Change the path for template lookups
====================================
Some Bootstrap Plugins are shipped with templates, which are optimized to be rendered by Angular-UI_
rather than the default jQuery. These alternative templates are located in the folder
``cascade/bootstrap3/angular-ui``. If your project uses AngularJS instead of jQuery, then configure
the lookup path in ``settings.py`` with
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'bootstrap3': {
...
'template_basedir': 'angular-ui',
},
}
This lookup path is applied only to the Plugin's field ``render_template`` prepared for it. Such a
template contains the placeholder ``{}``, which is expanded to the configured ``template_basedir``.
For instance, the **CarouselPlugin** defines its ``render_template`` such as:
.. code-block:: python
class CarouselPlugin(BootstrapPluginBase):
...
render_template = 'cascade/bootstrap3/{}/carousel.html'
...
.. _Angular-UI: http://angular-ui.github.io/bootstrap/versioned-docs/0.13.4/
Configure Cascade Plugins to be rendered using alternative templates
====================================================================
All plugins which offer more than one rendering template, shall be added in the projects
``settings.py`` to the dictionary ``CMSPLUGIN_CASCADE['plugins_with_extra_render_templates']``.
Each item in this dictionary consists of a key, naming the plugin, and a value containing a list of
two-tuples. The first element of this two-tuple must be the templates filename, while the second
element shall contain an arbitrary name to identify that template.
Example:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_extra_render_templates': {
'TextLinkPlugin': (
('cascade/link/text-link.html', _("default")),
('cascade/link/text-link-linebreak.html', _("with linebreak")),
)
},
...
}
Usage
-----
When editing a **djangoCMS** plugins with alternative rendering templates, the plugin editor
adds a select box containing choices for alternative rendering templates. Choose one other than the
default, and the plugin will be rendered using that template.
================================================
FILE: docs/source/section.rst
================================================
=================
Section Bookmarks
=================
If you have a long page, and you want to allow the visitors of your site to quickly navigate to
different sections, then you can use bookmarks and create links to the different sections of any
HTML page.
When a user clicks on a bookmark link, then that page will load as usual but will scroll down
immediately, so that the bookmark is at the very top of the page. Bookmarks are also known as
anchors. They can be added to any HTML element using the attribute ``id``. For example:
.. code-block:: html
For obvious reasons, this identifier must be unambiguous, otherwise the browser does not know
where to jump to. Therefore **djangocms-cascade** enforces the uniqueness of all bookmarks used on
each CMS page.
Configuration
=============
The HTML standard allows the usage of the ``id`` attribute on any element, but in practice it only
makes sense on ````, ```` and the heading elements ````...````.
Cascade by default is configured to allow bookmarks on the **SimpleWrapperPlugin** and the
**HeadingPlugin**. This can be overridden in the project's configuration settings using:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_bookmark': [list-of-plugins],
...
}
Hashbang Mode
-------------
Links onto bookmarks do not work properly in hashbang mode. Depending on the HTML settings, you may
have to prefix them with ``/`` or ``!``. Therefore **djangocms-cascade** offers a configuration
directive:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'bookmark_prefix': '/',
...
}
which automatically prefixes the used bookmark.
Usage
=====
When editing a plugin that is eligible for adding a bookmark, an extra input field is shown:
|section-bookmark|
.. |section-bookmark| image:: /_static/section-bookmark.png
You may add any identifier to this field, as long as it is unique on that page. Otherwise the
plugin's editor will be reject the given inputs, while saving.
Hyperlinking to a Bookmark
==========================
When editing a **TextLink**, **BootstrapButton** or the link fields inside the **Image** or
**Picture** plugins, the user gets an additional drop-down menu to choose one of the bookmarks for
the given page. This additional drop-down is only available if the **Link** is of type *CMS page*.
|link-bookmark|
.. |link-bookmark| image:: /_static/link-bookmark.png
If no bookmarks have been associated with the chosen CMS page, the drop-down menu displays only
*Page root*, which is the default.
================================================
FILE: docs/source/segmentation.rst
================================================
=======================
Segmentation of the DOM
=======================
The **SegmentationPlugin** allows to personalize the DOM structure, depending on the context used to
render the corresponding page. Since **django-CMS** always uses a RequestContext_ while rendering
its pages, we always have access onto the request object. Some use cases are:
* Depending on the user, show a different portion of the DOM, if he is a certain user or not logged
in at all.
* Show different parts of the DOM, depending on the browsers estimated geolocation. Useful to
render different content depending on the visitors country.
* Show different parts of the DOM, depending on the supplied marketing channel.
* Show different parts of the DOM, depending on the content in the session objects from previous
visits of the users.
* Segment visitors into different groups used for A/B-testing.
Configuration
=============
The **SegmentationPlugin** must be activated separately on top of other **djangocms-cascade**
plugins. In ``settings.py``, add to
.. code-block:: python
INSTALLED_APPS = (
...
'cmsplugin_cascade',
'cmsplugin_cascade.segmentation',
...
)
Then, depending on what kind of data shall be emulated, add a list of two-tuples to the
configuration settings ``CMSPLUGIN_CASCADE['segmentation_mixins']``. The first entry of each
two-tuple specifies the mixin class added the the proxy model for the ``SegmentationPlugin``. The
second entry specifies the mixin class added the model admin class for the ``SegmentationPlugin``.
.. code-block:: python
# this entry is optional:
CMSPLUGIN_CASCADE = {
...
'segmentation_mixins': (
('cmsplugin_cascade.segmentation.mixins.EmulateUserModelMixin', 'cmsplugin_cascade.segmentation.mixins.EmulateUserAdminMixin',), # the default
# other segmentation plugin classes
),
...
}
Usage
=====
When editing **djangoCMS** plugins in **Structure** mode, below the section **Generic** a new plugin
type appears, named **Segment**.
|segment-plugin|
.. |segment-plugin| image:: _static/segment-plugin.png
This plugin now behaves as an ``if`` block, which is rendered only, if the specified condition
evaluates to true. The syntax used to specify the condition, is the same as used in the Django
template language. Therefore it is possible to evaluate against more than one condition and combine
them with ``and``, ``or`` and ``not`` as described in `boolean operators`_ in the Django docs
Immediately below a segmentation block using the condition tag ``if``, it is possible to use the
tags ``elif`` or ``else``. This kind of conditional blocks is well known to Python programmers.
Note, that when rendering pages in djangoCMS, a RequestContext_- rather than a Context-object is used.
This RequestContext is populated by the ``user`` object if ``'django.contrib.auth.context_processors.auth'``
is added to your settings.py ``TEMPLATE_CONTEXT_PROCESSORS``. This therefore is a prerequisite
when the Segmentation plugin evaluates conditions such as ``user.username == "john"``.
.. _RequestContext: https://docs.djangoproject.com/en/1.8/ref/templates/api/#django.template.RequestContext
.. _boolean operators: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#boolean-operators
.. _request object: https://docs.djangoproject.com/en/dev/ref/request-response/#httprequest-objects
Emulating Users
===============
Only staff users or administrators can emulate the currently logged in user. Staff-only users must
possess the four permissions `cmsplugin_cascade.add_segmentation`,
`cmsplugin_cascade.change_segmentation`, `cmsplugin_cascade.delete_segmentation` and
`cmsplugin_cascade.view_segmentation`.
If this plugin is activated and the permissions are set, then in the CMS toolbar a new menu
tag appears named “Segmentation”. Here the currently logged in staff user can select another user.
All evaluation conditions then evaluate against this selected user, instead of the currently logged
in user.
It is quite simple to add other overriding emulations. Have a look at the class
``cmsplugin_cascade.segmentation.mixins.EmulateUserMixin``. This class then has to be added to
your configuration settings ``CMSPLUGIN_CASCADE_SEGMENTATION_MIXINS``. It then overrides the
evaluation conditions and the toolbar menu.
================================================
FILE: docs/source/sharable-fields.rst
================================================
.. _sharable-fields:
============================
Working with sharable fields
============================
Sometime you'd want to remember sizes, links or any other options for rendering a plugin instance
across the project. In order to not have to do this job for each managed entity, you can remember
these settings using a name of your choice, controllable in a special section of the administration
backend.
Now, whenever someone adds a new instance using this plugin, a select box with these remembered
settings appears. He then can choose from one of the remembered settings, which frees him to
reenter all the values.
Configure a Cascade Plugins to optionally share some fields
===========================================================
Configuring a plugin to share specific fields with other plugins of the same type is very easy.
In the projects ``settings.py``, assure that ``'cmsplugin_cascade.sharable'`` is part of your
``INSTALLED_APPS``.
Then add a dictionary of Cascade plugins, with a list of fields which shall be sharable. For
example, with this settings, the image plugin can be configured to share its sizes and rendering
options among each other.
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_sharables': {
'BootstrapImagePlugin': ['image-shapes', 'image-width-responsive', 'image-width-fixed', 'image-height', 'resize-options'],
},
...
}
Control some named settings
===========================
Whenever a plugin is configured to allow to share fields, at the bottom of the plugin editor a
special field appears:
|remember-settings|
.. |remember-settings| image:: /_static/remember-settings.png
By activating the checkbox, adding an arbitrary name next to it and saving the plugin, an entity
of sharable fields is saved in the database. Now, whenever someone starts to edit a plugin of this
type, a select box appears on the top of the editor:
|use-shared-settings|
.. |use-shared-settings| image:: /_static/use-shared-settings.png
By choosing a previously named shared settings, the configured fields are disabled for input and
replaced by their shared field's counterparts.
In order to edit these shared fields in the administration backend, one must access
**Home › Cmsplugin_cascade › Shared between Plugins**. By choosing a named shared setting, one can
enter into the shared field's editor. This editor auto adopts to the fields declared as shared,
hence will change from entity to entity. For the above example, it may look like this:
|edit-shared-fields|
.. |edit-shared-fields| image:: /_static/edit-shared-fields.png
In this editor one can change these shared settings globally, for all plugin instances where this
named shared settings have been applied to.
================================================
FILE: docs/source/sphinx.rst
================================================
==============================
Integrate Sphinx Documentation
==============================
Restructured Text (ReST) is the de facto standard for documenting Python projects and is even widely
used outside of this realm, by applications written in other languages. Sphinx_ is a compiler to
generate HTML, Latex, PDF, e-books, etc. out of sources written in ReST.
HTML rendered by Sphinx, typically is rendered as static content by the web server. This makes it
difficult to serve documentation, side by side with content from **django-CMS**, because these are
completely different technologies. Furthermore, since Sphinx uses Jinja2 templates, but **django-CMS**'s
internal templatetags are not available for Jinja2, template sharing is not possible.
Therefore **djangocms-cascade** offers an integration service, which makes it possible to integrate
documentation generated by Sphinx, unintrusively inside the menu tree of **django-CMS**.
Configuration
=============
To the project's ``settings.py``, add these options to the configuration directives:
.. code-block:: python
INSTALLED_APPS = [
...
'cmsplugin_cascade',
'cmsplugin_cascade.sphinx',
...
]
CMS_TEMPLATES = [
...
('path/to/documentation.html', "Documentation Page"),
...
]
SPHINX_DOCS_ROOT = '/path/to/docs/_build/fragments'
Replace ``'/path/to/documentation.html'`` with a filename pointing to your documentation
root template (see below).
Point ``SPHINX_DOCS_ROOT`` onto the directory, into which the HTML page fragments are generated.
Configure Sphinx Builder
------------------------
Locate the file ``conf.py`` and add:
.. code-block:: python
extensions = [
...
'cmsplugin_cascade.sphinx.fragmentsbuilder',
]
By invoking ``make fragments``, Sphinx generates a HTML fragment for each page inside the
documentation folder, typically into ``docs/build/fragments``. Later we use these fragments
and include them using a normal Django view.
Integration with the CMS
========================
In Django's admin backend, add a page as the starting point for the documentation inside
the CMS menu tree. Typically, one would name that page "*Documentation*" using ``docs`` or
``documentation`` as its slug.
In the *Advanced Settings* tab, choose **Documentation Page** as the template. This settings
has been configured using the directive ``CMS_TEMPLATES``, as shown above.
As *Application*, select **Sphinx Documentation** from the pull down menu. This attaches the
complete documentation tree just below the chosen slug.
Optionally select **Documentation Menu** from the pull down menu as the *Attached menu*. It adds
a submenu for each main chapter of the documentation. If omitted, only **Documentation** is added
the the CMS menu tree.
The Documentation Template
--------------------------
You must provide a template to be used by the documentation view. This template typically extends
a base CMS page template, providing a header, the navigation bar and the footer. In the block,
responsible for rendering the main content, add this template code:
.. code-block:: django
{% extends "path/to/base.html" %}
{% load static cascade_tags %}
...
{% block head %}
{{ block.super }}
{% endblock %}
...
{% block main-content %}
{% if page_content %}
{{ page_content }}
{% else %}
{% sphinx_docs_include "index.html" %}
{% endif %}
{% endblock %}
This Django template now includes the HTML fragments compiled by Sphinx. This allows us to use
**django-CMS** and combine it with Sphinx. In the URL, the part behind the documentation's slug
corresponds 1:1 to the name of the ReST document.
In this example we add a stylesheet to adopt the output to the `Bootstrap theme`_ for Sphinx_.
Depending on your template layout, the way you import this may vary.
.. _Sphinx: http://www.sphinx-doc.org/
.. _Bootstrap theme: http://ryan-roemer.github.io/sphinx-bootstrap-theme/README.html
Linking onto Documentation Pages
--------------------------------
By overriding the :ref:`link-plugin` with a special target named **Documentation**, we can
even add links onto our documentation pages symbolically. This means, that whenever we open the
**LinkPlugin** editor, an additional target is added. It offers a select box showing all
pages from our documentation tree. This prevents us, having to hard code the URL pointing
onto the documentation.
This feature has to be configured in the project's ``settings.py``, by replacing the LinkPlugin
with a modified version of itself:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'link_plugin_classes': [
'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkPlugin',
'cmsplugin_cascade.link.plugin_base.LinkElementMixin',
'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkForm',
],
...
}
================================================
FILE: docs/source/strides.rst
================================================
.. _strides:
==============================
Use Cascade outside of the CMS
==============================
One of the most legitimate points **djangocms-cascade** can be criticised for, is the lack of
static content rendering. Specially in projects, where we want to work with static pages instead
of CMS pages, one might fall back to handcrafting HTML, giving up all the benefits of rapid
prototyping as provided by the Cascade plugin system.
Since version 0.14 of **djangocms-cascade**, one can prototype the page content and export it as
JSON file using :ref:`clipboard`. Later on, one can reuse that persisted data and create the same
content outside of a CMS page. This is specially useful, if you must persist the page content
in the project's version control system.
Usage
=====
After the placeholder of a CMS page, is filled up with plugins from **djangocms-cascade**,
switch into *Structure Mode*, go to the context menu of that placeholder and click *Copy all*.
Next, inside Django's administration backend, go to
Home › Django CMS Cascade › Persited Clipboard Content
and click onto *Add Persisted Clipboard Content*. The *Data* field will now be filled with a
cascade of plugins serialized as JSON data. Copy that data and paste it into a file locatable
by Django's static file finders, for example ``myproject/static/myapp/cascades/slug.json``.
In Templates
============
Create a Django template, where instead of adding a Django-CMS placeholder, use the templatetag
``render_cascade``. Example:
.. code-block:: Django
{% load cascade_tags %}
{% render_cascade "myapp/cascades/slug.json" %}
This templatetag now renders the content just as if it would be rendered by the CMS. This means
that changing the template of a **djangocms-cascade** plugin, immediately has effect on the rendered
output. This is so to say **Model View Control**, where the Model is the content peristed as JSON,
and the View is the template provided by the plugin. It separates the composition of HTML components
from their actual representation, allowing a much better division of work during the page creation.
Caveats when creating your own Plugins
======================================
When developing your own plugins, consider the following precautions:
Invoking ``super``
------------------
Instead of invoking ``super(MyPlugin, self).some_method()`` use
``self.super(MyPlugin, self).some_method()``. This is required because **djangocms-cascade**
creates a list of "shadow" plugins, which do not inherit from ``CMSPluginBase``.
Templatetag ``render_plugin``
-----------------------------
Django-CMS provides a templatetag ``render_plugin``. Don't use it in templates provided by
**djangocms-cascade** plugins. Instead use the templatetag named ``render_plugin`` from
Cascade. Example:
.. code-block:: Django
{% load cascade_tags %}
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
Caching
=======
Even though rendering using this templatetag is slightly faster than the classic ``placeholder``
tag provided by the CMS (because we don't hit the database for each plugin instance), combining
each plugin template with its context also takes its time. Therefore plugins rendered by
``render_cascade``, by default are cached as well, just as their CMS counterparts.
This caching is disabled for plugins containing the attribute ``cache = False``. It can be turned
off globally using the directive ``CMSPLUGIN_CASCADE['cache_strides'] = True`` in the project's
``settings.py``.
================================================
FILE: examples/bs4demo/.coveragerc
================================================
[run]
branch = True
source =
cmsplugin_cascade
[report]
precision = 2
omit =
../*migrations*
gs960
================================================
FILE: examples/bs4demo/bs4demo/__init__.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: examples/bs4demo/bs4demo/cms_plugins.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.forms import widgets
from django.utils.translation import ugettext_lazy as _
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.fields import GlossaryField
from cmsplugin_cascade.plugin_base import CascadePluginBase
class Badge(CascadePluginBase):
"""
This is a simple example of a plugin suitable for the djangocms-cascade system.
It contains one single field: `content` rendered via the template `bs4demo/badge.html`.
"""
name = _("Badge")
require_parent = False
allow_children = False
render_template = 'bs4demo/badge.html'
content = GlossaryField(
widgets.TextInput(),
label=_("Content"),
)
plugin_pool.register_plugin(Badge)
================================================
FILE: examples/bs4demo/bs4demo/context_processors.py
================================================
# -*- coding: utf-8 -*-
from django.conf import settings
def cascade(request):
"""
Adds additional context variables to the default context.
"""
context = {
'DJANGO_CLIENT_FRAMEWORK': settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'),
}
return context
================================================
FILE: examples/bs4demo/bs4demo/models.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: examples/bs4demo/bs4demo/settings.py
================================================
# Django settings for unit test project.
from __future__ import unicode_literals
import os
import sys
from django.urls import reverse_lazy
from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig
from django.utils.text import format_lazy
DEBUG = True
BASE_DIR = os.path.dirname(__file__)
# Root directory for this Django project
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.path.pardir))
# Directory where working files, such as media and databases are kept
WORK_DIR = os.path.join(PROJECT_ROOT, 'workdir')
if not os.path.isdir(WORK_DIR):
os.makedirs(WORK_DIR)
SITE_ID = 1
ROOT_URLCONF = 'bs4demo.urls'
SECRET_KEY = 'secret'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(WORK_DIR, 'db.sqlite3'),
},
}
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.staticfiles',
'django.contrib.sitemaps',
#'reversion',
'djangocms_text_ckeditor',
'django_select2',
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.segmentation',
'cms',
'cms_bootstrap',
'adminsortable2',
'menus',
'treebeard',
'filer',
'easy_thumbnails',
'sass_processor',
'sekizai',
'bs4demo',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
]
# silence false-positive warning 1_6.W001
# https://docs.djangoproject.com/en/1.8/ref/checks/#backwards-compatibility
#TEST_RUNNER = 'django.test.runner.DiscoverRunner'
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = os.path.join(WORK_DIR, 'media')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/media/'
# Absolute path to the directory that holds static files.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = os.path.join(WORK_DIR, 'static')
# URL that handles the static files served from STATIC_ROOT.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'sass_processor.finders.CssFinder',
]
STATICFILES_DIRS = [
('node_modules', os.path.join(PROJECT_ROOT, 'node_modules')),
]
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
'bs4demo.context_processors.cascade',
),
},
}]
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
LANGUAGE_CODE = 'en'
LANGUAGES = (
('en', 'English'),
)
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'formatters': {
'simple': {
'format': '[%(asctime)s %(module)s] %(levelname)s: %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
},
}
X_FRAME_OPTIONS = 'SAMEORIGIN'
XS_SHARING_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']
#############################################################
# Application specific settings
if sys.argv[1] == 'test':
CMS_TEMPLATES = (
('testing.html', "Default Page"),
)
else:
CMS_TEMPLATES = (
('bs4demo/main.html', "Main Content"),
('bs4demo/wrapped.html', "Wrapped Bootstrap Column"),
)
CMS_SEO_FIELDS = True
CMS_CACHE_DURATIONS = {
'content': 3600,
'menus': 3600,
'permissions': 86400,
}
CMSPLUGIN_CASCADE_PLUGINS = (
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.leaflet',
'cmsplugin_cascade.link',
'cmsplugin_cascade.bootstrap4',
'bs4demo',
)
CMSPLUGIN_CASCADE = {
'alien_plugins': ('TextPlugin', 'TextLinkPlugin',),
'plugins_with_sharables': {
'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed',
'image_height', 'resize_options',),
'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',),
},
'exclude_hiding_plugin': ('SegmentPlugin', 'Badge'),
'allow_plugin_hiding': True,
'leaflet': {'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6}},
'cache_strides': True,
}
CMS_PLACEHOLDER_CONF = {
# this placeholder is used in templates/main.html, it shows how to
# scaffold a djangoCMS page starting with an empty placeholder
'Main Content': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None},
},
# this placeholder is used in templates/wrapped.html, it shows how to
# add content to an existing Bootstrap column
'Bootstrap Column': {
'plugins': ['BootstrapRowPlugin', 'TextPlugin', ],
'parent_classes': {'BootstrapRowPlugin': None},
'require_parent': False,
},
}
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'skin': 'moono-lisa',
'toolbar': 'CMS',
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),
}
SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'
SELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'
FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS = True
FILER_DUMP_PAYLOAD = True
THUMBNAIL_PROCESSORS = (
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
)
THUMBNAIL_HIGH_RESOLUTION = False
THUMBNAIL_PRESERVE_EXTENSIONS = True
THUMBNAIL_OPTIMIZE_COMMAND = {
'png': '/opt/local/bin/optipng {filename}',
'gif': '/opt/local/bin/optipng {filename}',
'jpeg': '/opt/local/bin/jpegoptim {filename}',
}
SASS_PROCESSOR_INCLUDE_DIRS = [
os.path.join(PROJECT_ROOT, 'node_modules'),
]
SASS_PROCESSOR_ROOT = STATIC_ROOT
# to access files such as fonts via staticfiles finders
NODE_MODULES_URL = STATIC_URL + 'node_modules/'
try:
from .private_settings import *
except ImportError:
pass
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/cascades/strides.json
================================================
{
"plugins":[
[
"BootstrapJumbotronPlugin",
{
"glossary":{
"background_width_height":{
"width":"",
"height":""
},
"background_vertical_position":"center",
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"lg":[
"(min-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
]
},
"background_attachment":"scroll",
"image":{
"pk":5,
"model":"filer.Image"
},
"background_repeat":"no-repeat",
"hide_plugin":"",
"fluid":true,
"container_max_heights":{
"xs":"100%",
"md":"100%",
"sm":"100%",
"lg":"100%"
},
"extra_inline_styles:Paddings":{
"padding-top":"500px",
"padding-bottom":""
},
"background_size":"cover",
"resize_options":[
"crop",
"subject_location",
"high_resolution"
],
"container_max_widths":{
"xs":768,
"lg":1980,
"sm":992,
"md":1200
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"background_color":[
"",
"#12308b"
],
"background_horizontal_position":"center"
},
"pk":900
},
[
[
"TextPlugin",
{
"body":"Django-CMS Cascade
",
"pk":901
},
[]
]
]
],
[
"BootstrapContainerPlugin",
{
"glossary":{
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"lg":[
"(min-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
]
},
"container_max_widths":{
"xs":750,
"lg":1170,
"sm":750,
"md":970
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"hide_plugin":"",
"fluid":""
},
"pk":902
},
[
[
"HeadingPlugin",
{
"glossary":{
"content":"Cascade Demo Page",
"element_id":"heading-1",
"tag_type":"h1",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-right":"",
"margin-top":"",
"margin-left":"",
"margin-bottom":""
}
},
"pk":936
},
[]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":""
}
},
"pk":903
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"col-md-6",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":720.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":904
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"2",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"367px"
},
"extra_inline_styles:Font Size":{
"font-size":"15px"
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#474747"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#ffffff"
],
"extra_inline_styles:Paddings":{
"padding-top":"20px",
"padding-right":"50px",
"padding-bottom":"20px",
"padding-left":"50px"
}
},
"pk":905
},
[
[
"TextPlugin",
{
"body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum. Nullam id dolor id nibh ultricies vehicula ut id elit.
",
"pk":906
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"col-md-6",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"extra_css_classes":[],
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":720.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":907
},
[
[
"BootstrapImagePlugin",
{
"glossary":{
"image_width_responsive":"100%",
"target":"",
"title":"",
"image":{
"pk":11,
"model":"filer.Image"
},
"alt_tag":"",
"hide_plugin":"",
"image_width_fixed":"",
"image_height":"",
"link":{
"type":"none"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image_shapes":[
"img-responsive"
]
},
"pk":908
},
[]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
},
"pk":909
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":910
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":911
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"wrench",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":912
},
[]
],
[
"TextPlugin",
{
"body":"Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":913
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":918
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":919
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"cog-alt",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":920
},
[]
],
[
"TextPlugin",
{
"body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":921
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":914
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":915
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"bell-alt",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":916
},
[]
],
[
"TextPlugin",
{
"body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":917
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
},
"pk":929
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"container_max_widths":{
"xs":720.0,
"lg":1140.0,
"sm":720.0,
"md":940.0
},
"xs-column-width":"col-xs-12"
},
"pk":930
},
[
[
"CarouselPlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"container_max_heights":{
"xs":"200px",
"md":"300px",
"sm":"250px",
"lg":"350px"
},
"interval":"5",
"hide_plugin":"",
"options":[
"slide",
"pause",
"wrap"
]
},
"pk":931
},
[
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":4,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":932
},
[
[
"TextPlugin",
{
"body":"Hallo Welt!
",
"pk":933
},
[]
]
]
],
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":7,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":934
},
[]
],
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":6,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":935
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":"20px"
}
},
"pk":922
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"col-sm-6",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":345.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":923
},
[
[
"BootstrapImagePlugin",
{
"glossary":{
"image_width_responsive":"100%",
"target":"",
"title":"",
"image":{
"pk":12,
"model":"filer.Image"
},
"alt_tag":"",
"hide_plugin":"",
"image_width_fixed":"",
"image_height":"",
"link":{
"type":"none"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image_shapes":[
"img-responsive"
]
},
"pk":924
},
[]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"col-sm-6",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":345.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":925
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"370px"
},
"extra_inline_styles:Font Size":{
"font-size":"130%"
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#ef3e42"
],
"extra_inline_styles:Paddings":{
"padding-top":"50px",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":926
},
[
[
"TextPlugin",
{
"body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
",
"pk":927
},
[]
],
[
"BootstrapButtonPlugin",
{
"glossary":{
"icon_align":"icon-right",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
},
"button_options":[],
"button_size":"btn-lg",
"quick_float":"pull-right",
"hide_plugin":"",
"icon_font":"3",
"link_content":"Continue",
"link":{
"pk":5,
"model":"cms.Page",
"type":"cmspage",
"section":""
},
"button_type":"btn-default",
"symbol":"right-open"
},
"pk":928
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"20px"
}
},
"pk":937
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"container_max_widths":{
"xs":720.0,
"lg":1140.0,
"sm":720.0,
"md":940.0
},
"xs-column-width":"col-xs-12"
},
"pk":938
},
[
[
"LeafletPlugin",
{
"glossary":{
"render_template":"cascade/plugins/leaflet.html",
"map_position":{
"lat":47.27337658656428,
"lng":11.399195194244387,
"zoom":15
},
"hide_plugin":"",
"map_height":"400px",
"map_width":"100%"
},
"pk":939,
"inlines":[
{
"title":"Kirche St. Nikolaus",
"image":{
"pk":15,
"model":"filer.Image"
},
"marker_width":"25px",
"position":{
"lat":47.274337475394645,
"lng":11.393036842346191
},
"popup_text":null,
"marker_anchor":{
"top":"50%",
"left":"50%"
}
}
]
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_footer.scss
================================================
// include this file when using a static footer
@import "variables";
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: $body-footer-height;
}
#footer {
position: absolute;
bottom: 0;
width: 100%;
height: $body-footer-height;
color: $body-footer-color;
background: $body-footer-bg;
padding-top: 20px;
padding-bottom: 20px;
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_variables.scss
================================================
@import "bootstrap/scss/_functions";
@import "bootstrap/scss/_variables";
@import "bootstrap/scss/mixins/_breakpoints.scss";
// footer
$body-footer-height: 200px;
$body-footer-color: $yiq-text-light;
$body-footer-bg: $dark;
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/badge.scss
================================================
.badge-ribbon {
position: relative;
background: lightgrey;
font-size: 50px;
height: 100px;
width: 100px;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
text-align: center;
padding-top: 12px;
margin-bottom: 50px;
&:before, &:after {
content: '';
position: absolute;
border-bottom: 70px solid lightgrey;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
top: 90px;
left: -10px;
-webkit-transform: rotate(-150deg);
-moz-transform: rotate(-150deg);
-ms-transform: rotate(-150deg);
-o-transform: rotate(-150deg);
}
&:after {
left: auto;
right: -10px;
-webkit-transform: rotate(150deg);
-moz-transform: rotate(150deg);
-ms-transform: rotate(150deg);
-o-transform: rotate(150deg);
}
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/main.scss
================================================
@import "variables";
@import "bootstrap/scss/bootstrap";
@import "footer";
body {
background-color: #eee8e8;
}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/badge.html
================================================
{% load sekizai_tags sass_tags %}
{% addtoblock "css" %}{% endaddtoblock %}
{% with inline_styles=instance.inline_styles %}
{{ instance.glossary.content }}
{% endwith %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/base.html
================================================
{% load static cms_tags sekizai_tags %}
{% block title %}Page Title{% endblock %}
{% block head %}{% endblock %}
{% render_block "css" %}
{% cms_toolbar %}
{% block header %}{% endblock %}
{% block main %}{% endblock %}
{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}
{% else %}
{% endif %}
{% render_block "js" %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/main.html
================================================
{% extends "bs4demo/base.html" %}
{% load static cms_tags bootstrap_tags sekizai_tags sass_tags %}
{% block head %}
{% addtoblock "css" %}{% endaddtoblock %}
{% endblock head %}
{% block header %}
{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}
{% include "bootstrap4/includes/ng-nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" %}
{% else %}
{% include "bootstrap4/includes/nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" role="navigation" %}
{% endif %}
{% if cms_version >= "3.5.0" and request.toolbar %}
{% endif %}
{% endblock header %}
{% block main %}{% placeholder "Main Content" or %}
Add Bootstrap container here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL, switch to “Structure” mode
and add a Bootstrap Container Plugin or Jumbotron Plugin.
{% endplaceholder %}{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/strides.html
================================================
{% extends "bs4demo/main.html" %}
{% load cascade_tags %}
{% block main %}
Content is rendered using the templatetag {% verbatim %}{% render_cascade "bs4demo/cascades/strides.json" %}{% endverbatim %}
{% render_cascade "bs4demo/cascades/strides.json" %}
{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/wrapped.html
================================================
{% extends "bs4demo/main.html" %}
{% load cms_tags %}
{% block main %}
{% placeholder "Bootstrap Column" or %}
Add some Bootstrap plugins here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL and switch to “Structure” mode.
Into this hard coded Bootstrap Column, add a Text Plugin.
If you want to further subdivide this column, add a Row Plugin with their own columns.
{% endplaceholder %}
{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/urls.py
================================================
# -*- coding: utf-8 -*-
from django.conf import settings
from django.urls import include, path, re_path
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
class CascadeDemoView(TemplateView):
template_name = 'bs4demo/strides.html'
admin.autodiscover()
urlpatterns = [
path('admin/select2/', include('django_select2.urls')),
path('admin/', admin.site.urls),
path('cascade/', CascadeDemoView.as_view()),
path('', include('cms.urls')),
]
urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
================================================
FILE: examples/bs4demo/bs4demo/utils.py
================================================
def find_django_migrations_module(module_name):
""" Tries to locate .migrations_django (without actually importing it).
Appends either ".migrations_django" or ".migrations" to module_name.
For details why:
https://docs.djangoproject.com/en/1.7/topics/migrations/#libraries-third-party-apps
"""
import imp
try:
module_info = imp.find_module(module_name)
module = imp.load_module(module_name, *module_info)
imp.find_module('migrations_django', module.__path__)
return module_name + '.migrations_django'
except ImportError:
return module_name + '.migrations' # conforms to Django 1.7 defaults
================================================
FILE: examples/bs4demo/manage.py
================================================
#!/usr/bin/env python
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.pardir))
if __name__ == "__main__":
from django.core.management import execute_from_command_line
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bs4demo.settings')
execute_from_command_line(sys.argv)
================================================
FILE: examples/bs4demo/package.json
================================================
{
"name": "djangocms-cascade",
"version": "0.10.0",
"description": "DjangoCMS-Cascade is the Swiss army knife for working with Django CMS plugins",
"directories": {
"doc": "docs",
"example": "examples",
"test": "tests"
},
"dependencies": {
"angular": "^1.5.11",
"angular-animate": "^1.5.11",
"angular-sanitize": "^1.5.11",
"ui-bootstrap4": "^3.0.5",
"bootstrap": "^4.1.3",
"jquery": "^3.2.1",
"leaflet": "^1.2.0",
"leaflet-easybutton": "^2.2.0",
"picturefill": "^3.0.2",
"popper.js": "^1.12.9",
"select2": "^4.0.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jrief/djangocms-cascade.git"
},
"author": "Jacob Rief",
"license": "MIT",
"bugs": {
"url": "https://github.com/jrief/djangocms-cascade/issues"
},
"homepage": "https://github.com/jrief/djangocms-cascade#readme"
}
================================================
FILE: pytest.ini
================================================
[pytest]
DJANGO_SETTINGS_MODULE = tests.settings
addopts = --tb native
================================================
FILE: requirements/base.txt
================================================
Pillow
argparse
requests
django-admin-sortable2<2
django-classy-tags
django-sekizai
django-entangled
django-filer
django-select2
djangocms-text-ckeditor
easy-thumbnails[svg]
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
from setuptools import setup, find_packages
from cmsplugin_cascade import __version__
with open('README.md', 'r') as fh:
long_description = fh.read()
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
]
setup(
name='djangocms-cascade',
version=__version__,
description='Build Single Page Applications using the Django-CMS plugin system',
author='Jacob Rief',
author_email='jacob.rief@gmail.com',
url='https://github.com/jrief/djangocms-cascade',
packages=find_packages(exclude=['examples', 'docs', 'tests']),
install_requires=[
'django>=3.2,<5',
'django-classy-tags>=1.0',
'django-cms>=3.10,<4',
'django-entangled>=0.5.3',
'djangocms-text-ckeditor>=4.0',
'django-select2>=7.7',
'requests',
],
license='MIT',
platforms=['OS Independent'],
classifiers=CLASSIFIERS,
long_description=long_description,
long_description_content_type='text/markdown',
include_package_data=True,
zip_safe=False,
)
================================================
FILE: tests/__init__.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: tests/bootstrap4/__init__.py
================================================
================================================
FILE: tests/bootstrap4/conftest.py
================================================
import pytest
from cms.api import add_plugin
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.container import BootstrapContainerPlugin, BootstrapRowPlugin, BootstrapColumnPlugin
@pytest.fixture
@pytest.mark.django_db
def bootstrap_container(admin_site, cms_placeholder):
# add a Bootstrap Container Plugin
glossary = {'breakpoints': ['xs', 'sm', 'md', 'lg', 'xl'], 'fluid': ''}
container_model = add_plugin(cms_placeholder, BootstrapContainerPlugin, 'en', glossary=glossary)
assert isinstance(container_model, CascadeElement)
container_plugin = container_model.get_plugin_class_instance(admin_site)
assert isinstance(container_plugin, BootstrapContainerPlugin)
return container_plugin, container_model
@pytest.fixture
@pytest.mark.django_db
def bootstrap_row(admin_site, bootstrap_container):
# add a Bootstrap Row Plugin to the given container
container_plugin, container_model = bootstrap_container
row_model = add_plugin(container_model.placeholder, BootstrapRowPlugin, 'en', target=container_model)
assert isinstance(row_model, CascadeElement)
row_plugin = row_model.get_plugin_class_instance()
assert isinstance(row_plugin, BootstrapRowPlugin)
return row_plugin, row_model
@pytest.fixture
@pytest.mark.django_db
def bootstrap_column(admin_site, bootstrap_row):
# add a Bootstrap Column Plugin to the given row
row_plugin, row_model = bootstrap_row
glossary = {'xs-column-width': 'col'}
column_model = add_plugin(row_model.placeholder, BootstrapColumnPlugin, 'en', target=row_model, glossary=glossary)
assert isinstance(column_model, CascadeElement)
column_plugin = column_model.get_plugin_class_instance()
assert isinstance(column_plugin, BootstrapColumnPlugin)
return column_plugin, column_model
================================================
FILE: tests/bootstrap4/test_accordion.py
================================================
import pytest
from django.template.context import RequestContext
from cms.api import add_plugin
from cms.plugin_rendering import ContentRenderer
from cms.utils.plugins import build_plugin_tree
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.accordion import BootstrapAccordionGroupPlugin, BootstrapAccordionPlugin
@pytest.fixture
@pytest.mark.django_db
def bootstrap_accordion(rf, admin_site, bootstrap_column):
request = rf.get('/')
column_plugin, column_model = bootstrap_column
# add accordion plugin
accordion_model = add_plugin(column_model.placeholder, BootstrapAccordionPlugin, 'en', target=column_model)
assert isinstance(accordion_model, CascadeElement)
accordion_plugin = accordion_model.get_plugin_class_instance(admin_site)
assert isinstance(accordion_plugin, BootstrapAccordionPlugin)
data = {'num_children': 2, 'close_others': 'on', 'first_is_open': 'on'}
ModelForm = accordion_plugin.get_form(request, accordion_model)
form = ModelForm(data, None, instance=accordion_model)
assert form.is_valid()
accordion_plugin.save_model(request, accordion_model, form, False)
assert accordion_model.glossary['close_others'] is True
assert accordion_model.glossary['first_is_open'] is True
for child in accordion_model.get_children():
assert isinstance(child.get_plugin_class_instance(admin_site), BootstrapAccordionGroupPlugin)
return accordion_plugin, accordion_model
@pytest.mark.django_db
def test_edit_accordion_group(rf, admin_site, bootstrap_accordion):
request = rf.get('/')
accordion_plugin, accordion_model = bootstrap_accordion
first_group = accordion_model.get_first_child()
group_model, group_plugin = first_group.get_plugin_instance(admin_site)
data = {'heading': "Hello", 'body_padding': 'on'}
ModelForm = group_plugin.get_form(request, group_model)
form = ModelForm(data, None, instance=group_model)
assert form.is_valid()
group_plugin.save_model(request, group_model, form, False)
assert group_model.glossary['heading'] == "Hello"
assert group_model.glossary['body_padding'] is True
# render the plugin
build_plugin_tree([accordion_model, group_model])
context = RequestContext(request)
content_renderer = ContentRenderer(request)
html = content_renderer.render_plugin(accordion_model, context).strip()
html = html.replace('\n', '').replace('\t', '')
expected = """
""".format(accordion_id=accordion_model.id, group_id=group_model.id)
expected = expected.replace('\n', '').replace('\t', '')
assert html == expected
================================================
FILE: tests/bootstrap4/test_container.py
================================================
import pytest
from bs4 import BeautifulSoup
from django.utils.html import strip_spaces_between_tags
from cms.plugin_rendering import ContentRenderer
from cms.utils.plugins import build_plugin_tree
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.container import BootstrapColumnPlugin
@pytest.mark.django_db
def test_edit_bootstrap_container(rf, bootstrap_container):
container_plugin, container_model = bootstrap_container
request = rf.get('/')
ModelForm = container_plugin.get_form(request, container_model)
data = {'breakpoints': ['sm', 'md']}
form = ModelForm(data, None, instance=container_model)
assert form.is_valid()
soup = BeautifulSoup(form.as_p(), features='lxml')
input_element = soup.find(id="id_breakpoints_0")
assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'xs'}.items() <= input_element.attrs.items()
input_element = soup.find(id="id_breakpoints_2")
assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'md', 'checked': ''}.items() <= input_element.attrs.items()
input_element = soup.find(id="id_fluid")
assert {'type': 'checkbox', 'name': 'fluid'}.items() <= input_element.attrs.items()
container_plugin.save_model(request, container_model, form, False)
assert container_model.glossary['breakpoints'] == ['sm', 'md']
assert 'fluid' in container_model.glossary
assert str(container_model) == "for Landscape Phones, Tablets"
@pytest.mark.django_db
def test_edit_bootstrap_row(rf, bootstrap_row):
row_plugin, row_model = bootstrap_row
request = rf.get('/')
ModelForm = row_plugin.get_form(request, row_model)
data = {'num_children': 3}
form = ModelForm(data, None, instance=row_model)
assert form.is_valid()
row_plugin.save_model(request, row_model, form, False)
container_model, container_plugin = row_model.parent.get_plugin_instance()
plugin_list = [container_model, row_model]
# we now should have three columns attached to the row
assert row_model.get_descendant_count() == 3
for cms_plugin in row_model.get_descendants():
column_model, column_plugin = cms_plugin.get_plugin_instance()
assert isinstance(column_model, CascadeElement)
assert isinstance(column_plugin, BootstrapColumnPlugin)
assert column_model.parent.id == row_model.id
plugin_list.append(column_model)
# change data inside the first column
cms_plugin = row_model.get_descendants().first()
column_model, column_plugin = cms_plugin.get_plugin_instance()
data = {'xs-column-width': 'col', 'sm-column-offset': 'offset-sm-1', 'sm-column-width': 'col-sm-3'}
ModelForm = column_plugin.get_form(request, column_model)
form = ModelForm(data, None, instance=column_model)
assert form.is_valid()
column_plugin.save_model(request, column_model, form, True)
# change data inside the last column
cms_plugin = row_model.get_descendants().last()
column_model, column_plugin = cms_plugin.get_plugin_instance()
data = {'xs-column-width': 'col', 'sm-responsive-utils': 'hidden-sm', 'sm-column-width': 'col-sm-4'}
ModelForm = column_plugin.get_form(request, column_model)
form = ModelForm(data, None, instance=column_model)
assert form.is_valid()
column_plugin.save_model(request, column_model, form, False)
# render the plugin and check the output
context = {
'request': request,
}
content_renderer = ContentRenderer(request)
row_model.parent.child_plugin_instances
for plugin in plugin_list:
plugin.refresh_from_db()
build_plugin_tree(plugin_list)
html = content_renderer.render_plugin(container_model, context)
html = strip_spaces_between_tags(html).strip()
assert html == '' \
''
================================================
FILE: tests/bootstrap4/test_grid.py
================================================
import pytest
from cmsplugin_cascade.bootstrap4.grid import (Bootstrap4Container, Bootstrap4Row, Bootstrap4Column, BootstrapException,
Breakpoint, Bound, fluid_bounds)
def test_breakpoint_iter():
for k, bp in enumerate(Breakpoint):
if k == 0: assert bp == Breakpoint.xs
if k == 1: assert bp == Breakpoint.sm
if k == 2: assert bp == Breakpoint.md
if k == 3: assert bp == Breakpoint.lg
if k == 4: assert bp == Breakpoint.xl
assert k == 4
def test_breakpoint_range():
for k, bp in enumerate(Breakpoint.range(Breakpoint.xs, Breakpoint.xl)):
if k == 0: assert bp == Breakpoint.xs
if k == 1: assert bp == Breakpoint.sm
if k == 2: assert bp == Breakpoint.md
if k == 3: assert bp == Breakpoint.lg
if k == 4: assert bp == Breakpoint.xl
assert k == 4
def test_breakpoint_partial():
for k, bp in enumerate(Breakpoint.range(Breakpoint.sm, Breakpoint.lg)):
if k == 0: assert bp == Breakpoint.sm
if k == 1: assert bp == Breakpoint.md
if k == 2: assert bp == Breakpoint.lg
assert k == 2
def test_xs_cols():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
for _ in range(3):
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[2].get_bound(Breakpoint.sm) == Bound(180.0, 180.0)
assert row[2].get_bound(Breakpoint.md) == Bound(240.0, 240.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
def test_fluid_xs_cols():
"""
"""
container = Bootstrap4Container(bounds=fluid_bounds)
row = container.add_row(Bootstrap4Row())
for _ in range(3):
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(192.0, 256.0)
assert row[2].get_bound(Breakpoint.md) == Bound(256.0, 330.7)
assert row[2].get_bound(Breakpoint.lg) == Bound(330.7, 400.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(400.0, 660.0)
def test_xs_cols_with_flex():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-3'))
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col'))
repr(row[0])
assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)
assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(120.0, 214.5)
assert row[1].get_bound(Breakpoint.sm) == Bound(202.5, 202.5)
assert row[1].get_bound(Breakpoint.md) == Bound(270.0, 270.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(360.0, 360.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(427.5, 427.5)
def test_xs_cols_with_auto_and_flex():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-3'))
row.add_column(Bootstrap4Column('col-auto'))
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)
assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)
assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)
assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 480.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)
assert row[2].get_bound(Breakpoint.md) == Bound(30.0, 480.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)
def test_mix_flex_with_fixed():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col col-1'))
def test_mix_flex_with_auto():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col col-auto'))
def test_mix_fixed_with_auto():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col-1 col-auto'))
def test_growing_columns():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))
row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))
row.add_column(Bootstrap4Column('col-12 col-sm-12 col-lg-4'))
assert row[0].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(270.0, 270.0)
assert row[0].get_bound(Breakpoint.md) == Bound(360.0, 360.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(540.0, 540.0)
assert row[2].get_bound(Breakpoint.md) == Bound(720.0, 720.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
def test_haricot():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col-auto'))
row.add_column(Bootstrap4Column('col-2'))
assert row[0].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)
assert row[0].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)
assert row[0].get_bound(Breakpoint.md) == Bound(30.0, 540.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)
assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)
assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 540.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(53.3, 95.3)
assert row[2].get_bound(Breakpoint.sm) == Bound(90.0, 90.0)
assert row[2].get_bound(Breakpoint.md) == Bound(120.0, 120.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(160.0, 160.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(190.0, 190.0)
def test_nested_row():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col'))
nested_row = row[0].add_row(Bootstrap4Row())
nested_row.add_column(Bootstrap4Column('col-5'))
nested_row.add_column(Bootstrap4Column('col-7'))
assert nested_row[0].get_bound(Breakpoint.xs) == Bound(66.7, 119.2)
assert nested_row[1].get_bound(Breakpoint.xs) == Bound(93.3, 166.8)
assert nested_row[0].get_bound(Breakpoint.sm) == Bound(112.5, 112.5)
assert nested_row[1].get_bound(Breakpoint.sm) == Bound(157.5, 157.5)
assert nested_row[0].get_bound(Breakpoint.md) == Bound(150.0, 150.0)
assert nested_row[1].get_bound(Breakpoint.md) == Bound(210.0, 210.0)
assert nested_row[0].get_bound(Breakpoint.lg) == Bound(200.0, 200.0)
assert nested_row[1].get_bound(Breakpoint.lg) == Bound(280.0, 280.0)
assert nested_row[0].get_bound(Breakpoint.xl) == Bound(237.5, 237.5)
assert nested_row[1].get_bound(Breakpoint.xl) == Bound(332.5, 332.5)
def test_repr():
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.compute_column_bounds()
assert repr(container) == ', , , , >>>'
================================================
FILE: tests/conftest.py
================================================
import factory.fuzzy
import pytest
from pytest_factoryboy import register
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password
from cms.api import create_page
from cmsplugin_cascade.models import CascadePage
@pytest.fixture
def admin_site():
return admin.sites.AdminSite()
@pytest.fixture
@pytest.mark.django_db
def cms_page():
home_page = create_page(title='HOME', template='testing.html', language='en')
if not home_page.is_home:
home_page.set_as_homepage()
CascadePage.assure_relation(home_page)
return home_page
@pytest.fixture
@pytest.mark.django_db
def cms_placeholder(cms_page):
placeholder = cms_page.placeholders.get(slot='Main Content')
return placeholder
@register
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
@classmethod
def create(cls, **kwargs):
user = super().create(**kwargs)
assert isinstance(user, get_user_model())
assert user.is_authenticated == True
return user
username = factory.Sequence(lambda n: 'uid-{}'.format(n))
password = make_password('secret')
email = factory.fuzzy.FuzzyText(suffix='@example.com')
================================================
FILE: tests/requirements.txt
================================================
lxml
beautifulsoup4
pluggy
py
pytest
pytest-django
coverage
django-reversion
factory-boy
pytest-factoryboy
================================================
FILE: tests/settings.py
================================================
from django.utils.text import format_lazy
from django.urls import reverse_lazy
from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
]
ROOT_URLCONF = 'tests.urls'
SECRET_KEY = 'test'
SITE_ID = 1
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': ['tests/templates'],
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
)
}
}]
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.staticfiles',
'filer',
'easy_thumbnails',
'treebeard',
'menus',
'sekizai',
'cms',
'adminsortable2',
'djangocms_text_ckeditor',
'django_select2',
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.segmentation',
'tests',
]
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('en', 'English'),
]
LANGUAGE_CODE = 'en'
CMS_TEMPLATES = [
('testing.html', 'Default Page'),
]
CMSPLUGIN_CASCADE_PLUGINS = [
'cmsplugin_cascade.link',
'cmsplugin_cascade.bootstrap4',
]
CMSPLUGIN_CASCADE = {
'plugins_with_extra_fields': {
'BootstrapButtonPlugin': PluginExtraFieldsConfig(),
'BootstrapContainerPlugin': PluginExtraFieldsConfig(),
'BootstrapColumnPlugin': PluginExtraFieldsConfig(),
'BootstrapRowPlugin': PluginExtraFieldsConfig(),
'BootstrapPicturePlugin': PluginExtraFieldsConfig(),
'SimpleWrapperPlugin': PluginExtraFieldsConfig(),
},
'plugins_with_sharables': {
'BootstrapImagePlugin': (
'image_shapes',
'image_width_responsive',
'image_width_fixed',
'image_height',
'resize_options',
),
'BootstrapPicturePlugin': (
'image_shapes',
'responsive_heights',
'image_size',
'resize_options',
),
},
}
CMS_PLACEHOLDER_CONF = {
'Main Content': {
'plugins': ['BootstrapContainerPlugin'],
'parent_classes': {
'BootstrapContainerPlugin': None,
'TextLinkPlugin': ['TextPlugin'],
},
},
}
THUMBNAIL_PROCESSORS = (
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
)
THUMBNAIL_PRESERVE_EXTENSIONS = True,
THUMBNAIL_OPTIMIZE_COMMAND = {
'png': '/opt/local/bin/optipng {filename}',
'gif': '/opt/local/bin/optipng {filename}',
'jpeg': '/opt/local/bin/jpegoptim {filename}',
}
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'skin': 'moono',
'toolbar': 'CMS',
'toolbar_HTMLField': [
['Undo', 'Redo'],
['cmsplugins', '-', 'ShowBlocks'],
['Format', 'Styles'],
['TextColor', 'BGColor', '-', 'PasteText', 'PasteFromWord'],
['Maximize', ''],
'/',
['Bold', 'Italic', 'Underline', '-', 'Subscript', 'Superscript', '-', 'RemoveFormat'],
['JustifyLeft', 'JustifyCenter', 'JustifyRight'],
['HorizontalRule'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Table'],
['Source']
],
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),
}
SILENCED_SYSTEM_CHECKS = ['2_0.W001']
================================================
FILE: tests/static/strides/bootstrap-button.json
================================================
{
"plugins":[
[
"BootstrapContainerPlugin",
{
"glossary":{
"hide_plugin":false,
"padding_xs":"",
"padding_sm":"",
"padding_md":"",
"padding_lg":"",
"breakpoints":[
"xs",
"sm",
"md",
"lg",
"xl"
],
"fluid":""
},
"pk":2511
},
[
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":""
},
"pk":2512
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"xs-column-width":"col"
},
"pk":2513
},
[
[
"BootstrapButtonPlugin",
{
"glossary":{
"hide_plugin":"",
"link_type":"",
"cms_page":null,
"section":"",
"download_file":null,
"ext_url":"",
"mail_to":"",
"link_target":"",
"link_title":"",
"icon_font":{
"model":"cmsplugin_cascade.iconfont",
"pk":1
},
"symbol":"",
"link_content":"button_content",
"button_type":"btn-secondary",
"button_size":"",
"button_options":[],
"icon_align":"icon-right",
"stretched_link":false
},
"pk":2514
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-column.json
================================================
{
"plugins": [
[
"BootstrapColumnPlugin",
{
"pk":1539,
"glossary":{
"container_max_widths":{
"xs":720.0,
"md":940.0,
"sm":720.0,
"lg":1140.0
},
"xs-column-width":"col-xs-12"
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-container.json
================================================
{
"plugins": [
[
"BootstrapContainerPlugin",
{
"pk":1503,
"glossary":{
"container_max_widths":{
"xs":750,
"md":970,
"sm":750,
"lg":1170
},
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"lg":[
"(min-width: 1200px)"
]
},
"hide_plugin":"",
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"fluid":"",
"extra_css_classes": "foo bar"
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-jumbotron.json
================================================
{
"plugins": [
[
"BootstrapJumbotronPlugin",
{
"pk": 1501,
"glossary": {
"background_vertical_position": "center",
"extra_inline_styles:Paddings": {
"padding-top": "500px",
"padding-bottom": ""
},
"resize_options": [
"crop",
"subject_location",
"high_resolution"
],
"breakpoints": [
"xs",
"sm",
"md",
"lg"
],
"hide_plugin": "",
"background_color": [
"",
"#12308b"
],
"fluid": true,
"container_max_widths": {
"xs": 768,
"md": 1200,
"sm": 992,
"lg": 1980
},
"media_queries": {
"xs": [
"(max-width: 768px)"
],
"md": [
"(min-width: 992px)",
"(max-width: 1200px)"
],
"sm": [
"(min-width: 768px)",
"(max-width: 992px)"
],
"lg": [
"(min-width: 1200px)"
]
},
"image": {
"pk": 5,
"model": "filer.Image"
},
"background_size": "cover",
"background_horizontal_position": "center",
"background_repeat": "no-repeat",
"container_max_heights": {
"xs": "100%",
"md": "100%",
"lg": "100%",
"sm": "100%"
},
"background_width_height": {
"height": "",
"width": ""
},
"background_attachment": "scroll"
}
},
[
[
"TextPlugin",
{
"body": "Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
",
"pk": 1502
},
[]
]
]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-row.json
================================================
{
"plugins": [
[
"BootstrapRowPlugin",
{
"pk":1538,
"glossary":{
"hide_plugin":"",
"extra_css_classes":"",
"extra_inline_styles:Margins":{
"margin-bottom":"20px",
"margin-top":""
},
"extra_element_id":""
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/carousel-plugin.json
================================================
{
"plugins": [
[
"BootstrapCarouselPlugin",
{
"glossary":{
"hide_plugin":false,
"margins_xs":"",
"margins_sm":"",
"margins_md":"",
"margins_lg":"",
"interval":5,
"options":[
"slide",
"pause",
"wrap"
],
"container_max_heights":{
"xs":"9rem",
"sm":"9rem",
"md":"9rem",
"lg":"9rem",
"xl":"9rem"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
]
},
"pk":229
},
[
[
"BootstrapCarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":4,
"model":"filer.Image"
},
"media_queries":{
"xs":{
"width":572,
"media":"(max-width: 575.98px)"
},
"sm":{
"width":540,
"media":"(min-width: 576px) and (max-width: 767.98px)"
},
"md":{
"width":720,
"media":"(min-width: 768px) and (max-width: 991.98px)"
},
"lg":{
"width":960,
"media":"(min-width: 992px) and (max-width: 1199.98px)"
},
"xl":{
"width":1140,
"media":"(min-width: 1200px)"
}
}
},
"pk":1526
},
[]
]
]
]
]
}
================================================
FILE: tests/static/strides/framed-icon.json
================================================
{
"plugins": [
[
"FramedIconPlugin",
{
"pk":1513,
"glossary":{
"symbol":"umbrella",
"border_radius":"",
"color":[
"#ffffff",
""
],
"text_align":"text-center",
"hide_plugin":"",
"icon_font":"1",
"font_size":"10em",
"background_color":[
"#ffffff",
"true"
],
"border":[
"0px",
"none",
"#000000"
]
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/simple-wrapper.json
================================================
{
"plugins": [
[
"SimpleWrapperPlugin",
{
"pk":1512,
"glossary":{
"tag_type":"div",
"extra_inline_styles:background-color":[
"#42c8c6",
""
],
"extra_inline_styles:line-height":"",
"extra_css_classes":[],
"hide_plugin":"",
"extra_inline_styles:color":[
"#ffffff",
""
],
"element_id":"",
"extra_inline_styles:Paddings":{
"padding-left":"50px",
"padding-bottom":"",
"padding-top":"",
"padding-right":"50px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:Heights":{
"height":"360px",
"min-height":"",
"max-height":""
}
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/text-plugin.json
================================================
{
"plugins": [
[
"TextPlugin",
{
"body": "Customizable
\n\nLorem ipsum dolor
",
"pk": 1518
},
[]
]
]
}
================================================
FILE: tests/static/strides.json
================================================
{
"plugins":[
[
"BootstrapJumbotronPlugin",
{
"pk":1584,
"glossary":{
"background_repeat":"no-repeat",
"image":{
"model":"filer.Image",
"pk":5
},
"background_size":"cover",
"fluid":true,
"resize_options":[
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"container_max_heights":{
"sm":"100%",
"md":"100%",
"lg":"100%",
"xs":"100%"
},
"background_horizontal_position":"center",
"media_queries":{
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"lg":[
"(min-width: 1200px)"
],
"xs":[
"(max-width: 768px)"
]
},
"background_vertical_position":"center",
"container_max_widths":{
"sm":992,
"md":1200,
"lg":1980,
"xs":768
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"background_color":[
"",
"#12308b"
],
"background_attachment":"scroll",
"extra_inline_styles:Paddings":{
"padding-bottom":"",
"padding-top":"500px"
},
"background_width_height":{
"width":"",
"height":""
}
}
},
[
[
"TextPlugin",
{
"body":"Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
",
"pk":1585
},
[]
]
]
],
[
"BootstrapContainerPlugin",
{
"pk":1586,
"glossary":{
"fluid":"",
"media_queries":{
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"lg":[
"(min-width: 1200px)"
],
"xs":[
"(max-width: 768px)"
]
},
"container_max_widths":{
"sm":750,
"md":970,
"lg":1170,
"xs":750
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"hide_plugin":""
}
},
[
[
"HeadingPlugin",
{
"pk":1624,
"glossary":{
"element_id":"heading-1",
"content":"Voluptate velit esse cillum dolore",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"",
"margin-left":"50px",
"margin-right":""
},
"tag_type":"h1"
}
},
[]
],
[
"BootstrapRowPlugin",
{
"pk":1587,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1588,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":720.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"col-md-6",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1589,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"height":"347px"
},
"extra_inline_styles:color":[
"",
"#474747"
],
"extra_inline_styles:line-height":"2",
"extra_inline_styles:background-color":[
"",
"#ffffff"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"20px",
"padding-right":"50px",
"padding-bottom":"20px",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"TextPlugin",
{
"body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum.
",
"pk":1590
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1591,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":720.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"col-md-6",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"",
"md-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"BootstrapImagePlugin",
{
"pk":1592,
"glossary":{
"target":"",
"image_width_responsive":"100%",
"image":{
"model":"filer.Image",
"pk":8
},
"image_height":"",
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"alt_tag":"",
"image_shapes":[
"img-responsive"
],
"link":{
"type":"none"
},
"title":"",
"image_width_fixed":"",
"image_title":""
}
},
[]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1593,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1594,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1595,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1596,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"umbrella"
}
},
[]
],
[
"TextPlugin",
{
"body":"\u00a0\u00a0Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1597
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1598,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1599,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1600,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"cog-alt"
}
},
[]
],
[
"TextPlugin",
{
"body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1601
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1602,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1603,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1604,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"bell-alt"
}
},
[]
],
[
"TextPlugin",
{
"body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1605
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1606,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1607,
"glossary":{
"container_max_widths":{
"sm":720.0,
"md":940.0,
"lg":1140.0,
"xs":720.0
},
"xs-column-width":"col-xs-12"
}
},
[
[
"CarouselPlugin",
{
"pk":1608,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"options":[
"slide",
"pause",
"wrap"
],
"container_max_heights":{
"sm":"250px",
"md":"300px",
"lg":"350px",
"xs":"200px"
},
"interval":"5",
"hide_plugin":""
}
},
[
[
"CarouselSlidePlugin",
{
"pk":1609,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":4
},
"alt_tag":"",
"hide_plugin":""
}
},
[
[
"TextPlugin",
{
"body":"Hallo Welt!
",
"pk":1610
},
[]
]
]
],
[
"CarouselSlidePlugin",
{
"pk":1611,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":7
},
"alt_tag":"",
"hide_plugin":""
}
},
[]
],
[
"CarouselSlidePlugin",
{
"pk":1612,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":6
},
"alt_tag":"",
"hide_plugin":""
}
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1614,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":"20px"
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1615,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":345.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"col-sm-6",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"BootstrapImagePlugin",
{
"pk":1616,
"glossary":{
"target":"",
"image_width_responsive":"100%",
"image":{
"model":"filer.Image",
"pk":9
},
"image_height":"",
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"alt_tag":"",
"image_shapes":[
"img-responsive"
],
"link":{
"type":"none"
},
"title":"",
"image_width_fixed":"",
"image_title":""
}
},
[]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1617,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":345.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"col-sm-6",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1618,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"370px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":"130%"
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#ef3e42"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"50px",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"TextPlugin",
{
"body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
",
"pk":1619
},
[]
],
[
"BootstrapButtonPlugin",
{
"pk":1620,
"glossary":{
"link":{
"section":"",
"model":"cms.Page",
"type":"cmspage",
"pk":5
},
"button_options":[],
"icon_font":"3",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
},
"link_content":"Continue",
"button_size":"btn-lg",
"symbol":"right-open",
"icon_align":"icon-right",
"quick_float":"pull-right",
"button_type":"btn-default",
"hide_plugin":""
}
},
[]
]
]
]
]
]
]
],
[
"HeadingPlugin",
{
"pk":1613,
"glossary":{
"element_id":"heading",
"content":"Where we are",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"",
"margin-left":"",
"margin-right":""
},
"tag_type":"h3"
}
},
[]
],
[
"BootstrapRowPlugin",
{
"pk":1621,
"glossary":{
"extra_element_id":"",
"extra_css_classes":"",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"20px"
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1622,
"glossary":{
"container_max_widths":{
"sm":720.0,
"md":940.0,
"lg":1140.0,
"xs":720.0
},
"xs-column-width":"col-xs-12"
}
},
[
[
"LeafletPlugin",
{
"inlines":[
{
"marker_width":"75px",
"title":"Kirche St. Nikolaus",
"image":{
"model":"filer.Image",
"pk":3
},
"marker_anchor":{
"top":"35px",
"left":"35px"
},
"popup_text":"Kirche in St. Nikolaus
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
",
"position":{
"lng":11.392865180969238,
"lat":47.27430835780767
}
},
{
"popup_text":null,
"marker_width":"",
"title":"SOHO 2.0",
"position":{
"lng":11.44007205963135,
"lat":47.26479535533907
},
"marker_anchor":{
"top":"",
"left":""
}
}
],
"pk":1623,
"glossary":{
"map_width":"100%",
"map_position":{
"zoom":16,
"lat":47.263227953097356,
"lng":11.434611082077026
},
"map_height":"400px",
"hide_plugin":"",
"render_template":"cascade/plugins/googlemap.html"
}
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: tests/templates/testing.html
================================================
{% load cms_tags sekizai_tags %}
Test Template
{% render_block "css" %}
{% placeholder "Main Content" %}
================================================
FILE: tests/test_base.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password
from django.template.context import Context
from cms.api import create_page
from cms.test_utils.testcases import CMSTestCase
from cmsplugin_cascade.models import CascadePage
class CascadeTestCase(CMSTestCase):
home_page = None
def setUp(self):
self.home_page = create_page(title='HOME', template='testing.html', language='en')
if not self.home_page.is_home:
# >= Django CMS v3.5.x
self.home_page.set_as_homepage()
CascadePage.assure_relation(self.home_page)
self.placeholder = self.home_page.placeholders.get(slot='Main Content')
self.request = self.get_request(self.home_page, 'en')
self.admin_site = admin.sites.AdminSite()
UserModel = get_user_model()
UserModel.objects.get_or_create(
username='admin',
is_staff=True,
is_superuser=True,
is_active=True,
password=make_password('admin'),
)
UserModel.objects.get_or_create(
username='staff',
is_staff=True,
is_superuser=False,
is_active=True,
password=make_password('staff'),
)
def get_request_context(self):
context = {}
context['request'] = self.request
context['user'] = self.request.user
try:
# >= Django CMS v3.4.x
context['cms_content_renderer'] = self.get_content_renderer(request=self.request)
except AttributeError:
# < Django CMS v3.4.x
pass
return Context(context)
def get_html(self, model_instance, context):
try:
# >= Django CMS v3.4.x
return context['cms_content_renderer'].render_plugin(model_instance, context)
except KeyError:
# < Django CMS v3.4.x
return model_instance.render_plugin(context)
================================================
FILE: tests/test_customplugin.py
================================================
from django.test import TestCase
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.plugin_base import CascadePluginBase
class CustomPlugin(CascadePluginBase):
name = 'Custom Element'
render_template = 'cascade/generic/naked.html'
class CustomPluginTest(TestCase):
def test_register(self):
plugin_pool.register_plugin(CustomPlugin)
def test_proxy_model_has_correct_app_label(self):
self.assertEqual(CustomPlugin.model._meta.app_label, 'cmsplugin_cascade')
================================================
FILE: tests/test_http.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from django.conf import settings
from django.contrib import admin
from django.test.utils import override_settings
from cms.models import Page
from cms.utils.compat.dj import is_installed
from cms.test_utils.testcases import CMSTestCase, URL_CMS_PAGE_ADD, URL_CMS_PLUGIN_ADD
import pytest
APPS_WITHOUT_REVERSION = [app for app in settings.INSTALLED_APPS if app != 'reversion']
class ContainerPluginTest(CMSTestCase):
def setUp(self):
self.admin_site = admin.sites.AdminSite()
self.user = self.get_superuser()
self.password = "top_secret"
self.user.set_password(self.password)
self.user.save()
self.client.login(username=self.user.username, password=self.password)
self.language = 'en'
self.site_id = settings.SITE_ID
# create page
response = self.client.post(
'/' + self.language + URL_CMS_PAGE_ADD[3:],
data={
'language': self.language,
'site': self.site_id,
'template': 'INHERIT',
'title': 'HOME',
'slug': 'home',
'_save': 'Save',
},
follow=True,
)
self.assertEqual(response.status_code, 200)
# get page and placeholder
self.page = Page.objects.get(publisher_is_draft=True, is_home=True)
self.placeholder = self.page.placeholders.get(slot='Main Content')
def _create_and_configure_a_container_plugin(self):
# create a plugin
response = self.client.post(
'/' + self.language + URL_CMS_PLUGIN_ADD[3:],
data={
'plugin_parent': '',
'plugin_type': 'BootstrapContainerPlugin',
'plugin_language': self.language,
'placeholder_id': self.placeholder.id,
},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content.decode('utf-8'))
plugin_url = response_data['url']
# configure that plugin
response = self.client.post(
plugin_url,
data={
'_popup': '1',
'breakpoints': ['xs', 'lg'],
'_save': 'Save',
},
)
self.assertEqual(response.status_code, 200)
@override_settings(INSTALLED_APPS=APPS_WITHOUT_REVERSION)
@pytest.mark.skip(reason="no way of currently testing this")
def test_without_reversion(self):
self.assertFalse(is_installed('reversion'))
self._create_and_configure_a_container_plugin()
@pytest.mark.skip(reason="no way of currently testing this")
def test_with_reversion(self):
self.assertTrue(is_installed('reversion'))
self._create_and_configure_a_container_plugin()
================================================
FILE: tests/test_iconfont.py
================================================
import os
from bs4 import BeautifulSoup
import pytest
import factory.fuzzy
from pytest_factoryboy import register
from django import VERSION as DJANGO_VERSION
from django.forms.models import ModelForm
from django.urls import reverse, resolve
from django.core.files import File as DjangoFile
from django.template.context import RequestContext
from filer.models.filemodels import File as FilerFileModel
from cms.api import add_plugin
from cms.plugin_rendering import ContentRenderer
from cmsplugin_cascade.models import CascadeElement, IconFont
from cmsplugin_cascade.icon.forms import IconFormMixin
from cmsplugin_cascade.icon.simpleicon import SimpleIconPlugin
from .conftest import UserFactory
@register
class IconFileFactory(factory.django.DjangoModelFactory):
class Meta:
model = FilerFileModel
@classmethod
def create(cls, **kwargs):
filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')
fileobj = DjangoFile(open(filename, 'rb'), name='fontello-b504201f.zip')
owner = UserFactory(is_active=True, is_staff=True)
filer_fileobj = FilerFileModel.objects.create(
owner=owner,
original_filename=fileobj.name,
file=fileobj,
)
return filer_fileobj
@pytest.fixture
@pytest.mark.django_db
def icon_font(admin_client, icon_file_factory):
icon_file = icon_file_factory()
data = {
'identifier': "Fontellico",
'zip_file': icon_file.id,
'is_default': 'on',
'_continue': "Save and continue editing",
}
add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')
response = admin_client.post(add_iconfont_url, data)
assert response.status_code == 302
resolver_match = resolve(response.url)
assert resolver_match.url_name == 'cmsplugin_cascade_iconfont_change'
# check the content of the uploaded file
if DJANGO_VERSION >= (2, 0):
icon_font = IconFont.objects.get(pk=resolver_match.kwargs['object_id'])
else:
icon_font = IconFont.objects.get(pk=resolver_match.args[0])
assert icon_font.identifier == "Fontellico"
assert icon_font.config_data['name'] == 'fontelico'
assert len(icon_font.config_data['glyphs']) == 34
return icon_font
@pytest.mark.django_db
def test_iconfont_change_view(admin_client, icon_font):
# check if the uploaded fonts are rendered inside Preview Icons
change_url = reverse('admin:cmsplugin_cascade_iconfont_change', args=[icon_font.id])
response = admin_client.get(change_url)
assert response.status_code == 200
soup = BeautifulSoup(response.content, 'lxml')
css_prefix = soup.find('div', class_='field-css_prefix').find('div', class_='readonly')
assert css_prefix.text == 'icon-'
preview_iconfont = soup.find('div', class_='preview-iconfont')
icon_items = preview_iconfont.ul.find_all('li')
assert len(icon_items) == 34
assert icon_items[0].i.attrs['class'] == ['icon-emo-happy']
assert icon_items[33].i.attrs['class'] == ['icon-marquee']
@pytest.fixture
@pytest.mark.django_db
def simple_icon(admin_site, cms_placeholder, icon_font):
"""Create and edit a SimpleIconPlugin"""
class IconFontForm(IconFormMixin, ModelForm):
class Meta(IconFormMixin.Meta):
model = CascadeElement
# add simple icon plugin
simple_icon_model = add_plugin(cms_placeholder, SimpleIconPlugin, 'en')
assert isinstance(simple_icon_model, CascadeElement)
# edit simple icon plugin
data = {'icon_font': str(icon_font.id), 'symbol': 'icon-skiing'}
form = IconFontForm(data=data, instance=simple_icon_model)
assert form.is_valid()
simple_icon_model = form.save()
assert simple_icon_model.glossary['icon_font']['model'] == 'cmsplugin_cascade.iconfont'
assert simple_icon_model.glossary['symbol'] == 'icon-skiing'
simple_icon_plugin = simple_icon_model.get_plugin_class_instance(admin_site)
assert isinstance(simple_icon_plugin, SimpleIconPlugin)
return simple_icon_plugin, simple_icon_model
@pytest.mark.django_db
def test_simple_icon(rf, simple_icon):
"""Render a SimpleIconPlugin"""
simple_icon_plugin, simple_icon_model = simple_icon
request = rf.get('/')
context = RequestContext(request)
content_renderer = ContentRenderer(request)
html = content_renderer.render_plugin(simple_icon_model, context).strip()
assert html == ''
================================================
FILE: tests/test_missingmigrations.py
================================================
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import pytest
from django.core.management import call_command
@pytest.mark.django_db
def test_for_missing_migrations():
out = StringIO()
call_command('makemigrations', '--dry-run', 'cmsplugin_cascade', verbosity=3, interactive=False, stdout=out)
assert out.getvalue() == "No changes detected in app 'cmsplugin_cascade'\n"
================================================
FILE: tests/test_segmentation.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from bs4 import BeautifulSoup
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import get_user_model
from cms.api import add_plugin
from cms.utils.plugins import build_plugin_tree
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from cmsplugin_cascade.generic.simple_wrapper import SimpleWrapperPlugin
from cmsplugin_cascade.segmentation.cms_plugins import SegmentPlugin
from .test_base import CascadeTestCase
class SegmentationPluginTest(CascadeTestCase):
def setUp(self):
super(SegmentationPluginTest, self).setUp()
UserModel = get_user_model()
self.admin_user = UserModel.objects.get(username='admin')
self.staff_user = UserModel.objects.get(username='staff')
def test_plugin_context(self):
# create container
wrapper_model = add_plugin(self.placeholder, SimpleWrapperPlugin, 'en',
glossary={'tag_type': 'naked'})
wrapper_plugin = wrapper_model.get_plugin_class_instance(self.admin_site)
self.assertIsInstance(wrapper_plugin, SimpleWrapperPlugin)
# add an `if`-segment with some text as child
if_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'if', 'condition': 'user.is_superuser'})
self.assertIsInstance(if_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_admin = add_plugin(self.placeholder, TextPlugin, 'en', target=if_segment_model,
body='User is admin
')
self.assertIsInstance(text_model_admin.get_plugin_class_instance(), TextPlugin)
# add an `elif`-segment with some text as child
elif_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'elif', 'condition': 'user.is_authenticated'})
self.assertIsInstance(elif_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_staff = add_plugin(self.placeholder, TextPlugin, 'en', target=elif_segment_model,
body='User is staff
')
self.assertIsInstance(text_model_staff.get_plugin_class_instance(), TextPlugin)
# add an `else`-segment with some text as child
else_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'else'})
self.assertIsInstance(else_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_anon = add_plugin(self.placeholder, TextPlugin, 'en', target=else_segment_model,
body='User is anonymous
')
self.assertIsInstance(text_model_anon.get_plugin_class_instance(), TextPlugin)
# build the DOM
plugin_list = [wrapper_model, if_segment_model, text_model_admin, elif_segment_model,
text_model_staff, else_segment_model, text_model_anon]
build_plugin_tree(plugin_list)
# test for if-segment (render the plugins as admin user)
self.request.user = self.admin_user
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is admin')
# test for elif-segment (render the plugins as staff user)
self.request.user = self.staff_user
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is staff')
# test for else-segment (render the plugins as anonymous user)
self.request.user = AnonymousUser
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is anonymous')
================================================
FILE: tests/test_strides.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
import django
import os
from bs4 import BeautifulSoup
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from django.template import RequestContext, Template
from django.test import RequestFactory
from cmsplugin_cascade.models import IconFont
from filer.admin.clipboardadmin import ajax_upload
from .test_base import CascadeTestCase
class StridePluginTest(CascadeTestCase):
def setUp(self):
super().setUp()
request = RequestFactory().get('/')
self.context = RequestContext(request, {})
def assertStyleEqual(self, provided, expected):
styles = dict((pair.split(':')[0].strip(), pair.split(':')[1].strip())
for pair in provided.split(';') if ':' in pair)
self.assertDictEqual(styles, expected)
def skiptest_bootstrap_jumbotron(self):
template = Template('{% load cascade_tags sekizai_tags %}{% render_block "css" %}{% render_cascade "strides/bootstrap-jumbotron.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(soup.style.text.find('#cascadeelement_id-1501 {\n\tbackground-color: #12308b;\n\tbackground-attachment: scroll;\n\tbackground-position: center center;\n\tbackground-repeat: no-repeat;\n\tbackground-size: cover;\n\tpadding-top: 500px;'), 1 )
element = soup.find(id='cascadeelement_id-1501')
self.assertEqual(element.h1.text, "Manage your website")
self.assertStyleEqual(element.h1.attrs['style'], {'text-align': 'center'})
def test_bootstrap_container(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-container.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
element = soup.find(class_='container')
self.assertSetEqual(set(element.attrs['class']), {'foo', 'bar', 'container'})
def skiptest_bootstrap_row(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-row.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(str(soup.div), '\n')
def skiptest_bootstrap_column(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-column.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(str(soup.div), '\n')
def skiptest_simple_wrapper(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/simple-wrapper.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
expected_styles = {
'background-color':'#42c8c6',
'color':'#ffffff',
'height':'360px',
'padding-left':'50px',
'padding-right':'50px',
}
self.assertStyleEqual(soup.div.attrs['style'], expected_styles)
def upload_icon_font(self):
UserModel = get_user_model()
admin_user = UserModel.objects.get(username='admin')
with self.login_user_context(admin_user):
filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')
with open(filename, 'rb') as zipfile:
uploaded_file = SimpleUploadedFile('fontello-b504201f.zip', zipfile.read(), content_type='application/zip')
request = self.get_request(reverse('admin:filer-ajax_upload'))
request.FILES.update(file=uploaded_file)
response = ajax_upload(request)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
# save the form and submit the remaining fields
add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')
data = {
'identifier': "Fontellico",
'zip_file': content['file_id'],
'_continue': "Save and continue editing",
}
response = self.client.post(add_iconfont_url, data)
self.assertEqual(response.status_code, 302)
self.assertEqual(IconFont.objects.count(), 1)
def test_framed_icon(self):
self.upload_icon_font()
icon_font = IconFont.objects.first()
icon_font.id = 1 # to match id in fixture "strides/framed-icon.json"
icon_font.save()
template = Template('{% load cascade_tags sekizai_tags %}{% render_block "css" %}{% render_cascade "strides/framed-icon.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertSetEqual(set(soup.div.attrs['class']), {'text-center'})
self.assertStyleEqual(soup.div.attrs['style'], {'font-size': '10em'})
expected_style = {
'color': '#ffffff',
'display': 'inline-block',
}
self.assertStyleEqual(soup.div.span.attrs['style'], expected_style)
def test_text_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/text-plugin.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(soup.h2.text, "Customizable")
self.assertStyleEqual(soup.h2.attrs['style'], {'text-align': 'center'})
self.assertEqual(soup.p.text, "Lorem ipsum dolor")
def test_carousel_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/carousel-plugin.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
carousel = soup.find(class_='carousel')
self.assertSetEqual(set(carousel.attrs['class']), {'carousel', 'slide', 'pause', 'wrap', 'slide'})
self.assertListEqual(carousel.ol.attrs['class'], ['carousel-indicators'])
self.assertListEqual(carousel.ol.li.attrs['class'], ['active'])
slide = carousel.find(class_='carousel-inner')
self.assertSetEqual(set(slide.div.attrs['class']), {'carousel-item', 'active'})
def test_button_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-button.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
button = soup.find(class_='btn')
self.assertSetEqual(set(button.attrs['class']), {'btn', 'btn-secondary'})
================================================
FILE: tests/urls.py
================================================
from django import VERSION as DJANGO_VERSION
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
if DJANGO_VERSION < (2, 0):
from django.conf.urls import url, include
urlpatterns = i18n_patterns(
url(r'^admin/', admin.site.urls),
url(r'^', include('cms.urls')),
)
if DJANGO_VERSION >= (2, 0):
from django.urls import path, include
urlpatterns = i18n_patterns(
path('admin/', admin.site.urls),
path('', include('cms.urls')),
)
================================================
FILE: tests/utils.py
================================================
from django.template import RequestContext
def get_request_context(request, extra_context=None):
if extra_context is None:
extra_context = {}
context = RequestContext(request, extra_context)
# XXX: Workaround for an issue with django-cms that we do not fully
# understand yet. It seems that the template context processors are not
# run early enough, so context['request'] is not available when
# django-cms tries to access it. We should do further analysis on this.
context['request'] = request
# The same problem seems to exist with request.user.
context['user'] = request.user
return context
``...````.
Cascade by default is configured to allow bookmarks on the **SimpleWrapperPlugin** and the
**HeadingPlugin**. This can be overridden in the project's configuration settings using:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_bookmark': [list-of-plugins],
...
}
Hashbang Mode
-------------
Links onto bookmarks do not work properly in hashbang mode. Depending on the HTML settings, you may
have to prefix them with ``/`` or ``!``. Therefore **djangocms-cascade** offers a configuration
directive:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'bookmark_prefix': '/',
...
}
which automatically prefixes the used bookmark.
Usage
=====
When editing a plugin that is eligible for adding a bookmark, an extra input field is shown:
|section-bookmark|
.. |section-bookmark| image:: /_static/section-bookmark.png
You may add any identifier to this field, as long as it is unique on that page. Otherwise the
plugin's editor will be reject the given inputs, while saving.
Hyperlinking to a Bookmark
==========================
When editing a **TextLink**, **BootstrapButton** or the link fields inside the **Image** or
**Picture** plugins, the user gets an additional drop-down menu to choose one of the bookmarks for
the given page. This additional drop-down is only available if the **Link** is of type *CMS page*.
|link-bookmark|
.. |link-bookmark| image:: /_static/link-bookmark.png
If no bookmarks have been associated with the chosen CMS page, the drop-down menu displays only
*Page root*, which is the default.
================================================
FILE: docs/source/segmentation.rst
================================================
=======================
Segmentation of the DOM
=======================
The **SegmentationPlugin** allows to personalize the DOM structure, depending on the context used to
render the corresponding page. Since **django-CMS** always uses a RequestContext_ while rendering
its pages, we always have access onto the request object. Some use cases are:
* Depending on the user, show a different portion of the DOM, if he is a certain user or not logged
in at all.
* Show different parts of the DOM, depending on the browsers estimated geolocation. Useful to
render different content depending on the visitors country.
* Show different parts of the DOM, depending on the supplied marketing channel.
* Show different parts of the DOM, depending on the content in the session objects from previous
visits of the users.
* Segment visitors into different groups used for A/B-testing.
Configuration
=============
The **SegmentationPlugin** must be activated separately on top of other **djangocms-cascade**
plugins. In ``settings.py``, add to
.. code-block:: python
INSTALLED_APPS = (
...
'cmsplugin_cascade',
'cmsplugin_cascade.segmentation',
...
)
Then, depending on what kind of data shall be emulated, add a list of two-tuples to the
configuration settings ``CMSPLUGIN_CASCADE['segmentation_mixins']``. The first entry of each
two-tuple specifies the mixin class added the the proxy model for the ``SegmentationPlugin``. The
second entry specifies the mixin class added the model admin class for the ``SegmentationPlugin``.
.. code-block:: python
# this entry is optional:
CMSPLUGIN_CASCADE = {
...
'segmentation_mixins': (
('cmsplugin_cascade.segmentation.mixins.EmulateUserModelMixin', 'cmsplugin_cascade.segmentation.mixins.EmulateUserAdminMixin',), # the default
# other segmentation plugin classes
),
...
}
Usage
=====
When editing **djangoCMS** plugins in **Structure** mode, below the section **Generic** a new plugin
type appears, named **Segment**.
|segment-plugin|
.. |segment-plugin| image:: _static/segment-plugin.png
This plugin now behaves as an ``if`` block, which is rendered only, if the specified condition
evaluates to true. The syntax used to specify the condition, is the same as used in the Django
template language. Therefore it is possible to evaluate against more than one condition and combine
them with ``and``, ``or`` and ``not`` as described in `boolean operators`_ in the Django docs
Immediately below a segmentation block using the condition tag ``if``, it is possible to use the
tags ``elif`` or ``else``. This kind of conditional blocks is well known to Python programmers.
Note, that when rendering pages in djangoCMS, a RequestContext_- rather than a Context-object is used.
This RequestContext is populated by the ``user`` object if ``'django.contrib.auth.context_processors.auth'``
is added to your settings.py ``TEMPLATE_CONTEXT_PROCESSORS``. This therefore is a prerequisite
when the Segmentation plugin evaluates conditions such as ``user.username == "john"``.
.. _RequestContext: https://docs.djangoproject.com/en/1.8/ref/templates/api/#django.template.RequestContext
.. _boolean operators: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#boolean-operators
.. _request object: https://docs.djangoproject.com/en/dev/ref/request-response/#httprequest-objects
Emulating Users
===============
Only staff users or administrators can emulate the currently logged in user. Staff-only users must
possess the four permissions `cmsplugin_cascade.add_segmentation`,
`cmsplugin_cascade.change_segmentation`, `cmsplugin_cascade.delete_segmentation` and
`cmsplugin_cascade.view_segmentation`.
If this plugin is activated and the permissions are set, then in the CMS toolbar a new menu
tag appears named “Segmentation”. Here the currently logged in staff user can select another user.
All evaluation conditions then evaluate against this selected user, instead of the currently logged
in user.
It is quite simple to add other overriding emulations. Have a look at the class
``cmsplugin_cascade.segmentation.mixins.EmulateUserMixin``. This class then has to be added to
your configuration settings ``CMSPLUGIN_CASCADE_SEGMENTATION_MIXINS``. It then overrides the
evaluation conditions and the toolbar menu.
================================================
FILE: docs/source/sharable-fields.rst
================================================
.. _sharable-fields:
============================
Working with sharable fields
============================
Sometime you'd want to remember sizes, links or any other options for rendering a plugin instance
across the project. In order to not have to do this job for each managed entity, you can remember
these settings using a name of your choice, controllable in a special section of the administration
backend.
Now, whenever someone adds a new instance using this plugin, a select box with these remembered
settings appears. He then can choose from one of the remembered settings, which frees him to
reenter all the values.
Configure a Cascade Plugins to optionally share some fields
===========================================================
Configuring a plugin to share specific fields with other plugins of the same type is very easy.
In the projects ``settings.py``, assure that ``'cmsplugin_cascade.sharable'`` is part of your
``INSTALLED_APPS``.
Then add a dictionary of Cascade plugins, with a list of fields which shall be sharable. For
example, with this settings, the image plugin can be configured to share its sizes and rendering
options among each other.
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'plugins_with_sharables': {
'BootstrapImagePlugin': ['image-shapes', 'image-width-responsive', 'image-width-fixed', 'image-height', 'resize-options'],
},
...
}
Control some named settings
===========================
Whenever a plugin is configured to allow to share fields, at the bottom of the plugin editor a
special field appears:
|remember-settings|
.. |remember-settings| image:: /_static/remember-settings.png
By activating the checkbox, adding an arbitrary name next to it and saving the plugin, an entity
of sharable fields is saved in the database. Now, whenever someone starts to edit a plugin of this
type, a select box appears on the top of the editor:
|use-shared-settings|
.. |use-shared-settings| image:: /_static/use-shared-settings.png
By choosing a previously named shared settings, the configured fields are disabled for input and
replaced by their shared field's counterparts.
In order to edit these shared fields in the administration backend, one must access
**Home › Cmsplugin_cascade › Shared between Plugins**. By choosing a named shared setting, one can
enter into the shared field's editor. This editor auto adopts to the fields declared as shared,
hence will change from entity to entity. For the above example, it may look like this:
|edit-shared-fields|
.. |edit-shared-fields| image:: /_static/edit-shared-fields.png
In this editor one can change these shared settings globally, for all plugin instances where this
named shared settings have been applied to.
================================================
FILE: docs/source/sphinx.rst
================================================
==============================
Integrate Sphinx Documentation
==============================
Restructured Text (ReST) is the de facto standard for documenting Python projects and is even widely
used outside of this realm, by applications written in other languages. Sphinx_ is a compiler to
generate HTML, Latex, PDF, e-books, etc. out of sources written in ReST.
HTML rendered by Sphinx, typically is rendered as static content by the web server. This makes it
difficult to serve documentation, side by side with content from **django-CMS**, because these are
completely different technologies. Furthermore, since Sphinx uses Jinja2 templates, but **django-CMS**'s
internal templatetags are not available for Jinja2, template sharing is not possible.
Therefore **djangocms-cascade** offers an integration service, which makes it possible to integrate
documentation generated by Sphinx, unintrusively inside the menu tree of **django-CMS**.
Configuration
=============
To the project's ``settings.py``, add these options to the configuration directives:
.. code-block:: python
INSTALLED_APPS = [
...
'cmsplugin_cascade',
'cmsplugin_cascade.sphinx',
...
]
CMS_TEMPLATES = [
...
('path/to/documentation.html', "Documentation Page"),
...
]
SPHINX_DOCS_ROOT = '/path/to/docs/_build/fragments'
Replace ``'/path/to/documentation.html'`` with a filename pointing to your documentation
root template (see below).
Point ``SPHINX_DOCS_ROOT`` onto the directory, into which the HTML page fragments are generated.
Configure Sphinx Builder
------------------------
Locate the file ``conf.py`` and add:
.. code-block:: python
extensions = [
...
'cmsplugin_cascade.sphinx.fragmentsbuilder',
]
By invoking ``make fragments``, Sphinx generates a HTML fragment for each page inside the
documentation folder, typically into ``docs/build/fragments``. Later we use these fragments
and include them using a normal Django view.
Integration with the CMS
========================
In Django's admin backend, add a page as the starting point for the documentation inside
the CMS menu tree. Typically, one would name that page "*Documentation*" using ``docs`` or
``documentation`` as its slug.
In the *Advanced Settings* tab, choose **Documentation Page** as the template. This settings
has been configured using the directive ``CMS_TEMPLATES``, as shown above.
As *Application*, select **Sphinx Documentation** from the pull down menu. This attaches the
complete documentation tree just below the chosen slug.
Optionally select **Documentation Menu** from the pull down menu as the *Attached menu*. It adds
a submenu for each main chapter of the documentation. If omitted, only **Documentation** is added
the the CMS menu tree.
The Documentation Template
--------------------------
You must provide a template to be used by the documentation view. This template typically extends
a base CMS page template, providing a header, the navigation bar and the footer. In the block,
responsible for rendering the main content, add this template code:
.. code-block:: django
{% extends "path/to/base.html" %}
{% load static cascade_tags %}
...
{% block head %}
{{ block.super }}
{% endblock %}
...
{% block main-content %}
{% if page_content %}
{{ page_content }}
{% else %}
{% sphinx_docs_include "index.html" %}
{% endif %}
{% endblock %}
This Django template now includes the HTML fragments compiled by Sphinx. This allows us to use
**django-CMS** and combine it with Sphinx. In the URL, the part behind the documentation's slug
corresponds 1:1 to the name of the ReST document.
In this example we add a stylesheet to adopt the output to the `Bootstrap theme`_ for Sphinx_.
Depending on your template layout, the way you import this may vary.
.. _Sphinx: http://www.sphinx-doc.org/
.. _Bootstrap theme: http://ryan-roemer.github.io/sphinx-bootstrap-theme/README.html
Linking onto Documentation Pages
--------------------------------
By overriding the :ref:`link-plugin` with a special target named **Documentation**, we can
even add links onto our documentation pages symbolically. This means, that whenever we open the
**LinkPlugin** editor, an additional target is added. It offers a select box showing all
pages from our documentation tree. This prevents us, having to hard code the URL pointing
onto the documentation.
This feature has to be configured in the project's ``settings.py``, by replacing the LinkPlugin
with a modified version of itself:
.. code-block:: python
CMSPLUGIN_CASCADE = {
...
'link_plugin_classes': [
'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkPlugin',
'cmsplugin_cascade.link.plugin_base.LinkElementMixin',
'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkForm',
],
...
}
================================================
FILE: docs/source/strides.rst
================================================
.. _strides:
==============================
Use Cascade outside of the CMS
==============================
One of the most legitimate points **djangocms-cascade** can be criticised for, is the lack of
static content rendering. Specially in projects, where we want to work with static pages instead
of CMS pages, one might fall back to handcrafting HTML, giving up all the benefits of rapid
prototyping as provided by the Cascade plugin system.
Since version 0.14 of **djangocms-cascade**, one can prototype the page content and export it as
JSON file using :ref:`clipboard`. Later on, one can reuse that persisted data and create the same
content outside of a CMS page. This is specially useful, if you must persist the page content
in the project's version control system.
Usage
=====
After the placeholder of a CMS page, is filled up with plugins from **djangocms-cascade**,
switch into *Structure Mode*, go to the context menu of that placeholder and click *Copy all*.
Next, inside Django's administration backend, go to
Home › Django CMS Cascade › Persited Clipboard Content
and click onto *Add Persisted Clipboard Content*. The *Data* field will now be filled with a
cascade of plugins serialized as JSON data. Copy that data and paste it into a file locatable
by Django's static file finders, for example ``myproject/static/myapp/cascades/slug.json``.
In Templates
============
Create a Django template, where instead of adding a Django-CMS placeholder, use the templatetag
``render_cascade``. Example:
.. code-block:: Django
{% load cascade_tags %}
{% render_cascade "myapp/cascades/slug.json" %}
This templatetag now renders the content just as if it would be rendered by the CMS. This means
that changing the template of a **djangocms-cascade** plugin, immediately has effect on the rendered
output. This is so to say **Model View Control**, where the Model is the content peristed as JSON,
and the View is the template provided by the plugin. It separates the composition of HTML components
from their actual representation, allowing a much better division of work during the page creation.
Caveats when creating your own Plugins
======================================
When developing your own plugins, consider the following precautions:
Invoking ``super``
------------------
Instead of invoking ``super(MyPlugin, self).some_method()`` use
``self.super(MyPlugin, self).some_method()``. This is required because **djangocms-cascade**
creates a list of "shadow" plugins, which do not inherit from ``CMSPluginBase``.
Templatetag ``render_plugin``
-----------------------------
Django-CMS provides a templatetag ``render_plugin``. Don't use it in templates provided by
**djangocms-cascade** plugins. Instead use the templatetag named ``render_plugin`` from
Cascade. Example:
.. code-block:: Django
{% load cascade_tags %}
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
Caching
=======
Even though rendering using this templatetag is slightly faster than the classic ``placeholder``
tag provided by the CMS (because we don't hit the database for each plugin instance), combining
each plugin template with its context also takes its time. Therefore plugins rendered by
``render_cascade``, by default are cached as well, just as their CMS counterparts.
This caching is disabled for plugins containing the attribute ``cache = False``. It can be turned
off globally using the directive ``CMSPLUGIN_CASCADE['cache_strides'] = True`` in the project's
``settings.py``.
================================================
FILE: examples/bs4demo/.coveragerc
================================================
[run]
branch = True
source =
cmsplugin_cascade
[report]
precision = 2
omit =
../*migrations*
gs960
================================================
FILE: examples/bs4demo/bs4demo/__init__.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: examples/bs4demo/bs4demo/cms_plugins.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.forms import widgets
from django.utils.translation import ugettext_lazy as _
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.fields import GlossaryField
from cmsplugin_cascade.plugin_base import CascadePluginBase
class Badge(CascadePluginBase):
"""
This is a simple example of a plugin suitable for the djangocms-cascade system.
It contains one single field: `content` rendered via the template `bs4demo/badge.html`.
"""
name = _("Badge")
require_parent = False
allow_children = False
render_template = 'bs4demo/badge.html'
content = GlossaryField(
widgets.TextInput(),
label=_("Content"),
)
plugin_pool.register_plugin(Badge)
================================================
FILE: examples/bs4demo/bs4demo/context_processors.py
================================================
# -*- coding: utf-8 -*-
from django.conf import settings
def cascade(request):
"""
Adds additional context variables to the default context.
"""
context = {
'DJANGO_CLIENT_FRAMEWORK': settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'),
}
return context
================================================
FILE: examples/bs4demo/bs4demo/models.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: examples/bs4demo/bs4demo/settings.py
================================================
# Django settings for unit test project.
from __future__ import unicode_literals
import os
import sys
from django.urls import reverse_lazy
from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig
from django.utils.text import format_lazy
DEBUG = True
BASE_DIR = os.path.dirname(__file__)
# Root directory for this Django project
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.path.pardir))
# Directory where working files, such as media and databases are kept
WORK_DIR = os.path.join(PROJECT_ROOT, 'workdir')
if not os.path.isdir(WORK_DIR):
os.makedirs(WORK_DIR)
SITE_ID = 1
ROOT_URLCONF = 'bs4demo.urls'
SECRET_KEY = 'secret'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(WORK_DIR, 'db.sqlite3'),
},
}
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.staticfiles',
'django.contrib.sitemaps',
#'reversion',
'djangocms_text_ckeditor',
'django_select2',
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.segmentation',
'cms',
'cms_bootstrap',
'adminsortable2',
'menus',
'treebeard',
'filer',
'easy_thumbnails',
'sass_processor',
'sekizai',
'bs4demo',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
]
# silence false-positive warning 1_6.W001
# https://docs.djangoproject.com/en/1.8/ref/checks/#backwards-compatibility
#TEST_RUNNER = 'django.test.runner.DiscoverRunner'
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = os.path.join(WORK_DIR, 'media')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/media/'
# Absolute path to the directory that holds static files.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = os.path.join(WORK_DIR, 'static')
# URL that handles the static files served from STATIC_ROOT.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'sass_processor.finders.CssFinder',
]
STATICFILES_DIRS = [
('node_modules', os.path.join(PROJECT_ROOT, 'node_modules')),
]
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
'bs4demo.context_processors.cascade',
),
},
}]
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
LANGUAGE_CODE = 'en'
LANGUAGES = (
('en', 'English'),
)
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'formatters': {
'simple': {
'format': '[%(asctime)s %(module)s] %(levelname)s: %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
},
}
X_FRAME_OPTIONS = 'SAMEORIGIN'
XS_SHARING_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']
#############################################################
# Application specific settings
if sys.argv[1] == 'test':
CMS_TEMPLATES = (
('testing.html', "Default Page"),
)
else:
CMS_TEMPLATES = (
('bs4demo/main.html', "Main Content"),
('bs4demo/wrapped.html', "Wrapped Bootstrap Column"),
)
CMS_SEO_FIELDS = True
CMS_CACHE_DURATIONS = {
'content': 3600,
'menus': 3600,
'permissions': 86400,
}
CMSPLUGIN_CASCADE_PLUGINS = (
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.leaflet',
'cmsplugin_cascade.link',
'cmsplugin_cascade.bootstrap4',
'bs4demo',
)
CMSPLUGIN_CASCADE = {
'alien_plugins': ('TextPlugin', 'TextLinkPlugin',),
'plugins_with_sharables': {
'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed',
'image_height', 'resize_options',),
'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',),
},
'exclude_hiding_plugin': ('SegmentPlugin', 'Badge'),
'allow_plugin_hiding': True,
'leaflet': {'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6}},
'cache_strides': True,
}
CMS_PLACEHOLDER_CONF = {
# this placeholder is used in templates/main.html, it shows how to
# scaffold a djangoCMS page starting with an empty placeholder
'Main Content': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None},
},
# this placeholder is used in templates/wrapped.html, it shows how to
# add content to an existing Bootstrap column
'Bootstrap Column': {
'plugins': ['BootstrapRowPlugin', 'TextPlugin', ],
'parent_classes': {'BootstrapRowPlugin': None},
'require_parent': False,
},
}
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'skin': 'moono-lisa',
'toolbar': 'CMS',
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),
}
SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'
SELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'
FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS = True
FILER_DUMP_PAYLOAD = True
THUMBNAIL_PROCESSORS = (
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
)
THUMBNAIL_HIGH_RESOLUTION = False
THUMBNAIL_PRESERVE_EXTENSIONS = True
THUMBNAIL_OPTIMIZE_COMMAND = {
'png': '/opt/local/bin/optipng {filename}',
'gif': '/opt/local/bin/optipng {filename}',
'jpeg': '/opt/local/bin/jpegoptim {filename}',
}
SASS_PROCESSOR_INCLUDE_DIRS = [
os.path.join(PROJECT_ROOT, 'node_modules'),
]
SASS_PROCESSOR_ROOT = STATIC_ROOT
# to access files such as fonts via staticfiles finders
NODE_MODULES_URL = STATIC_URL + 'node_modules/'
try:
from .private_settings import *
except ImportError:
pass
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/cascades/strides.json
================================================
{
"plugins":[
[
"BootstrapJumbotronPlugin",
{
"glossary":{
"background_width_height":{
"width":"",
"height":""
},
"background_vertical_position":"center",
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"lg":[
"(min-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
]
},
"background_attachment":"scroll",
"image":{
"pk":5,
"model":"filer.Image"
},
"background_repeat":"no-repeat",
"hide_plugin":"",
"fluid":true,
"container_max_heights":{
"xs":"100%",
"md":"100%",
"sm":"100%",
"lg":"100%"
},
"extra_inline_styles:Paddings":{
"padding-top":"500px",
"padding-bottom":""
},
"background_size":"cover",
"resize_options":[
"crop",
"subject_location",
"high_resolution"
],
"container_max_widths":{
"xs":768,
"lg":1980,
"sm":992,
"md":1200
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"background_color":[
"",
"#12308b"
],
"background_horizontal_position":"center"
},
"pk":900
},
[
[
"TextPlugin",
{
"body":"Django-CMS Cascade
",
"pk":901
},
[]
]
]
],
[
"BootstrapContainerPlugin",
{
"glossary":{
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"lg":[
"(min-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
]
},
"container_max_widths":{
"xs":750,
"lg":1170,
"sm":750,
"md":970
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"hide_plugin":"",
"fluid":""
},
"pk":902
},
[
[
"HeadingPlugin",
{
"glossary":{
"content":"Cascade Demo Page",
"element_id":"heading-1",
"tag_type":"h1",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-right":"",
"margin-top":"",
"margin-left":"",
"margin-bottom":""
}
},
"pk":936
},
[]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":""
}
},
"pk":903
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"col-md-6",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":720.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":904
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"2",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"367px"
},
"extra_inline_styles:Font Size":{
"font-size":"15px"
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#474747"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#ffffff"
],
"extra_inline_styles:Paddings":{
"padding-top":"20px",
"padding-right":"50px",
"padding-bottom":"20px",
"padding-left":"50px"
}
},
"pk":905
},
[
[
"TextPlugin",
{
"body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum. Nullam id dolor id nibh ultricies vehicula ut id elit.
",
"pk":906
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"col-md-6",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"extra_css_classes":[],
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":720.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":907
},
[
[
"BootstrapImagePlugin",
{
"glossary":{
"image_width_responsive":"100%",
"target":"",
"title":"",
"image":{
"pk":11,
"model":"filer.Image"
},
"alt_tag":"",
"hide_plugin":"",
"image_width_fixed":"",
"image_height":"",
"link":{
"type":"none"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image_shapes":[
"img-responsive"
]
},
"pk":908
},
[]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
},
"pk":909
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":910
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":911
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"wrench",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":912
},
[]
],
[
"TextPlugin",
{
"body":"Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":913
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":918
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":919
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"cog-alt",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":920
},
[]
],
[
"TextPlugin",
{
"body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":921
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"md-column-offset":"",
"extra_inline_styles:line-height":"",
"sm-column-offset":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"xs-column-width":"col-xs-4",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"sm-column-width":"",
"md-responsive-utils":"",
"lg-column-ordering":"",
"sm-column-ordering":"",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"container_max_widths":{
"xs":220.0,
"lg":360.0,
"sm":220.0,
"md":293.33
},
"md-column-width":"",
"xs-column-ordering":"",
"lg-column-offset":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_css_classes":[],
"lg-responsive-utils":"",
"xs-column-offset":"",
"md-column-ordering":"",
"lg-column-width":""
},
"pk":914
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"360px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":915
},
[
[
"FramedIconPlugin",
{
"glossary":{
"font_size":"10em",
"color":[
"",
"#ffffff"
],
"background_color":[
"disabled",
"#ffffff"
],
"symbol":"bell-alt",
"hide_plugin":"",
"text_align":"text-center",
"icon_font":"3",
"border_radius":"",
"border":[
"0px",
"none",
"#000000"
]
},
"pk":916
},
[]
],
[
"TextPlugin",
{
"body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":917
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
},
"pk":929
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"container_max_widths":{
"xs":720.0,
"lg":1140.0,
"sm":720.0,
"md":940.0
},
"xs-column-width":"col-xs-12"
},
"pk":930
},
[
[
"CarouselPlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"container_max_heights":{
"xs":"200px",
"md":"300px",
"sm":"250px",
"lg":"350px"
},
"interval":"5",
"hide_plugin":"",
"options":[
"slide",
"pause",
"wrap"
]
},
"pk":931
},
[
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":4,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":932
},
[
[
"TextPlugin",
{
"body":"Hallo Welt!
",
"pk":933
},
[]
]
]
],
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":7,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":934
},
[]
],
[
"CarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":6,
"model":"filer.Image"
},
"image_title":"",
"hide_plugin":"",
"alt_tag":""
},
"pk":935
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":"20px"
}
},
"pk":922
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"col-sm-6",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":345.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":923
},
[
[
"BootstrapImagePlugin",
{
"glossary":{
"image_width_responsive":"100%",
"target":"",
"title":"",
"image":{
"pk":12,
"model":"filer.Image"
},
"alt_tag":"",
"hide_plugin":"",
"image_width_fixed":"",
"image_height":"",
"link":{
"type":"none"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image_shapes":[
"img-responsive"
]
},
"pk":924
},
[]
]
]
],
[
"BootstrapColumnPlugin",
{
"glossary":{
"sm-responsive-utils":"",
"xs-responsive-utils":"",
"md-column-offset":"",
"sm-column-width":"col-sm-6",
"md-responsive-utils":"",
"xs-column-offset":"",
"md-column-width":"",
"hide_plugin":"",
"sm-column-ordering":"",
"sm-column-offset":"",
"lg-column-ordering":"",
"lg-column-offset":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-12",
"lg-responsive-utils":"",
"container_max_widths":{
"xs":720.0,
"lg":555.0,
"sm":345.0,
"md":455.0
},
"lg-column-width":"",
"md-column-ordering":""
},
"pk":925
},
[
[
"SimpleWrapperPlugin",
{
"glossary":{
"extra_inline_styles:line-height":"",
"hide_plugin":"",
"extra_inline_styles:Heights":{
"max-height":"",
"min-height":"",
"height":"370px"
},
"extra_inline_styles:Font Size":{
"font-size":"130%"
},
"element_id":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_css_classes":[],
"tag_type":"div",
"extra_inline_styles:background-color":[
"",
"#ef3e42"
],
"extra_inline_styles:Paddings":{
"padding-top":"50px",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
}
},
"pk":926
},
[
[
"TextPlugin",
{
"body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
",
"pk":927
},
[]
],
[
"BootstrapButtonPlugin",
{
"glossary":{
"icon_align":"icon-right",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
},
"button_options":[],
"button_size":"btn-lg",
"quick_float":"pull-right",
"hide_plugin":"",
"icon_font":"3",
"link_content":"Continue",
"link":{
"pk":5,
"model":"cms.Page",
"type":"cmspage",
"section":""
},
"button_type":"btn-default",
"symbol":"right-open"
},
"pk":928
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"20px"
}
},
"pk":937
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"container_max_widths":{
"xs":720.0,
"lg":1140.0,
"sm":720.0,
"md":940.0
},
"xs-column-width":"col-xs-12"
},
"pk":938
},
[
[
"LeafletPlugin",
{
"glossary":{
"render_template":"cascade/plugins/leaflet.html",
"map_position":{
"lat":47.27337658656428,
"lng":11.399195194244387,
"zoom":15
},
"hide_plugin":"",
"map_height":"400px",
"map_width":"100%"
},
"pk":939,
"inlines":[
{
"title":"Kirche St. Nikolaus",
"image":{
"pk":15,
"model":"filer.Image"
},
"marker_width":"25px",
"position":{
"lat":47.274337475394645,
"lng":11.393036842346191
},
"popup_text":null,
"marker_anchor":{
"top":"50%",
"left":"50%"
}
}
]
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_footer.scss
================================================
// include this file when using a static footer
@import "variables";
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: $body-footer-height;
}
#footer {
position: absolute;
bottom: 0;
width: 100%;
height: $body-footer-height;
color: $body-footer-color;
background: $body-footer-bg;
padding-top: 20px;
padding-bottom: 20px;
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_variables.scss
================================================
@import "bootstrap/scss/_functions";
@import "bootstrap/scss/_variables";
@import "bootstrap/scss/mixins/_breakpoints.scss";
// footer
$body-footer-height: 200px;
$body-footer-color: $yiq-text-light;
$body-footer-bg: $dark;
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/badge.scss
================================================
.badge-ribbon {
position: relative;
background: lightgrey;
font-size: 50px;
height: 100px;
width: 100px;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
text-align: center;
padding-top: 12px;
margin-bottom: 50px;
&:before, &:after {
content: '';
position: absolute;
border-bottom: 70px solid lightgrey;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
top: 90px;
left: -10px;
-webkit-transform: rotate(-150deg);
-moz-transform: rotate(-150deg);
-ms-transform: rotate(-150deg);
-o-transform: rotate(-150deg);
}
&:after {
left: auto;
right: -10px;
-webkit-transform: rotate(150deg);
-moz-transform: rotate(150deg);
-ms-transform: rotate(150deg);
-o-transform: rotate(150deg);
}
}
================================================
FILE: examples/bs4demo/bs4demo/static/bs4demo/css/main.scss
================================================
@import "variables";
@import "bootstrap/scss/bootstrap";
@import "footer";
body {
background-color: #eee8e8;
}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/badge.html
================================================
{% load sekizai_tags sass_tags %}
{% addtoblock "css" %}{% endaddtoblock %}
{% with inline_styles=instance.inline_styles %}
{{ instance.glossary.content }}
{% endwith %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/base.html
================================================
{% load static cms_tags sekizai_tags %}
{% block title %}Page Title{% endblock %}
{% block head %}{% endblock %}
{% render_block "css" %}
{% cms_toolbar %}
{% block header %}{% endblock %}
{% block main %}{% endblock %}
{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}
{% else %}
{% endif %}
{% render_block "js" %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/main.html
================================================
{% extends "bs4demo/base.html" %}
{% load static cms_tags bootstrap_tags sekizai_tags sass_tags %}
{% block head %}
{% addtoblock "css" %}{% endaddtoblock %}
{% endblock head %}
{% block header %}
{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}
{% include "bootstrap4/includes/ng-nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" %}
{% else %}
{% include "bootstrap4/includes/nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" role="navigation" %}
{% endif %}
{% if cms_version >= "3.5.0" and request.toolbar %}
{% endif %}
{% endblock header %}
{% block main %}{% placeholder "Main Content" or %}
Add Bootstrap container here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL, switch to “Structure” mode
and add a Bootstrap Container Plugin or Jumbotron Plugin.
{% endplaceholder %}{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/strides.html
================================================
{% extends "bs4demo/main.html" %}
{% load cascade_tags %}
{% block main %}
Content is rendered using the templatetag {% verbatim %}{% render_cascade "bs4demo/cascades/strides.json" %}{% endverbatim %}
{% render_cascade "bs4demo/cascades/strides.json" %}
{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/templates/bs4demo/wrapped.html
================================================
{% extends "bs4demo/main.html" %}
{% load cms_tags %}
{% block main %}
{% placeholder "Bootstrap Column" or %}
Add some Bootstrap plugins here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL and switch to “Structure” mode.
Into this hard coded Bootstrap Column, add a Text Plugin.
If you want to further subdivide this column, add a Row Plugin with their own columns.
{% endplaceholder %}
{% endblock main %}
================================================
FILE: examples/bs4demo/bs4demo/urls.py
================================================
# -*- coding: utf-8 -*-
from django.conf import settings
from django.urls import include, path, re_path
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
class CascadeDemoView(TemplateView):
template_name = 'bs4demo/strides.html'
admin.autodiscover()
urlpatterns = [
path('admin/select2/', include('django_select2.urls')),
path('admin/', admin.site.urls),
path('cascade/', CascadeDemoView.as_view()),
path('', include('cms.urls')),
]
urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
================================================
FILE: examples/bs4demo/bs4demo/utils.py
================================================
def find_django_migrations_module(module_name):
""" Tries to locate .migrations_django (without actually importing it).
Appends either ".migrations_django" or ".migrations" to module_name.
For details why:
https://docs.djangoproject.com/en/1.7/topics/migrations/#libraries-third-party-apps
"""
import imp
try:
module_info = imp.find_module(module_name)
module = imp.load_module(module_name, *module_info)
imp.find_module('migrations_django', module.__path__)
return module_name + '.migrations_django'
except ImportError:
return module_name + '.migrations' # conforms to Django 1.7 defaults
================================================
FILE: examples/bs4demo/manage.py
================================================
#!/usr/bin/env python
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.pardir))
if __name__ == "__main__":
from django.core.management import execute_from_command_line
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bs4demo.settings')
execute_from_command_line(sys.argv)
================================================
FILE: examples/bs4demo/package.json
================================================
{
"name": "djangocms-cascade",
"version": "0.10.0",
"description": "DjangoCMS-Cascade is the Swiss army knife for working with Django CMS plugins",
"directories": {
"doc": "docs",
"example": "examples",
"test": "tests"
},
"dependencies": {
"angular": "^1.5.11",
"angular-animate": "^1.5.11",
"angular-sanitize": "^1.5.11",
"ui-bootstrap4": "^3.0.5",
"bootstrap": "^4.1.3",
"jquery": "^3.2.1",
"leaflet": "^1.2.0",
"leaflet-easybutton": "^2.2.0",
"picturefill": "^3.0.2",
"popper.js": "^1.12.9",
"select2": "^4.0.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jrief/djangocms-cascade.git"
},
"author": "Jacob Rief",
"license": "MIT",
"bugs": {
"url": "https://github.com/jrief/djangocms-cascade/issues"
},
"homepage": "https://github.com/jrief/djangocms-cascade#readme"
}
================================================
FILE: pytest.ini
================================================
[pytest]
DJANGO_SETTINGS_MODULE = tests.settings
addopts = --tb native
================================================
FILE: requirements/base.txt
================================================
Pillow
argparse
requests
django-admin-sortable2<2
django-classy-tags
django-sekizai
django-entangled
django-filer
django-select2
djangocms-text-ckeditor
easy-thumbnails[svg]
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
from setuptools import setup, find_packages
from cmsplugin_cascade import __version__
with open('README.md', 'r') as fh:
long_description = fh.read()
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
]
setup(
name='djangocms-cascade',
version=__version__,
description='Build Single Page Applications using the Django-CMS plugin system',
author='Jacob Rief',
author_email='jacob.rief@gmail.com',
url='https://github.com/jrief/djangocms-cascade',
packages=find_packages(exclude=['examples', 'docs', 'tests']),
install_requires=[
'django>=3.2,<5',
'django-classy-tags>=1.0',
'django-cms>=3.10,<4',
'django-entangled>=0.5.3',
'djangocms-text-ckeditor>=4.0',
'django-select2>=7.7',
'requests',
],
license='MIT',
platforms=['OS Independent'],
classifiers=CLASSIFIERS,
long_description=long_description,
long_description_content_type='text/markdown',
include_package_data=True,
zip_safe=False,
)
================================================
FILE: tests/__init__.py
================================================
# -*- coding: utf-8 -*-
================================================
FILE: tests/bootstrap4/__init__.py
================================================
================================================
FILE: tests/bootstrap4/conftest.py
================================================
import pytest
from cms.api import add_plugin
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.container import BootstrapContainerPlugin, BootstrapRowPlugin, BootstrapColumnPlugin
@pytest.fixture
@pytest.mark.django_db
def bootstrap_container(admin_site, cms_placeholder):
# add a Bootstrap Container Plugin
glossary = {'breakpoints': ['xs', 'sm', 'md', 'lg', 'xl'], 'fluid': ''}
container_model = add_plugin(cms_placeholder, BootstrapContainerPlugin, 'en', glossary=glossary)
assert isinstance(container_model, CascadeElement)
container_plugin = container_model.get_plugin_class_instance(admin_site)
assert isinstance(container_plugin, BootstrapContainerPlugin)
return container_plugin, container_model
@pytest.fixture
@pytest.mark.django_db
def bootstrap_row(admin_site, bootstrap_container):
# add a Bootstrap Row Plugin to the given container
container_plugin, container_model = bootstrap_container
row_model = add_plugin(container_model.placeholder, BootstrapRowPlugin, 'en', target=container_model)
assert isinstance(row_model, CascadeElement)
row_plugin = row_model.get_plugin_class_instance()
assert isinstance(row_plugin, BootstrapRowPlugin)
return row_plugin, row_model
@pytest.fixture
@pytest.mark.django_db
def bootstrap_column(admin_site, bootstrap_row):
# add a Bootstrap Column Plugin to the given row
row_plugin, row_model = bootstrap_row
glossary = {'xs-column-width': 'col'}
column_model = add_plugin(row_model.placeholder, BootstrapColumnPlugin, 'en', target=row_model, glossary=glossary)
assert isinstance(column_model, CascadeElement)
column_plugin = column_model.get_plugin_class_instance()
assert isinstance(column_plugin, BootstrapColumnPlugin)
return column_plugin, column_model
================================================
FILE: tests/bootstrap4/test_accordion.py
================================================
import pytest
from django.template.context import RequestContext
from cms.api import add_plugin
from cms.plugin_rendering import ContentRenderer
from cms.utils.plugins import build_plugin_tree
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.accordion import BootstrapAccordionGroupPlugin, BootstrapAccordionPlugin
@pytest.fixture
@pytest.mark.django_db
def bootstrap_accordion(rf, admin_site, bootstrap_column):
request = rf.get('/')
column_plugin, column_model = bootstrap_column
# add accordion plugin
accordion_model = add_plugin(column_model.placeholder, BootstrapAccordionPlugin, 'en', target=column_model)
assert isinstance(accordion_model, CascadeElement)
accordion_plugin = accordion_model.get_plugin_class_instance(admin_site)
assert isinstance(accordion_plugin, BootstrapAccordionPlugin)
data = {'num_children': 2, 'close_others': 'on', 'first_is_open': 'on'}
ModelForm = accordion_plugin.get_form(request, accordion_model)
form = ModelForm(data, None, instance=accordion_model)
assert form.is_valid()
accordion_plugin.save_model(request, accordion_model, form, False)
assert accordion_model.glossary['close_others'] is True
assert accordion_model.glossary['first_is_open'] is True
for child in accordion_model.get_children():
assert isinstance(child.get_plugin_class_instance(admin_site), BootstrapAccordionGroupPlugin)
return accordion_plugin, accordion_model
@pytest.mark.django_db
def test_edit_accordion_group(rf, admin_site, bootstrap_accordion):
request = rf.get('/')
accordion_plugin, accordion_model = bootstrap_accordion
first_group = accordion_model.get_first_child()
group_model, group_plugin = first_group.get_plugin_instance(admin_site)
data = {'heading': "Hello", 'body_padding': 'on'}
ModelForm = group_plugin.get_form(request, group_model)
form = ModelForm(data, None, instance=group_model)
assert form.is_valid()
group_plugin.save_model(request, group_model, form, False)
assert group_model.glossary['heading'] == "Hello"
assert group_model.glossary['body_padding'] is True
# render the plugin
build_plugin_tree([accordion_model, group_model])
context = RequestContext(request)
content_renderer = ContentRenderer(request)
html = content_renderer.render_plugin(accordion_model, context).strip()
html = html.replace('\n', '').replace('\t', '')
expected = """
""".format(accordion_id=accordion_model.id, group_id=group_model.id)
expected = expected.replace('\n', '').replace('\t', '')
assert html == expected
================================================
FILE: tests/bootstrap4/test_container.py
================================================
import pytest
from bs4 import BeautifulSoup
from django.utils.html import strip_spaces_between_tags
from cms.plugin_rendering import ContentRenderer
from cms.utils.plugins import build_plugin_tree
from cmsplugin_cascade.models import CascadeElement
from cmsplugin_cascade.bootstrap4.container import BootstrapColumnPlugin
@pytest.mark.django_db
def test_edit_bootstrap_container(rf, bootstrap_container):
container_plugin, container_model = bootstrap_container
request = rf.get('/')
ModelForm = container_plugin.get_form(request, container_model)
data = {'breakpoints': ['sm', 'md']}
form = ModelForm(data, None, instance=container_model)
assert form.is_valid()
soup = BeautifulSoup(form.as_p(), features='lxml')
input_element = soup.find(id="id_breakpoints_0")
assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'xs'}.items() <= input_element.attrs.items()
input_element = soup.find(id="id_breakpoints_2")
assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'md', 'checked': ''}.items() <= input_element.attrs.items()
input_element = soup.find(id="id_fluid")
assert {'type': 'checkbox', 'name': 'fluid'}.items() <= input_element.attrs.items()
container_plugin.save_model(request, container_model, form, False)
assert container_model.glossary['breakpoints'] == ['sm', 'md']
assert 'fluid' in container_model.glossary
assert str(container_model) == "for Landscape Phones, Tablets"
@pytest.mark.django_db
def test_edit_bootstrap_row(rf, bootstrap_row):
row_plugin, row_model = bootstrap_row
request = rf.get('/')
ModelForm = row_plugin.get_form(request, row_model)
data = {'num_children': 3}
form = ModelForm(data, None, instance=row_model)
assert form.is_valid()
row_plugin.save_model(request, row_model, form, False)
container_model, container_plugin = row_model.parent.get_plugin_instance()
plugin_list = [container_model, row_model]
# we now should have three columns attached to the row
assert row_model.get_descendant_count() == 3
for cms_plugin in row_model.get_descendants():
column_model, column_plugin = cms_plugin.get_plugin_instance()
assert isinstance(column_model, CascadeElement)
assert isinstance(column_plugin, BootstrapColumnPlugin)
assert column_model.parent.id == row_model.id
plugin_list.append(column_model)
# change data inside the first column
cms_plugin = row_model.get_descendants().first()
column_model, column_plugin = cms_plugin.get_plugin_instance()
data = {'xs-column-width': 'col', 'sm-column-offset': 'offset-sm-1', 'sm-column-width': 'col-sm-3'}
ModelForm = column_plugin.get_form(request, column_model)
form = ModelForm(data, None, instance=column_model)
assert form.is_valid()
column_plugin.save_model(request, column_model, form, True)
# change data inside the last column
cms_plugin = row_model.get_descendants().last()
column_model, column_plugin = cms_plugin.get_plugin_instance()
data = {'xs-column-width': 'col', 'sm-responsive-utils': 'hidden-sm', 'sm-column-width': 'col-sm-4'}
ModelForm = column_plugin.get_form(request, column_model)
form = ModelForm(data, None, instance=column_model)
assert form.is_valid()
column_plugin.save_model(request, column_model, form, False)
# render the plugin and check the output
context = {
'request': request,
}
content_renderer = ContentRenderer(request)
row_model.parent.child_plugin_instances
for plugin in plugin_list:
plugin.refresh_from_db()
build_plugin_tree(plugin_list)
html = content_renderer.render_plugin(container_model, context)
html = strip_spaces_between_tags(html).strip()
assert html == '' \
''
================================================
FILE: tests/bootstrap4/test_grid.py
================================================
import pytest
from cmsplugin_cascade.bootstrap4.grid import (Bootstrap4Container, Bootstrap4Row, Bootstrap4Column, BootstrapException,
Breakpoint, Bound, fluid_bounds)
def test_breakpoint_iter():
for k, bp in enumerate(Breakpoint):
if k == 0: assert bp == Breakpoint.xs
if k == 1: assert bp == Breakpoint.sm
if k == 2: assert bp == Breakpoint.md
if k == 3: assert bp == Breakpoint.lg
if k == 4: assert bp == Breakpoint.xl
assert k == 4
def test_breakpoint_range():
for k, bp in enumerate(Breakpoint.range(Breakpoint.xs, Breakpoint.xl)):
if k == 0: assert bp == Breakpoint.xs
if k == 1: assert bp == Breakpoint.sm
if k == 2: assert bp == Breakpoint.md
if k == 3: assert bp == Breakpoint.lg
if k == 4: assert bp == Breakpoint.xl
assert k == 4
def test_breakpoint_partial():
for k, bp in enumerate(Breakpoint.range(Breakpoint.sm, Breakpoint.lg)):
if k == 0: assert bp == Breakpoint.sm
if k == 1: assert bp == Breakpoint.md
if k == 2: assert bp == Breakpoint.lg
assert k == 2
def test_xs_cols():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
for _ in range(3):
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)
assert row[2].get_bound(Breakpoint.sm) == Bound(180.0, 180.0)
assert row[2].get_bound(Breakpoint.md) == Bound(240.0, 240.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
def test_fluid_xs_cols():
"""
"""
container = Bootstrap4Container(bounds=fluid_bounds)
row = container.add_row(Bootstrap4Row())
for _ in range(3):
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(192.0, 256.0)
assert row[2].get_bound(Breakpoint.md) == Bound(256.0, 330.7)
assert row[2].get_bound(Breakpoint.lg) == Bound(330.7, 400.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(400.0, 660.0)
def test_xs_cols_with_flex():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-3'))
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col'))
repr(row[0])
assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)
assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(120.0, 214.5)
assert row[1].get_bound(Breakpoint.sm) == Bound(202.5, 202.5)
assert row[1].get_bound(Breakpoint.md) == Bound(270.0, 270.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(360.0, 360.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(427.5, 427.5)
def test_xs_cols_with_auto_and_flex():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-3'))
row.add_column(Bootstrap4Column('col-auto'))
row.add_column(Bootstrap4Column('col'))
assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)
assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)
assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)
assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 480.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)
assert row[2].get_bound(Breakpoint.md) == Bound(30.0, 480.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)
def test_mix_flex_with_fixed():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col col-1'))
def test_mix_flex_with_auto():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col col-auto'))
def test_mix_fixed_with_auto():
row = Bootstrap4Row()
with pytest.raises(BootstrapException):
row.add_column(Bootstrap4Column('col-1 col-auto'))
def test_growing_columns():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))
row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))
row.add_column(Bootstrap4Column('col-12 col-sm-12 col-lg-4'))
assert row[0].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)
assert row[0].get_bound(Breakpoint.sm) == Bound(270.0, 270.0)
assert row[0].get_bound(Breakpoint.md) == Bound(360.0, 360.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)
assert row[2].get_bound(Breakpoint.sm) == Bound(540.0, 540.0)
assert row[2].get_bound(Breakpoint.md) == Bound(720.0, 720.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)
def test_haricot():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col-auto'))
row.add_column(Bootstrap4Column('col-2'))
assert row[0].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)
assert row[0].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)
assert row[0].get_bound(Breakpoint.md) == Bound(30.0, 540.0)
assert row[0].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)
assert row[0].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)
assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)
assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)
assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 540.0)
assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)
assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)
assert row[2].get_bound(Breakpoint.xs) == Bound(53.3, 95.3)
assert row[2].get_bound(Breakpoint.sm) == Bound(90.0, 90.0)
assert row[2].get_bound(Breakpoint.md) == Bound(120.0, 120.0)
assert row[2].get_bound(Breakpoint.lg) == Bound(160.0, 160.0)
assert row[2].get_bound(Breakpoint.xl) == Bound(190.0, 190.0)
def test_nested_row():
"""
"""
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.add_column(Bootstrap4Column('col'))
nested_row = row[0].add_row(Bootstrap4Row())
nested_row.add_column(Bootstrap4Column('col-5'))
nested_row.add_column(Bootstrap4Column('col-7'))
assert nested_row[0].get_bound(Breakpoint.xs) == Bound(66.7, 119.2)
assert nested_row[1].get_bound(Breakpoint.xs) == Bound(93.3, 166.8)
assert nested_row[0].get_bound(Breakpoint.sm) == Bound(112.5, 112.5)
assert nested_row[1].get_bound(Breakpoint.sm) == Bound(157.5, 157.5)
assert nested_row[0].get_bound(Breakpoint.md) == Bound(150.0, 150.0)
assert nested_row[1].get_bound(Breakpoint.md) == Bound(210.0, 210.0)
assert nested_row[0].get_bound(Breakpoint.lg) == Bound(200.0, 200.0)
assert nested_row[1].get_bound(Breakpoint.lg) == Bound(280.0, 280.0)
assert nested_row[0].get_bound(Breakpoint.xl) == Bound(237.5, 237.5)
assert nested_row[1].get_bound(Breakpoint.xl) == Bound(332.5, 332.5)
def test_repr():
container = Bootstrap4Container()
row = container.add_row(Bootstrap4Row())
row.add_column(Bootstrap4Column('col'))
row.compute_column_bounds()
assert repr(container) == ', , , , >>>'
================================================
FILE: tests/conftest.py
================================================
import factory.fuzzy
import pytest
from pytest_factoryboy import register
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password
from cms.api import create_page
from cmsplugin_cascade.models import CascadePage
@pytest.fixture
def admin_site():
return admin.sites.AdminSite()
@pytest.fixture
@pytest.mark.django_db
def cms_page():
home_page = create_page(title='HOME', template='testing.html', language='en')
if not home_page.is_home:
home_page.set_as_homepage()
CascadePage.assure_relation(home_page)
return home_page
@pytest.fixture
@pytest.mark.django_db
def cms_placeholder(cms_page):
placeholder = cms_page.placeholders.get(slot='Main Content')
return placeholder
@register
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
@classmethod
def create(cls, **kwargs):
user = super().create(**kwargs)
assert isinstance(user, get_user_model())
assert user.is_authenticated == True
return user
username = factory.Sequence(lambda n: 'uid-{}'.format(n))
password = make_password('secret')
email = factory.fuzzy.FuzzyText(suffix='@example.com')
================================================
FILE: tests/requirements.txt
================================================
lxml
beautifulsoup4
pluggy
py
pytest
pytest-django
coverage
django-reversion
factory-boy
pytest-factoryboy
================================================
FILE: tests/settings.py
================================================
from django.utils.text import format_lazy
from django.urls import reverse_lazy
from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
]
ROOT_URLCONF = 'tests.urls'
SECRET_KEY = 'test'
SITE_ID = 1
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': ['tests/templates'],
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
)
}
}]
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.staticfiles',
'filer',
'easy_thumbnails',
'treebeard',
'menus',
'sekizai',
'cms',
'adminsortable2',
'djangocms_text_ckeditor',
'django_select2',
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.segmentation',
'tests',
]
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('en', 'English'),
]
LANGUAGE_CODE = 'en'
CMS_TEMPLATES = [
('testing.html', 'Default Page'),
]
CMSPLUGIN_CASCADE_PLUGINS = [
'cmsplugin_cascade.link',
'cmsplugin_cascade.bootstrap4',
]
CMSPLUGIN_CASCADE = {
'plugins_with_extra_fields': {
'BootstrapButtonPlugin': PluginExtraFieldsConfig(),
'BootstrapContainerPlugin': PluginExtraFieldsConfig(),
'BootstrapColumnPlugin': PluginExtraFieldsConfig(),
'BootstrapRowPlugin': PluginExtraFieldsConfig(),
'BootstrapPicturePlugin': PluginExtraFieldsConfig(),
'SimpleWrapperPlugin': PluginExtraFieldsConfig(),
},
'plugins_with_sharables': {
'BootstrapImagePlugin': (
'image_shapes',
'image_width_responsive',
'image_width_fixed',
'image_height',
'resize_options',
),
'BootstrapPicturePlugin': (
'image_shapes',
'responsive_heights',
'image_size',
'resize_options',
),
},
}
CMS_PLACEHOLDER_CONF = {
'Main Content': {
'plugins': ['BootstrapContainerPlugin'],
'parent_classes': {
'BootstrapContainerPlugin': None,
'TextLinkPlugin': ['TextPlugin'],
},
},
}
THUMBNAIL_PROCESSORS = (
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
)
THUMBNAIL_PRESERVE_EXTENSIONS = True,
THUMBNAIL_OPTIMIZE_COMMAND = {
'png': '/opt/local/bin/optipng {filename}',
'gif': '/opt/local/bin/optipng {filename}',
'jpeg': '/opt/local/bin/jpegoptim {filename}',
}
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'skin': 'moono',
'toolbar': 'CMS',
'toolbar_HTMLField': [
['Undo', 'Redo'],
['cmsplugins', '-', 'ShowBlocks'],
['Format', 'Styles'],
['TextColor', 'BGColor', '-', 'PasteText', 'PasteFromWord'],
['Maximize', ''],
'/',
['Bold', 'Italic', 'Underline', '-', 'Subscript', 'Superscript', '-', 'RemoveFormat'],
['JustifyLeft', 'JustifyCenter', 'JustifyRight'],
['HorizontalRule'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Table'],
['Source']
],
'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),
}
SILENCED_SYSTEM_CHECKS = ['2_0.W001']
================================================
FILE: tests/static/strides/bootstrap-button.json
================================================
{
"plugins":[
[
"BootstrapContainerPlugin",
{
"glossary":{
"hide_plugin":false,
"padding_xs":"",
"padding_sm":"",
"padding_md":"",
"padding_lg":"",
"breakpoints":[
"xs",
"sm",
"md",
"lg",
"xl"
],
"fluid":""
},
"pk":2511
},
[
[
"BootstrapRowPlugin",
{
"glossary":{
"hide_plugin":""
},
"pk":2512
},
[
[
"BootstrapColumnPlugin",
{
"glossary":{
"xs-column-width":"col"
},
"pk":2513
},
[
[
"BootstrapButtonPlugin",
{
"glossary":{
"hide_plugin":"",
"link_type":"",
"cms_page":null,
"section":"",
"download_file":null,
"ext_url":"",
"mail_to":"",
"link_target":"",
"link_title":"",
"icon_font":{
"model":"cmsplugin_cascade.iconfont",
"pk":1
},
"symbol":"",
"link_content":"button_content",
"button_type":"btn-secondary",
"button_size":"",
"button_options":[],
"icon_align":"icon-right",
"stretched_link":false
},
"pk":2514
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-column.json
================================================
{
"plugins": [
[
"BootstrapColumnPlugin",
{
"pk":1539,
"glossary":{
"container_max_widths":{
"xs":720.0,
"md":940.0,
"sm":720.0,
"lg":1140.0
},
"xs-column-width":"col-xs-12"
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-container.json
================================================
{
"plugins": [
[
"BootstrapContainerPlugin",
{
"pk":1503,
"glossary":{
"container_max_widths":{
"xs":750,
"md":970,
"sm":750,
"lg":1170
},
"media_queries":{
"xs":[
"(max-width: 768px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"lg":[
"(min-width: 1200px)"
]
},
"hide_plugin":"",
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"fluid":"",
"extra_css_classes": "foo bar"
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-jumbotron.json
================================================
{
"plugins": [
[
"BootstrapJumbotronPlugin",
{
"pk": 1501,
"glossary": {
"background_vertical_position": "center",
"extra_inline_styles:Paddings": {
"padding-top": "500px",
"padding-bottom": ""
},
"resize_options": [
"crop",
"subject_location",
"high_resolution"
],
"breakpoints": [
"xs",
"sm",
"md",
"lg"
],
"hide_plugin": "",
"background_color": [
"",
"#12308b"
],
"fluid": true,
"container_max_widths": {
"xs": 768,
"md": 1200,
"sm": 992,
"lg": 1980
},
"media_queries": {
"xs": [
"(max-width: 768px)"
],
"md": [
"(min-width: 992px)",
"(max-width: 1200px)"
],
"sm": [
"(min-width: 768px)",
"(max-width: 992px)"
],
"lg": [
"(min-width: 1200px)"
]
},
"image": {
"pk": 5,
"model": "filer.Image"
},
"background_size": "cover",
"background_horizontal_position": "center",
"background_repeat": "no-repeat",
"container_max_heights": {
"xs": "100%",
"md": "100%",
"lg": "100%",
"sm": "100%"
},
"background_width_height": {
"height": "",
"width": ""
},
"background_attachment": "scroll"
}
},
[
[
"TextPlugin",
{
"body": "Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
",
"pk": 1502
},
[]
]
]
]
]
}
================================================
FILE: tests/static/strides/bootstrap-row.json
================================================
{
"plugins": [
[
"BootstrapRowPlugin",
{
"pk":1538,
"glossary":{
"hide_plugin":"",
"extra_css_classes":"",
"extra_inline_styles:Margins":{
"margin-bottom":"20px",
"margin-top":""
},
"extra_element_id":""
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/carousel-plugin.json
================================================
{
"plugins": [
[
"BootstrapCarouselPlugin",
{
"glossary":{
"hide_plugin":false,
"margins_xs":"",
"margins_sm":"",
"margins_md":"",
"margins_lg":"",
"interval":5,
"options":[
"slide",
"pause",
"wrap"
],
"container_max_heights":{
"xs":"9rem",
"sm":"9rem",
"md":"9rem",
"lg":"9rem",
"xl":"9rem"
},
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
]
},
"pk":229
},
[
[
"BootstrapCarouselSlidePlugin",
{
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image":{
"pk":4,
"model":"filer.Image"
},
"media_queries":{
"xs":{
"width":572,
"media":"(max-width: 575.98px)"
},
"sm":{
"width":540,
"media":"(min-width: 576px) and (max-width: 767.98px)"
},
"md":{
"width":720,
"media":"(min-width: 768px) and (max-width: 991.98px)"
},
"lg":{
"width":960,
"media":"(min-width: 992px) and (max-width: 1199.98px)"
},
"xl":{
"width":1140,
"media":"(min-width: 1200px)"
}
}
},
"pk":1526
},
[]
]
]
]
]
}
================================================
FILE: tests/static/strides/framed-icon.json
================================================
{
"plugins": [
[
"FramedIconPlugin",
{
"pk":1513,
"glossary":{
"symbol":"umbrella",
"border_radius":"",
"color":[
"#ffffff",
""
],
"text_align":"text-center",
"hide_plugin":"",
"icon_font":"1",
"font_size":"10em",
"background_color":[
"#ffffff",
"true"
],
"border":[
"0px",
"none",
"#000000"
]
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/simple-wrapper.json
================================================
{
"plugins": [
[
"SimpleWrapperPlugin",
{
"pk":1512,
"glossary":{
"tag_type":"div",
"extra_inline_styles:background-color":[
"#42c8c6",
""
],
"extra_inline_styles:line-height":"",
"extra_css_classes":[],
"hide_plugin":"",
"extra_inline_styles:color":[
"#ffffff",
""
],
"element_id":"",
"extra_inline_styles:Paddings":{
"padding-left":"50px",
"padding-bottom":"",
"padding-top":"",
"padding-right":"50px"
},
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:Heights":{
"height":"360px",
"min-height":"",
"max-height":""
}
}
},
[]
]
]
}
================================================
FILE: tests/static/strides/text-plugin.json
================================================
{
"plugins": [
[
"TextPlugin",
{
"body": "Customizable
\n\nLorem ipsum dolor
",
"pk": 1518
},
[]
]
]
}
================================================
FILE: tests/static/strides.json
================================================
{
"plugins":[
[
"BootstrapJumbotronPlugin",
{
"pk":1584,
"glossary":{
"background_repeat":"no-repeat",
"image":{
"model":"filer.Image",
"pk":5
},
"background_size":"cover",
"fluid":true,
"resize_options":[
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"container_max_heights":{
"sm":"100%",
"md":"100%",
"lg":"100%",
"xs":"100%"
},
"background_horizontal_position":"center",
"media_queries":{
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"lg":[
"(min-width: 1200px)"
],
"xs":[
"(max-width: 768px)"
]
},
"background_vertical_position":"center",
"container_max_widths":{
"sm":992,
"md":1200,
"lg":1980,
"xs":768
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"background_color":[
"",
"#12308b"
],
"background_attachment":"scroll",
"extra_inline_styles:Paddings":{
"padding-bottom":"",
"padding-top":"500px"
},
"background_width_height":{
"width":"",
"height":""
}
}
},
[
[
"TextPlugin",
{
"body":"Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
",
"pk":1585
},
[]
]
]
],
[
"BootstrapContainerPlugin",
{
"pk":1586,
"glossary":{
"fluid":"",
"media_queries":{
"sm":[
"(min-width: 768px)",
"(max-width: 992px)"
],
"md":[
"(min-width: 992px)",
"(max-width: 1200px)"
],
"lg":[
"(min-width: 1200px)"
],
"xs":[
"(max-width: 768px)"
]
},
"container_max_widths":{
"sm":750,
"md":970,
"lg":1170,
"xs":750
},
"breakpoints":[
"xs",
"sm",
"md",
"lg"
],
"hide_plugin":""
}
},
[
[
"HeadingPlugin",
{
"pk":1624,
"glossary":{
"element_id":"heading-1",
"content":"Voluptate velit esse cillum dolore",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"",
"margin-left":"50px",
"margin-right":""
},
"tag_type":"h1"
}
},
[]
],
[
"BootstrapRowPlugin",
{
"pk":1587,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1588,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":720.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"col-md-6",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1589,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"height":"347px"
},
"extra_inline_styles:color":[
"",
"#474747"
],
"extra_inline_styles:line-height":"2",
"extra_inline_styles:background-color":[
"",
"#ffffff"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"20px",
"padding-right":"50px",
"padding-bottom":"20px",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"TextPlugin",
{
"body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum.
",
"pk":1590
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1591,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":720.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"col-md-6",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"",
"md-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"BootstrapImagePlugin",
{
"pk":1592,
"glossary":{
"target":"",
"image_width_responsive":"100%",
"image":{
"model":"filer.Image",
"pk":8
},
"image_height":"",
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"alt_tag":"",
"image_shapes":[
"img-responsive"
],
"link":{
"type":"none"
},
"title":"",
"image_width_fixed":"",
"image_title":""
}
},
[]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1593,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1594,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1595,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#42c8c6"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1596,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"umbrella"
}
},
[]
],
[
"TextPlugin",
{
"body":"\u00a0\u00a0Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1597
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1598,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1599,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#f5b10e"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1600,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"cog-alt"
}
},
[]
],
[
"TextPlugin",
{
"body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1601
},
[]
]
]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1602,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-width":"col-xs-4",
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_inline_styles:line-height":"",
"lg-column-offset":"",
"md-responsive-utils":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"md-column-width":"",
"md-column-offset":"",
"xs-column-offset":"",
"extra_css_classes":[],
"lg-responsive-utils":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"",
"padding-bottom":"",
"padding-left":""
},
"sm-responsive-utils":"",
"lg-column-width":"",
"md-column-ordering":"",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"hide_plugin":"",
"extra_inline_styles:color":[
"",
"#ffffff"
],
"lg-column-ordering":"",
"container_max_widths":{
"sm":220.0,
"md":293.33,
"lg":360.0,
"xs":220.0
},
"sm-column-ordering":"",
"sm-column-width":"",
"sm-column-offset":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1603,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"360px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":""
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#56ba41"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"FramedIconPlugin",
{
"pk":1604,
"glossary":{
"border_radius":"",
"text_align":"text-center",
"icon_font":"1",
"color":[
"",
"#ffffff"
],
"font_size":"10em",
"background_color":[
"disabled",
"#ffffff"
],
"border":[
"0px",
"none",
"#000000"
],
"hide_plugin":"",
"symbol":"bell-alt"
}
},
[]
],
[
"TextPlugin",
{
"body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
",
"pk":1605
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1606,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1607,
"glossary":{
"container_max_widths":{
"sm":720.0,
"md":940.0,
"lg":1140.0,
"xs":720.0
},
"xs-column-width":"col-xs-12"
}
},
[
[
"CarouselPlugin",
{
"pk":1608,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"options":[
"slide",
"pause",
"wrap"
],
"container_max_heights":{
"sm":"250px",
"md":"300px",
"lg":"350px",
"xs":"200px"
},
"interval":"5",
"hide_plugin":""
}
},
[
[
"CarouselSlidePlugin",
{
"pk":1609,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":4
},
"alt_tag":"",
"hide_plugin":""
}
},
[
[
"TextPlugin",
{
"body":"Hallo Welt!
",
"pk":1610
},
[]
]
]
],
[
"CarouselSlidePlugin",
{
"pk":1611,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":7
},
"alt_tag":"",
"hide_plugin":""
}
},
[]
],
[
"CarouselSlidePlugin",
{
"pk":1612,
"glossary":{
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"image_title":"",
"image":{
"model":"filer.Image",
"pk":6
},
"alt_tag":"",
"hide_plugin":""
}
},
[]
]
]
]
]
]
]
],
[
"BootstrapRowPlugin",
{
"pk":1614,
"glossary":{
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":"20px"
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1615,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":345.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"col-sm-6",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"BootstrapImagePlugin",
{
"pk":1616,
"glossary":{
"target":"",
"image_width_responsive":"100%",
"image":{
"model":"filer.Image",
"pk":9
},
"image_height":"",
"resize_options":[
"upscale",
"crop",
"subject_location",
"high_resolution"
],
"hide_plugin":"",
"alt_tag":"",
"image_shapes":[
"img-responsive"
],
"link":{
"type":"none"
},
"title":"",
"image_width_fixed":"",
"image_title":""
}
},
[]
]
]
],
[
"BootstrapColumnPlugin",
{
"pk":1617,
"glossary":{
"xs-responsive-utils":"",
"xs-column-ordering":"",
"xs-column-offset":"",
"xs-column-width":"col-xs-12",
"sm-column-offset":"",
"lg-column-offset":"",
"hide_plugin":"",
"md-column-ordering":"",
"lg-column-ordering":"",
"md-responsive-utils":"",
"container_max_widths":{
"sm":345.0,
"md":455.0,
"lg":555.0,
"xs":720.0
},
"md-column-width":"",
"lg-column-width":"",
"sm-column-ordering":"",
"sm-column-width":"col-sm-6",
"md-column-offset":"",
"lg-responsive-utils":"",
"sm-responsive-utils":""
}
},
[
[
"SimpleWrapperPlugin",
{
"pk":1618,
"glossary":{
"element_id":"",
"extra_inline_styles:Heights":{
"max-height":"",
"height":"370px",
"min-height":""
},
"extra_inline_styles:color":[
"",
"#ffffff"
],
"extra_inline_styles:Font Size":{
"font-size":"130%"
},
"extra_css_classes":[],
"extra_inline_styles:line-height":"",
"extra_inline_styles:background-color":[
"",
"#ef3e42"
],
"hide_plugin":"",
"extra_inline_styles:Paddings":{
"padding-top":"50px",
"padding-right":"50px",
"padding-bottom":"",
"padding-left":"50px"
},
"tag_type":"div"
}
},
[
[
"TextPlugin",
{
"body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
",
"pk":1619
},
[]
],
[
"BootstrapButtonPlugin",
{
"pk":1620,
"glossary":{
"link":{
"section":"",
"model":"cms.Page",
"type":"cmspage",
"pk":5
},
"button_options":[],
"icon_font":"3",
"extra_inline_styles:Margins":{
"margin-top":"20px",
"margin-bottom":""
},
"link_content":"Continue",
"button_size":"btn-lg",
"symbol":"right-open",
"icon_align":"icon-right",
"quick_float":"pull-right",
"button_type":"btn-default",
"hide_plugin":""
}
},
[]
]
]
]
]
]
]
],
[
"HeadingPlugin",
{
"pk":1613,
"glossary":{
"element_id":"heading",
"content":"Where we are",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"",
"margin-left":"",
"margin-right":""
},
"tag_type":"h3"
}
},
[]
],
[
"BootstrapRowPlugin",
{
"pk":1621,
"glossary":{
"extra_element_id":"",
"extra_css_classes":"",
"hide_plugin":"",
"extra_inline_styles:Margins":{
"margin-top":"",
"margin-bottom":"20px"
}
}
},
[
[
"BootstrapColumnPlugin",
{
"pk":1622,
"glossary":{
"container_max_widths":{
"sm":720.0,
"md":940.0,
"lg":1140.0,
"xs":720.0
},
"xs-column-width":"col-xs-12"
}
},
[
[
"LeafletPlugin",
{
"inlines":[
{
"marker_width":"75px",
"title":"Kirche St. Nikolaus",
"image":{
"model":"filer.Image",
"pk":3
},
"marker_anchor":{
"top":"35px",
"left":"35px"
},
"popup_text":"Kirche in St. Nikolaus
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
",
"position":{
"lng":11.392865180969238,
"lat":47.27430835780767
}
},
{
"popup_text":null,
"marker_width":"",
"title":"SOHO 2.0",
"position":{
"lng":11.44007205963135,
"lat":47.26479535533907
},
"marker_anchor":{
"top":"",
"left":""
}
}
],
"pk":1623,
"glossary":{
"map_width":"100%",
"map_position":{
"zoom":16,
"lat":47.263227953097356,
"lng":11.434611082077026
},
"map_height":"400px",
"hide_plugin":"",
"render_template":"cascade/plugins/googlemap.html"
}
},
[]
]
]
]
]
]
]
]
]
}
================================================
FILE: tests/templates/testing.html
================================================
{% load cms_tags sekizai_tags %}
Test Template
{% render_block "css" %}
{% placeholder "Main Content" %}
================================================
FILE: tests/test_base.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password
from django.template.context import Context
from cms.api import create_page
from cms.test_utils.testcases import CMSTestCase
from cmsplugin_cascade.models import CascadePage
class CascadeTestCase(CMSTestCase):
home_page = None
def setUp(self):
self.home_page = create_page(title='HOME', template='testing.html', language='en')
if not self.home_page.is_home:
# >= Django CMS v3.5.x
self.home_page.set_as_homepage()
CascadePage.assure_relation(self.home_page)
self.placeholder = self.home_page.placeholders.get(slot='Main Content')
self.request = self.get_request(self.home_page, 'en')
self.admin_site = admin.sites.AdminSite()
UserModel = get_user_model()
UserModel.objects.get_or_create(
username='admin',
is_staff=True,
is_superuser=True,
is_active=True,
password=make_password('admin'),
)
UserModel.objects.get_or_create(
username='staff',
is_staff=True,
is_superuser=False,
is_active=True,
password=make_password('staff'),
)
def get_request_context(self):
context = {}
context['request'] = self.request
context['user'] = self.request.user
try:
# >= Django CMS v3.4.x
context['cms_content_renderer'] = self.get_content_renderer(request=self.request)
except AttributeError:
# < Django CMS v3.4.x
pass
return Context(context)
def get_html(self, model_instance, context):
try:
# >= Django CMS v3.4.x
return context['cms_content_renderer'].render_plugin(model_instance, context)
except KeyError:
# < Django CMS v3.4.x
return model_instance.render_plugin(context)
================================================
FILE: tests/test_customplugin.py
================================================
from django.test import TestCase
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.plugin_base import CascadePluginBase
class CustomPlugin(CascadePluginBase):
name = 'Custom Element'
render_template = 'cascade/generic/naked.html'
class CustomPluginTest(TestCase):
def test_register(self):
plugin_pool.register_plugin(CustomPlugin)
def test_proxy_model_has_correct_app_label(self):
self.assertEqual(CustomPlugin.model._meta.app_label, 'cmsplugin_cascade')
================================================
FILE: tests/test_http.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from django.conf import settings
from django.contrib import admin
from django.test.utils import override_settings
from cms.models import Page
from cms.utils.compat.dj import is_installed
from cms.test_utils.testcases import CMSTestCase, URL_CMS_PAGE_ADD, URL_CMS_PLUGIN_ADD
import pytest
APPS_WITHOUT_REVERSION = [app for app in settings.INSTALLED_APPS if app != 'reversion']
class ContainerPluginTest(CMSTestCase):
def setUp(self):
self.admin_site = admin.sites.AdminSite()
self.user = self.get_superuser()
self.password = "top_secret"
self.user.set_password(self.password)
self.user.save()
self.client.login(username=self.user.username, password=self.password)
self.language = 'en'
self.site_id = settings.SITE_ID
# create page
response = self.client.post(
'/' + self.language + URL_CMS_PAGE_ADD[3:],
data={
'language': self.language,
'site': self.site_id,
'template': 'INHERIT',
'title': 'HOME',
'slug': 'home',
'_save': 'Save',
},
follow=True,
)
self.assertEqual(response.status_code, 200)
# get page and placeholder
self.page = Page.objects.get(publisher_is_draft=True, is_home=True)
self.placeholder = self.page.placeholders.get(slot='Main Content')
def _create_and_configure_a_container_plugin(self):
# create a plugin
response = self.client.post(
'/' + self.language + URL_CMS_PLUGIN_ADD[3:],
data={
'plugin_parent': '',
'plugin_type': 'BootstrapContainerPlugin',
'plugin_language': self.language,
'placeholder_id': self.placeholder.id,
},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content.decode('utf-8'))
plugin_url = response_data['url']
# configure that plugin
response = self.client.post(
plugin_url,
data={
'_popup': '1',
'breakpoints': ['xs', 'lg'],
'_save': 'Save',
},
)
self.assertEqual(response.status_code, 200)
@override_settings(INSTALLED_APPS=APPS_WITHOUT_REVERSION)
@pytest.mark.skip(reason="no way of currently testing this")
def test_without_reversion(self):
self.assertFalse(is_installed('reversion'))
self._create_and_configure_a_container_plugin()
@pytest.mark.skip(reason="no way of currently testing this")
def test_with_reversion(self):
self.assertTrue(is_installed('reversion'))
self._create_and_configure_a_container_plugin()
================================================
FILE: tests/test_iconfont.py
================================================
import os
from bs4 import BeautifulSoup
import pytest
import factory.fuzzy
from pytest_factoryboy import register
from django import VERSION as DJANGO_VERSION
from django.forms.models import ModelForm
from django.urls import reverse, resolve
from django.core.files import File as DjangoFile
from django.template.context import RequestContext
from filer.models.filemodels import File as FilerFileModel
from cms.api import add_plugin
from cms.plugin_rendering import ContentRenderer
from cmsplugin_cascade.models import CascadeElement, IconFont
from cmsplugin_cascade.icon.forms import IconFormMixin
from cmsplugin_cascade.icon.simpleicon import SimpleIconPlugin
from .conftest import UserFactory
@register
class IconFileFactory(factory.django.DjangoModelFactory):
class Meta:
model = FilerFileModel
@classmethod
def create(cls, **kwargs):
filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')
fileobj = DjangoFile(open(filename, 'rb'), name='fontello-b504201f.zip')
owner = UserFactory(is_active=True, is_staff=True)
filer_fileobj = FilerFileModel.objects.create(
owner=owner,
original_filename=fileobj.name,
file=fileobj,
)
return filer_fileobj
@pytest.fixture
@pytest.mark.django_db
def icon_font(admin_client, icon_file_factory):
icon_file = icon_file_factory()
data = {
'identifier': "Fontellico",
'zip_file': icon_file.id,
'is_default': 'on',
'_continue': "Save and continue editing",
}
add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')
response = admin_client.post(add_iconfont_url, data)
assert response.status_code == 302
resolver_match = resolve(response.url)
assert resolver_match.url_name == 'cmsplugin_cascade_iconfont_change'
# check the content of the uploaded file
if DJANGO_VERSION >= (2, 0):
icon_font = IconFont.objects.get(pk=resolver_match.kwargs['object_id'])
else:
icon_font = IconFont.objects.get(pk=resolver_match.args[0])
assert icon_font.identifier == "Fontellico"
assert icon_font.config_data['name'] == 'fontelico'
assert len(icon_font.config_data['glyphs']) == 34
return icon_font
@pytest.mark.django_db
def test_iconfont_change_view(admin_client, icon_font):
# check if the uploaded fonts are rendered inside Preview Icons
change_url = reverse('admin:cmsplugin_cascade_iconfont_change', args=[icon_font.id])
response = admin_client.get(change_url)
assert response.status_code == 200
soup = BeautifulSoup(response.content, 'lxml')
css_prefix = soup.find('div', class_='field-css_prefix').find('div', class_='readonly')
assert css_prefix.text == 'icon-'
preview_iconfont = soup.find('div', class_='preview-iconfont')
icon_items = preview_iconfont.ul.find_all('li')
assert len(icon_items) == 34
assert icon_items[0].i.attrs['class'] == ['icon-emo-happy']
assert icon_items[33].i.attrs['class'] == ['icon-marquee']
@pytest.fixture
@pytest.mark.django_db
def simple_icon(admin_site, cms_placeholder, icon_font):
"""Create and edit a SimpleIconPlugin"""
class IconFontForm(IconFormMixin, ModelForm):
class Meta(IconFormMixin.Meta):
model = CascadeElement
# add simple icon plugin
simple_icon_model = add_plugin(cms_placeholder, SimpleIconPlugin, 'en')
assert isinstance(simple_icon_model, CascadeElement)
# edit simple icon plugin
data = {'icon_font': str(icon_font.id), 'symbol': 'icon-skiing'}
form = IconFontForm(data=data, instance=simple_icon_model)
assert form.is_valid()
simple_icon_model = form.save()
assert simple_icon_model.glossary['icon_font']['model'] == 'cmsplugin_cascade.iconfont'
assert simple_icon_model.glossary['symbol'] == 'icon-skiing'
simple_icon_plugin = simple_icon_model.get_plugin_class_instance(admin_site)
assert isinstance(simple_icon_plugin, SimpleIconPlugin)
return simple_icon_plugin, simple_icon_model
@pytest.mark.django_db
def test_simple_icon(rf, simple_icon):
"""Render a SimpleIconPlugin"""
simple_icon_plugin, simple_icon_model = simple_icon
request = rf.get('/')
context = RequestContext(request)
content_renderer = ContentRenderer(request)
html = content_renderer.render_plugin(simple_icon_model, context).strip()
assert html == ''
================================================
FILE: tests/test_missingmigrations.py
================================================
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import pytest
from django.core.management import call_command
@pytest.mark.django_db
def test_for_missing_migrations():
out = StringIO()
call_command('makemigrations', '--dry-run', 'cmsplugin_cascade', verbosity=3, interactive=False, stdout=out)
assert out.getvalue() == "No changes detected in app 'cmsplugin_cascade'\n"
================================================
FILE: tests/test_segmentation.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from bs4 import BeautifulSoup
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import get_user_model
from cms.api import add_plugin
from cms.utils.plugins import build_plugin_tree
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from cmsplugin_cascade.generic.simple_wrapper import SimpleWrapperPlugin
from cmsplugin_cascade.segmentation.cms_plugins import SegmentPlugin
from .test_base import CascadeTestCase
class SegmentationPluginTest(CascadeTestCase):
def setUp(self):
super(SegmentationPluginTest, self).setUp()
UserModel = get_user_model()
self.admin_user = UserModel.objects.get(username='admin')
self.staff_user = UserModel.objects.get(username='staff')
def test_plugin_context(self):
# create container
wrapper_model = add_plugin(self.placeholder, SimpleWrapperPlugin, 'en',
glossary={'tag_type': 'naked'})
wrapper_plugin = wrapper_model.get_plugin_class_instance(self.admin_site)
self.assertIsInstance(wrapper_plugin, SimpleWrapperPlugin)
# add an `if`-segment with some text as child
if_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'if', 'condition': 'user.is_superuser'})
self.assertIsInstance(if_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_admin = add_plugin(self.placeholder, TextPlugin, 'en', target=if_segment_model,
body='User is admin
')
self.assertIsInstance(text_model_admin.get_plugin_class_instance(), TextPlugin)
# add an `elif`-segment with some text as child
elif_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'elif', 'condition': 'user.is_authenticated'})
self.assertIsInstance(elif_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_staff = add_plugin(self.placeholder, TextPlugin, 'en', target=elif_segment_model,
body='User is staff
')
self.assertIsInstance(text_model_staff.get_plugin_class_instance(), TextPlugin)
# add an `else`-segment with some text as child
else_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,
glossary={'open_tag': 'else'})
self.assertIsInstance(else_segment_model.get_plugin_class_instance(), SegmentPlugin)
text_model_anon = add_plugin(self.placeholder, TextPlugin, 'en', target=else_segment_model,
body='User is anonymous
')
self.assertIsInstance(text_model_anon.get_plugin_class_instance(), TextPlugin)
# build the DOM
plugin_list = [wrapper_model, if_segment_model, text_model_admin, elif_segment_model,
text_model_staff, else_segment_model, text_model_anon]
build_plugin_tree(plugin_list)
# test for if-segment (render the plugins as admin user)
self.request.user = self.admin_user
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is admin')
# test for elif-segment (render the plugins as staff user)
self.request.user = self.staff_user
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is staff')
# test for else-segment (render the plugins as anonymous user)
self.request.user = AnonymousUser
soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')
self.assertHTMLEqual(soup.p.text, 'User is anonymous')
================================================
FILE: tests/test_strides.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
import django
import os
from bs4 import BeautifulSoup
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from django.template import RequestContext, Template
from django.test import RequestFactory
from cmsplugin_cascade.models import IconFont
from filer.admin.clipboardadmin import ajax_upload
from .test_base import CascadeTestCase
class StridePluginTest(CascadeTestCase):
def setUp(self):
super().setUp()
request = RequestFactory().get('/')
self.context = RequestContext(request, {})
def assertStyleEqual(self, provided, expected):
styles = dict((pair.split(':')[0].strip(), pair.split(':')[1].strip())
for pair in provided.split(';') if ':' in pair)
self.assertDictEqual(styles, expected)
def skiptest_bootstrap_jumbotron(self):
template = Template('{% load cascade_tags sekizai_tags %}{% render_block "css" %}{% render_cascade "strides/bootstrap-jumbotron.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(soup.style.text.find('#cascadeelement_id-1501 {\n\tbackground-color: #12308b;\n\tbackground-attachment: scroll;\n\tbackground-position: center center;\n\tbackground-repeat: no-repeat;\n\tbackground-size: cover;\n\tpadding-top: 500px;'), 1 )
element = soup.find(id='cascadeelement_id-1501')
self.assertEqual(element.h1.text, "Manage your website")
self.assertStyleEqual(element.h1.attrs['style'], {'text-align': 'center'})
def test_bootstrap_container(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-container.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
element = soup.find(class_='container')
self.assertSetEqual(set(element.attrs['class']), {'foo', 'bar', 'container'})
def skiptest_bootstrap_row(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-row.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(str(soup.div), '\n')
def skiptest_bootstrap_column(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-column.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(str(soup.div), '\n')
def skiptest_simple_wrapper(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/simple-wrapper.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
expected_styles = {
'background-color':'#42c8c6',
'color':'#ffffff',
'height':'360px',
'padding-left':'50px',
'padding-right':'50px',
}
self.assertStyleEqual(soup.div.attrs['style'], expected_styles)
def upload_icon_font(self):
UserModel = get_user_model()
admin_user = UserModel.objects.get(username='admin')
with self.login_user_context(admin_user):
filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')
with open(filename, 'rb') as zipfile:
uploaded_file = SimpleUploadedFile('fontello-b504201f.zip', zipfile.read(), content_type='application/zip')
request = self.get_request(reverse('admin:filer-ajax_upload'))
request.FILES.update(file=uploaded_file)
response = ajax_upload(request)
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
# save the form and submit the remaining fields
add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')
data = {
'identifier': "Fontellico",
'zip_file': content['file_id'],
'_continue': "Save and continue editing",
}
response = self.client.post(add_iconfont_url, data)
self.assertEqual(response.status_code, 302)
self.assertEqual(IconFont.objects.count(), 1)
def test_framed_icon(self):
self.upload_icon_font()
icon_font = IconFont.objects.first()
icon_font.id = 1 # to match id in fixture "strides/framed-icon.json"
icon_font.save()
template = Template('{% load cascade_tags sekizai_tags %}{% render_block "css" %}{% render_cascade "strides/framed-icon.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertSetEqual(set(soup.div.attrs['class']), {'text-center'})
self.assertStyleEqual(soup.div.attrs['style'], {'font-size': '10em'})
expected_style = {
'color': '#ffffff',
'display': 'inline-block',
}
self.assertStyleEqual(soup.div.span.attrs['style'], expected_style)
def test_text_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/text-plugin.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
self.assertEqual(soup.h2.text, "Customizable")
self.assertStyleEqual(soup.h2.attrs['style'], {'text-align': 'center'})
self.assertEqual(soup.p.text, "Lorem ipsum dolor")
def test_carousel_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/carousel-plugin.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
carousel = soup.find(class_='carousel')
self.assertSetEqual(set(carousel.attrs['class']), {'carousel', 'slide', 'pause', 'wrap', 'slide'})
self.assertListEqual(carousel.ol.attrs['class'], ['carousel-indicators'])
self.assertListEqual(carousel.ol.li.attrs['class'], ['active'])
slide = carousel.find(class_='carousel-inner')
self.assertSetEqual(set(slide.div.attrs['class']), {'carousel-item', 'active'})
def test_button_plugin(self):
template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-button.json" %}')
html = template.render(self.context)
soup = BeautifulSoup(html, features='lxml')
button = soup.find(class_='btn')
self.assertSetEqual(set(button.attrs['class']), {'btn', 'btn-secondary'})
================================================
FILE: tests/urls.py
================================================
from django import VERSION as DJANGO_VERSION
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
if DJANGO_VERSION < (2, 0):
from django.conf.urls import url, include
urlpatterns = i18n_patterns(
url(r'^admin/', admin.site.urls),
url(r'^', include('cms.urls')),
)
if DJANGO_VERSION >= (2, 0):
from django.urls import path, include
urlpatterns = i18n_patterns(
path('admin/', admin.site.urls),
path('', include('cms.urls')),
)
================================================
FILE: tests/utils.py
================================================
from django.template import RequestContext
def get_request_context(request, extra_context=None):
if extra_context is None:
extra_context = {}
context = RequestContext(request, extra_context)
# XXX: Workaround for an issue with django-cms that we do not fully
# understand yet. It seems that the template context processors are not
# run early enough, so context['request'] is not available when
# django-cms tries to access it. We should do further analysis on this.
context['request'] = request
# The same problem seems to exist with request.user.
context['user'] = request.user
return context
Django-CMS Cascade
", "pk":901 }, [] ] ] ], [ "BootstrapContainerPlugin", { "glossary":{ "media_queries":{ "xs":[ "(max-width: 768px)" ], "lg":[ "(min-width: 1200px)" ], "sm":[ "(min-width: 768px)", "(max-width: 992px)" ], "md":[ "(min-width: 992px)", "(max-width: 1200px)" ] }, "container_max_widths":{ "xs":750, "lg":1170, "sm":750, "md":970 }, "breakpoints":[ "xs", "sm", "md", "lg" ], "hide_plugin":"", "fluid":"" }, "pk":902 }, [ [ "HeadingPlugin", { "glossary":{ "content":"Cascade Demo Page", "element_id":"heading-1", "tag_type":"h1", "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-right":"", "margin-top":"", "margin-left":"", "margin-bottom":"" } }, "pk":936 }, [] ], [ "BootstrapRowPlugin", { "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"" } }, "pk":903 }, [ [ "BootstrapColumnPlugin", { "glossary":{ "sm-responsive-utils":"", "xs-responsive-utils":"", "md-column-offset":"", "sm-column-width":"", "md-responsive-utils":"", "xs-column-offset":"", "md-column-width":"col-md-6", "hide_plugin":"", "sm-column-ordering":"", "sm-column-offset":"", "lg-column-ordering":"", "lg-column-offset":"", "xs-column-ordering":"", "xs-column-width":"col-xs-12", "lg-responsive-utils":"", "container_max_widths":{ "xs":720.0, "lg":555.0, "sm":720.0, "md":455.0 }, "lg-column-width":"", "md-column-ordering":"" }, "pk":904 }, [ [ "SimpleWrapperPlugin", { "glossary":{ "extra_inline_styles:line-height":"2", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"367px" }, "extra_inline_styles:Font Size":{ "font-size":"15px" }, "element_id":"", "extra_inline_styles:color":[ "", "#474747" ], "extra_css_classes":[], "tag_type":"div", "extra_inline_styles:background-color":[ "", "#ffffff" ], "extra_inline_styles:Paddings":{ "padding-top":"20px", "padding-right":"50px", "padding-bottom":"20px", "padding-left":"50px" } }, "pk":905 }, [ [ "TextPlugin", { "body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum. Nullam id dolor id nibh ultricies vehicula ut id elit.
", "pk":906 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "glossary":{ "sm-responsive-utils":"", "xs-responsive-utils":"", "md-column-offset":"", "sm-column-width":"", "md-responsive-utils":"", "xs-column-offset":"", "md-column-width":"col-md-6", "hide_plugin":"", "sm-column-ordering":"", "sm-column-offset":"", "lg-column-ordering":"", "lg-column-offset":"", "xs-column-ordering":"", "xs-column-width":"col-xs-12", "lg-responsive-utils":"", "extra_css_classes":[], "container_max_widths":{ "xs":720.0, "lg":555.0, "sm":720.0, "md":455.0 }, "lg-column-width":"", "md-column-ordering":"" }, "pk":907 }, [ [ "BootstrapImagePlugin", { "glossary":{ "image_width_responsive":"100%", "target":"", "title":"", "image":{ "pk":11, "model":"filer.Image" }, "alt_tag":"", "hide_plugin":"", "image_width_fixed":"", "image_height":"", "link":{ "type":"none" }, "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image_title":"", "image_shapes":[ "img-responsive" ] }, "pk":908 }, [] ] ] ] ] ], [ "BootstrapRowPlugin", { "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" } }, "pk":909 }, [ [ "BootstrapColumnPlugin", { "glossary":{ "md-column-offset":"", "extra_inline_styles:line-height":"", "sm-column-offset":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "xs-column-width":"col-xs-4", "extra_inline_styles:background-color":[ "", "#42c8c6" ], "sm-responsive-utils":"", "xs-responsive-utils":"", "sm-column-width":"", "md-responsive-utils":"", "lg-column-ordering":"", "sm-column-ordering":"", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "container_max_widths":{ "xs":220.0, "lg":360.0, "sm":220.0, "md":293.33 }, "md-column-width":"", "xs-column-ordering":"", "lg-column-offset":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_css_classes":[], "lg-responsive-utils":"", "xs-column-offset":"", "md-column-ordering":"", "lg-column-width":"" }, "pk":910 }, [ [ "SimpleWrapperPlugin", { "glossary":{ "extra_inline_styles:line-height":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_inline_styles:Font Size":{ "font-size":"" }, "element_id":"", "extra_inline_styles:color":[ "", "#ffffff" ], "extra_css_classes":[], "tag_type":"div", "extra_inline_styles:background-color":[ "", "#42c8c6" ], "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" } }, "pk":911 }, [ [ "FramedIconPlugin", { "glossary":{ "font_size":"10em", "color":[ "", "#ffffff" ], "background_color":[ "disabled", "#ffffff" ], "symbol":"wrench", "hide_plugin":"", "text_align":"text-center", "icon_font":"3", "border_radius":"", "border":[ "0px", "none", "#000000" ] }, "pk":912 }, [] ], [ "TextPlugin", { "body":"Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":913 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "glossary":{ "md-column-offset":"", "extra_inline_styles:line-height":"", "sm-column-offset":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "xs-column-width":"col-xs-4", "extra_inline_styles:background-color":[ "", "#f5b10e" ], "sm-responsive-utils":"", "xs-responsive-utils":"", "sm-column-width":"", "md-responsive-utils":"", "lg-column-ordering":"", "sm-column-ordering":"", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "container_max_widths":{ "xs":220.0, "lg":360.0, "sm":220.0, "md":293.33 }, "md-column-width":"", "xs-column-ordering":"", "lg-column-offset":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_css_classes":[], "lg-responsive-utils":"", "xs-column-offset":"", "md-column-ordering":"", "lg-column-width":"" }, "pk":918 }, [ [ "SimpleWrapperPlugin", { "glossary":{ "extra_inline_styles:line-height":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_inline_styles:Font Size":{ "font-size":"" }, "element_id":"", "extra_inline_styles:color":[ "", "#ffffff" ], "extra_css_classes":[], "tag_type":"div", "extra_inline_styles:background-color":[ "", "#f5b10e" ], "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" } }, "pk":919 }, [ [ "FramedIconPlugin", { "glossary":{ "font_size":"10em", "color":[ "", "#ffffff" ], "background_color":[ "disabled", "#ffffff" ], "symbol":"cog-alt", "hide_plugin":"", "text_align":"text-center", "icon_font":"3", "border_radius":"", "border":[ "0px", "none", "#000000" ] }, "pk":920 }, [] ], [ "TextPlugin", { "body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":921 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "glossary":{ "md-column-offset":"", "extra_inline_styles:line-height":"", "sm-column-offset":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "xs-column-width":"col-xs-4", "extra_inline_styles:background-color":[ "", "#56ba41" ], "sm-responsive-utils":"", "xs-responsive-utils":"", "sm-column-width":"", "md-responsive-utils":"", "lg-column-ordering":"", "sm-column-ordering":"", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "container_max_widths":{ "xs":220.0, "lg":360.0, "sm":220.0, "md":293.33 }, "md-column-width":"", "xs-column-ordering":"", "lg-column-offset":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_css_classes":[], "lg-responsive-utils":"", "xs-column-offset":"", "md-column-ordering":"", "lg-column-width":"" }, "pk":914 }, [ [ "SimpleWrapperPlugin", { "glossary":{ "extra_inline_styles:line-height":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"360px" }, "extra_inline_styles:Font Size":{ "font-size":"" }, "element_id":"", "extra_inline_styles:color":[ "", "#ffffff" ], "extra_css_classes":[], "tag_type":"div", "extra_inline_styles:background-color":[ "", "#56ba41" ], "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" } }, "pk":915 }, [ [ "FramedIconPlugin", { "glossary":{ "font_size":"10em", "color":[ "", "#ffffff" ], "background_color":[ "disabled", "#ffffff" ], "symbol":"bell-alt", "hide_plugin":"", "text_align":"text-center", "icon_font":"3", "border_radius":"", "border":[ "0px", "none", "#000000" ] }, "pk":916 }, [] ], [ "TextPlugin", { "body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":917 }, [] ] ] ] ] ] ] ], [ "BootstrapRowPlugin", { "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" } }, "pk":929 }, [ [ "BootstrapColumnPlugin", { "glossary":{ "container_max_widths":{ "xs":720.0, "lg":1140.0, "sm":720.0, "md":940.0 }, "xs-column-width":"col-xs-12" }, "pk":930 }, [ [ "CarouselPlugin", { "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "container_max_heights":{ "xs":"200px", "md":"300px", "sm":"250px", "lg":"350px" }, "interval":"5", "hide_plugin":"", "options":[ "slide", "pause", "wrap" ] }, "pk":931 }, [ [ "CarouselSlidePlugin", { "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image":{ "pk":4, "model":"filer.Image" }, "image_title":"", "hide_plugin":"", "alt_tag":"" }, "pk":932 }, [ [ "TextPlugin", { "body":"Hallo Welt!
", "pk":933 }, [] ] ] ], [ "CarouselSlidePlugin", { "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image":{ "pk":7, "model":"filer.Image" }, "image_title":"", "hide_plugin":"", "alt_tag":"" }, "pk":934 }, [] ], [ "CarouselSlidePlugin", { "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image":{ "pk":6, "model":"filer.Image" }, "image_title":"", "hide_plugin":"", "alt_tag":"" }, "pk":935 }, [] ] ] ] ] ] ] ], [ "BootstrapRowPlugin", { "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"20px" } }, "pk":922 }, [ [ "BootstrapColumnPlugin", { "glossary":{ "sm-responsive-utils":"", "xs-responsive-utils":"", "md-column-offset":"", "sm-column-width":"col-sm-6", "md-responsive-utils":"", "xs-column-offset":"", "md-column-width":"", "hide_plugin":"", "sm-column-ordering":"", "sm-column-offset":"", "lg-column-ordering":"", "lg-column-offset":"", "xs-column-ordering":"", "xs-column-width":"col-xs-12", "lg-responsive-utils":"", "container_max_widths":{ "xs":720.0, "lg":555.0, "sm":345.0, "md":455.0 }, "lg-column-width":"", "md-column-ordering":"" }, "pk":923 }, [ [ "BootstrapImagePlugin", { "glossary":{ "image_width_responsive":"100%", "target":"", "title":"", "image":{ "pk":12, "model":"filer.Image" }, "alt_tag":"", "hide_plugin":"", "image_width_fixed":"", "image_height":"", "link":{ "type":"none" }, "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image_title":"", "image_shapes":[ "img-responsive" ] }, "pk":924 }, [] ] ] ], [ "BootstrapColumnPlugin", { "glossary":{ "sm-responsive-utils":"", "xs-responsive-utils":"", "md-column-offset":"", "sm-column-width":"col-sm-6", "md-responsive-utils":"", "xs-column-offset":"", "md-column-width":"", "hide_plugin":"", "sm-column-ordering":"", "sm-column-offset":"", "lg-column-ordering":"", "lg-column-offset":"", "xs-column-ordering":"", "xs-column-width":"col-xs-12", "lg-responsive-utils":"", "container_max_widths":{ "xs":720.0, "lg":555.0, "sm":345.0, "md":455.0 }, "lg-column-width":"", "md-column-ordering":"" }, "pk":925 }, [ [ "SimpleWrapperPlugin", { "glossary":{ "extra_inline_styles:line-height":"", "hide_plugin":"", "extra_inline_styles:Heights":{ "max-height":"", "min-height":"", "height":"370px" }, "extra_inline_styles:Font Size":{ "font-size":"130%" }, "element_id":"", "extra_inline_styles:color":[ "", "#ffffff" ], "extra_css_classes":[], "tag_type":"div", "extra_inline_styles:background-color":[ "", "#ef3e42" ], "extra_inline_styles:Paddings":{ "padding-top":"50px", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" } }, "pk":926 }, [ [ "TextPlugin", { "body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
", "pk":927 }, [] ], [ "BootstrapButtonPlugin", { "glossary":{ "icon_align":"icon-right", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" }, "button_options":[], "button_size":"btn-lg", "quick_float":"pull-right", "hide_plugin":"", "icon_font":"3", "link_content":"Continue", "link":{ "pk":5, "model":"cms.Page", "type":"cmspage", "section":"" }, "button_type":"btn-default", "symbol":"right-open" }, "pk":928 }, [] ] ] ] ] ] ] ], [ "BootstrapRowPlugin", { "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"20px" } }, "pk":937 }, [ [ "BootstrapColumnPlugin", { "glossary":{ "container_max_widths":{ "xs":720.0, "lg":1140.0, "sm":720.0, "md":940.0 }, "xs-column-width":"col-xs-12" }, "pk":938 }, [ [ "LeafletPlugin", { "glossary":{ "render_template":"cascade/plugins/leaflet.html", "map_position":{ "lat":47.27337658656428, "lng":11.399195194244387, "zoom":15 }, "hide_plugin":"", "map_height":"400px", "map_width":"100%" }, "pk":939, "inlines":[ { "title":"Kirche St. Nikolaus", "image":{ "pk":15, "model":"filer.Image" }, "marker_width":"25px", "position":{ "lat":47.274337475394645, "lng":11.393036842346191 }, "popup_text":null, "marker_anchor":{ "top":"50%", "left":"50%" } } ] }, [] ] ] ] ] ] ] ] ] } ================================================ FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_footer.scss ================================================ // include this file when using a static footer @import "variables"; html { position: relative; min-height: 100%; } body { margin-bottom: $body-footer-height; } #footer { position: absolute; bottom: 0; width: 100%; height: $body-footer-height; color: $body-footer-color; background: $body-footer-bg; padding-top: 20px; padding-bottom: 20px; } ================================================ FILE: examples/bs4demo/bs4demo/static/bs4demo/css/_variables.scss ================================================ @import "bootstrap/scss/_functions"; @import "bootstrap/scss/_variables"; @import "bootstrap/scss/mixins/_breakpoints.scss"; // footer $body-footer-height: 200px; $body-footer-color: $yiq-text-light; $body-footer-bg: $dark; ================================================ FILE: examples/bs4demo/bs4demo/static/bs4demo/css/badge.scss ================================================ .badge-ribbon { position: relative; background: lightgrey; font-size: 50px; height: 100px; width: 100px; -moz-border-radius: 50px; -webkit-border-radius: 50px; border-radius: 50px; text-align: center; padding-top: 12px; margin-bottom: 50px; &:before, &:after { content: ''; position: absolute; border-bottom: 70px solid lightgrey; border-left: 40px solid transparent; border-right: 40px solid transparent; top: 90px; left: -10px; -webkit-transform: rotate(-150deg); -moz-transform: rotate(-150deg); -ms-transform: rotate(-150deg); -o-transform: rotate(-150deg); } &:after { left: auto; right: -10px; -webkit-transform: rotate(150deg); -moz-transform: rotate(150deg); -ms-transform: rotate(150deg); -o-transform: rotate(150deg); } } ================================================ FILE: examples/bs4demo/bs4demo/static/bs4demo/css/main.scss ================================================ @import "variables"; @import "bootstrap/scss/bootstrap"; @import "footer"; body { background-color: #eee8e8; } ================================================ FILE: examples/bs4demo/bs4demo/templates/bs4demo/badge.html ================================================ {% load sekizai_tags sass_tags %} {% addtoblock "css" %}{% endaddtoblock %} {% with inline_styles=instance.inline_styles %}Add Bootstrap container here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL, switch to “Structure” mode
and add a Bootstrap Container Plugin or Jumbotron Plugin.
Content is rendered using the templatetag {% verbatim %}{% render_cascade "bs4demo/cascades/strides.json" %}{% endverbatim %}
Add some Bootstrap plugins here
Use this placeholder as a quick way to start editing a new CMS page.
All you have to do is to append ?edit to the URL and switch to “Structure” mode.
Into this hard coded Bootstrap Column, add a Text Plugin.
If you want to further subdivide this column, add a Row Plugin with their own columns.
Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
", "pk": 1502 }, [] ] ] ] ] } ================================================ FILE: tests/static/strides/bootstrap-row.json ================================================ { "plugins": [ [ "BootstrapRowPlugin", { "pk":1538, "glossary":{ "hide_plugin":"", "extra_css_classes":"", "extra_inline_styles:Margins":{ "margin-bottom":"20px", "margin-top":"" }, "extra_element_id":"" } }, [] ] ] } ================================================ FILE: tests/static/strides/carousel-plugin.json ================================================ { "plugins": [ [ "BootstrapCarouselPlugin", { "glossary":{ "hide_plugin":false, "margins_xs":"", "margins_sm":"", "margins_md":"", "margins_lg":"", "interval":5, "options":[ "slide", "pause", "wrap" ], "container_max_heights":{ "xs":"9rem", "sm":"9rem", "md":"9rem", "lg":"9rem", "xl":"9rem" }, "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ] }, "pk":229 }, [ [ "BootstrapCarouselSlidePlugin", { "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image":{ "pk":4, "model":"filer.Image" }, "media_queries":{ "xs":{ "width":572, "media":"(max-width: 575.98px)" }, "sm":{ "width":540, "media":"(min-width: 576px) and (max-width: 767.98px)" }, "md":{ "width":720, "media":"(min-width: 768px) and (max-width: 991.98px)" }, "lg":{ "width":960, "media":"(min-width: 992px) and (max-width: 1199.98px)" }, "xl":{ "width":1140, "media":"(min-width: 1200px)" } } }, "pk":1526 }, [] ] ] ] ] } ================================================ FILE: tests/static/strides/framed-icon.json ================================================ { "plugins": [ [ "FramedIconPlugin", { "pk":1513, "glossary":{ "symbol":"umbrella", "border_radius":"", "color":[ "#ffffff", "" ], "text_align":"text-center", "hide_plugin":"", "icon_font":"1", "font_size":"10em", "background_color":[ "#ffffff", "true" ], "border":[ "0px", "none", "#000000" ] } }, [] ] ] } ================================================ FILE: tests/static/strides/simple-wrapper.json ================================================ { "plugins": [ [ "SimpleWrapperPlugin", { "pk":1512, "glossary":{ "tag_type":"div", "extra_inline_styles:background-color":[ "#42c8c6", "" ], "extra_inline_styles:line-height":"", "extra_css_classes":[], "hide_plugin":"", "extra_inline_styles:color":[ "#ffffff", "" ], "element_id":"", "extra_inline_styles:Paddings":{ "padding-left":"50px", "padding-bottom":"", "padding-top":"", "padding-right":"50px" }, "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:Heights":{ "height":"360px", "min-height":"", "max-height":"" } } }, [] ] ] } ================================================ FILE: tests/static/strides/text-plugin.json ================================================ { "plugins": [ [ "TextPlugin", { "body": "Customizable
\n\nLorem ipsum dolor
", "pk": 1518 }, [] ] ] } ================================================ FILE: tests/static/strides.json ================================================ { "plugins":[ [ "BootstrapJumbotronPlugin", { "pk":1584, "glossary":{ "background_repeat":"no-repeat", "image":{ "model":"filer.Image", "pk":5 }, "background_size":"cover", "fluid":true, "resize_options":[ "crop", "subject_location", "high_resolution" ], "hide_plugin":"", "container_max_heights":{ "sm":"100%", "md":"100%", "lg":"100%", "xs":"100%" }, "background_horizontal_position":"center", "media_queries":{ "sm":[ "(min-width: 768px)", "(max-width: 992px)" ], "md":[ "(min-width: 992px)", "(max-width: 1200px)" ], "lg":[ "(min-width: 1200px)" ], "xs":[ "(max-width: 768px)" ] }, "background_vertical_position":"center", "container_max_widths":{ "sm":992, "md":1200, "lg":1980, "xs":768 }, "breakpoints":[ "xs", "sm", "md", "lg" ], "background_color":[ "", "#12308b" ], "background_attachment":"scroll", "extra_inline_styles:Paddings":{ "padding-bottom":"", "padding-top":"500px" }, "background_width_height":{ "width":"", "height":"" } } }, [ [ "TextPlugin", { "body":"Manage your website
\n\nwith ease
\n\n\u00a0
\n\n\u00a0
", "pk":1585 }, [] ] ] ], [ "BootstrapContainerPlugin", { "pk":1586, "glossary":{ "fluid":"", "media_queries":{ "sm":[ "(min-width: 768px)", "(max-width: 992px)" ], "md":[ "(min-width: 992px)", "(max-width: 1200px)" ], "lg":[ "(min-width: 1200px)" ], "xs":[ "(max-width: 768px)" ] }, "container_max_widths":{ "sm":750, "md":970, "lg":1170, "xs":750 }, "breakpoints":[ "xs", "sm", "md", "lg" ], "hide_plugin":"" } }, [ [ "HeadingPlugin", { "pk":1624, "glossary":{ "element_id":"heading-1", "content":"Voluptate velit esse cillum dolore", "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"", "margin-left":"50px", "margin-right":"" }, "tag_type":"h1" } }, [] ], [ "BootstrapRowPlugin", { "pk":1587, "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"" } } }, [ [ "BootstrapColumnPlugin", { "pk":1588, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-offset":"", "xs-column-width":"col-xs-12", "sm-column-offset":"", "lg-column-offset":"", "hide_plugin":"", "md-column-ordering":"", "lg-column-ordering":"", "md-responsive-utils":"", "container_max_widths":{ "sm":720.0, "md":455.0, "lg":555.0, "xs":720.0 }, "md-column-width":"col-md-6", "lg-column-width":"", "sm-column-ordering":"", "sm-column-width":"", "md-column-offset":"", "lg-responsive-utils":"", "sm-responsive-utils":"" } }, [ [ "SimpleWrapperPlugin", { "pk":1589, "glossary":{ "element_id":"", "extra_inline_styles:Heights":{ "height":"347px" }, "extra_inline_styles:color":[ "", "#474747" ], "extra_inline_styles:line-height":"2", "extra_inline_styles:background-color":[ "", "#ffffff" ], "hide_plugin":"", "extra_inline_styles:Paddings":{ "padding-top":"20px", "padding-right":"50px", "padding-bottom":"20px", "padding-left":"50px" }, "tag_type":"div" } }, [ [ "TextPlugin", { "body":"What we do?
\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue. Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Cras mattis consectetur purus sit amet fermentum.
", "pk":1590 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "pk":1591, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-offset":"", "xs-column-width":"col-xs-12", "sm-column-offset":"", "lg-column-offset":"", "hide_plugin":"", "md-column-ordering":"", "lg-column-ordering":"", "md-responsive-utils":"", "container_max_widths":{ "sm":720.0, "md":455.0, "lg":555.0, "xs":720.0 }, "md-column-width":"col-md-6", "lg-column-width":"", "sm-column-ordering":"", "sm-column-width":"", "md-column-offset":"", "extra_css_classes":[], "lg-responsive-utils":"", "sm-responsive-utils":"" } }, [ [ "BootstrapImagePlugin", { "pk":1592, "glossary":{ "target":"", "image_width_responsive":"100%", "image":{ "model":"filer.Image", "pk":8 }, "image_height":"", "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "hide_plugin":"", "alt_tag":"", "image_shapes":[ "img-responsive" ], "link":{ "type":"none" }, "title":"", "image_width_fixed":"", "image_title":"" } }, [] ] ] ] ] ], [ "BootstrapRowPlugin", { "pk":1593, "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" } } }, [ [ "BootstrapColumnPlugin", { "pk":1594, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-width":"col-xs-4", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:line-height":"", "lg-column-offset":"", "md-responsive-utils":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "md-column-width":"", "md-column-offset":"", "xs-column-offset":"", "extra_css_classes":[], "lg-responsive-utils":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "sm-responsive-utils":"", "lg-column-width":"", "md-column-ordering":"", "extra_inline_styles:background-color":[ "", "#42c8c6" ], "hide_plugin":"", "extra_inline_styles:color":[ "", "#ffffff" ], "lg-column-ordering":"", "container_max_widths":{ "sm":220.0, "md":293.33, "lg":360.0, "xs":220.0 }, "sm-column-ordering":"", "sm-column-width":"", "sm-column-offset":"" } }, [ [ "SimpleWrapperPlugin", { "pk":1595, "glossary":{ "element_id":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_css_classes":[], "extra_inline_styles:line-height":"", "extra_inline_styles:background-color":[ "", "#42c8c6" ], "hide_plugin":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" }, "tag_type":"div" } }, [ [ "FramedIconPlugin", { "pk":1596, "glossary":{ "border_radius":"", "text_align":"text-center", "icon_font":"1", "color":[ "", "#ffffff" ], "font_size":"10em", "background_color":[ "disabled", "#ffffff" ], "border":[ "0px", "none", "#000000" ], "hide_plugin":"", "symbol":"umbrella" } }, [] ], [ "TextPlugin", { "body":"\u00a0\u00a0Quick Installs
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":1597 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "pk":1598, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-width":"col-xs-4", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:line-height":"", "lg-column-offset":"", "md-responsive-utils":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "md-column-width":"", "md-column-offset":"", "xs-column-offset":"", "extra_css_classes":[], "lg-responsive-utils":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "sm-responsive-utils":"", "lg-column-width":"", "md-column-ordering":"", "extra_inline_styles:background-color":[ "", "#f5b10e" ], "hide_plugin":"", "extra_inline_styles:color":[ "", "#ffffff" ], "lg-column-ordering":"", "container_max_widths":{ "sm":220.0, "md":293.33, "lg":360.0, "xs":220.0 }, "sm-column-ordering":"", "sm-column-width":"", "sm-column-offset":"" } }, [ [ "SimpleWrapperPlugin", { "pk":1599, "glossary":{ "element_id":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_css_classes":[], "extra_inline_styles:line-height":"", "extra_inline_styles:background-color":[ "", "#f5b10e" ], "hide_plugin":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" }, "tag_type":"div" } }, [ [ "FramedIconPlugin", { "pk":1600, "glossary":{ "border_radius":"", "text_align":"text-center", "icon_font":"1", "color":[ "", "#ffffff" ], "font_size":"10em", "background_color":[ "disabled", "#ffffff" ], "border":[ "0px", "none", "#000000" ], "hide_plugin":"", "symbol":"cog-alt" } }, [] ], [ "TextPlugin", { "body":"Customizable
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":1601 }, [] ] ] ] ] ], [ "BootstrapColumnPlugin", { "pk":1602, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-width":"col-xs-4", "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_inline_styles:line-height":"", "lg-column-offset":"", "md-responsive-utils":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "md-column-width":"", "md-column-offset":"", "xs-column-offset":"", "extra_css_classes":[], "lg-responsive-utils":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"", "padding-bottom":"", "padding-left":"" }, "sm-responsive-utils":"", "lg-column-width":"", "md-column-ordering":"", "extra_inline_styles:background-color":[ "", "#56ba41" ], "hide_plugin":"", "extra_inline_styles:color":[ "", "#ffffff" ], "lg-column-ordering":"", "container_max_widths":{ "sm":220.0, "md":293.33, "lg":360.0, "xs":220.0 }, "sm-column-ordering":"", "sm-column-width":"", "sm-column-offset":"" } }, [ [ "SimpleWrapperPlugin", { "pk":1603, "glossary":{ "element_id":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"360px", "min-height":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "extra_inline_styles:Font Size":{ "font-size":"" }, "extra_css_classes":[], "extra_inline_styles:line-height":"", "extra_inline_styles:background-color":[ "", "#56ba41" ], "hide_plugin":"", "extra_inline_styles:Paddings":{ "padding-top":"", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" }, "tag_type":"div" } }, [ [ "FramedIconPlugin", { "pk":1604, "glossary":{ "border_radius":"", "text_align":"text-center", "icon_font":"1", "color":[ "", "#ffffff" ], "font_size":"10em", "background_color":[ "disabled", "#ffffff" ], "border":[ "0px", "none", "#000000" ], "hide_plugin":"", "symbol":"bell-alt" } }, [] ], [ "TextPlugin", { "body":"Support
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.
", "pk":1605 }, [] ] ] ] ] ] ] ], [ "BootstrapRowPlugin", { "pk":1606, "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" } } }, [ [ "BootstrapColumnPlugin", { "pk":1607, "glossary":{ "container_max_widths":{ "sm":720.0, "md":940.0, "lg":1140.0, "xs":720.0 }, "xs-column-width":"col-xs-12" } }, [ [ "CarouselPlugin", { "pk":1608, "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "options":[ "slide", "pause", "wrap" ], "container_max_heights":{ "sm":"250px", "md":"300px", "lg":"350px", "xs":"200px" }, "interval":"5", "hide_plugin":"" } }, [ [ "CarouselSlidePlugin", { "pk":1609, "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image_title":"", "image":{ "model":"filer.Image", "pk":4 }, "alt_tag":"", "hide_plugin":"" } }, [ [ "TextPlugin", { "body":"Hallo Welt!
", "pk":1610 }, [] ] ] ], [ "CarouselSlidePlugin", { "pk":1611, "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image_title":"", "image":{ "model":"filer.Image", "pk":7 }, "alt_tag":"", "hide_plugin":"" } }, [] ], [ "CarouselSlidePlugin", { "pk":1612, "glossary":{ "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "image_title":"", "image":{ "model":"filer.Image", "pk":6 }, "alt_tag":"", "hide_plugin":"" } }, [] ] ] ] ] ] ] ], [ "BootstrapRowPlugin", { "pk":1614, "glossary":{ "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"20px" } } }, [ [ "BootstrapColumnPlugin", { "pk":1615, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-offset":"", "xs-column-width":"col-xs-12", "sm-column-offset":"", "lg-column-offset":"", "hide_plugin":"", "md-column-ordering":"", "lg-column-ordering":"", "md-responsive-utils":"", "container_max_widths":{ "sm":345.0, "md":455.0, "lg":555.0, "xs":720.0 }, "md-column-width":"", "lg-column-width":"", "sm-column-ordering":"", "sm-column-width":"col-sm-6", "md-column-offset":"", "lg-responsive-utils":"", "sm-responsive-utils":"" } }, [ [ "BootstrapImagePlugin", { "pk":1616, "glossary":{ "target":"", "image_width_responsive":"100%", "image":{ "model":"filer.Image", "pk":9 }, "image_height":"", "resize_options":[ "upscale", "crop", "subject_location", "high_resolution" ], "hide_plugin":"", "alt_tag":"", "image_shapes":[ "img-responsive" ], "link":{ "type":"none" }, "title":"", "image_width_fixed":"", "image_title":"" } }, [] ] ] ], [ "BootstrapColumnPlugin", { "pk":1617, "glossary":{ "xs-responsive-utils":"", "xs-column-ordering":"", "xs-column-offset":"", "xs-column-width":"col-xs-12", "sm-column-offset":"", "lg-column-offset":"", "hide_plugin":"", "md-column-ordering":"", "lg-column-ordering":"", "md-responsive-utils":"", "container_max_widths":{ "sm":345.0, "md":455.0, "lg":555.0, "xs":720.0 }, "md-column-width":"", "lg-column-width":"", "sm-column-ordering":"", "sm-column-width":"col-sm-6", "md-column-offset":"", "lg-responsive-utils":"", "sm-responsive-utils":"" } }, [ [ "SimpleWrapperPlugin", { "pk":1618, "glossary":{ "element_id":"", "extra_inline_styles:Heights":{ "max-height":"", "height":"370px", "min-height":"" }, "extra_inline_styles:color":[ "", "#ffffff" ], "extra_inline_styles:Font Size":{ "font-size":"130%" }, "extra_css_classes":[], "extra_inline_styles:line-height":"", "extra_inline_styles:background-color":[ "", "#ef3e42" ], "hide_plugin":"", "extra_inline_styles:Paddings":{ "padding-top":"50px", "padding-right":"50px", "padding-bottom":"", "padding-left":"50px" }, "tag_type":"div" } }, [ [ "TextPlugin", { "body":"Let us make
\na difference in your
\nweb design
\n\nLorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
", "pk":1619 }, [] ], [ "BootstrapButtonPlugin", { "pk":1620, "glossary":{ "link":{ "section":"", "model":"cms.Page", "type":"cmspage", "pk":5 }, "button_options":[], "icon_font":"3", "extra_inline_styles:Margins":{ "margin-top":"20px", "margin-bottom":"" }, "link_content":"Continue", "button_size":"btn-lg", "symbol":"right-open", "icon_align":"icon-right", "quick_float":"pull-right", "button_type":"btn-default", "hide_plugin":"" } }, [] ] ] ] ] ] ] ], [ "HeadingPlugin", { "pk":1613, "glossary":{ "element_id":"heading", "content":"Where we are", "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"", "margin-left":"", "margin-right":"" }, "tag_type":"h3" } }, [] ], [ "BootstrapRowPlugin", { "pk":1621, "glossary":{ "extra_element_id":"", "extra_css_classes":"", "hide_plugin":"", "extra_inline_styles:Margins":{ "margin-top":"", "margin-bottom":"20px" } } }, [ [ "BootstrapColumnPlugin", { "pk":1622, "glossary":{ "container_max_widths":{ "sm":720.0, "md":940.0, "lg":1140.0, "xs":720.0 }, "xs-column-width":"col-xs-12" } }, [ [ "LeafletPlugin", { "inlines":[ { "marker_width":"75px", "title":"Kirche St. Nikolaus", "image":{ "model":"filer.Image", "pk":3 }, "marker_anchor":{ "top":"35px", "left":"35px" }, "popup_text":"Kirche in St. Nikolaus
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
", "position":{ "lng":11.392865180969238, "lat":47.27430835780767 } }, { "popup_text":null, "marker_width":"", "title":"SOHO 2.0", "position":{ "lng":11.44007205963135, "lat":47.26479535533907 }, "marker_anchor":{ "top":"", "left":"" } } ], "pk":1623, "glossary":{ "map_width":"100%", "map_position":{ "zoom":16, "lat":47.263227953097356, "lng":11.434611082077026 }, "map_height":"400px", "hide_plugin":"", "render_template":"cascade/plugins/googlemap.html" } }, [] ] ] ] ] ] ] ] ] } ================================================ FILE: tests/templates/testing.html ================================================ {% load cms_tags sekizai_tags %}User is admin
') self.assertIsInstance(text_model_admin.get_plugin_class_instance(), TextPlugin) # add an `elif`-segment with some text as child elif_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model, glossary={'open_tag': 'elif', 'condition': 'user.is_authenticated'}) self.assertIsInstance(elif_segment_model.get_plugin_class_instance(), SegmentPlugin) text_model_staff = add_plugin(self.placeholder, TextPlugin, 'en', target=elif_segment_model, body='User is staff
') self.assertIsInstance(text_model_staff.get_plugin_class_instance(), TextPlugin) # add an `else`-segment with some text as child else_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model, glossary={'open_tag': 'else'}) self.assertIsInstance(else_segment_model.get_plugin_class_instance(), SegmentPlugin) text_model_anon = add_plugin(self.placeholder, TextPlugin, 'en', target=else_segment_model, body='User is anonymous
') self.assertIsInstance(text_model_anon.get_plugin_class_instance(), TextPlugin) # build the DOM plugin_list = [wrapper_model, if_segment_model, text_model_admin, elif_segment_model, text_model_staff, else_segment_model, text_model_anon] build_plugin_tree(plugin_list) # test for if-segment (render the plugins as admin user) self.request.user = self.admin_user soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser') self.assertHTMLEqual(soup.p.text, 'User is admin') # test for elif-segment (render the plugins as staff user) self.request.user = self.staff_user soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser') self.assertHTMLEqual(soup.p.text, 'User is staff') # test for else-segment (render the plugins as anonymous user) self.request.user = AnonymousUser soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser') self.assertHTMLEqual(soup.p.text, 'User is anonymous') ================================================ FILE: tests/test_strides.py ================================================ # -*- coding: utf-8 -*- from __future__ import unicode_literals import json import django import os from bs4 import BeautifulSoup from django.contrib.auth import get_user_model from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse from django.template import RequestContext, Template from django.test import RequestFactory from cmsplugin_cascade.models import IconFont from filer.admin.clipboardadmin import ajax_upload from .test_base import CascadeTestCase class StridePluginTest(CascadeTestCase): def setUp(self): super().setUp() request = RequestFactory().get('/') self.context = RequestContext(request, {}) def assertStyleEqual(self, provided, expected): styles = dict((pair.split(':')[0].strip(), pair.split(':')[1].strip()) for pair in provided.split(';') if ':' in pair) self.assertDictEqual(styles, expected) def skiptest_bootstrap_jumbotron(self): template = Template('{% load cascade_tags sekizai_tags %}{% render_block "css" %}{% render_cascade "strides/bootstrap-jumbotron.json" %}') html = template.render(self.context) soup = BeautifulSoup(html, features='lxml') self.assertEqual(soup.style.text.find('#cascadeelement_id-1501 {\n\tbackground-color: #12308b;\n\tbackground-attachment: scroll;\n\tbackground-position: center center;\n\tbackground-repeat: no-repeat;\n\tbackground-size: cover;\n\tpadding-top: 500px;'), 1 ) element = soup.find(id='cascadeelement_id-1501') self.assertEqual(element.h1.text, "Manage your website") self.assertStyleEqual(element.h1.attrs['style'], {'text-align': 'center'}) def test_bootstrap_container(self): template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-container.json" %}') html = template.render(self.context) soup = BeautifulSoup(html, features='lxml') element = soup.find(class_='container') self.assertSetEqual(set(element.attrs['class']), {'foo', 'bar', 'container'}) def skiptest_bootstrap_row(self): template = Template('{% load cascade_tags %}{% render_cascade "strides/bootstrap-row.json" %}') html = template.render(self.context) soup = BeautifulSoup(html, features='lxml') self.assertEqual(str(soup.div), '