[
  {
    "path": ".coveragerc",
    "content": "[run]\nbranch = True\n\n[report]\nshow_missing = True\nomit =\n    .tox/*\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n\n# Set default charset\n[*.{js,py,rst,json,yml,html,txt,css,scss}]\ncharset = utf-8\n\n[*.{js,py,rst,yml,html,scss}]\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.py]\nmax_line_length =   119\nindent_style = space\nindent_size = 4\n\n[*.{html,js,rst,css,scss}]\nindent_style = tab\nindent_size = 4\n\n[*.rst]\nmax_line_length = 100\n\n[*.{json,yml,css}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish djangocms-cascade\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  publish:\n    name: \"Publish release\"\n    runs-on: \"ubuntu-latest\"\n\n    environment:\n       name: deploy\n\n    strategy:\n      matrix:\n        python-version: [\"3.9\"]\n\n    steps:\n    - uses: actions/checkout@v3\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v3\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install twine\n        python -m pip install build\n    - name: Build 🐍 Python 📦 Package\n      run: |\n        python -m build --sdist --wheel --outdir dist/\n        twine check --strict dist/*\n    - name: Publish 🐍 Python 📦 Package to PyPI\n      if: startsWith(github.ref, 'refs/tags')\n      uses: pypa/gh-action-pypi-publish@release/v1\n      with:\n        user: __token__\n        password: ${{ secrets.PYPI_API_TOKEN_CMSPLUGIN_CASCADE }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Test djangocms-cascade\n\non:\n  push:\n    branches: [ \"master\", \"releases/*\" ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.8\", \"3.9\", \"3.10\"]\n        django-version: [\"3.1\", \"3.2\"]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v2\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Upgrade PIP\n      run: python -m pip install --upgrade pip\n    - name: Install Django-3.1\n      if: matrix.django-version == '3.1'\n      run: python -m pip install \"Django<3.2\"\n    - name: Install Django-3.2\n      if: matrix.django-version == '3.2'\n      run: python -m pip install \"Django<4.0\"\n    - name: Install Django-4.0\n      if: matrix.django-version == '4.0'\n      run: |\n        python -m pip install \"Django<4.1\"\n        python -m pip install \"django-admin-sortable>=2.0\"\n    - name: Install dependencies\n      run: |\n        pip install -r requirements/base.txt\n        pip install -r tests/requirements.txt\n    - name: Lint with flake8\n      run: |\n        pip install flake8\n        # stop the build if there are Python syntax errors or undefined names\n        flake8 . --count --select=E9,F63,F7,F82 --ignore=F821 --show-source --statistics\n        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide\n        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics\n    - name: Test with pytest\n      run: |\n        python -m pytest tests\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n*.log\n*.pot\n*.pyc\n.project\n.pydevproject\n.settings\n.coverage\n.DS_Store\n.sass-cache\n.cache\n.pytest_cache\n*.db\n*.egg-info\n.eggs\n.tox\nbuild\nenv*\ndist\nhtmlcov\nbower_components/\nnode_modules/\nworkdir/\nfiler_public/\nfiler_public_thumbnails/\nexamples/bs3demo/private_settings.py\n.venv\npackage-lock.json\npoetry.lock\n"
  },
  {
    "path": "BOOSTRAP-4.md",
    "content": "# Migrating towards Bootstrap-4\n\nCurrently I'am refactoring **djangocms-cascade**, adding support for Bootstrap-4. This is not as\neasy as one might expect, because of the way Bootstrap-4 handles the width of columns. Remember,\nin Bootstrap-3 the width of each column was predictable. With the advent of auto-columns in\nBootstrap-4, this isn't possible anymore.\n\n\n## Why does this matter?\n\nOne of **djangocms-cascade**'s great features is, that it always keeps track of the column\nwidth for each available breakpoint. This is important, so that responsive images can be\nthumbnailed to fit exactly into a given column and hence save bandwidth. Therefore in Bootstrap-3,\nCascade rendered images with up to 8 thumbnails in different sizes. From these the browser then is\nable to choose that one, which best fits into the given viewport.\n\nThis in modern HTML is handled by the `<img ...>` element itself, using the attributes `sizes`\ntogether with `srcset`. The `<picture>` element, uses the children elements `<source ...>`.\n\n\n## In Bootstrap-4, this approach doesn't work anymore \n\nIn Bootstrap-4, containers use the flexbox model rather than floating divs. This allows the use\nof columns with equally distributed width, in addition to the fixed sized columns we already know.\nTherefore the widths of the columns can only be calculated, if we know how their siblings are made\nup. Therefore the existing approach to determine the widths of columns used by **djangocms-cascade**\ndoesn't work anymore.\n\nTherefore uses another approach.\n\nInstead of keeping track on the widths of containers, rows and columns, version 0.17 of\n**djangocms-cascade** calculates this only for images.\n\n\nIn the meantime I tried to come up with alternative ideas for my problem with computing image\nwidths.\nAs you know, in Cascade-BS3, for each image 4 different (8 for retina) widths are computed and\ndelivered with their <img srcset=\"...\">. This was relatively easy, since in BS-3 we had to deal\nwith pixels and the widths of columns was determinable. As much as I bump my head against the table,\nI can't find a generic solution for BS-4. Consider columns such as `<div class=\"col-auto\">some content</div>`,\nhow do you determine the maximum width in order to thumbnail the image and compute the srcset-s?\nBut even without auto-width columns, it gets really difficult to get all edge cases.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 Jacob Rief\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE-MIT\ninclude README.md\ninclude setup.py\nrecursive-include cmsplugin_cascade/locale *\nrecursive-include cmsplugin_cascade/static *\nrecursive-include cmsplugin_cascade/templates *\nrecursive-include cmsplugin_cascade *.py\nrecursive-include cmsplugin_cascade/sphinx/static *\nrecursive-include cmsplugin_cascade/sphinx/theme *\nrecursive-exclude tests *\nrecursive-exclude * *.pyc\n"
  },
  {
    "path": "README.md",
    "content": "# djangocms-cascade\n\n[![Build Status](https://github.com/jrief/djangocms-cascade/actions/workflows/tests.yml/badge.svg)](https://github.com/jrief/djangocms-cascade/actions)\n[![PyPI version](https://img.shields.io/pypi/v/djangocms-cascade.svg)](https://pypi.python.org/pypi/djangocms-cascade)\n[![Django versions](https://img.shields.io/pypi/djversions/djangocms-cascade)](https://pypi.python.org/pypi/djangocms-cascade)\n[![Python versions](https://img.shields.io/pypi/pyversions/djangocms-cascade.svg)](https://pypi.python.org/pypi/djangocms-cascade)\n[![Software license](https://img.shields.io/pypi/l/djangocms-cascade.svg)](https://github.com/jrief/djangocms-cascade/blob/master/LICENSE)\n\nThe Swiss army knife for working with Django-CMS plugins.\n\n## Why Use DjangoCMS-Cascade?\n\n**DjangoCMS-Cascade** is a collection of plugins for Django-CMS\n[placeholders](http://docs.django-cms.org/en/latest/introduction/templates_placeholders.html#templates-placeholders).\nInstead of creating one database model for each CMS plugin, Cascade shares one database model for\nall of them. The payload then is stored inside a JSON field instead of declaring each attribute\nexplicitly. This furthermore prevents us to handle all kind of nasty database migration problems.\n\n\n## Features\n\n### Perfect for nested grid systems\n\nSince **Cascade** keeps track on the widths of all columns, ``<img>`` and ``<picture>`` elements can\nbe rendered in a responsive way, so that the browser only loads the image required for the visible\nviewport.\n\n\n### Extend plugins with additional features\n\nUsing a JSON field to store the payload gives us much more flexibility. We can for instance enrich\nour plugins with additional attributes, configured during runtime. This can be used to optionally\nshare attributes across different plugins (referenced by an alias name), add CSS classes and styles,\nor offer alternative rendering templates.\n\n\n### Set links onto your own views\n\nAnother nice aspect of this approach is, that we can override the functionality used to set links\nonto pages which are not part of the CMS. This is specially useful, since we do not want to\nre-implement this functionality for all plugins, which require links, ie. images, pictures,\nbuttons and text-links.\n\n\n### Copy content and paste it somewhere else\n\nSince the payload of plugins is already serialized, we can even copy them from one site to another\nsite supporting **djangocms-cascade**.\n\n\n## Documentation\n\nFind detailed documentation on [ReadTheDocs](http://djangocms-cascade.readthedocs.io/en/latest/).\n\nPlease see the [Release Notes](http://djangocms-cascade.readthedocs.io/en/latest/changelog.html)\nbefore upgrading from an older version.\n\n\n## Architecture\n\n### It's pluggable\n\n**DjangoCMS-Cascade** is very modular, keeping its CMS modules in functional groups. These groups\nhave to be activated independently in the project's `settings.py`. It also is possible to activate\nonly certain plugins out of a group. Currently Bootstrap-4 is implemented, but this app could\neasily be extended for other CSS frameworks.\n\n### Configurable individually\n\nEach **Cascade** plugin can be styled individually. The site-administrator can specify which CSS\nstyles and CSS classes can be added to each plugin. Then the page-editor can pick one of the\nallowed styles to adopt his elements accordingly.\n\n\n### Reuse your data\n\nEach **Cascade** plugin can be configured by the site-administrator to share some or all of its\ndata fields. This for instance is handy, to keep references onto external URLs in a central place.\nOr is can be used to resize all images sharing a cetrain property in one go.\n\n\n### Segment the DOM\n\nIt is even possible to group plugins into seperate evaluation contexts. This for instance is used\nto render different Plugins, depending on whether a user is authenticated or anonymous.\n\n\n### Responsive Images\n\nIn modern web development, images must adopt to the column width in which they are rendered.\nTherefore the ``<img ...>`` tag, in addition to the well known ``src`` attribute, also accepts\nadditional ``srcset``'s, one for each media query. Here **djangocms-cascade** calculates the\nrequired widths for each image, depending on the current column layout considering all media\nbreakpoints.\n\nThis is also implemented for the ``<picture>`` element with all of it's children, normally\n``<source srcset=\"...\">``.\n\nIt also supports resolutions of more than one physical pixel per logical pixel as found in Retina\ndisplays.\n\n\n### Other Features\n\n* Use the scaffolding technique from the preferred CSS framework to subdivide a placeholder into a\n  [grid system](http://getbootstrap.com/css/#grid).\n* Make full usage of responsive techniques, by allowing\n  [stacked to horizontal](http://getbootstrap.com/css/#grid-example-basic) classes per element.\n* Use styled [buttons](http://getbootstrap.com/css/#buttons) to add links.\n* Wrap special content into a [Jumbotron](http://getbootstrap.com/components/#jumbotron) or a\n  [Carousel](http://getbootstrap.com/javascript/#carousel).\n* Add ``<img>`` and ``<picture>`` elements in a responsive way, so that more than one image URL\n  points onto the resized sources, one for each viewport using the ``srcset`` tags or the\n  ``<source>`` elements.\n* Use segmentation to conditionally render parts of the DOM.\n* Temporarily hide a plugin to show up in the DOM.\n* Upload an self composed font from [Fontello](http://fontello.com/) and use it's icon in plain text\n  or as framed eye catchers.\n* It is very easy to integrate additional elements from the preferred CSS framework. For instance,\n  implementing the Bootstrap Carousel, requires only 50 lines of Python code and two simple Django\n  templates.\n* Since all the data is stored in JSON, no database migration is required if a field is added,\n  modified or removed from the plugin.\n* Currently **Bootstrap-4** is supported, but other CSS frameworks can be easily added in a\n  pluggable manner.\n* It follows the \"batteries included\" philosophy, but still remains very modular.\n\nIn addition to easily implement any kind of plugin, **DjangoCMS-Cascade** makes it possible to add\nreusable helpers. Such a helper enriches a plugin with an additional, configurable functionality:\n\n* By making some of the plugin fields sharable, one can reuse these values for other plugins of the\n  same kind. This for instance is handy for the image and picture plugin, so that images always are\n  resized to predefined values.\n* By allowing extra fields, one can add an optional ``id`` tag, CSS classes and inline styles. This\n  is configurable on a plugin and site base.\n* It is possible to customize the rendering templates shipped with the plugins.\n* Since all data is JSON, you can dump the content of one placeholder and insert it into another one,\n  even on a foreign site. This for instance is useful to transfer pages from the staging site to production.\n\n\n### Help appreciated\n\nIf someone wants to start a subproject for a CSS framework, other than Bootstrap-4/5.\n\nIf you are a native English speaker, please check the documentation for spelling mistakes and\ngrammar, since English is not my mother tongue.\n\n\n[![Twitter](https://img.shields.io/twitter/follow/jacobrief?style=social)](https://twitter.com/jacobrief)\n"
  },
  {
    "path": "cmsplugin_cascade/__init__.py",
    "content": "\"\"\"\nSee PEP 386 (https://www.python.org/dev/peps/pep-0386/)\n\nRelease logic:\n 1. Remove \".devX\" from __version__ (below)\n 2. Remove \".devX\" latest version in docs/source/changelog.rst\n 3. git add cmsplugin_cascade/__init__.py docs/source/changelog.rst\n 4. git commit -m 'Bump to <version>'\n 5. git tag <version>\n 6. git push\n 7. assure that all tests pass on https://github.com/jrief/django-formset/actions/workflows/tests.yml\n 8. git push --tags\n 9. assure that a new version is published on https://github.com/jrief/django-formset/actions/workflows/publish.yml\n10. bump the version, append \".dev0\" to __version__\n11. Add a new heading to docs/source/changelog.rst, named \"<next-version>.dev0\"\n12. git add cmsplugin_cascade/__init__.py docs/source/changelog.rst\n13. git commit -m 'Start with <version>'\n14. git push\n\"\"\"\n__version__ = \"2.3.14\"\n\ndefault_app_config = 'cmsplugin_cascade.apps.CascadeConfig'\n"
  },
  {
    "path": "cmsplugin_cascade/admin.py",
    "content": "from urllib.parse import urlparse\nimport requests\n\nfrom django.contrib import admin\nfrom django.contrib.sites.shortcuts import get_current_site\nfrom django.forms import Media, widgets\nfrom django.db.models import Q\nfrom django.http import JsonResponse, HttpResponseForbidden, HttpResponseNotFound\nfrom django.urls import re_path\nfrom django.utils.translation import get_language_from_request, get_language_from_path\n\nfrom cms.models.pagemodel import Page\nfrom cms.extensions import PageExtensionAdmin\nfrom cms.utils.page import get_pages_from_path\nfrom cmsplugin_cascade.models import CascadePage, IconFont\nfrom cmsplugin_cascade.link.forms import format_page_link\n\n\n@admin.register(CascadePage)\nclass CascadePageAdmin(PageExtensionAdmin):\n    add_form_template = change_form_template = 'cascade/admin/change_form.html'\n    fields = ['icon_font', 'menu_symbol']\n\n    @property\n    def media(self):\n        media = super().media\n        media += Media(css={'all': ['cascade/css/admin/cascadepage.css']},\n                       js=['admin/js/jquery.init.js', 'cascade/js/admin/cascadepage.js'])\n        return media\n\n    def get_form(self, request, obj=None, **kwargs):\n        options = dict(kwargs, widgets={'menu_symbol': widgets.HiddenInput})\n        ModelForm = super().get_form(request, obj, **options)\n        return ModelForm\n\n    def get_urls(self):\n        urls = [\n            re_path(r'^get_page_sections/$', lambda _: JsonResponse({'element_ids': []}),\n                name='get_page_sections'),  # just to reverse\n            re_path(r'^get_page_sections/(?P<page_pk>\\d+)$',\n                self.admin_site.admin_view(self.get_page_sections)),\n            re_path(r'^published_pages/$', self.get_published_pagelist, name='get_published_pagelist'),\n            re_path(r'^fetch_fonticons/(?P<iconfont_id>[0-9]+)$', self.fetch_fonticons),\n            re_path(r'^fetch_fonticons/$', self.fetch_fonticons, name='fetch_fonticons'),\n            re_path(r'^validate_exturl/$', self.validate_exturl, name='validate_exturl'),\n        ]\n        urls.extend(super().get_urls())\n        return urls\n\n    def get_page_sections(self, request, page_pk=None):\n        choices = []\n        language = get_language_from_request(request, check_path=True)\n        try:\n            cascade_page = self.model.objects.get(extended_object_id=page_pk)\n            extended_glossary = cascade_page.glossary\n            for key, val in extended_glossary['element_ids'][language].items():\n                if val:\n                    choices.append((key, val))\n        except (self.model.DoesNotExist, KeyError):\n            pass\n        return JsonResponse({'element_ids': choices})\n\n    def get_published_pagelist(self, request, *args, **kwargs):\n        \"\"\"\n        This view is used by the SearchLinkField as the user types to feed the autocomplete drop-down.\n        \"\"\"\n        if request.headers.get('x-requested-with') != 'XMLHttpRequest':\n            return HttpResponseForbidden()\n        data = {'results': []}\n        language = get_language_from_request(request, check_path=True)\n        query_term = request.GET.get('term').strip()\n        if not query_term:\n            return JsonResponse(data)\n\n        # first, try to resolve by URL if it points to a local resource\n        parse_result = urlparse(query_term)\n        if parse_result.netloc.split(':')[0] == request.META['HTTP_HOST'].split(':')[0]:\n            site = get_current_site(request)\n            path = parse_result.path.strip('/')\n            if get_language_from_path(parse_result.path):\n                path = '/'.join(path.split('/')[1:])\n            pages = get_pages_from_path(site, path)\n            for page in pages:\n                data['results'].append(self.get_result_set(language, page))\n            if len(data['results']) > 0:\n                return JsonResponse(data)\n\n        # otherwise resolve by search term\n        matching_published_pages = Page.objects.published().public().filter(\n            Q(title_set__title__icontains=query_term, title_set__language=language)\n            | Q(title_set__path__icontains=query_term, title_set__language=language)\n            | Q(title_set__menu_title__icontains=query_term, title_set__language=language)\n            | Q(title_set__page_title__icontains=query_term, title_set__language=language)\n        ).distinct().order_by('title_set__title').iterator()\n\n        for page in matching_published_pages:\n            data['results'].append(self.get_result_set(language, page))\n            if len(data['results']) > 15:\n                break\n        return JsonResponse(data)\n\n    def get_result_set(self, language, page):\n        title = page.get_title(language=language)\n        path = page.get_absolute_url(language=language)\n        return {\n            'id': page.pk,\n            'text': format_page_link(title, path),\n        }\n\n    def fetch_fonticons(self, request, iconfont_id=None):\n        try:\n            icon_font = IconFont.objects.get(id=iconfont_id)\n        except IconFont.DoesNotExist:\n            return HttpResponseNotFound(\"IconFont with id={} does not exist\".format(iconfont_id))\n        else:\n            data = dict(icon_font.config_data)\n            data.pop('glyphs', None)\n            data['families'] = icon_font.get_icon_families()\n            return JsonResponse(data)\n\n    def validate_exturl(self, request):\n        \"\"\"\n        Perform a GET request onto the given external URL and return its status.\n        \"\"\"\n        exturl = request.GET.get('exturl')\n        if not exturl:\n            return JsonResponse({})\n        request_headers = {'User-Agent': 'Django-CMS-Cascade'}\n        try:\n            response = requests.get(exturl, allow_redirects=True, headers=request_headers, timeout=5.0)\n        except Exception:\n            return JsonResponse({'status_code': 500})\n        else:\n            return JsonResponse({'status_code': response.status_code})\n\n    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):\n        extra_context = dict(extra_context or {}, icon_fonts=IconFont.objects.all())\n        return super().changeform_view(\n             request, object_id=object_id, form_url=form_url, extra_context=extra_context)\n"
  },
  {
    "path": "cmsplugin_cascade/app_settings.py",
    "content": "\nclass AppSettings:\n\n    def _setting(self, name, default=None):\n        from django.conf import settings\n        return getattr(settings, name, default)\n\n    @property\n    def CASCADE_PLUGINS(self):\n        return self._setting('CMSPLUGIN_CASCADE_PLUGINS', (\n            'cmsplugin_cascade.generic',\n            'cmsplugin_cascade.icon',\n            'cmsplugin_cascade.link',\n        ))\n\n    @property\n    def CMSPLUGIN_CASCADE(self):\n        import os\n        from collections import OrderedDict\n        from importlib import import_module\n        from django.forms.fields import NumberInput\n        from django.core.exceptions import ImproperlyConfigured\n        from django.utils.translation import gettext_lazy\n        from cmsplugin_cascade.fields import (ColorField, SelectTextAlignField, SelectOverflowField, SizeField,\n                                              BorderChoiceField)\n\n        if hasattr(self, '_config_CMSPLUGIN_CASCADE'):\n            return self._config_CMSPLUGIN_CASCADE\n\n        INSTALLED_APPS = self._setting('INSTALLED_APPS')\n        config = self._setting('CMSPLUGIN_CASCADE', {})\n        config.setdefault('alien_plugins', ['TextPlugin'])\n        config.setdefault('color_picker_with_alpha', False)\n        config.setdefault('plugin_prefix', None)\n\n        config.setdefault('plugins_with_extra_fields', {})\n        if 'cmsplugin_cascade.extra_fields' in INSTALLED_APPS:\n            from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig\n\n            plugins_with_extra_fields = config['plugins_with_extra_fields']\n            plugins_with_extra_fields.setdefault('SimpleWrapperPlugin', PluginExtraFieldsConfig())\n            for plugin, plugin_config in plugins_with_extra_fields.items():\n                if not isinstance(plugin_config, PluginExtraFieldsConfig):\n                    msg = \"CMSPLUGIN_CASCADE['plugins_with_extra_fields']['{}'] must instantiate a class of type PluginExtraFieldsConfig\"\n                    raise ImproperlyConfigured(msg.format(plugin))\n\n        config.setdefault('plugins_with_extra_mixins', {})\n\n        config.setdefault('plugins_with_sharables', {})\n        if 'cmsplugin_cascade.sharable' in INSTALLED_APPS:\n            config['plugins_with_sharables'].setdefault(\n                'FramedIconPlugin',\n                ['font_size', 'color', 'background_color', 'text_align', 'border', 'border_radius'])\n\n        config['exclude_hiding_plugin'] = list(config.get('exclude_hiding_plugin', []))\n        config['exclude_hiding_plugin'].append('SegmentPlugin')\n\n        config.setdefault('link_plugin_classes', (\n            'cmsplugin_cascade.link.plugin_base.DefaultLinkPluginBase',\n            'cmsplugin_cascade.link.forms.LinkForm'))\n\n        config['plugins_with_bookmark'] = list(config.get('plugins_with_bookmark', []))\n        config['plugins_with_bookmark'].extend(['SimpleWrapperPlugin', 'HeadingPlugin'])\n        config.setdefault('bookmark_prefix', '')\n\n        config.setdefault('extra_inline_styles', OrderedDict())\n        extra_inline_styles = config['extra_inline_styles']\n        extra_inline_styles.setdefault(\n            'Margins',\n            (['margin-top', 'margin-right', 'margin-bottom', 'margin-left'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Paddings',\n            (['padding-top', 'padding-right', 'padding-bottom', 'padding-left'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Widths',\n            (['min-width', 'width', 'max-width'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Heights',\n            (['min-height', 'height', 'max-height'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Text Alignement',\n            (['text-align'], SelectTextAlignField)\n        )\n        extra_inline_styles.setdefault(\n            'Font Size',\n            (['font-size'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Line Height',\n            (['line-height'], NumberInput)\n        )\n        extra_inline_styles.setdefault(\n            'Colors',\n            (['color', 'background-color'], ColorField)\n        )\n        extra_inline_styles.setdefault(\n            'Border',\n            (['border', 'border-top', 'border-right', 'border-bottom', 'border-left'], BorderChoiceField)\n        )\n        extra_inline_styles.setdefault(\n            'Border Radius',\n            (['border-radius'], SizeField)\n        )\n        extra_inline_styles.setdefault(\n            'Overflow',\n            (['overflow', 'overflow-x', 'overflow-y'], SelectOverflowField)\n        )\n\n        if 'cmsplugin_cascade.segmentation' in INSTALLED_APPS:\n            config.setdefault('segmentation_mixins', [\n                ('cmsplugin_cascade.segmentation.mixins.EmulateUserModelMixin',\n                 'cmsplugin_cascade.segmentation.mixins.EmulateUserAdminMixin')])\n\n        config.setdefault(\n            'icon_font_root',\n            os.path.abspath(os.path.join(self._setting('MEDIA_ROOT'), 'icon_fonts')))\n\n        config.setdefault('plugins_with_extra_render_templates', {})\n        config['plugins_with_extra_render_templates'].setdefault(\n            'TextLinkPlugin',\n            [('cascade/link/text-link.html', gettext_lazy(\"default\")),\n             ('cascade/link/text-link-linebreak.html', gettext_lazy(\"with line break\")),])\n        config['plugins_with_extra_render_templates'].setdefault(\n            'LeafletPlugin',\n            [('cascade/plugins/leaflet.html', gettext_lazy(\"default\")),\n             ('cascade/plugins/googlemap.html', gettext_lazy(\"Google Map\")),])\n\n        config.setdefault('allow_plugin_hiding', False)\n\n        config.setdefault('cache_strides', True)\n\n        config.setdefault('register_page_editor', True)\n\n        for module_name in self.CASCADE_PLUGINS:\n            try:\n                settings_module = import_module('{}.settings'.format(module_name))\n                getattr(settings_module, 'set_defaults')(config)\n            except (ImportError, AttributeError):\n                continue\n\n        self._config_CMSPLUGIN_CASCADE = config\n        return config\n\n    @property\n    def CSS_PREFIXES(self):\n        return {\n            'image_set': ['-webkit-image-set', '-moz-image-set', '-o-image-set', '-ms-image-set', 'image-set'],\n        }\n\n    @property\n    def RESPONSIVE_IMAGE_MAX_STEPS(self):\n        \"\"\"\n        Responsive images are offered in a set of various widths. This number specifies the maximum number of\n        generated thumbnails for a specific ``srcset`` of an image.\n        \"\"\"\n        return 12\n\n    @property\n    def RESPONSIVE_IMAGE_STEP_SIZE(self):\n        \"\"\"\n        Responsive images are offered in a set of various widths. This number specifies the minimum step width\n        in pixels between the generated thumbnails for a specific ``srcset`` of an image. If the resulting number\n        of steps would exceed ``RESPONSIVE_IMAGE_MAX_STEPS``, then a higher step width is used.\n        \"\"\"\n        return 50\n\nimport sys  # noqa\napp_settings = AppSettings()\napp_settings.__name__ = __name__\nsys.modules[__name__] = app_settings\n"
  },
  {
    "path": "cmsplugin_cascade/apps.py",
    "content": "from django.apps import AppConfig, apps\nfrom django.conf import settings\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db.models.signals import pre_migrate, post_migrate\nfrom django.db.utils import DatabaseError\nfrom django.urls import reverse\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass CascadeConfig(AppConfig):\n    name = 'cmsplugin_cascade'\n    verbose_name = _(\"django CMS Cascade\")\n    default_permissions = ('add', 'change', 'delete')\n\n    def ready(self):\n        if 'django_select2' not in settings.INSTALLED_APPS:\n            raise ImproperlyConfigured('django_select2 not configured')\n\n        stylesSet = str(settings.CKEDITOR_SETTINGS.get('stylesSet'))\n        if stylesSet != 'default:{}'.format(reverse('admin:cascade_texteditor_config')):\n            msg = \"settings.CKEDITOR_SETTINGS['stylesSet'] should be `format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config'))`\"\n            raise ImproperlyConfigured(msg)\n\n        pre_migrate.connect(self.__class__.pre_migrate, sender=self)\n        post_migrate.connect(self.__class__.post_migrate, sender=self)\n\n    @classmethod\n    def pre_migrate(cls, sender=None, **kwargs):\n        \"\"\"\n        Iterate over contenttypes and remove those not in proxy models\n        \"\"\"\n        ContentType = apps.get_model('contenttypes', 'ContentType')\n        try:\n            queryset = ContentType.objects.filter(app_label=sender.label)\n            for ctype in queryset.exclude(model__in=sender.get_proxy_models().keys()):\n                model = ctype.model_class()\n                if model is None:\n                    sender.revoke_permissions(ctype)\n                    ContentType.objects.get(app_label=sender.label, model=ctype).delete()\n        except DatabaseError:\n            return\n\n    @classmethod\n    def post_migrate(cls, sender=None, **kwargs):\n        \"\"\"\n        Iterate over fake_proxy_models and add contenttypes and permissions for missing proxy\n        models, if this has not been done by Django yet\n        \"\"\"\n        ContentType = apps.get_model('contenttypes', 'ContentType')\n\n        for model_name, proxy_model in sender.get_proxy_models().items():\n            ctype, created = ContentType.objects.get_or_create(app_label=sender.label, model=model_name)\n            if created:\n                sender.grant_permissions(proxy_model)\n\n    def get_proxy_models(self):\n        from cmsplugin_cascade.plugin_base import fake_proxy_models\n\n        proxy_models = {}\n        for model_name in fake_proxy_models.keys():\n            proxy_model = self.get_model(model_name)\n            model_name = proxy_model._meta.model_name  # the model_name in lowercase normally\n            proxy_models[model_name] = proxy_model\n        return proxy_models\n\n    def grant_permissions(self, proxy_model):\n        \"\"\"\n        Create the default permissions for the just added proxy model\n        \"\"\"\n        ContentType = apps.get_model('contenttypes', 'ContentType')\n        try:\n            Permission = apps.get_model('auth', 'Permission')\n        except LookupError:\n            return\n\n        # searched_perms will hold the permissions we're looking for as (content_type, (codename, name))\n        searched_perms = []\n        ctype = ContentType.objects.get_for_model(proxy_model)\n        for perm in self.default_permissions:\n            searched_perms.append((\n                '{0}_{1}'.format(perm, proxy_model._meta.model_name),\n                \"Can {0} {1}\".format(perm, proxy_model._meta.verbose_name_raw)\n            ))\n\n        all_perms = set(Permission.objects.filter(\n            content_type=ctype,\n        ).values_list(\n            'content_type', 'codename'\n        ))\n        permissions = [\n            Permission(codename=codename, name=name, content_type=ctype)\n            for codename, name in searched_perms if (ctype.pk, codename) not in all_perms\n        ]\n        Permission.objects.bulk_create(permissions)\n\n    def revoke_permissions(self, ctype):\n        \"\"\"\n        Remove all permissions for the content type to be removed\n        \"\"\"\n        ContentType = apps.get_model('contenttypes', 'ContentType')\n        try:\n            Permission = apps.get_model('auth', 'Permission')\n        except LookupError:\n            return\n\n        codenames = ['{0}_{1}'.format(perm, ctype) for perm in self.default_permissions]\n        cascade_element = apps.get_model(self.label, 'cascadeelement')\n        element_ctype = ContentType.objects.get_for_model(cascade_element)\n        Permission.objects.filter(content_type=element_ctype, codename__in=codenames).delete()\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/accordion.py",
    "content": "from django.forms import widgets, BooleanField, CharField\nfrom django.forms.fields import IntegerField\nfrom django.utils.translation import ngettext_lazy, gettext_lazy as _\nfrom django.utils.safestring import mark_safe\nfrom django.utils.text import Truncator\nfrom django.utils.html import escape\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.forms import ManageChildrenFormMixin\nfrom cmsplugin_cascade.plugin_base import TransparentWrapper, TransparentContainer\nfrom cmsplugin_cascade.widgets import NumberInputWidget\nfrom .plugin_base import BootstrapPluginBase\n\n\nclass AccordionFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin):\n    num_children = IntegerField(\n        min_value=1,\n        initial=1,\n        widget=NumberInputWidget(attrs={'size': '3', 'style': 'width: 5em !important;'}),\n        label=_(\"Groups\"),\n        help_text=_(\"Number of groups for this accordion.\"),\n    )\n\n    close_others = BooleanField(\n         label=_(\"Close others\"),\n         initial=True,\n         required=False,\n         help_text=_(\"Open only one card at a time.\")\n    )\n\n    first_is_open = BooleanField(\n         label=_(\"First open\"),\n         initial=True,\n         required=False,\n         help_text=_(\"Start with the first card open.\")\n    )\n\n    class Meta:\n        untangled_fields = ['num_children']\n        entangled_fields = {'glossary': ['close_others', 'first_is_open']}\n\n\nclass BootstrapAccordionPlugin(TransparentWrapper, BootstrapPluginBase):\n    name = _(\"Accordion\")\n    default_css_class = 'accordion'\n    require_parent = True\n    parent_classes = ['BootstrapColumnPlugin']\n    direct_child_classes = ['BootstrapAccordionGroupPlugin']\n    allow_children = True\n    form = AccordionFormMixin\n    render_template = 'cascade/bootstrap4/{}accordion.html'\n\n    @classmethod\n    def get_identifier(cls, obj):\n        num_cards = obj.get_num_children()\n        content = ngettext_lazy('with {0} card', 'with {0} cards', num_cards).format(num_cards)\n        return mark_safe(content)\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapAccordionPlugin, self).render(context, instance, placeholder)\n        context.update({\n            'close_others': instance.glossary.get('close_others', True),\n            'first_is_open': instance.glossary.get('first_is_open', True),\n        })\n        return context\n\n    def save_model(self, request, obj, form, change):\n        wanted_children = int(form.cleaned_data.get('num_children'))\n        super().save_model(request, obj, form, change)\n        self.extend_children(obj, wanted_children, BootstrapAccordionGroupPlugin)\n\nplugin_pool.register_plugin(BootstrapAccordionPlugin)\n\n\nclass AccordionGroupFormMixin(EntangledModelFormMixin):\n    heading = CharField(\n        label=_(\"Heading\"),\n        widget=widgets.TextInput(attrs={'size': 80}),\n    )\n\n    body_padding = BooleanField(\n         label=_(\"Body with padding\"),\n         initial=True,\n         required=False,\n         help_text=_(\"Add standard padding to card body.\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['heading', 'body_padding']}\n\n    def clean_heading(self):\n        return escape(self.cleaned_data['heading'])\n\n\nclass BootstrapAccordionGroupPlugin(TransparentContainer, BootstrapPluginBase):\n    name = _(\"Accordion Group\")\n    direct_parent_classes = parent_classes = ['BootstrapAccordionPlugin']\n    render_template = 'cascade/generic/naked.html'\n    require_parent = True\n    form = AccordionGroupFormMixin\n    alien_child_classes = True\n\n    @classmethod\n    def get_identifier(cls, instance):\n        heading = instance.glossary.get('heading', '')\n        return Truncator(heading).words(3, truncate=' ...')\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapAccordionGroupPlugin, self).render(context, instance, placeholder)\n        context.update({\n            'heading': mark_safe(instance.glossary.get('heading', '')),\n            'no_body_padding': not instance.glossary.get('body_padding', True),\n        })\n        return context\n\nplugin_pool.register_plugin(BootstrapAccordionGroupPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/buttons.py",
    "content": "from django.forms import widgets\nfrom django.forms.fields import BooleanField, CharField, ChoiceField, MultipleChoiceField\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.icon.plugin_base import IconPluginMixin\nfrom cmsplugin_cascade.icon.forms import IconFormMixin\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\n\n\nclass ButtonTypeWidget(widgets.RadioSelect):\n    \"\"\"\n    Render sample buttons in different colors in the button's backend editor.\n    \"\"\"\n    template_name = 'cascade/admin/widgets/button_types.html'\n\n\nclass ButtonSizeWidget(widgets.RadioSelect):\n    \"\"\"\n    Render sample buttons in different sizes in the button's backend editor.\n    \"\"\"\n    template_name = 'cascade/admin/widgets/button_sizes.html'\n\n\nclass ButtonFormMixin(EntangledModelFormMixin):\n    BUTTON_TYPES = [\n        ('btn-primary', _(\"Primary\")),\n        ('btn-secondary', _(\"Secondary\")),\n        ('btn-success', _(\"Success\")),\n        ('btn-danger', _(\"Danger\")),\n        ('btn-warning', _(\"Warning\")),\n        ('btn-info', _(\"Info\")),\n        ('btn-light', _(\"Light\")),\n        ('btn-dark', _(\"Dark\")),\n        ('btn-link', _(\"Link\")),\n        ('btn-outline-primary', _(\"Primary\")),\n        ('btn-outline-secondary', _(\"Secondary\")),\n        ('btn-outline-success', _(\"Success\")),\n        ('btn-outline-danger', _(\"Danger\")),\n        ('btn-outline-warning', _(\"Warning\")),\n        ('btn-outline-info', _(\"Info\")),\n        ('btn-outline-light', _(\"Light\")),\n        ('btn-outline-dark', _(\"Dark\")),\n        ('btn-outline-link', _(\"Link\")),\n    ]\n\n    BUTTON_SIZES = [\n        ('btn-lg', _(\"Large button\")),\n        ('', _(\"Default button\")),\n        ('btn-sm', _(\"Small button\")),\n    ]\n\n    link_content = CharField(\n        required=False,\n        label=_(\"Button Content\"),\n        widget=widgets.TextInput(attrs={'size': 50}),\n    )\n\n    button_type = ChoiceField(\n        label=_(\"Button Type\"),\n        widget=ButtonTypeWidget(choices=BUTTON_TYPES),\n        choices=BUTTON_TYPES,\n        initial='btn-primary',\n        help_text=_(\"Display Link using this Button Style\")\n    )\n\n    button_size = ChoiceField(\n        label=_(\"Button Size\"),\n        widget=ButtonSizeWidget(choices=BUTTON_SIZES),\n        choices=BUTTON_SIZES,\n        initial='',\n        required=False,\n        help_text=_(\"Display Link using this Button Size\")\n    )\n\n    button_options = MultipleChoiceField(\n        label=_(\"Button Options\"),\n        choices=[\n            ('btn-block', _('Block level')),\n            ('disabled', _('Disabled')),\n        ],\n        required=False,\n        widget=widgets.CheckboxSelectMultiple,\n    )\n\n    stretched_link = BooleanField(\n        label=_(\"Stretched link\"),\n        required=False,\n        help_text=_(\"Stretched-link utility to make any anchor the size of it’s nearest position: \" \\\n                    \"relative parent, perfect for entirely clickable cards!\")\n    )\n\n    icon_align = ChoiceField(\n        label=_(\"Icon alignment\"),\n        choices=[\n            ('icon-left', _(\"Icon placed left\")),\n            ('icon-right', _(\"Icon placed right\")),\n        ],\n        widget=widgets.RadioSelect,\n        initial='icon-right',\n        help_text=_(\"Add an Icon before or after the button content.\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['link_content', 'button_type', 'button_size', 'button_options', 'icon_align',\n                                         'stretched_link']}\n\n\nclass BootstrapButtonMixin(IconPluginMixin):\n    require_parent = True\n    parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin']\n    render_template = 'cascade/bootstrap4/button.html'\n    allow_children = False\n    default_css_class = 'btn'\n    default_css_attributes = ['button_type', 'button_size', 'button_options', 'stretched_link']\n    ring_plugin = 'ButtonMixin'\n\n    class Media:\n        css = {'all': ['cascade/css/admin/bootstrap4-buttons.css', 'cascade/css/admin/iconplugin.css']}\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonmixin.js']\n\n    def render(self, context, instance, placeholder):\n        context = super().render(context, instance, placeholder)\n        if 'icon_font_class' in context:\n            mini_template = '{0}<i class=\"{1} {2}\" aria-hidden=\"true\"></i>{3}'\n            icon_align = instance.glossary.get('icon_align')\n            if icon_align == 'icon-left':\n                context['icon_left'] = format_html(mini_template, '', context['icon_font_class'], 'cascade-icon-left',\n                                                   ' ')\n            elif icon_align == 'icon-right':\n                context['icon_right'] = format_html(mini_template, ' ', context['icon_font_class'],\n                                                    'cascade-icon-right', '')\n        return context\n\n\nclass BootstrapButtonFormMixin(LinkFormMixin, IconFormMixin, ButtonFormMixin):\n    require_link = False\n    require_icon = False\n\n\nclass BootstrapButtonPlugin(BootstrapButtonMixin, LinkPluginBase):\n    module = 'Bootstrap'\n    name = _(\"Button\")\n    model_mixins = (LinkElementMixin,)\n    form = BootstrapButtonFormMixin\n    ring_plugin = 'ButtonPlugin'\n    DEFAULT_BUTTON_ATTRIBUTES = {'role': 'button'}\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonplugin.js']\n\n    @classmethod\n    def get_identifier(cls, instance):\n        content = instance.glossary.get('link_content')\n        if not content:\n            try:\n                button_types = dict(ButtonFormMixin.BUTTON_TYPES)\n                content = str(button_types[instance.glossary['button_type']])\n            except KeyError:\n                content = _(\"Empty\")\n        return content\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapButtonPlugin, cls).get_css_classes(obj)\n        if obj.glossary.get('stretched_link'):\n            css_classes.append('stretched_link')\n        return css_classes\n\n    @classmethod\n    def get_html_tag_attributes(cls, obj):\n        attributes = cls.super(BootstrapButtonPlugin, cls).get_html_tag_attributes(obj)\n        attributes.update(cls.DEFAULT_BUTTON_ATTRIBUTES)\n        return attributes\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapButtonPlugin, self).render(context, instance, placeholder)\n        return context\n\nplugin_pool.register_plugin(BootstrapButtonPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/card.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.plugin_base import TransparentContainer, TransparentWrapper\nfrom cmsplugin_cascade.bootstrap4.plugin_base import BootstrapPluginBase\n\n\nclass CardChildBase(BootstrapPluginBase):\n    require_parent = True\n    parent_classes = ['BootstrapCardPlugin']\n    allow_children = True\n    render_template = 'cascade/generic/wrapper.html'\n    child_classes = ['BootstrapCardHeaderPlugin', 'BootstrapCardBodyPlugin', 'BootstrapCardFooterPlugin']\n\n\nclass BootstrapCardHeaderPlugin(TransparentContainer, CardChildBase):\n    name = _(\"Card Header\")\n    default_css_class = 'card-header'\n\nplugin_pool.register_plugin(BootstrapCardHeaderPlugin)\n\n\nclass BootstrapCardBodyPlugin(TransparentContainer, CardChildBase):\n    name = _(\"Card Body\")\n    default_css_class = 'card-body'\n\nplugin_pool.register_plugin(BootstrapCardBodyPlugin)\n\n\nclass BootstrapCardFooterPlugin(TransparentContainer, CardChildBase):\n    name = _(\"Card Footer\")\n    default_css_class = 'card-footer'\n\nplugin_pool.register_plugin(BootstrapCardFooterPlugin)\n\n\nclass BootstrapCardPlugin(TransparentWrapper, BootstrapPluginBase):\n    \"\"\"\n    Use this plugin to display a card with optional card-header and card-footer.\n    \"\"\"\n    name = _(\"Card\")\n    default_css_class = 'card'\n    require_parent = False\n    parent_classes = ['BootstrapColumnPlugin']\n    allow_children = True\n    render_template = 'cascade/bootstrap4/card.html'\n\n    @classmethod\n    def get_identifier(cls, instance):\n        try:\n            return instance.card_header or instance.card_footer\n        except AttributeError:\n            pass\n        return super().get_identifier(instance)\n\n    @classmethod\n    def get_child_classes(cls, slot, page, instance=None):\n        \"\"\"Restrict child classes of Card to one of each: Header, Body and Footer\"\"\"\n        child_classes = super().get_child_classes(slot, page, instance)\n        # allow only one child of type Header, Body, Footer\n        for child in instance.get_children():\n            if child.plugin_type in child_classes:\n                child_classes.remove(child.plugin_type)\n        return child_classes\n\nplugin_pool.register_plugin(BootstrapCardPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/carousel.py",
    "content": "import re\nimport logging\nfrom django.forms import widgets\nfrom django.forms.fields import IntegerField, MultipleChoiceField\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import ngettext_lazy, gettext_lazy as _\n\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.bootstrap4.fields import BootstrapMultiSizeField\nfrom cmsplugin_cascade.bootstrap4.grid import Breakpoint\nfrom cmsplugin_cascade.bootstrap4.picture import get_picture_elements\nfrom cmsplugin_cascade.bootstrap4.plugin_base import BootstrapPluginBase\nfrom cmsplugin_cascade.bootstrap4.utils import IMAGE_RESIZE_OPTIONS\nfrom cmsplugin_cascade.forms import ManageChildrenFormMixin\nfrom cmsplugin_cascade.image import ImagePropertyMixin, ImageFormMixin\n\nlogger = logging.getLogger('cascade')\n\n\nclass CarouselSlidesFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin):\n    OPTION_CHOICES = [('slide', _(\"Animate\")), ('pause', _(\"Pause\")), ('wrap', _(\"Wrap\"))]\n\n    num_children = IntegerField(min_value=1, initial=1,\n        label=_('Slides'),\n        help_text=_('Number of slides for this carousel.'),\n    )\n\n    interval = IntegerField(\n        label=_(\"Interval\"),\n        initial=5,\n        help_text=_(\"Change slide after this number of seconds.\"),\n    )\n\n    options = MultipleChoiceField(\n        label=_('Options'),\n        choices=OPTION_CHOICES,\n        widget=widgets.CheckboxSelectMultiple,\n        initial=['slide', 'wrap', 'pause'],\n        help_text=_(\"Adjust interval for the carousel.\"),\n    )\n\n    container_max_heights = BootstrapMultiSizeField(\n        label=_(\"Carousel heights\"),\n        allowed_units=['px'],\n        initial=['100px', '150px', '200px', '250px', '300px'],\n        help_text=_(\"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"),\n    )\n\n    resize_options = MultipleChoiceField(\n        label=_(\"Resize Options\"),\n        choices=IMAGE_RESIZE_OPTIONS,\n        widget=widgets.CheckboxSelectMultiple,\n        help_text=_(\"Options to use when resizing the image.\"),\n        initial=['upscale', 'crop', 'subject_location', 'high_resolution'],\n    )\n\n    class Meta:\n        untangled_fields = ['num_children']\n        entangled_fields = {'glossary': ['interval', 'options', 'container_max_heights', 'resize_options']}\n\n\nclass BootstrapCarouselPlugin(BootstrapPluginBase):\n    name = _(\"Carousel\")\n    default_css_class = 'carousel'\n    default_css_attributes = ['options']\n    parent_classes = ['BootstrapColumnPlugin']\n    render_template = 'cascade/bootstrap4/{}carousel.html'\n    default_inline_styles = {'overflow': 'hidden'}\n    form = CarouselSlidesFormMixin\n    DEFAULT_CAROUSEL_ATTRIBUTES = {'data-ride': 'carousel'}\n\n    @classmethod\n    def get_identifier(cls, obj):\n        num_cols = obj.get_num_children()\n        content = ngettext_lazy('with {0} slide', 'with {0} slides', num_cols).format(num_cols)\n        return mark_safe(content)\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapCarouselPlugin, cls).get_css_classes(obj)\n        if 'slide' in obj.glossary.get('options', []):\n            css_classes.append('slide')\n        return css_classes\n\n    @classmethod\n    def get_html_tag_attributes(cls, obj):\n        attributes = cls.super(BootstrapCarouselPlugin, cls).get_html_tag_attributes(obj)\n        attributes.update(cls.DEFAULT_CAROUSEL_ATTRIBUTES)\n        attributes['data-interval'] = 1000 * int(obj.glossary.get('interval', 5))\n        options = obj.glossary.get('options', [])\n        attributes['data-pause'] = 'pause' in options and 'hover' or 'false'\n        attributes['data-wrap'] = 'wrap' in options and 'true' or 'false'\n        return attributes\n\n    def save_model(self, request, obj, form, change):\n        wanted_children = int(form.cleaned_data.get('num_children'))\n        super().save_model(request, obj, form, change)\n        self.extend_children(obj, wanted_children, BootstrapCarouselSlidePlugin)\n        obj.sanitize_children()\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = super().sanitize_model(obj)\n        complete_glossary = obj.get_complete_glossary()\n        # fill all invalid heights for this container to a meaningful value\n        max_height = max(obj.glossary['container_max_heights'].values())\n        pattern = re.compile(r'^(\\d+)px$')\n        for bp in complete_glossary.get('breakpoints', ()):\n            if not pattern.match(obj.glossary['container_max_heights'].get(bp, '')):\n                obj.glossary['container_max_heights'][bp] = max_height\n        return sanitized\n\nplugin_pool.register_plugin(BootstrapCarouselPlugin)\n\n\nclass BootstrapCarouselSlidePlugin(BootstrapPluginBase):\n    name = _(\"Slide\")\n    model_mixins = (ImagePropertyMixin,)\n    default_css_class = 'img-fluid'\n    parent_classes = ['BootstrapCarouselPlugin']\n    raw_id_fields = ['image_file']\n    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}\n    render_template = 'cascade/bootstrap4/carousel-slide.html'\n    form = ImageFormMixin\n    alien_child_classes = True\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapCarouselSlidePlugin, self).render(context, instance, placeholder)\n        # slide image shall be rendered in a responsive context using the ``<picture>`` element\n        try:\n            parent_glossary = instance.parent.get_bound_plugin().glossary\n            instance.glossary.update(responsive_heights=parent_glossary['container_max_heights'])\n            elements = get_picture_elements(instance)\n        except Exception as exc:\n            logger.warning(\"Unable generate picture elements. Reason: {}\".format(exc))\n        else:\n            context.update({\n                'is_fluid': False,\n                'elements': elements,\n            })\n        return context\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = super().sanitize_model(obj)\n        resize_options = obj.get_parent_glossary().get('resize_options', [])\n        if obj.glossary.get('resize_options') != resize_options:\n            obj.glossary.update(resize_options=resize_options)\n            sanitized = True\n        parent = obj.parent\n        while parent.plugin_type != 'BootstrapColumnPlugin':\n            parent = parent.parent\n            if parent is None:\n                logger.warning(\"PicturePlugin(pk={}) has no ColumnPlugin as ancestor.\".format(obj.pk))\n                return\n        grid_column = parent.get_bound_plugin().get_grid_instance()\n        obj.glossary.setdefault('media_queries', {})\n        for bp in Breakpoint:\n            obj.glossary['media_queries'].setdefault(bp.name, {})\n            width = round(grid_column.get_bound(bp).max)\n            if obj.glossary['media_queries'][bp.name].get('width') != width:\n                obj.glossary['media_queries'][bp.name]['width'] = width\n                sanitized = True\n            if obj.glossary['media_queries'][bp.name].get('media') != bp.media_query:\n                obj.glossary['media_queries'][bp.name]['media'] = bp.media_query\n                sanitized = True\n        return sanitized\n\n    @classmethod\n    def get_identifier(cls, obj):\n        try:\n            content = obj.image.name or obj.image.original_filename\n        except AttributeError:\n            content = _(\"Empty Slide\")\n        return mark_safe(content)\n\nplugin_pool.register_plugin(BootstrapCarouselSlidePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/container.py",
    "content": "from django.core.exceptions import ValidationError\nfrom django.db.models import Q\nfrom django.forms import widgets\nfrom django.forms.fields import BooleanField, ChoiceField, MultipleChoiceField\nfrom django.utils.safestring import mark_safe\nfrom django.utils.text import format_lazy\nfrom django.utils.translation import ngettext_lazy, gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom entangled.forms import EntangledModelFormMixin\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.bootstrap4.grid import Breakpoint\nfrom cmsplugin_cascade.forms import ManageChildrenFormMixin\nfrom .plugin_base import BootstrapPluginBase\nfrom . import grid\n\n\ndef get_widget_choices():\n    breakpoints = app_settings.CMSPLUGIN_CASCADE['bootstrap4']['fluid_bounds']\n    widget_choices = []\n    for index, (bp, bound) in enumerate(breakpoints.items()):\n        if index == 0:\n            widget_choices.append((bp.name, \"{} (<{:.1f}px)\".format(bp.label, bound.max)))\n        elif index == len(breakpoints) - 1:\n            widget_choices.append((bp.name, \"{} (≥{:.1f}px)\".format(bp.label, bound.min)))\n        else:\n            widget_choices.append((bp.name, \"{} (≥{:.1f}px and <{:.1f}px)\".format(bp.label, bound.min, bound.max)))\n    return widget_choices\n\n\nclass ContainerBreakpointsWidget(widgets.CheckboxSelectMultiple):\n    template_name = 'cascade/admin/widgets/container_breakpoints.html'\n\n\nclass ContainerFormMixin(EntangledModelFormMixin):\n    breakpoints = MultipleChoiceField(\n        label=_('Available Breakpoints'),\n        choices=get_widget_choices(),\n        widget=ContainerBreakpointsWidget(choices=get_widget_choices()),\n        initial=[bp.name for bp in app_settings.CMSPLUGIN_CASCADE['bootstrap4']['fluid_bounds'].keys()],\n        help_text=_(\"Supported display widths for Bootstrap's grid system.\"),\n    )\n\n    fluid = BooleanField(\n        label=_('Fluid Container'),\n        initial=False,\n        required=False,\n        help_text=_(\"Changing your outermost '.container' to '.container-fluid'.\")\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['breakpoints', 'fluid']}\n\n    def clean_breapoints(self):\n        # TODO: check this\n        if len(self.cleaned_data['glossary']['breakpoints']) == 0:\n            raise ValidationError(_(\"At least one breakpoint must be selected.\"))\n        return self.cleaned_data['glossary']\n\n\nclass ContainerGridMixin:\n    def get_grid_instance(self):\n        fluid = self.glossary.get('fluid', False)\n        try:\n            breakpoints = [getattr(grid.Breakpoint, bp) for bp in self.glossary['breakpoints']]\n        except KeyError:\n            breakpoints = [bp for bp in grid.Breakpoint]\n        if fluid:\n            bounds = dict((bp, grid.fluid_bounds[bp]) for bp in breakpoints)\n        else:\n            bounds = dict((bp, grid.default_bounds[bp]) for bp in breakpoints)\n        return grid.Bootstrap4Container(bounds=bounds)\n\n\nclass BootstrapContainerPlugin(BootstrapPluginBase):\n    name = _(\"Container\")\n    parent_classes = None\n    require_parent = False\n    model_mixins = (ContainerGridMixin,)\n    form = ContainerFormMixin\n    footnote_html = \"\"\"<p>\n    For more information about the Container please read the\n    <a href=\"https://getbootstrap.com/docs/4.3/layout/overview/#containers\" target=\"_new\">Bootstrap documentation</a>.\n    </p>\"\"\"\n\n    @classmethod\n    def get_identifier(cls, obj):\n        breakpoints = obj.glossary.get('breakpoints')\n        content = obj.glossary.get('fluid') and '(fluid) ' or ''\n        if breakpoints:\n            BREAKPOINTS = app_settings.CMSPLUGIN_CASCADE['bootstrap4']['fluid_bounds']\n            devices = ', '.join([str(bp.label) for bp in BREAKPOINTS if bp.name in breakpoints])\n            content = _(\"{0}for {1}\").format(content, devices)\n        return mark_safe(content)\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapContainerPlugin, cls).get_css_classes(obj)\n        if obj.glossary.get('fluid'):\n            css_classes.append('container-fluid')\n        else:\n            css_classes.append('container')\n        return css_classes\n\n    def save_model(self, request, obj, form, change):\n        super().save_model(request, obj, form, change)\n        obj.sanitize_children()\n\nplugin_pool.register_plugin(BootstrapContainerPlugin)\n\n\nclass BootstrapRowFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin):\n    \"\"\"\n    Form class to add non-materialized field to count the number of children.\n    \"\"\"\n    ROW_NUM_COLUMNS = [1, 2, 3, 4, 6, 12]\n    num_children = ChoiceField(\n        label=_('Columns'),\n        choices=[(i, ngettext_lazy('{0} column', '{0} columns', i).format(i)) for i in ROW_NUM_COLUMNS],\n        initial=3,\n        help_text=_('Number of columns to be created with this row.'),\n    )\n\n    class Meta:\n        untangled_fields = ['num_children']\n\n\nclass RowGridMixin:\n    def get_grid_instance(self):\n        row = grid.Bootstrap4Row()\n        query = Q(plugin_type='BootstrapContainerPlugin') | Q(plugin_type='BootstrapColumnPlugin') \\\n          | Q(plugin_type='BootstrapJumbotronPlugin')\n        container = self.get_ancestors().order_by('depth').filter(query).last().get_bound_plugin().get_grid_instance()\n        container.add_row(row)\n        return row\n\n\nclass BootstrapRowPlugin(BootstrapPluginBase):\n    name = _(\"Row\")\n    default_css_class = 'row'\n    parent_classes = ['BootstrapContainerPlugin', 'BootstrapColumnPlugin', 'BootstrapJumbotronPlugin']\n    model_mixins = (RowGridMixin,)\n    form = BootstrapRowFormMixin\n\n    @classmethod\n    def get_identifier(cls, obj):\n        num_cols = obj.get_num_children()\n        content = ngettext_lazy(\"with {0} column\", \"with {0} columns\", num_cols).format(num_cols)\n        return mark_safe(content)\n\n    def save_model(self, request, obj, form, change):\n        wanted_children = int(form.cleaned_data.get('num_children'))\n        super().save_model(request, obj, form, change)\n        child_glossary = {'xs-column-width': 'col'}\n        self.extend_children(obj, wanted_children, BootstrapColumnPlugin, child_glossary=child_glossary)\n\nplugin_pool.register_plugin(BootstrapRowPlugin)\n\n\nclass ColumnGridMixin:\n    valid_keys = ['xs-column-width', 'sm-column-width', 'md-column-width', 'lg-column-width', 'xs-column-width',\n                  'xs-column-offset', 'sm-column-offset', 'md-column-offset', 'lg-column-offset', 'xs-column-offset']\n    def get_grid_instance(self):\n        column = None\n        query = Q(plugin_type='BootstrapRowPlugin')\n        row_obj = self.get_ancestors().order_by('depth').filter(query).last().get_bound_plugin()\n        # column_siblings = row_obj.get_descendants().order_by('depth').filter(plugin_type='BootstrapColumnPlugin')\n        row = row_obj.get_grid_instance()\n        for column_sibling in self.get_siblings():\n            classes = [val for key, val in column_sibling.get_bound_plugin().glossary.items()\n                       if key in self.valid_keys and val]\n            if column_sibling.pk == self.pk:\n                column = grid.Bootstrap4Column(classes)\n                row.add_column(column)\n            else:\n                row.add_column(grid.Bootstrap4Column(classes))\n        return column\n\n\nclass BootstrapColumnPlugin(BootstrapPluginBase):\n    name = _(\"Column\")\n    parent_classes = ['BootstrapRowPlugin']\n    child_classes = ['BootstrapJumbotronPlugin']\n    alien_child_classes = True\n    default_css_attributes = [fmt.format(bp.name) for bp in grid.Breakpoint\n        for fmt in ('{}-column-width', '{}-column-offset', '{}-column-ordering', '{}-responsive-utils')]\n    model_mixins = (ColumnGridMixin,)\n\n    def get_form(self, request, obj=None, **kwargs):\n        def choose_help_text(*phrases):\n            bounds = 'fluid_bounds' if container.glossary.get('fluid') else 'default_bounds'\n            bs4_breakpoints = app_settings.CMSPLUGIN_CASCADE['bootstrap4'][bounds]\n            if last:\n                return phrases[0].format(bs4_breakpoints[last].max)\n            elif len(breakpoints) > 1:\n                return phrases[1].format(bs4_breakpoints[first].min)\n            else:\n                return phrases[2]\n\n        if 'parent' in self._cms_initial_attributes:\n            container=self._cms_initial_attributes['parent'].get_ancestors().order_by('depth').last().get_bound_plugin()\n        else:\n            containers=obj.get_ancestors().filter(plugin_type='BootstrapContainerPlugin')\n            if containers:\n                container=containers.order_by('depth').last().get_bound_plugin()\n            else:\n                jumbotrons=obj.get_ancestors().filter(plugin_type='BootstrapJumbotronPlugin')\n                container=jumbotrons.order_by('depth').last().get_bound_plugin()\n        breakpoints = container.glossary['breakpoints']\n\n        width_fields, offset_fields, reorder_fields, responsive_fields = {}, {}, {}, {}\n        units = [ngettext_lazy(\"{} unit\", \"{} units\", i).format(i) for i in range(0, 13)]\n        previous_devices, previous_label = '', ''\n        for bp in breakpoints:\n            try:\n                last = getattr(grid.Breakpoint, breakpoints[breakpoints.index(bp)])\n            except IndexError:\n                last = None\n            finally:\n                first = getattr(grid.Breakpoint, bp)\n                devices = ', '.join([str(b.label) for b in grid.Breakpoint.range(first, last)])\n\n            if bp == 'xs':\n                choices = [('col', _(\"Flex column\"))]\n                choices.extend(('col-{}'.format(i), _(\"{} fixed column\").format(units[i])) for i in range(1, 13))\n                choices.append(('col-auto', _(\"Auto column\")))\n            else:\n                choices = [('col-{}'.format(bp), _(\"Flex column\"))]\n                choices.extend(('col-{}-{}'.format(bp, i), _(\"{} fixed column\").format(units[i])) for i in range(1, 13))\n                choices.append(('col-{}-auto'.format(bp), _(\"Auto column\")))\n            if breakpoints.index(bp) == 0:\n                # first breakpoint\n                field_name = '{}-column-width'.format(bp)\n                width_fields[field_name] = ChoiceField(\n                    choices=choices,\n                    label=_(\"Column width for {}\").format(devices),\n                    initial='col' if bp == 'xs' else 'col-{}'.format(bp),\n                    help_text=choose_help_text(\n                        _(\"Column width for devices narrower than {:.1f} pixels.\"),\n                        _(\"Column width for devices wider than {:.1f} pixels.\"),\n                        _(\"Column width for all devices.\"),\n                    )\n                )\n            else:\n                # wider breakpoints may inherit from next narrower ones\n                choices.insert(0, ('', format_lazy(_(\"Inherit column width from {}\"), previous_devices)))\n                field_name = '{}-column-width'.format(bp)\n                width_fields[field_name] = ChoiceField(\n                    choices=choices,\n                    label=_(\"Column width for {}\").format(devices),\n                    initial='',\n                    required=False,\n                    help_text=choose_help_text(\n                        _(\"Override column width for devices narrower than {:.1f} pixels.\"),\n                        _(\"Override column width for devices wider than {:.1f} pixels.\"),\n                        _(\"Override column width for all devices.\"),\n                    )\n                )\n            previous_devices = devices\n\n            # handle offset\n            if breakpoints.index(bp) == 0:\n                choices = [('', _(\"No offset\"))]\n                offset_range = range(1, 13)\n            else:\n                choices = [('', format_lazy(_(\"Inherit offset from {}\"), previous_label))]\n                offset_range = range(0, 13)\n            previous_label = Breakpoint[bp].label\n            if bp == 'xs':\n                choices.extend(('offset-{}'.format(i), units[i]) for i in offset_range)\n            else:\n                choices.extend(('offset-{}-{}'.format(bp, i), units[i]) for i in offset_range)\n            label = _(\"Offset for {}\").format(devices)\n            help_text = choose_help_text(\n                _(\"Offset width for devices narrower than {:.1f} pixels.\"),\n                _(\"Offset width for devices wider than {:.1f} pixels.\"),\n                _(\"Offset width for all devices.\")\n            )\n            field_name = '{}-column-offset'.format(bp)\n            offset_fields[field_name] = ChoiceField(\n                choices=choices,\n                label=label,\n                required=False,\n                help_text=help_text,\n            )\n\n            # handle column reordering\n            choices = [('', _(\"No reordering\"))]\n            if bp == 'xs':\n                choices.extend(('order-{}'.format(i), _(\"Reorder by {}\").format(units[i])) for i in range(1, 13))\n            else:\n                choices.extend(('order-{}-{}'.format(bp, i), _(\"Reorder by {}\").format(units[i])) for i in range(1, 13))\n            label = _(\"Reordering for {}\").format(devices)\n            help_text = choose_help_text(\n                _(\"Reordering for devices narrower than {:.1f} pixels.\"),\n                _(\"Reordering for devices wider than {:.1f} pixels.\"),\n                _(\"Reordering for all devices.\")\n            )\n            field_name = '{}-column-ordering'.format(bp)\n            reorder_fields[field_name] = ChoiceField(\n                choices=choices,\n                label=label,\n                required=False,\n                help_text=help_text,\n            )\n\n            # handle responsive utilities\n            choices = [('', _(\"Default\")), ('visible-{}'.format(bp), _(\"Visible\")), ('hidden-{}'.format(bp), _(\"Hidden\"))]\n            label = _(\"Responsive utilities for {}\").format(devices)\n            help_text = choose_help_text(\n                _(\"Utility classes for showing and hiding content by devices narrower than {:.1f} pixels.\"),\n                _(\"Utility classes for showing and hiding content by devices wider than {:.1f} pixels.\"),\n                _(\"Utility classes for showing and hiding content for all devices.\")\n            )\n            field_name = '{}-responsive-utils'.format(bp)\n            responsive_fields[field_name] = ChoiceField(\n                choices=choices,\n                label=label,\n                initial='',\n                widget=widgets.RadioSelect,\n                required=False,\n                help_text=help_text,\n            )\n        glossary_fields = list(width_fields.keys())\n        glossary_fields.extend(offset_fields.keys())\n        glossary_fields.extend(reorder_fields.keys())\n        glossary_fields.extend(responsive_fields.keys())\n\n        class Meta:\n            entangled_fields = {'glossary': glossary_fields}\n\n        attrs = dict(width_fields, **offset_fields, **reorder_fields, **responsive_fields, Meta=Meta)\n        kwargs['form'] = type('ColumnForm', (EntangledModelFormMixin,), attrs)\n        return super().get_form(request, obj, **kwargs)\n\n    def save_model(self, request, obj, form, change):\n        super().save_model(request, obj, form, change)\n        obj.sanitize_children()\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = super().sanitize_model(obj)\n        return sanitized\n\n    @classmethod\n    def get_identifier(cls, obj):\n        glossary = obj.get_complete_glossary()\n        widths = []\n        for bp in glossary.get('breakpoints', []):\n            width = obj.glossary.get('{0}-column-width'.format(bp), '').replace('col-{0}-'.format(bp), '')\n            if width:\n                widths.append(width)\n        if len(widths) > 0:\n            content = _(\"widths: {}\").format(' / '.join(widths))\n        else:\n            content = _(\"unknown width\")\n        return mark_safe(content)\n\nplugin_pool.register_plugin(BootstrapColumnPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/embeds.py",
    "content": "import re\nfrom urllib.parse import urlparse, urlunparse, ParseResult\n\nfrom django.core.exceptions import ValidationError\nfrom django.forms import widgets\nfrom django.forms.fields import BooleanField, ChoiceField, URLField\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin, EntangledField\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.bootstrap4.plugin_base import BootstrapPluginBase\n\n\nclass YoutubeFormMixin(EntangledModelFormMixin):\n    ASPECT_RATIO_CHOICES = [\n        ('embed-responsive-21by9', _(\"Responsive 21:9\")),\n        ('embed-responsive-16by9', _(\"Responsive 16:9\")),\n        ('embed-responsive-4by3', _(\"Responsive 4:3\")),\n        ('embed-responsive-1by1', _(\"Responsive 1:1\")),\n    ]\n\n    videoid = EntangledField()\n\n    url = URLField(\n        label=_(\"YouTube URL\"),\n        widget=widgets.URLInput(attrs={'size': 50}),\n    )\n\n    aspect_ratio = ChoiceField(\n        label=_(\"Aspect Ratio\"),\n        choices=ASPECT_RATIO_CHOICES,\n        widget=widgets.RadioSelect,\n        initial=ASPECT_RATIO_CHOICES[1][0],\n    )\n\n    allow_fullscreen = BooleanField(\n        label=_(\"Allow Fullscreen\"),\n        required=False,\n        initial=True,\n    )\n\n    autoplay = BooleanField(\n        label=_(\"Autoplay\"),\n        required=False,\n    )\n\n    controls = BooleanField(\n        label=_(\"Display Controls\"),\n        required=False,\n    )\n\n    loop = BooleanField(\n        label=_(\"Enable Looping\"),\n        required=False,\n    )\n\n    rel = BooleanField(\n        label=_(\"Show related\"),\n        required=False,\n        help_text=_(\"Show videos suggested by YouTube at the end.\"),\n    )\n\n    class Meta:\n        untangled_fields = ['url']\n        entangled_fields = {'glossary': ['videoid', 'aspect_ratio', 'allow_fullscreen', 'autoplay',\n                                         'controls', 'loop', 'rel']}\n\n    def __init__(self, *args, **kwargs):\n        instance = kwargs.get('instance')\n        if instance:\n            videoid = instance.glossary.get('videoid')\n            if videoid:\n                parts = ParseResult('https', 'youtu.be', videoid, '', '', '')\n                initial = {'url': urlunparse(parts)}\n                kwargs.update(initial=initial)\n        super().__init__(*args, **kwargs)\n\n    def clean(self):\n        cleaned_data = super().clean()\n        url = cleaned_data.get('url')\n        if url:\n            parts = urlparse(url)\n            match = re.search(r'^v=([^&]+)', parts.query)\n            if match:\n                cleaned_data['videoid'] = match.group(1)\n                return cleaned_data\n            match = re.search(r'([^/]+)$', parts.path)\n            if match:\n                cleaned_data['videoid'] = match.group(1)\n                return cleaned_data\n        raise ValidationError(_(\"Please enter a valid YouTube URL\"))\n\n\nclass BootstrapYoutubePlugin(BootstrapPluginBase):\n    \"\"\"\n    Use this plugin to display a YouTube video.\n    \"\"\"\n    name = _(\"You Tube\")\n    require_parent = False\n    parent_classes = ['BootstrapColumnPlugin']\n    child_classes = None\n    render_template = 'cascade/bootstrap4/youtube.html'\n    form = YoutubeFormMixin\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapYoutubePlugin, self).render(context, instance, placeholder)\n        query_params = ['autoplay', 'controls', 'loop', 'rel']\n        videoid = instance.glossary.get('videoid')\n        if videoid:\n            query = ['{}=1'.format(key) for key in query_params if instance.glossary.get(key)]\n            parts = ParseResult('https', 'www.youtube.com', '/embed/' + videoid, '', '&'.join(query), '')\n            context.update({\n                'youtube_url': urlunparse(parts),\n                'allowfullscreen': 'allowfullscreen' if instance.glossary.get('allow_fullscreen') else '',\n            })\n        return context\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapYoutubePlugin, cls).get_css_classes(obj)\n        css_classes.append('embed-responsive')\n        css_class = obj.glossary.get('aspect_ratio')\n        if css_class:\n            css_classes.append(css_class)\n        return css_classes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        return obj.glossary.get('videoid', '')\n\nplugin_pool.register_plugin(BootstrapYoutubePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/fields.py",
    "content": "from cmsplugin_cascade.bootstrap4.grid import Breakpoint\nfrom cmsplugin_cascade.fields import MultiSizeField\n\n\nclass BootstrapMultiSizeField(MultiSizeField):\n    \"\"\"\n    Some size input fields must be specified per Bootstrap breakpoint. Use this multiple\n    input field to handle this.\n    \"\"\"\n    def __init__(self, *args, **kwargs):\n        properties = [bp.name for bp in Breakpoint]\n        kwargs['sublabels'] = [bp.label for bp in Breakpoint]\n        super().__init__(properties, *args, **kwargs)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/grid.py",
    "content": "from copy import copy\nfrom enum import Enum, unique\nfrom functools import reduce\nimport itertools\nfrom operator import add\nimport re\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass BootstrapException(Exception):\n    \"\"\"\n    Raised if arrangement of Bootstrap-4 elements would render weird results.\n    \"\"\"\n\n\n@unique\nclass Breakpoint(Enum):\n    \"\"\"\n    Enumerate the five breakpoints defined by the Bootstrap-4 CSS framework.\n    \"\"\"\n    xs = 0\n    sm = 1\n    md = 2\n    lg = 3\n    xl = 4\n\n    @classmethod\n    def range(cls, first, last):\n        \"\"\"\n        Iterate over all elements, starting from `first` until `last`.\n        The last element is included.\n        \"\"\"\n        if first: first = first.value\n        if last: last = last.value + 1\n        return itertools.islice(cls, first, last)\n\n    def __gt__(self, other):\n        return self.value > other.value\n\n    def __ge__(self, other):\n        return self.value >= other.value\n\n    def __lt__(self, other):\n        return self.value < other.value\n\n    def __le__(self, other):\n        return self.value <= other.value\n\n    def __iter__(self):\n        yield self.xs\n        yield self.sm\n        yield self.md\n        yield self.lg\n        yield self.xl\n\n    @property\n    def label(self):\n        return [\n            _(\"Portrait Phones\"),\n            _(\"Landscape Phones\"),\n            _(\"Tablets\"),\n            _(\"Laptops\"),\n            _(\"Large Desktops\"),\n        ][self.value]\n\n    @property\n    def media_query(self):\n        return [\n            '(max-width: 575.98px)',\n            '(min-width: 576px) and (max-width: 767.98px)',\n            '(min-width: 768px) and (max-width: 991.98px)',\n            '(min-width: 992px) and (max-width: 1199.98px)',\n            '(min-width: 1200px)',\n        ][self.value]\n\n\nclass Bound:\n    def __init__(self, min, max):\n        self.min = float(min)\n        self.max = float(max)\n\n    def __copy__(self):\n        return type(self)(self.min, self.max)\n\n    def __eq__(self, other):\n        return round(self.min, 1) == round(other.min, 1) and round(self.max, 1) == round(other.max, 1)\n\n    def __add__(self, other):\n        return Bound(\n            self.min + other.min,\n            self.max + other.max,\n        )\n\n    def __sub__(self, other):\n        return Bound(\n            self.min - other.min,\n            self.max - other.max,\n        )\n\n    def __repr__(self):\n        return \"<{}: min={}, max={}>\".format(self.__class__.__name__, self.min, self.max)\n\n    def extend(self, other):\n        self.min = min(self.min, other.min)\n        self.max = max(self.max, other.max)\n\n\ndefault_bounds = {\n    Breakpoint.xs: Bound(320, 572),\n    Breakpoint.sm: Bound(540, 540),\n    Breakpoint.md: Bound(720, 720),\n    Breakpoint.lg: Bound(960, 960),\n    Breakpoint.xl: Bound(1140, 1140),\n}\n\nfluid_bounds = {\n    Breakpoint.xs: Bound(320, 576),\n    Breakpoint.sm: Bound(576, 768),\n    Breakpoint.md: Bound(768, 992),\n    Breakpoint.lg: Bound(992, 1200),\n    Breakpoint.xl: Bound(1200, 1980),\n}\n\n\nclass Break:\n    def __init__(self, breakpoint, classes, narrower=None):\n        self.breakpoint = breakpoint\n        self.fixed_units = 0\n        self.flex_column = False\n        self.auto_column = False\n        self._normalize_col_classes(classes)\n        if isinstance(narrower, Break):\n            self._inherit_from(narrower)\n        self.bound = None\n\n    def _normalize_col_classes(self, classes):\n        if self.breakpoint == Breakpoint.xs:\n            fixed_pat = re.compile(r'^col-(\\d+)$')\n            flex_pat = re.compile(r'^col$')\n            auto_pat = re.compile(r'^col-auto$')\n        else:\n            fixed_pat = re.compile(r'^col-{}-(\\d+)$'.format(self.breakpoint.name))\n            flex_pat = re.compile(r'^col-{}$'.format(self.breakpoint.name))\n            auto_pat = re.compile(r'^col-{}-auto$'.format(self.breakpoint.name))\n        for col_class in classes:\n            # look for CSS classes matching fixed size columns\n            fixed = fixed_pat.match(col_class)\n            if fixed:\n                if self.fixed_units or self.flex_column or self.auto_column:\n                    raise BootstrapException(\"Can not mix fixed- with flex- or auto-column\")\n                units = int(fixed.group(1))\n                if units < 1 or units > 12:\n                    raise BootstrapException(\"Column units value {} out of range\".format(units))\n                self.fixed_units = units\n\n            # look for CSS classes matching flex columns\n            flex = flex_pat.match(col_class)\n            if flex:\n                if self.fixed_units or self.flex_column or self.auto_column:\n                    raise BootstrapException(\"Can not mix flex- with fixed- or auto-column\")\n                self.flex_column = True\n\n            # look for CSS classes matching auto columns\n            auto = auto_pat.match(col_class)\n            if auto:\n                if self.fixed_units or self.flex_column or self.auto_column:\n                    raise BootstrapException(\"Can not mix auto- with fixed- or flex-column\")\n                self.auto_column = True\n\n    def _inherit_from(self, narrower):\n        if self.breakpoint <= narrower.breakpoint:\n            raise BootstrapException(\"Can only inherit column bounds from narrower breakpoint\")\n        if self.fixed_units == 0 and self.flex_column is False and self.auto_column is False:\n            self.fixed_units = narrower.fixed_units\n            self.flex_column = narrower.flex_column\n            self.auto_column = narrower.auto_column\n\n    def __copy__(self):\n        newone = type(self)()\n        if self.bound:\n            newone.bound = dict(self.bound)\n        return newone\n\n    def __repr__(self):\n        return \"<{}[{}]: fixed={}, flex={}, auto={}>\".format(\n            self.__class__.__name__, self.breakpoint.name, self.fixed_units, self.flex_column, self.auto_column)\n\n\nclass Bootstrap4Container(list):\n    \"\"\"\n    Abstracts a Bootstrap-4 container element, such as ``<div class=\"container\">...</div>``, so that the minimum and\n    maximum widths each each child row can be computed.\n    Each container object is a list of one to many ``Bootstrap4Row`` instances.\n    In order to model a \"fluid\" container, use ``fluid_bounds`` during construction.\n    \"\"\"\n    # def __init__(self, bounds=app_settings.CMSPLUGIN_CASCADE['bootstrap4']['default_bounds']):\n    def __init__(self, bounds=default_bounds):\n            self.bounds = bounds\n\n    def __repr__(self):\n        return \"<{}: {}>\".format(self.__class__.__name__, ', '.join([repr(o) for o in self]))\n\n    def add_row(self, row):\n        if isinstance(row.parent, (Bootstrap4Container, Bootstrap4Column)):\n            # detach from previous container or column\n            pos = row.parent.index(row)\n            row.parent.pop(pos)\n        row.parent = self\n        row.bounds = dict(self.bounds)\n        self.append(row)\n        return row\n\n\nclass Bootstrap4Row(list):\n    \"\"\"\n    Abstracts a Bootstrap-4 row element, such as ``<div class=\"row\">...</div>``, so that the minimum and maximum widths\n    each each child columns can be computed.\n    Each row object is a list of 1 to many ``Bootstrap4Column`` instances.\n    \"\"\"\n    parent = None\n    bounds = None\n\n    def __repr__(self):\n        return \"<{}: {}>\".format(self.__class__.__name__, ', '.join([repr(o) for o in self]))\n\n    def add_column(self, column):\n        if isinstance(column.parent, Bootstrap4Row):\n            pos = column.parent.index(column)\n            column.parent.pop(pos)\n        column.parent = self\n        self.append(column)\n\n    def compute_column_bounds(self):\n        assert isinstance(self.bounds, dict), \"Expected `bounds` to be a dict.\"\n        for bp in [Breakpoint.xs, Breakpoint.sm, Breakpoint.md, Breakpoint.lg, Breakpoint.xl]:\n            if bp in self.bounds:\n                remaining_width = copy(self.bounds[bp])\n\n            # first compute the bounds of columns with a fixed width\n            for column in self:\n                if column.breaks[bp].fixed_units:\n                    assert column.breaks[bp].bound is None, \"Expected `column.breaks[bp].bound` to be None.\"\n                    column.breaks[bp].bound = Bound(\n                        column.breaks[bp].fixed_units * self.bounds[bp].min / 12,\n                        column.breaks[bp].fixed_units * self.bounds[bp].max / 12,\n                    )\n                    remaining_width -= column.breaks[bp].bound\n\n            flex_columns = reduce(add, [int(col.breaks[bp].flex_column) for col in self], 0)\n            auto_columns = reduce(add, [int(col.breaks[bp].auto_column) for col in self], 0)\n            if auto_columns:\n                # we use auto-columns, therefore estimate the min- and max values\n                for column in self:\n                    if column.breaks[bp].flex_column or column.breaks[bp].auto_column:\n                        assert column.breaks[bp].bound is None, \"Expected `column.breaks[bp].bound` to be None.\"\n                        column.breaks[bp].bound = Bound(\n                            30,\n                            remaining_width.max - 30 * (flex_columns + auto_columns),\n                        )\n            else:\n                # we use flex-columns exclusively, therefore subdivide the remaining width\n                for column in self:\n                    if column.breaks[bp].flex_column:\n                        assert column.breaks[bp].bound is None, \"Expected `column.breaks[bp].bound` to be None.\"\n                        column.breaks[bp].bound = Bound(\n                            remaining_width.min / flex_columns,\n                            remaining_width.max / flex_columns,\n                        )\n\n\nclass Bootstrap4Column(list):\n    \"\"\"\n    Abstracts a Bootstrap-4 column element, such as ``<div class=\"col...\">...</div>``, which shall be added to a\n    ``Bootstrap4Row`` element.\n    Each column may contain elements of type ``Bootstrap4Row``.\n    For each breakpoint, a columns knows its extensions.\n    \"\"\"\n    parent = None\n\n    def __init__(self, classes=[]):\n        if isinstance(classes, str):\n            classes = classes.split()\n        narrower = None\n        self.breaks = {}\n        for bp in Breakpoint:\n            self.breaks[bp] = Break(bp, classes, narrower)\n            narrower = self.breaks[bp]\n\n    def __repr__(self):\n        return \"<{}: {}>\".format(self.__class__.__name__, ', '.join([repr(self.breaks[bp]) for bp in Breakpoint]))\n\n    def __copy__(self):\n        newone = type(self)()\n        newone.__dict__.update(self.__dict__)\n        return newone\n\n    def add_row(self, row):\n        if isinstance(row.parent, (Bootstrap4Container, Bootstrap4Column)):\n            # detach from previous container or column\n            pos = row.parent.index(row)\n            row.parent.pop(pos)\n            row.parent.bounds = None\n        row.parent = self\n        row.bounds = dict((bp, self.get_bound(bp)) for bp in Breakpoint)\n        self.append(row)\n        return row\n\n    def get_bound(self, breakpoint):\n        if self.breaks[breakpoint].bound is None:\n            self.parent.compute_column_bounds()\n            assert self.breaks[breakpoint].bound, \"Expected `column.breaks[bp].bound` not to be None.\"\n        return self.breaks[breakpoint].bound\n\n    def get_min_max_bounds(self):\n        \"\"\"\n        Return a dict of min- and max-values for the given column.\n        This is required to estimate the bounds of images.\n        \"\"\"\n        bound = Bound(999999.0, 0.0)\n        for bp in Breakpoint:\n            bound.extend(self.get_bound(bp))\n        return {'min': bound.min, 'max': bound.max}\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/icon.py",
    "content": "from django.forms import ChoiceField\nfrom django.utils.html import format_html, format_html_join\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.fields import SizeField, ColorField, BorderChoiceField\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\nfrom cmsplugin_cascade.icon.forms import IconFormMixin\nfrom cmsplugin_cascade.icon.plugin_base import IconPluginMixin\n\n\nclass FramedIconFormMixin(IconFormMixin):\n    SIZE_CHOICES = [('{}em'.format(c), \"{} em\".format(c)) for c in range(1, 13)]\n\n    RADIUS_CHOICES = [(None, _(\"Square\"))] + \\\n        [('{}px'.format(r), \"{} px\".format(r)) for r in (1, 2, 3, 5, 7, 10, 15, 20)] + \\\n        [('50%', _(\"Circle\"))]\n\n    TEXT_ALIGN_CHOICES = [\n        (None, _(\"Do not align\")),\n        ('text-left', _(\"Left\")),\n        ('text-center', _(\"Center\")),\n        ('text-right', _(\"Right\"))\n    ]\n\n    font_size = SizeField(\n        label=_(\"Icon size\"),\n        allowed_units=['px', 'em'],\n        initial='1em',\n    )\n\n    color = ColorField(\n        label=_(\"Icon color\"),\n    )\n\n    background_color = ColorField(\n        label=_(\"Background color\"),\n        inherit_color=True,\n    )\n\n    text_align = ChoiceField(\n        choices=TEXT_ALIGN_CHOICES,\n        label=_(\"Text alignment\"),\n        required=False,\n        help_text=_(\"Align the icon inside the parent column.\")\n    )\n\n    border = BorderChoiceField(\n        label=_(\"Set border\"),\n    )\n\n    border_radius = ChoiceField(\n        choices=RADIUS_CHOICES,\n        label=_(\"Border radius\"),\n        required=False,\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['font_size', 'color', 'background_color', 'text_align', 'border',\n                                         'border_radius']}\n\n\nclass FramedIconPlugin(IconPluginMixin, LinkPluginBase):\n    name = _(\"Icon with frame\")\n    parent_classes = None\n    require_parent = False\n    allow_children = False\n    render_template = 'cascade/bootstrap4/framedicon.html'\n    model_mixins = (LinkElementMixin,)\n    form = type('FramedIconForm', (LinkFormMixin, FramedIconFormMixin), {'require_link': False})\n    ring_plugin = 'FramedIconPlugin'\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/framediconplugin.js']\n\n    @classmethod\n    def get_tag_type(self, instance):\n        if instance.glossary.get('text_align') or instance.glossary.get('font_size'):\n            return 'div'\n\n    @classmethod\n    def get_css_classes(cls, instance):\n        css_classes = cls.super(FramedIconPlugin, cls).get_css_classes(instance)\n        text_align = instance.glossary.get('text_align')\n        if text_align:\n            css_classes.append(text_align)\n        return css_classes\n\n    @classmethod\n    def get_inline_styles(cls, instance):\n        inline_styles = cls.super(FramedIconPlugin, cls).get_inline_styles(instance)\n        inline_styles['font-size'] = instance.glossary.get('font_size', '1em')\n        return inline_styles\n\n    def render(self, context, instance, placeholder):\n        context = self.super(FramedIconPlugin, self).render(context, instance, placeholder)\n        styles = {'display': 'inline-block'}\n        color, inherit = instance.glossary.get('color', (ColorField.DEFAULT_COLOR, True))\n        if not inherit:\n            styles['color'] = color\n        background_color, inherit = instance.glossary.get('background_color', (ColorField.DEFAULT_COLOR, True))\n        if not inherit:\n            styles['background-color'] = background_color\n        border = instance.glossary.get('border')\n        if isinstance(border, list) and border[0] and border[1] != 'none':\n            styles.update(border='{0} {1} {2}'.format(*border))\n            radius = instance.glossary.get('border_radius')\n            if radius:\n                styles['border-radius'] = radius\n        attrs = []\n        if 'icon_font_class' in context:\n            attrs.append(format_html('class=\"{}\"', context['icon_font_class']))\n        attrs.append(format_html('style=\"{}\"', format_html_join('', '{0}:{1};', [(k, v) for k, v in styles.items()])))\n        context['icon_font_attrs'] = mark_safe(' '.join(attrs))\n        return context\n\nplugin_pool.register_plugin(FramedIconPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/image.py",
    "content": "import logging\nfrom django.forms import widgets, ChoiceField, MultipleChoiceField\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.bootstrap4.grid import Breakpoint\nfrom cmsplugin_cascade.bootstrap4.utils import get_image_tags, IMAGE_RESIZE_OPTIONS, IMAGE_SHAPE_CHOICES\nfrom cmsplugin_cascade.image import ImageFormMixin, ImagePropertyMixin\nfrom cmsplugin_cascade.fields import SizeField\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\n\nlogger = logging.getLogger('cascade.bootstrap4')\n\n\nclass BootstrapImageFormMixin(ImageFormMixin):\n    ALIGNMENT_OPTIONS = [\n        ('float-left', _(\"Left\")),\n        ('float-right', _(\"Right\")),\n        ('mx-auto', _(\"Center\")),\n    ]\n\n    image_shapes = MultipleChoiceField(\n        label=_(\"Image Shapes\"),\n        choices=IMAGE_SHAPE_CHOICES,\n        widget=widgets.CheckboxSelectMultiple,\n        initial=['img-fluid']\n    )\n\n    image_width_responsive = SizeField(\n        label=_(\"Responsive Image Width\"),\n        allowed_units=['%'],\n        initial='100%',\n        required = False,\n        help_text=_(\"Set the image width in percent relative to containing element.\"),\n    )\n\n    image_width_fixed = SizeField(\n        label=_(\"Fixed Image Width\"),\n        allowed_units=['px'],\n        required = False,\n        help_text=_(\"Set a fixed image width in pixels.\"),\n    )\n\n    image_height = SizeField(\n        label=_(\"Adapt Image Height\"),\n        allowed_units=['px', '%'],\n        required = False,\n        help_text=_(\"Set a fixed height in pixels, or percent relative to the image width.\"),\n    )\n\n    resize_options = MultipleChoiceField(\n        label=_(\"Resize Options\"),\n        choices=IMAGE_RESIZE_OPTIONS,\n        widget=widgets.CheckboxSelectMultiple,\n        required = False,\n        help_text=_(\"Options to use when resizing the image.\"),\n        initial=['subject_location', 'high_resolution'],\n    )\n\n    image_alignment = ChoiceField(\n        label=_(\"Image Alignment\"),\n        choices=ALIGNMENT_OPTIONS,\n        widget=widgets.RadioSelect,\n        required = False,\n        help_text=_(\"How to align a non-responsive image.\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['image_shapes', 'image_width_responsive', 'image_width_fixed',\n                                         'image_height', 'resize_options', 'image_alignment']}\n\n\nclass BootstrapImagePlugin(LinkPluginBase):\n    name = _(\"Image\")\n    module = 'Bootstrap'\n    parent_classes = ['BootstrapColumnPlugin']\n    require_parent = True\n    allow_children = False\n    raw_id_fields = LinkPluginBase.raw_id_fields + ['image_file']\n    model_mixins = (ImagePropertyMixin, LinkElementMixin,)\n    admin_preview = False\n    ring_plugin = 'ImagePlugin'\n    form = type('BootstrapImageForm', (LinkFormMixin, BootstrapImageFormMixin), {'require_link': False})\n    render_template = 'cascade/bootstrap4/linked-image.html'\n    default_css_attributes = ['image_shapes', 'image_alignment']\n    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}\n    html_tag_attributes.update(LinkPluginBase.html_tag_attributes)\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/imageplugin.js']\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapImagePlugin, self).render(context, instance, placeholder)\n        try:\n            image_tags = get_image_tags(instance)\n        except Exception as exc:\n            logger.warning(\"Unable generate image tags. Reason: {}\".format(exc))\n        else:\n            extra_styles = image_tags.pop('extra_styles', None)\n            if extra_styles:\n                inline_styles = instance.glossary.get('inline_styles', {})\n                inline_styles.update(extra_styles)\n                instance.glossary['inline_styles'] = inline_styles\n            context.update(dict(**image_tags))\n        return context\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapImagePlugin, cls).get_css_classes(obj)\n        css_class = obj.glossary.get('css_class')\n        if css_class:\n            css_classes.append(css_class)\n        return css_classes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        try:\n            content = str(obj.image)\n        except AttributeError:\n            content = _(\"No Image\")\n        return mark_safe(content)\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = False\n        parent = obj.parent\n        try:\n            while parent.plugin_type != 'BootstrapColumnPlugin':\n                parent = parent.parent\n            grid_column = parent.get_bound_plugin().get_grid_instance()\n            min_max_bounds = grid_column.get_min_max_bounds()\n            if obj.glossary.get('column_bounds') != min_max_bounds:\n                obj.glossary['column_bounds'] = min_max_bounds\n                sanitized = True\n            obj.glossary.setdefault('media_queries', {})\n            for bp in Breakpoint:\n                media_query = '{} {:.2f}px'.format(bp.media_query, grid_column.get_bound(bp).max)\n                if obj.glossary['media_queries'].get(bp.name) != media_query:\n                    obj.glossary['media_queries'][bp.name] = media_query\n                    sanitized = True\n        except AttributeError:\n            logger.warning(\"ImagePlugin(pk={}) has no ColumnPlugin as ancestor.\".format(obj.pk))\n            return\n        return sanitized\n\nplugin_pool.register_plugin(BootstrapImagePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/jumbotron.py",
    "content": "import logging\n\nfrom django.core.exceptions import ValidationError\nfrom django.forms import widgets, BooleanField, ChoiceField\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\n\nfrom entangled.forms import EntangledModelFormMixin\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.fields import ColorField, MultiSizeField, CascadeImageField\nfrom cmsplugin_cascade.image import ImagePropertyMixin\nfrom cmsplugin_cascade.bootstrap4.plugin_base import BootstrapPluginBase\nfrom cmsplugin_cascade.bootstrap4.container import ContainerGridMixin\nfrom cmsplugin_cascade.bootstrap4.fields import BootstrapMultiSizeField\nfrom cmsplugin_cascade.bootstrap4.picture import get_picture_elements\n\nlogger = logging.getLogger('cascade')\n\n\nclass ImageBackgroundMixin:\n    @property\n    def element_heights(self):\n        element_heights = self.glossary.get('element_heights', {})\n        for bp, media_query in self.glossary['media_queries'].items():\n            if bp in element_heights:\n                yield {'media': media_query['media'], 'height': element_heights[bp]}\n\n    @property\n    def background_color(self):\n        try:\n            color, disabled = self.glossary['background_color']\n            if not disabled and disabled != 'disabled':\n                return 'background-color: {};'.format(color)\n        except (KeyError, TypeError, ValueError):\n            pass\n        return ''\n\n    @property\n    def background_attachment(self):\n        try:\n            return 'background-attachment: {background_attachment};'.format(**self.glossary)\n        except KeyError:\n            return ''\n\n    @property\n    def background_position(self):\n        try:\n            return 'background-position: {background_vertical_position} {background_horizontal_position};'.format(**self.glossary)\n        except KeyError:\n            return ''\n\n    @property\n    def background_repeat(self):\n        try:\n            return 'background-repeat: {background_repeat};'.format(**self.glossary)\n        except KeyError:\n            return ''\n\n    @property\n    def background_size(self):\n        try:\n            size = self.glossary['background_size']\n            if size == 'width/height':\n                size = self.glossary['background_width_height']\n                return 'background-size: {width} {height};'.format(**size)\n            else:\n                return 'background-size: {};'.format(size)\n        except KeyError:\n            pass\n        return ''\n\n\nclass JumbotronFormMixin(EntangledModelFormMixin):\n    \"\"\"\n    Form class to validate the JumbotronPlugin.\n    \"\"\"\n    ATTACHMENT_CHOICES = ['scroll', 'fixed', 'local']\n    VERTICAL_POSITION_CHOICES = ['top', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'bottom']\n    HORIZONTAL_POSITION_CHOICES = ['left', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'right']\n    REPEAT_CHOICES = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']\n    SIZE_CHOICES = ['auto', 'width/height', 'cover', 'contain']\n\n    fluid = BooleanField(\n        label=_(\"Is fluid\"),\n        initial=True,\n        required=False,\n        help_text=_(\"Shall this element occupy the entire horizontal space of its parent.\"),\n    )\n\n    element_heights = BootstrapMultiSizeField(\n        label=(\"Element Heights\"),\n        required=True,\n        allowed_units=['rem', 'px', 'auto'],\n        initial='300px',\n        help_text=_(\"This property specifies the height for each Bootstrap breakpoint.\"),\n    )\n\n    background_color = ColorField(\n        label=_(\"Background color\"),\n    )\n\n    image_file = CascadeImageField(\n        label=_(\"Background image\"),\n        required=False,\n    )\n\n    background_repeat = ChoiceField(\n        label=_(\"Background repeat\"),\n        choices=[(c, c) for c in REPEAT_CHOICES],\n        widget=widgets.RadioSelect,\n        initial='no-repeat',\n        required=False,\n        help_text=_(\"This property specifies how the background image repeates.\"),\n    )\n\n    background_attachment = ChoiceField(\n        label=_(\"Background attachment\"),\n        choices=[(c, c) for c in ATTACHMENT_CHOICES],\n        widget=widgets.RadioSelect,\n        initial='local',\n        required=False,\n        help_text=_(\"This property specifies how to move the background image relative to the viewport.\"),\n    )\n\n    background_vertical_position = ChoiceField(\n        label=_(\"Background vertical position\"),\n        choices=[(c, c) for c in VERTICAL_POSITION_CHOICES],\n        initial='center',\n        required=False,\n        help_text=_(\"This property moves a background image vertically within its container.\"),\n    )\n\n    background_horizontal_position = ChoiceField(\n        label=_(\"Background horizontal position\"),\n        choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES],\n        initial='center',\n        required=False,\n        help_text=_(\"This property moves a background image horizontally within its container.\"),\n    )\n\n    background_size = ChoiceField(\n        label=_(\"Background size\"),\n        choices=[(c, c) for c in SIZE_CHOICES],\n        widget=widgets.RadioSelect,\n        initial='auto',\n        required=False,\n        help_text=_(\"This property specifies how the background image is sized.\"),\n    )\n\n    background_width_height = MultiSizeField(\n        ['width', 'height'],\n        label=_(\"Background width/height\"),\n        sublabels=[_(\"Width\"), _(\"Height\")],\n        allowed_units=['px', '%'],\n        required=False,\n        help_text=_(\"This property specifies the width and height of a background image in px or %.\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['fluid', 'background_color', 'element_heights', 'image_file',\n                                         'background_repeat', 'background_attachment',\n                                         'background_vertical_position', 'background_horizontal_position',\n                                         'background_size', 'background_width_height']}\n\n    def validate_optional_field(self, name):\n        field = self.fields[name]\n        value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))\n        if value in field.empty_values:\n            self.add_error(name, ValidationError(field.error_messages['required'], code='required'))\n        else:\n            return value\n\n    def clean(self):\n        cleaned_data = super().clean()\n        if cleaned_data['image_file']:\n            self.validate_optional_field('background_repeat')\n            self.validate_optional_field('background_attachment')\n            self.validate_optional_field('background_vertical_position')\n            self.validate_optional_field('background_horizontal_position')\n            if self.validate_optional_field('background_size') == 'width/height':\n                try:\n                    cleaned_data['background_width_height']['width']\n                except KeyError:\n                    msg = _(\"You must at least set a background width.\")\n                    self.add_error('background_width_height', msg)\n                    raise ValidationError(msg)\n        return cleaned_data\n\n\nclass BootstrapJumbotronPlugin(BootstrapPluginBase):\n    name = _(\"Jumbotron\")\n    model_mixins = (ContainerGridMixin, ImagePropertyMixin, ImageBackgroundMixin)\n    require_parent = False\n    parent_classes = ['BootstrapContainerPlugin', 'BootstrapColumnPlugin']\n    allow_children = True\n    alien_child_classes = True\n    form = JumbotronFormMixin\n    raw_id_fields = ['image_file']\n    render_template = 'cascade/bootstrap4/jumbotron.html'\n    ring_plugin = 'JumbotronPlugin'\n    footnote_html = \"\"\"<p>\n    For more information about the Jumbotron please read the\n    <a href=\"https://getbootstrap.com/docs/4.3/components/jumbotron/\" target=\"_new\">Bootstrap documentation</a>.\n    </p>\"\"\"\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/jumbotronplugin.js']\n\n    def render(self, context, instance, placeholder):\n        # image shall be rendered in a responsive context using the ``<picture>`` element\n        try:\n            elements = get_picture_elements(instance)\n        except Exception as exc:\n            logger.warning(\"Unable generate picture elements. Reason: {}\".format(exc))\n        else:\n            try:\n                if instance.child_plugin_instances and instance.child_plugin_instances[0].plugin_type == 'BootstrapRowPlugin':\n                    padding='padding: {0}px {0}px;'.format(int(app_settings.CMSPLUGIN_CASCADE['bootstrap4']['gutter']/2))\n                    context.update({'add_gutter_if_child_is_BootstrapRowPlugin': padding,})\n                context.update({\n                    'elements': [e for e in elements if 'media' in e] if elements else [],\n                    'CSS_PREFIXES': app_settings.CSS_PREFIXES,\n                })\n            except Exception as exc:\n                logger.warning(\"Unable generate picture elements. Reason: {}\".format(exc))\n        return self.super(BootstrapJumbotronPlugin, self).render(context, instance, placeholder)\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = False\n        super().sanitize_model(obj)\n        grid_container = obj.get_bound_plugin().get_grid_instance()\n        obj.glossary.setdefault('media_queries', {})\n        for bp, bound in grid_container.bounds.items():\n            obj.glossary['media_queries'].setdefault(bp.name, {})\n            width = round(bound.max)\n            if obj.glossary['media_queries'][bp.name].get('width') != width:\n                obj.glossary['media_queries'][bp.name]['width'] = width\n                sanitized = True\n            if obj.glossary['media_queries'][bp.name].get('media') != bp.media_query:\n                obj.glossary['media_queries'][bp.name]['media'] = bp.media_query\n                sanitized = True\n        return sanitized\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapJumbotronPlugin, cls).get_css_classes(obj)\n        if obj.glossary.get('fluid'):\n            css_classes.append('jumbotron-fluid')\n        else:\n            css_classes.append('jumbotron')\n        return css_classes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        identifier = super().get_identifier(obj)\n        try:\n            content = obj.image.name or obj.image.original_filename\n        except AttributeError:\n            content = _(\"Without background image\")\n        return format_html('{0}{1}', identifier, content)\n\nplugin_pool.register_plugin(BootstrapJumbotronPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/mixins.py",
    "content": "from django.forms.fields import ChoiceField\nfrom django.utils.text import format_lazy\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin\nfrom cmsplugin_cascade.utils import CascadeUtilitiesMixin\nfrom cmsplugin_cascade.bootstrap4.grid import Breakpoint\n\n\nclass BootstrapUtilities:\n    \"\"\"\n    Factory for building a class ``BootstrapUtilitiesMixin``. This class then is used as a mixin to\n    all sorts of Bootstrap-4 plugins. Various Bootstrap-4 plugins are shipped using this mixin class\n    in different configurations. These configurations can be overridden through the project's\n    settings using:\n    ```\n    CMSPLUGIN_CASCADE['plugins_with_extra_mixins'] = {\n        'Bootstrap<ANY>Plugin': BootstrapUtilities(\n            BootstrapUtilities.background_and_color,\n            BootstrapUtilities.margins,\n            BootstrapUtilities.paddings,\n            …\n        ),\n        …\n    }\n    ```\n\n    The class ``BootstrapUtilities`` offers a bunch of property methods which return a list of\n    input fields and/or select boxes. They then can be added to the plugin's editor. This is\n    specially useful to add CSS classes from the utilities section of Bootstrap-4, such as\n    margins, borders, colors, etc.\n    \"\"\"\n    def __new__(cls, *args):\n        form_fields = {}\n        for arg in args:\n            if isinstance(arg, property):\n                form_fields.update(arg.fget(cls))\n\n        class Meta:\n            entangled_fields = {'glossary': list(form_fields.keys())}\n\n        utility_form_mixin = type('UtilitiesFormMixin', (EntangledModelFormMixin,), dict(form_fields, Meta=Meta))\n        return type('BootstrapUtilitiesMixin', (CascadeUtilitiesMixin,), {'utility_form_mixin': utility_form_mixin})\n\n    @property\n    def background_and_color(cls):\n        choices = [\n            ('', _(\"Default\")),\n            ('bg-primary text-white', _(\"Primary with white text\")),\n            ('bg-secondary text-white', _(\"Secondary with white text\")),\n            ('bg-success text-white', _(\"Success with white text\")),\n            ('bg-danger text-white', _(\"Danger with white text\")),\n            ('bg-warning text-white', _(\"Warning with white text\")),\n            ('bg-info text-white', _(\"Info with white text\")),\n            ('bg-light text-dark', _(\"Light with dark text\")),\n            ('bg-dark text-white', _(\"Dark with white text\")),\n            ('bg-white text-dark', _(\"White with dark text\")),\n            ('bg-transparent text-dark', _(\"Transparent with dark text\")),\n            ('bg-transparent text-white', _(\"Transparent with white text\")),\n        ]\n        return {'background_and_color': ChoiceField(\n            label=_(\"Background and color\"),\n            choices=choices,\n            required=False,\n            initial='',\n        )}\n\n    @property\n    def margins(cls):\n        form_fields = {}\n        choices_format = [\n            ('m-{}{}', _(\"4 sided margins ({})\")),\n            ('mx-{}{}', _(\"Horizontal margins ({})\")),\n            ('my-{}{}', _(\"Vertical margins ({})\")),\n            ('mt-{}{}', _(\"Top margin ({})\")),\n            ('mr-{}{}', _(\"Right margin ({})\")),\n            ('mb-{}{}', _(\"Bottom margin ({})\")),\n            ('ml-{}{}', _(\"Left margin ({})\")),\n        ]\n        sizes = list(range(0, 6)) + ['auto']\n        previous_label = ''\n        for bp in Breakpoint:\n            if bp == Breakpoint.xs:\n                choices = [(c.format('', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', _(\"No Margins\")))\n            else:\n                choices = [(c.format(bp.name + '-', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', format_lazy(_(\"Inherit margin from {}\"), previous_label)))\n            previous_label = bp.label\n            form_fields['margins_{}'.format(bp.name)] = ChoiceField(\n                label=format_lazy(_(\"Margins for {breakpoint}\"), breakpoint=bp.label),\n                choices=choices,\n                required=False,\n                initial='',\n            )\n        return form_fields\n\n    @property\n    def vertical_margins(cls):\n        form_fields = {}\n        choices_format = [\n            ('my-{}{}', _(\"Vertical margins ({})\")),\n            ('mt-{}{}', _(\"Top margin ({})\")),\n            ('mb-{}{}', _(\"Bottom margin ({})\")),\n        ]\n        sizes = list(range(0, 6)) + ['auto']\n        previous_label = ''\n        for bp in Breakpoint:\n            if bp == Breakpoint.xs:\n                choices = [(c.format('', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', _(\"No Margins\")))\n            else:\n                choices = [(c.format(bp.name + '-', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', format_lazy(_(\"Inherit margin from {}\"), previous_label)))\n            previous_label = bp.label\n            form_fields['margins_{}'.format(bp.name)] = ChoiceField(\n                label=format_lazy(_(\"Margins for {breakpoint}\"), breakpoint=bp.label),\n                choices=choices,\n                required=False,\n                initial='',\n            )\n        return form_fields\n\n    @property\n    def paddings(cls):\n        form_fields = {}\n        choices_format = [\n            ('p-{}{}', _(\"4 sided padding ({})\")),\n            ('px-{}{}', _(\"Horizontal padding ({})\")),\n            ('py-{}{}', _(\"Vertical padding ({})\")),\n            ('pt-{}{}', _(\"Top padding ({})\")),\n            ('pr-{}{}', _(\"Right padding ({})\")),\n            ('pb-{}{}', _(\"Bottom padding ({})\")),\n            ('pl-{}{}', _(\"Left padding ({})\")),\n        ]\n        sizes = range(0, 6)\n        previous_label = ''\n        for bp in Breakpoint:\n            if bp == Breakpoint.xs:\n                choices = [(c.format('', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', _(\"No Padding\")))\n            else:\n                choices = [(c.format(bp.name + '-', s), format_lazy(l, s)) for c, l in choices_format for s in sizes]\n                choices.insert(0, ('', format_lazy(_(\"Inherit padding from {}\"), previous_label)))\n            previous_label = bp.label\n            form_fields['padding_{}'.format(bp.name)] = ChoiceField(\n                label=format_lazy(_(\"Padding for {breakpoint}\"), breakpoint=bp.label),\n                choices=choices,\n                required=False,\n                initial='',\n            )\n        return form_fields\n\n    @property\n    def floats(cls):\n        form_fields = {}\n        choices_format = [\n            ('float-{}none', _(\"Do not float\")),\n            ('float-{}left', _(\"Float left\")),\n            ('float-{}right', _(\"Float right\")),\n        ]\n        previous_label = ''\n        for bp in Breakpoint:\n            if bp == Breakpoint.xs:\n                choices = [(c.format(''), l) for c, l in choices_format]\n                choices.insert(0, ('', _(\"Unset\")))\n            else:\n                choices = [(c.format(bp.name + '-'), l) for c, l in choices_format]\n                choices.insert(0, ('', format_lazy(_(\"Inherit float from {}\"), previous_label)))\n            previous_label = bp.label\n            form_fields['float_{}'.format(bp.name)] = ChoiceField(\n                label=format_lazy(_(\"Floats for {breakpoint}\"), breakpoint=bp.label),\n                choices=choices,\n                required=False,\n                initial='',\n            )\n        return form_fields\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/picture.py",
    "content": "import logging\nfrom django.forms import widgets, MultipleChoiceField\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.bootstrap4.grid import Breakpoint\nfrom cmsplugin_cascade.bootstrap4.utils import get_picture_elements, IMAGE_RESIZE_OPTIONS, IMAGE_SHAPE_CHOICES\nfrom cmsplugin_cascade.bootstrap4.fields import BootstrapMultiSizeField\nfrom cmsplugin_cascade.image import ImageFormMixin, ImagePropertyMixin\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\n\nlogger = logging.getLogger('cascade.bootstrap4')\n\n\nclass BootstrapPictureFormMixin(ImageFormMixin):\n    responsive_heights = BootstrapMultiSizeField(\n        label=_(\"Adapt Picture Heights\"),\n        required=False,\n        require_all_fields=False,\n        allowed_units=['px', '%'],\n        initial='100%',\n        help_text=_(\"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"),\n    )\n\n    responsive_zoom = BootstrapMultiSizeField(\n        label=_(\"Adapt Picture Zoom\"),\n        required=False,\n        require_all_fields=False,\n        allowed_units=['%'],\n        initial=['0%', '0%', '0%', '0%', '0%'],\n        help_text=_(\"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"),\n    )\n\n    resize_options = MultipleChoiceField(\n        label=_(\"Resize Options\"),\n        choices=IMAGE_RESIZE_OPTIONS,\n        widget=widgets.CheckboxSelectMultiple,\n        initial=['subject_location', 'high_resolution'],\n        help_text = _(\"Options to use when resizing the image.\"),\n    )\n\n    image_shapes = MultipleChoiceField(\n        label=_(\"Image Shapes\"),\n        choices=IMAGE_SHAPE_CHOICES,\n        widget=widgets.CheckboxSelectMultiple,\n        initial=['img-fluid']\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['responsive_heights', 'responsive_zoom', 'resize_options', 'image_shapes']}\n\n\nclass BootstrapPicturePlugin(LinkPluginBase):\n    name = _(\"Picture\")\n    module = 'Bootstrap'\n    parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin']\n    require_parent = True\n    allow_children = False\n    raw_id_fields = LinkPluginBase.raw_id_fields + ['image_file']\n    model_mixins = (ImagePropertyMixin, LinkElementMixin,)\n    admin_preview = False\n    ring_plugin = 'PicturePlugin'\n    form = type('BootstrapPictureForm', (LinkFormMixin, BootstrapPictureFormMixin), {'require_link': False})\n    render_template = 'cascade/bootstrap4/linked-picture.html'\n    default_css_class = 'img-fluid'\n    default_css_attributes = ['image_shapes']\n    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}\n    html_tag_attributes.update(LinkPluginBase.html_tag_attributes)\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/pictureplugin.js']\n\n    def render(self, context, instance, placeholder):\n        # image shall be rendered in a responsive context using the picture element\n        context = self.super(BootstrapPicturePlugin, self).render(context, instance, placeholder)\n        try:\n            elements = get_picture_elements(instance)\n        except Exception as exc:\n            logger.warning(\"Unable generate picture elements. Reason: {}\".format(exc))\n        else:\n            context.update({\n                'instance': instance,\n                'is_fluid': True,\n                'placeholder': placeholder,\n                'elements': elements,\n            })\n        return context\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(BootstrapPicturePlugin, cls).get_css_classes(obj)\n        css_class = obj.glossary.get('css_class')\n        if css_class:\n            css_classes.append(css_class)\n        return css_classes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        try:\n            content = str(obj.image)\n        except AttributeError:\n            content = _(\"No Picture\")\n        return mark_safe(content)\n\n    @classmethod\n    def sanitize_model(cls, obj):\n        sanitized = False\n        parent = obj.parent\n        if parent:\n            while parent.plugin_type != 'BootstrapColumnPlugin':\n                parent = parent.parent\n            grid_column = parent.get_bound_plugin().get_grid_instance()\n            obj.glossary.setdefault('media_queries', {})\n            for bp in Breakpoint:\n                obj.glossary['media_queries'].setdefault(bp.name, {})\n                width = round(grid_column.get_bound(bp).max)\n                if obj.glossary['media_queries'][bp.name].get('width') != width:\n                    obj.glossary['media_queries'][bp.name]['width'] = width\n                    sanitized = True\n                if obj.glossary['media_queries'][bp.name].get('media') != bp.media_query:\n                    obj.glossary['media_queries'][bp.name]['media'] = bp.media_query\n                    sanitized = True\n        else:\n            logger.warning(\"PicturePlugin(pk={}) has no ColumnPlugin as ancestor.\".format(obj.pk))\n            return \n        return sanitized\n\nplugin_pool.register_plugin(BootstrapPicturePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/plugin_base.py",
    "content": "import os\nfrom django.template.loader import get_template, TemplateDoesNotExist\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\nclass BootstrapPluginBase(CascadePluginBase):\n    module = 'Bootstrap'\n    require_parent = True\n    allow_children = True\n    render_template = 'cascade/generic/wrapper.html'\n\n    def get_render_template(self, context, instance, placeholder):\n        render_template = getattr(self, 'render_template', None)\n        if render_template and '{}' in render_template:\n            try:\n                # check if overridden template exists\n                template = render_template.format(app_settings.CMSPLUGIN_CASCADE['bootstrap4']['template_basedir'])\n                template = os.path.normpath(template)\n                get_template(template)\n                return template\n            except (KeyError, TemplateDoesNotExist):\n                template = render_template.format('')\n                return os.path.normpath(template)\n        return render_template\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/secondary_menu.py",
    "content": "from django.forms.fields import ChoiceField, IntegerField\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cms.models.pagemodel import Page\nfrom .plugin_base import BootstrapPluginBase\n\n\nclass SecondaryMenuFormMixin(EntangledModelFormMixin):\n    page_id = ChoiceField(\n        label=_(\"CMS Page Id\"),\n        help_text = _(\"Select a CMS page with a given unique Id (in advanced settings).\"),\n    )\n\n    offset = IntegerField(\n        label=_(\"Offset\"),\n        initial=0,\n        help_text=_(\"Starting from which child menu.\"),\n    )\n\n    limit = IntegerField(\n        label=_(\"Limit\"),\n        initial=100,\n        help_text=_(\"Number of child menus.\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['page_id', 'offset', 'limit']}\n\n    def __init__(self, *args, **kwargs):\n        choices = [(p.reverse_id, str(p)) for p in Page.objects.filter(reverse_id__isnull=False, publisher_is_draft=False)]\n        self.base_fields['page_id'].choices = choices\n        super().__init__(*args, **kwargs)\n\n\nclass BootstrapSecondaryMenuPlugin(BootstrapPluginBase):\n    \"\"\"\n    Use this plugin to display a secondary menu in arbitrary locations.\n    This renders links onto  all CMS pages, which are children of the selected Page Id.\n    \"\"\"\n    name = _(\"Secondary Menu\")\n    default_css_class = 'list-group'\n    require_parent = False\n    parent_classes = None\n    allow_children = False\n    form = SecondaryMenuFormMixin\n    render_template = 'cascade/bootstrap4/secmenu-list-group.html'\n\n    @classmethod\n    def get_identifier(cls, obj):\n        return mark_safe(obj.glossary.get('page_id', ''))\n\n    def render(self, context, instance, placeholder):\n        context = self.super(BootstrapSecondaryMenuPlugin, self).render(context, instance, placeholder)\n        context.update({\n            'page_id': instance.glossary['page_id'],\n            'offset': instance.glossary.get('offset', 0),\n            'limit': instance.glossary.get('limit', 100),\n        })\n        return context\n\n    @classmethod\n    def sanitize_model(cls, instance):\n        try:\n            if int(instance.glossary['offset']) < 0 or int(instance.glossary['limit']) < 0:\n                raise ValueError()\n        except (KeyError, ValueError):\n            instance.glossary['offset'] = 0\n            instance.glossary['limit'] = 100\n\nplugin_pool.register_plugin(BootstrapSecondaryMenuPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/settings.py",
    "content": "from collections import OrderedDict\nfrom django.conf import settings\nfrom django.utils.translation import gettext_lazy as _\nfrom cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig\nfrom cmsplugin_cascade.bootstrap4.mixins import BootstrapUtilities\nfrom .grid import Breakpoint, Bound\n\n\nCASCADE_PLUGINS = ['accordion', 'buttons', 'card', 'carousel', 'container', 'embeds', 'icon', 'image', 'jumbotron',\n                   'picture', 'tabs']\nif 'cms_bootstrap' in settings.INSTALLED_APPS:\n    CASCADE_PLUGINS.append('secondary_menu')\n\n\ndef set_defaults(config):\n    config.setdefault('bootstrap4', {})\n    config['bootstrap4'].setdefault('default_bounds', OrderedDict([\n        (Breakpoint.xs, Bound(320, 572)),\n        (Breakpoint.sm, Bound(540, 540)),\n        (Breakpoint.md, Bound(720, 720)),\n        (Breakpoint.lg, Bound(960, 960)),\n        (Breakpoint.xl, Bound(1140, 1140)),\n    ]))\n    config['bootstrap4'].setdefault('fluid_bounds', OrderedDict([\n        (Breakpoint.xs, Bound(320, 576)),\n        (Breakpoint.sm, Bound(576, 768)),\n        (Breakpoint.md, Bound(768, 992)),\n        (Breakpoint.lg, Bound(992, 1200)),\n        (Breakpoint.xl, Bound(1200, 1980)),\n    ]))\n    config['bootstrap4'].setdefault('gutter', 30)\n\n    config['plugins_with_extra_mixins'].setdefault('BootstrapAccordionPlugin', BootstrapUtilities(\n        BootstrapUtilities.margins,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('BootstrapAccordionGroupPlugin', BootstrapUtilities(\n        BootstrapUtilities.background_and_color,\n        BootstrapUtilities.margins,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('BootstrapCardPlugin', BootstrapUtilities(\n        BootstrapUtilities.background_and_color,\n        BootstrapUtilities.margins,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('BootstrapCarouselPlugin', BootstrapUtilities(\n        BootstrapUtilities.margins,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('BootstrapContainerPlugin', BootstrapUtilities(\n        BootstrapUtilities.paddings,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('HeadingPlugin', BootstrapUtilities(\n        BootstrapUtilities.margins,\n    ))\n    config['plugins_with_extra_mixins'].setdefault('HorizontalRulePlugin', BootstrapUtilities(\n        BootstrapUtilities.margins,\n    ))\n\n    config['plugins_with_extra_fields'].setdefault('BootstrapTabSetPlugin', PluginExtraFieldsConfig(\n        css_classes={\n            'multiple': True,\n            'class_names': ['nav-tabs', 'nav-pills', 'nav-fill', 'nav-justified'],\n        },\n    ))\n\n    config['plugins_with_extra_render_templates'].setdefault('BootstrapSecondaryMenuPlugin', [\n        ('cascade/bootstrap4/secmenu-list-group.html', _(\"List Group\")),\n        ('cascade/bootstrap4/secmenu-unstyled-list.html', _(\"Unstyled List\"))\n    ])\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/tabs.py",
    "content": "from django.forms import widgets\nfrom django.forms.fields import BooleanField, CharField\nfrom django.utils.translation import ngettext_lazy, gettext_lazy as _\nfrom django.utils.text import Truncator\nfrom django.utils.safestring import mark_safe\nfrom django.forms.fields import IntegerField\n\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.forms import ManageChildrenFormMixin\nfrom cmsplugin_cascade.plugin_base import TransparentWrapper, TransparentContainer\nfrom cmsplugin_cascade.widgets import NumberInputWidget\nfrom .plugin_base import BootstrapPluginBase\n\n\nclass TabSetFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin):\n    num_children = IntegerField(\n        min_value=1,\n        initial=1,\n        widget=NumberInputWidget(attrs={'size': '3', 'style': 'width: 5em !important;'}),\n        label=_(\"Number of Tabs\"),\n        help_text=_(\"Number can be adjusted at any time.\"),\n    )\n\n    justified = BooleanField(\n        label=_(\"Justified tabs\"),\n        required=False,\n    )\n\n    class Meta:\n        untangled_fields = ['num_children']\n        entangled_fields = {'glossary': ['justified']}\n\n\nclass BootstrapTabSetPlugin(TransparentWrapper, BootstrapPluginBase):\n    name = _(\"Tab Set\")\n    parent_classes = ['BootstrapColumnPlugin']\n    direct_child_classes = ['BootstrapTabPanePlugin']\n    require_parent = True\n    allow_children = True\n    form = TabSetFormMixin\n    render_template = 'cascade/bootstrap4/{}tabset.html'\n    default_css_class = 'nav-tabs'\n\n    @classmethod\n    def get_identifier(cls, instance):\n        num_cols = instance.get_num_children()\n        content = ngettext_lazy('with {} tab', 'with {} tabs', num_cols).format(num_cols)\n        return mark_safe(content)\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = super().get_css_classes(obj)\n        if obj.glossary.get('justified'):\n            css_classes.append('nav-fill')\n        return css_classes\n\n    def save_model(self, request, obj, form, change):\n        wanted_children = int(form.cleaned_data.get('num_children'))\n        super().save_model(request, obj, form, change)\n        self.extend_children(obj, wanted_children, BootstrapTabPanePlugin)\n\nplugin_pool.register_plugin(BootstrapTabSetPlugin)\n\n\nclass TabPaneFormMixin(EntangledModelFormMixin):\n    tab_title = CharField(\n        label=_(\"Tab Title\"),\n        widget=widgets.TextInput(attrs={'size': 80}),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['tab_title']}\n\n\nclass BootstrapTabPanePlugin(TransparentContainer, BootstrapPluginBase):\n    name = _(\"Tab Pane\")\n    direct_parent_classes = parent_classes = ['BootstrapTabSetPlugin']\n    require_parent = True\n    allow_children = True\n    alien_child_classes = True\n    form = TabPaneFormMixin\n\n    @classmethod\n    def get_identifier(cls, obj):\n        content = obj.glossary.get('tab_title', '')\n        if content:\n            content = Truncator(content).words(3, truncate=' ...')\n        return mark_safe(content)\n\nplugin_pool.register_plugin(BootstrapTabPanePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/bootstrap4/utils.py",
    "content": "import logging\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.utils import (compute_aspect_ratio, get_image_size, parse_responsive_length,\n   compute_aspect_ratio_with_glossary)\n\nlogger = logging.getLogger('cascade')\n\nIMAGE_RESIZE_OPTIONS = [\n    ('upscale', _(\"Upscale image\")),\n    ('crop', _(\"Crop image\")),\n    ('subject_location', _(\"With subject location\")),\n    ('high_resolution', _(\"Optimized for Retina\")),\n]\n\nIMAGE_SHAPE_CHOICES = [\n    ('img-fluid', _(\"Responsive\")),\n    ('rounded', _('Rounded')),\n    ('rounded-circle', _('Circle')),\n    ('img-thumbnail', _('Thumbnail')),\n]\n\n\ndef get_image_tags(instance):\n    \"\"\"\n    Create a context returning the tags to render an ``<img ...>`` element with\n    ``sizes``, ``srcset``, a fallback ``src`` and if required inline styles.\n    \"\"\"\n    if hasattr(instance, 'image') and hasattr(instance.image, 'exif'):\n        aspect_ratio = compute_aspect_ratio(instance.image)\n    elif 'image' in instance.glossary and 'width' in instance.glossary['image']:\n        aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)\n    else:\n        # if accessing the image file fails or fake image fails, abort here\n        raise FileNotFoundError(\"Unable to compute aspect ratio of image\")\n\n    is_responsive = 'img-fluid' in instance.glossary.get('image_shapes', [])\n    resize_options = instance.glossary.get('resize_options', {})\n    crop = 'crop' in resize_options\n    upscale = 'upscale' in resize_options\n    if 'subject_location' in resize_options and hasattr(instance.image, 'subject_location'):\n        subject_location = instance.image.subject_location\n    else:\n        subject_location = None\n    tags = {'sizes': [], 'srcsets': {}, 'is_responsive': is_responsive, 'extra_styles': {}}\n    if is_responsive:\n        image_width = parse_responsive_length(instance.glossary.get('image_width_responsive') or '100%')\n        assert(image_width[1]), \"The given image has no valid width\"\n        if image_width[1] != 1.0:\n            tags['extra_styles'].update({'max-width': '{:.0f}%'.format(100 * image_width[1])})\n    else:\n        image_width = parse_responsive_length(instance.glossary['image_width_fixed'])\n        if not image_width[0]:\n            image_width = (instance.image.width, image_width[1])\n    try:\n        image_height = parse_responsive_length(instance.glossary['image_height'])\n    except KeyError:\n        image_height = (None, None)\n    if is_responsive:\n        column_bounds_min = instance.glossary['column_bounds']['min']\n        if 'high_resolution' in resize_options:\n            column_bounds_max = 2 * instance.glossary['column_bounds']['max']\n        else:\n            column_bounds_max = instance.glossary['column_bounds']['max']\n        num_steps = min(int((column_bounds_max - column_bounds_min) / app_settings.RESPONSIVE_IMAGE_STEP_SIZE),\n                        app_settings.RESPONSIVE_IMAGE_MAX_STEPS)\n        step_width, max_width = (column_bounds_max - column_bounds_min) / num_steps, 0\n        for step in range(0, num_steps + 1):\n            width = round(column_bounds_min + step_width * step)\n            max_width = max(max_width, width)\n            size = get_image_size(width, image_height, aspect_ratio)\n            key = '{0}w'.format(*size)\n            tags['srcsets'][key] = {'size': size, 'crop': crop, 'upscale': upscale,\n                                    'subject_location': subject_location}\n        tags['sizes'] = instance.glossary['media_queries'].values()\n        # use an existing image as fallback for the <img ...> element\n        if not max_width > 0:\n            logger.warning('image tags: image max width is zero')\n        size = (int(round(max_width)), int(round(max_width * aspect_ratio)))\n    else:\n        size = get_image_size(image_width[0], image_height, aspect_ratio)\n        if 'high_resolution' in resize_options:\n            tags['srcsets']['1x'] = {'size': size, 'crop': crop, 'upscale': upscale,\n                                     'subject_location': subject_location}\n            tags['srcsets']['2x'] = dict(tags['srcsets']['1x'], size=(size[0] * 2, size[1] * 2))\n    tags['src'] = {'size': size, 'crop': crop, 'upscale': upscale, 'subject_location': subject_location}\n    return tags\n\n\ndef get_picture_elements(instance):\n    \"\"\"\n    Create a context, used to render a <picture> together with all its ``<source>`` elements:\n    It returns a list of HTML elements, each containing the information to render a ``<source>``\n    element.\n    The purpose of this HTML entity is to display images with art directions. For normal images use\n    the ``<img>`` element.\n    \"\"\"\n\n    if hasattr(instance, 'image') and hasattr(instance.image, 'exif'):\n        aspect_ratio = compute_aspect_ratio(instance.image)\n    elif 'image' in instance.glossary and 'width' in instance.glossary['image']:\n        aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)\n    else:\n        # if accessing the image file fails or fake image fails, abort here\n        logger.warning(\"Unable to compute aspect ratio of image '{}'\".format(instance.image))\n        return\n\n    # container_max_heights = instance.glossary.get('container_max_heights', {})\n    resize_options = instance.glossary.get('resize_options', {})\n    crop = 'crop' in resize_options\n    upscale = 'upscale' in resize_options\n    if 'subject_location' in resize_options and hasattr(instance.image, 'subject_location'):\n        subject_location = instance.image.subject_location\n    else:\n        subject_location = None\n    max_width = 0\n    max_zoom = 0\n    elements = []\n    for bp, media_query in instance.glossary['media_queries'].items():\n        width, media = media_query['width'], media_query['media']\n        max_width = max(max_width, width)\n        size = None\n        try:\n            image_height = parse_responsive_length(instance.glossary['responsive_heights'][bp])\n        except KeyError:\n            image_height = (None, None)\n        if image_height[0]:  # height was given in px\n            size = (int(width), image_height[0])\n        elif image_height[1]:  # height was given in %\n            size = (int(width), int(round(width * aspect_ratio * image_height[1])))\n        try:\n            zoom = int(\n                instance.glossary['responsive_zoom'][bp].strip().rstrip('%')\n            )\n        except (AttributeError, KeyError, ValueError):\n            zoom = 0\n        max_zoom = max(max_zoom, zoom)\n        if size is None:\n            # as fallback, adopt height to current width\n            size = (int(width), int(round(width * aspect_ratio)))\n        elem = {'tag': 'source', 'size': size, 'zoom': zoom, 'crop': crop,\n                'upscale': upscale, 'subject_location': subject_location, 'media': media}\n        if 'high_resolution' in resize_options:\n            elem['size2'] = (size[0] * 2, size[1] * 2)\n        elements.append(elem)\n\n    # add a fallback image for old browsers which can't handle the <source> tags inside a <picture> element\n    if image_height[1]:\n        size = (int(max_width), int(round(max_width * aspect_ratio * image_height[1])))\n    else:\n        size = (int(max_width), int(round(max_width * aspect_ratio)))\n    elements.append({'tag': 'img', 'size': size, 'zoom': max_zoom, 'crop': crop,\n                     'upscale': upscale, 'subject_location': subject_location})\n    return elements\n"
  },
  {
    "path": "cmsplugin_cascade/clipboard/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/clipboard/admin.py",
    "content": "from django.contrib import admin\nfrom django.contrib import messages\nfrom django.forms import widgets\nfrom django.forms.utils import flatatt\nfrom django.db.models import JSONField\nfrom django.templatetags.static import static\nfrom django.utils.html import format_html\nfrom django.utils.timezone import now\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.models.placeholderpluginmodel import PlaceholderReference\nfrom cmsplugin_cascade.clipboard.utils import deserialize_to_clipboard, serialize_from_placeholder\nfrom cmsplugin_cascade.models import CascadeClipboard\n\n\nclass JSONAdminWidget(widgets.Textarea):\n    def __init__(self):\n        attrs = {'cols': '40', 'rows': '3'}\n        super(JSONAdminWidget, self).__init__(attrs)\n\n    def render(self, name, value, attrs=None, renderer=None):\n        if value is None:\n            value = ''\n        final_attrs = self.build_attrs(self.attrs, extra_attrs=dict(attrs, name=name))\n        id_data = attrs.get('id', 'id_data')\n        clippy_url = static('cascade/admin/clippy.svg')\n        return format_html('<textarea{0}>\\r\\n{1}</textarea> '\n            '<button data-clipboard-target=\"#{2}\" type=\"button\" title=\"{4}\" class=\"clip-btn\">'\n                '<img src=\"{3}\" alt=\"{4}\">'\n            '</button>\\n'\n            '<div class=\"status-line\"><label></label><strong id=\"pasted_success\">{5}</strong>'\n            '<strong id=\"copied_success\">{6}</strong></div>',\n            flatatt(final_attrs), str(value), id_data, clippy_url,\n            _(\"Copy to Clipboard\"),\n            _(\"Successfully pasted JSON data\"),\n            _(\"Successfully copied JSON data\"))\n\n\n@admin.register(CascadeClipboard)\nclass CascadeClipboardAdmin(admin.ModelAdmin):\n    fields = ['identifier', ('created_by', 'created_at', 'last_accessed_at'), 'save_clipboard', 'restore_clipboard', 'data']\n    readonly_fields = ['created_by', 'created_at', 'last_accessed_at', 'save_clipboard', 'restore_clipboard']\n    formfield_overrides = {\n        JSONField: {'widget': JSONAdminWidget},\n    }\n    list_display = ['identifier', 'created_by', 'created_at']\n\n    class Media:\n        css = {'all': ['cascade/css/admin/clipboard.css']}\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/clipboard.js']\n\n    def save_clipboard(self, obj):\n        return format_html('<input type=\"submit\" value=\"{}\" class=\"default pull-left\" name=\"save_clipboard\" />',\n                           _(\"Insert Data\"))\n    save_clipboard.short_description = _(\"From CMS Clipboard\")\n\n    def restore_clipboard(self, obj):\n        return format_html('<input type=\"submit\" value=\"{}\" class=\"default pull-left\" name=\"restore_clipboard\" />',\n                           _(\"Restore Data\"))\n    restore_clipboard.short_description = _(\"To CMS Clipboard\")\n\n    def save_model(self, request, obj, form, change):\n        if request.POST.get('save_clipboard'):\n            placeholder_reference = PlaceholderReference.objects.last()\n            if placeholder_reference:\n                placeholder = placeholder_reference.placeholder_ref\n                obj.data = serialize_from_placeholder(placeholder, self.admin_site)\n            request.POST = request.POST.copy()\n            request.POST['_continue'] = True\n            messages.add_message(request, messages.INFO, _(\"The clipboard's content has been persisted for later.\"))\n        if request.POST.get('restore_clipboard'):\n            request.POST = request.POST.copy()\n            request.POST['_continue'] = True\n            messages.add_message(request, messages.INFO, _(\"Persisted content has been restored to the clipboard.\"))\n        if request.POST.get('restore_clipboard'):\n            deserialize_to_clipboard(request, obj.data)\n            obj.last_accessed_at = now()\n        super().save_model(request, obj, form, change)\n\n"
  },
  {
    "path": "cmsplugin_cascade/clipboard/cms_plugins.py",
    "content": "import json\n\nfrom django.contrib.admin import site as default_admin_site\nfrom django.contrib.admin.helpers import AdminForm\nfrom django.core.exceptions import PermissionDenied\nfrom django.forms import CharField, ModelChoiceField\nfrom django.shortcuts import render\nfrom django.template.response import TemplateResponse\nfrom django.urls import re_path, reverse\nfrom django.utils.http import urlencode\nfrom django.utils.timezone import now\nfrom django.utils.translation import gettext_lazy as _, get_language_from_request\n\nfrom cms.plugin_base import CMSPluginBase, PluginMenuItem\nfrom cms.plugin_pool import plugin_pool\nfrom cms.toolbar.utils import get_plugin_tree_as_json\nfrom cmsplugin_cascade.clipboard.forms import ClipboardBaseForm\nfrom cmsplugin_cascade.clipboard.utils import deserialize_to_clipboard, serialize_from_placeholder\nfrom cmsplugin_cascade.models import CascadeClipboard\n\n\nclass CascadeClipboardPlugin(CMSPluginBase):\n    render_plugin = False\n    change_form_template = 'admin/cms/page/plugin/change_form.html'\n\n    def get_plugin_urls(self):\n        urlpatterns = [\n            re_path(r'^export-plugins/$', self.export_plugins_view, name='export_clipboard_plugins'),\n            re_path(r'^import-plugins/$', self.import_plugins_view, name='import_clipboard_plugins'),\n        ]\n        return urlpatterns\n\n    @classmethod\n    def get_extra_placeholder_menu_items(cls, request, placeholder):\n        data = urlencode({\n            'placeholder': placeholder.pk,\n            'language': get_language_from_request(request, check_path=True),\n        })\n        return [\n            PluginMenuItem(\n                _(\"Export to Clipboard\"),\n                reverse('admin:export_clipboard_plugins') + '?' + data,\n                data={},\n                action='modal',\n                attributes={\n                    'icon': 'export',\n                },\n            ),\n            PluginMenuItem(\n                _(\"Import from Clipboard\"),\n                reverse('admin:import_clipboard_plugins') + '?' + data,\n                data={},\n                action='modal',\n                attributes={\n                    'icon': 'import',\n                },\n            )\n        ]\n\n    def render_modal_window(self, request, form):\n        \"\"\"\n        Render a modal popup window with a select box to edit the form\n        \"\"\"\n        opts = self.model._meta\n        fieldsets = [(None, {'fields': list(form.fields)})]\n        adminForm = AdminForm(form, fieldsets, {}, [])\n        context = {\n            **default_admin_site.each_context(request),\n            'title': form.title,\n            'adminform': adminForm,\n            'add': False,\n            'change': True,\n            'save_as': False,\n            'has_add_permission': False,\n            'has_change_permission': True,\n            'opts': opts,\n            'root_path': reverse('admin:index'),\n            'is_popup': True,\n            'app_label': opts.app_label,\n            'media': self.media + form.media,\n        }\n        return TemplateResponse(request, self.change_form_template, context)\n\n    def import_plugins_view(self, request):\n        # TODO: check for permissions\n\n        title = _(\"Import from Clipboard\")\n        if request.method == 'GET':\n            Form = type('ClipboardImportForm', (ClipboardBaseForm,), {\n                'clipboard': ModelChoiceField(\n                    queryset=CascadeClipboard.objects.all(),\n                    label=_(\"Select Clipboard Content\"),\n                    required=False,\n                ),\n                'title': title,\n            })\n            form = Form(request.GET)\n            assert form.is_valid()\n        elif request.method == 'POST':\n            Form = type('ClipboardImportForm', (ClipboardBaseForm,), {\n                'clipboard': ModelChoiceField(\n                    queryset=CascadeClipboard.objects.all(),\n                    label=_(\"Select Clipboard Content\"),\n                ),\n                'title': title,\n            })\n            form = Form(request.POST)\n            if form.is_valid():\n                return self.paste_from_clipboard(request, form)\n        return self.render_modal_window(request, form)\n\n    def paste_from_clipboard(self, request, form):\n        placeholder = form.cleaned_data['placeholder']\n        language = form.cleaned_data['language']\n        cascade_clipboard = form.cleaned_data['clipboard']\n        tree_order = placeholder.get_plugin_tree_order(language)\n        deserialize_to_clipboard(request, cascade_clipboard.data)\n        cascade_clipboard.last_accessed_at = now()\n        cascade_clipboard.save(update_fields=['last_accessed_at'])\n\n        # detach plugins from clipboard and reattach them to current placeholder\n        cb_placeholder_plugin = request.toolbar.clipboard.cmsplugin_set.filter(plugin_type='PlaceholderPlugin').first()\n        cb_placeholder_instance, _ = cb_placeholder_plugin.get_plugin_instance()\n        new_plugins = cb_placeholder_instance.placeholder_ref.get_plugins()\n        new_plugins.update(placeholder=placeholder)\n\n        # reorder root plugins in placeholder\n        root_plugins = placeholder.get_plugins(language).filter(parent__isnull=True).order_by('changed_date')\n        for position, plugin in enumerate(root_plugins.iterator()):\n            plugin.update(position=position)\n        placeholder.mark_as_dirty(language, clear_cache=False)\n\n        # create a list of pasted plugins to be added to the structure view\n        all_plugins = placeholder.get_plugins(language)\n        if all_plugins.exists():\n            new_plugins = placeholder.get_plugins(language).exclude(pk__in=tree_order)\n            data = json.loads(get_plugin_tree_as_json(request, list(new_plugins)))\n            data['plugin_order'] = tree_order + ['__COPY__']\n        else:\n            return render(request, 'cascade/admin/clipboard_reload_page.html')\n        data['target_placeholder_id'] = placeholder.pk\n        context = {'structure_data': json.dumps(data)}\n        return render(request, 'cascade/admin/clipboard_paste_plugins.html', context)\n\n    def export_plugins_view(self, request):\n        if not request.user.is_staff:\n            raise PermissionDenied\n\n        title = _(\"Export to Clipboard\")\n        if request.method == 'GET':\n            Form = type('ClipboardExportForm', (ClipboardBaseForm,), {\n                'identifier': CharField(required=False),\n                'title': title,\n            })\n            form = Form(request.GET)\n            assert form.is_valid()\n        elif request.method == 'POST':\n            Form = type('ClipboardExportForm', (ClipboardBaseForm,), {\n                'identifier': CharField(),\n                'title': title,\n            })\n            form = Form(request.POST)\n            if form.is_valid():\n                return self.add_to_clipboard(request, form)\n        return self.render_modal_window(request, form)\n\n    def add_to_clipboard(self, request, form):\n        placeholder = form.cleaned_data['placeholder']\n        language = form.cleaned_data['language']\n        identifier = form.cleaned_data['identifier']\n        data = serialize_from_placeholder(placeholder)\n        CascadeClipboard.objects.create(\n            identifier=identifier,\n            data=data,\n            created_by=request.user,\n        )\n        return render(request, 'cascade/admin/clipboard_close_frame.html', {})\n\nplugin_pool.register_plugin(CascadeClipboardPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/clipboard/cms_toolbars.py",
    "content": "from cms.toolbar_base import CMSToolbar\nfrom cms.toolbar_pool import toolbar_pool\n\n\n@toolbar_pool.register\nclass CascadeClipboardToolbar(CMSToolbar):\n    class Media:\n        css = {\n            'all': ['cascade/css/admin/clipboard.css']\n        }\n"
  },
  {
    "path": "cmsplugin_cascade/clipboard/forms.py",
    "content": "from django import forms\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.models import Placeholder\nfrom cmsplugin_cascade.models import CascadeClipboard\n\n\nclass ClipboardBaseForm(forms.Form):\n    placeholder = forms.ModelChoiceField(\n        queryset=Placeholder.objects.all(),\n        required=True,\n        widget=forms.HiddenInput(),\n    )\n\n    language = forms.ChoiceField(\n        choices=settings.LANGUAGES,\n        required=True,\n        widget=forms.HiddenInput(),\n    )\n\n    def clean_identifier(self):\n        identifier = self.cleaned_data['identifier']\n        if CascadeClipboard.objects.filter(identifier=identifier).exists():\n            msg = _(\"This identifier has already been used, please choose another one.\")\n            raise ValidationError(msg)\n        return identifier\n"
  },
  {
    "path": "cmsplugin_cascade/clipboard/utils.py",
    "content": "from django.contrib.admin import site as default_admin_site\nfrom django.contrib import messages\nfrom django.utils.translation import get_language_from_request\n\nfrom cms.api import add_plugin\nfrom cms.models.placeholderpluginmodel import PlaceholderReference\nfrom cms.plugin_pool import plugin_pool\n\nfrom djangocms_text_ckeditor.models import Text\nfrom djangocms_text_ckeditor.utils import plugin_tags_to_id_list, replace_plugin_tags\n\nfrom cmsplugin_cascade.models import CascadeElement\n\n\ndef serialize_from_placeholder(placeholder, admin_site=default_admin_site):\n    \"\"\"\n    Create a serialized representation of all the plugins belonging to the clipboard.\n    \"\"\"\n    def populate_data(parent, data):\n        for child in plugin_qs.filter(parent=parent).order_by('position'):\n            instance, plugin = child.get_plugin_instance(admin_site)\n            plugin_type = plugin.__class__.__name__\n            try:\n                entry = (plugin_type, plugin.get_data_representation(instance), [])\n            except AttributeError:\n                if isinstance(instance, Text):\n                    entry = (plugin_type, {'body': instance.body, 'pk': instance.pk}, [])\n                else:\n                    continue\n            data.append(entry)\n            populate_data(child, entry[2])\n\n    data = {'plugins': []}\n    plugin_qs = placeholder.cmsplugin_set.all()\n    populate_data(None, data['plugins'])\n    return data\n\n\ndef deserialize_to_clipboard(request, data):\n    \"\"\"\n    Restore clipboard's content by creating plugins from given data.\n    \"\"\"\n    def plugins_from_data(placeholder, parent, data):\n        for plugin_type, data, children_data in data:\n            try:\n                plugin_class = plugin_pool.get_plugin(plugin_type)\n            except Exception:\n                messages.add_message(request, messages.ERROR, \"Unable create plugin of type: {}\".format(plugin_type))\n                continue\n            kwargs = dict(data)\n            inlines = kwargs.pop('inlines', [])\n            shared_glossary = kwargs.pop('shared_glossary', None)\n            try:\n                instance = add_plugin(placeholder, plugin_class, language, target=parent, **kwargs)\n            except Exception:\n                messages.add_message(request, messages.ERROR, \"Unable to create structure for plugin: {}\".format(plugin_class.name))\n                continue\n            if isinstance(instance, CascadeElement):\n                instance.plugin_class.add_inline_elements(instance, inlines)\n                instance.plugin_class.add_shared_reference(instance, shared_glossary)\n\n            # for some unknown reasons add_plugin sets instance.numchild to 0,\n            # but fixing and save()-ing 'instance' executes some filters in an unwanted manner\n            plugins_from_data(placeholder, instance, children_data)\n\n            if isinstance(instance, Text):\n                # we must convert the old plugin IDs into the new ones,\n                # otherwise links are not displayed\n                id_dict = dict(zip(\n                    plugin_tags_to_id_list(instance.body),\n                    (t[0] for t in instance.get_children().values_list('id'))\n                ))\n                instance.body = replace_plugin_tags(instance.body, id_dict)\n                instance.save()\n\n    language = get_language_from_request(request, check_path=True)\n\n    clipboard = request.toolbar.clipboard\n    ref_plugin = clipboard.cmsplugin_set.first()\n    if ref_plugin is None:\n        # the clipboard is empty\n        root_plugin = add_plugin(clipboard, 'PlaceholderPlugin', language, name='clipboard')\n    else:\n        # remove old entries from the clipboard\n        try:\n            root_plugin = ref_plugin.cms_placeholderreference\n        except PlaceholderReference.DoesNotExist:\n            root_plugin = add_plugin(clipboard, 'PlaceholderPlugin', language, name='clipboard')\n        else:\n            inst, _ = ref_plugin.get_plugin_instance()\n            inst.placeholder_ref.get_plugins().delete()\n    plugins_from_data(root_plugin.placeholder_ref, None, data['plugins'])\n"
  },
  {
    "path": "cmsplugin_cascade/cms_plugins.py",
    "content": "import sys\nfrom importlib import import_module\nfrom django.core.exceptions import ImproperlyConfigured\nfrom . import app_settings\n\n\nfor module in app_settings.CASCADE_PLUGINS:\n    try:\n        # if a module was specified, load all plugins in module settings\n        module_settings = import_module('{}.settings'.format(module))\n        module_plugins = getattr(module_settings, 'CASCADE_PLUGINS', [])\n        for p in module_plugins:\n            try:\n                import_module('{}.{}'.format(module, p))\n            except ImportError as err:\n                traceback = sys.exc_info()[2]\n                msg = \"Plugin {} as specified in {}.settings.CASCADE_PLUGINS could not be loaded: {}\"\n                raise ImproperlyConfigured(msg.format(p, module, err.with_traceback(traceback)))\n    except ImportError:\n        try:\n            # otherwise try with cms_plugins in the named module\n            import_module('{}.cms_plugins'.format(module))\n        except ImportError:\n            # otherwise just use the named module as plugin\n            import_module('{}'.format(module))\n"
  },
  {
    "path": "cmsplugin_cascade/cms_toolbars.py",
    "content": "from django.conf import settings\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.extensions.toolbar import ExtensionToolbar\nfrom cms.toolbar_pool import toolbar_pool\nfrom cms.toolbar.items import Break\nfrom cms.cms_toolbars import PAGE_MENU_SECOND_BREAK\nfrom cmsplugin_cascade.models import CascadePage\n\n\nclass CascadePageToolbar(ExtensionToolbar):\n    model = CascadePage\n\n    def populate(self):\n        current_page_menu = self._setup_extension_toolbar()\n        if current_page_menu:\n            # retrieves the instance of the current extension (if any) and the toolbar item URL\n            page_extension, url = self.get_page_extension_admin()\n            if url:\n                position = current_page_menu.find_first(Break, identifier=PAGE_MENU_SECOND_BREAK)\n                disabled = not self.toolbar.edit_mode_active\n                current_page_menu.add_modal_item(_(\"Extra Page Fields\"), position=position, url=url, disabled=disabled)\n\nif settings.CMSPLUGIN_CASCADE['register_page_editor']:\n    toolbar_pool.register(CascadePageToolbar)\n"
  },
  {
    "path": "cmsplugin_cascade/extra_fields/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "cmsplugin_cascade/extra_fields/admin.py",
    "content": "import re\n\nfrom django.conf import settings\nfrom django.contrib import admin\nfrom django.core.exceptions import ValidationError\nfrom django.forms import widgets\nfrom django.forms.fields import BooleanField, CharField, ChoiceField, MultipleChoiceField\nfrom django.forms.models import ModelForm\nfrom django.http.response import HttpResponse\nfrom django.template.loader import render_to_string\nfrom django.urls import re_path\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom entangled.forms import EntangledModelForm\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.fields import SizeField\nfrom cmsplugin_cascade.models import PluginExtraFields, TextEditorConfigFields, IconFont\nfrom cmsplugin_cascade.extra_fields.mixins import ExtraFieldsMixin\n\n\nclass PluginExtraFieldsForm(EntangledModelForm):\n    validation_pattern = re.compile(r'^[A-Za-z0-9_-]+$')\n\n    class_names = CharField(\n        label=_(\"CSS class names\"),\n        required=False,\n        widget=widgets.TextInput(attrs={'style': 'width: 50em;'}),\n        help_text=_(\"Freely selectable CSS classnames for this Plugin, separated by commas.\"),\n    )\n\n    multiple = BooleanField(\n        label=_(\"Allow multiple\"),\n        required=False,\n        help_text=_(\"Allow to select multiple of the above CSS classes.\"),\n    )\n\n    class Meta:\n        untangled_fields = ['plugin_type', 'site']\n        entangled_fields = {\n            'css_classes': ['class_names', 'multiple'],\n            'inline_styles': [],\n        }\n\n    def clean_class_names(self):\n        value = self.cleaned_data['class_names']\n        for val in value.split(','):\n            val = val.strip()\n            if val and not self.validation_pattern.match(val):\n                msg = _(\"CSS class '{}' contains invalid characters.\")\n                raise ValidationError(msg.format(val))\n        return value\n\n\nclass PluginExtraFieldsAdmin(admin.ModelAdmin):\n    list_display = ['name', 'module', 'site', 'allowed_classes_styles']\n    DISTANCE_UNITS = [\n        ('px,em,rem,%', _(\"px, em, rem and %\")),\n        ('px,em,%', _(\"px, em and %\")),\n        ('px,rem,em', _(\"px, rem and em\")),\n        ('px,em', _(\"px and em\")),\n        ('px,rem,%', _(\"px, rem and %\")),\n        ('px,%', _(\"px and %\")),\n        ('px,rem', _(\"px and rem\")),\n        ('px', _(\"px\")),\n        ('%,rem', _(\"% and rem\")),\n        ('%', _(\"%\")),\n        ('px,em,rem,%,auto', _(\"px, em, rem, % and auto\")),\n        ('px,em,%,auto', _(\"px, em, % and auto\")),\n        ('px,rem,em,auto', _(\"px, rem, em and auto\")),\n        ('px,em,auto', _(\"px, em and auto\")),\n        ('px,rem,%,auto', _(\"px, rem, % and auto\")),\n        ('px,%,,auto', _(\"px, % and auto\")),\n        ('px,rem,auto', _(\"px, rem and auto\")),\n        ('px,auto', _(\"px and auto\")),\n        ('%,rem,auto', _(\"%, rem and auto\")),\n        ('%,auto', _(\"% and auto\")),\n    ]\n\n    class Media:\n        css = {'all': ('cascade/css/admin/partialfields.css',)}\n\n    @cached_property\n    def plugins_for_site(self):\n        def show_in_backend(plugin):\n            try:\n                config = app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_fields'][plugin.__name__]\n            except KeyError:\n                return False\n            else:\n                assert issubclass(plugin, ExtraFieldsMixin), \"Expected plugin to be of type `ExtraFieldsMixin`.\"\n                return config.allow_override\n\n        cascade_plugins = set([p for p in plugin_pool.get_all_plugins() if show_in_backend(p)])\n        return [(p.__name__, '{}: {}'.format(p.module, str(p.name))) for p in cascade_plugins]\n\n    def get_form(self, request, obj=None, **kwargs):\n        form_fields = {\n            'plugin_type': ChoiceField(choices=self.plugins_for_site),\n        }\n        for style, choices_tuples in app_settings.CMSPLUGIN_CASCADE['extra_inline_styles'].items():\n            translated_style = gettext(style)\n            form_fields['extra_fields:{0}'.format(style)] = MultipleChoiceField(\n                label=_(\"Customized {0}:\").format(translated_style),\n                choices=[(c, c) for c in choices_tuples[0]],\n                required=False,\n                widget=widgets.CheckboxSelectMultiple,\n                help_text=_(\"Allow these extra inlines styles for the given plugin type.\"),\n            )\n            if issubclass(choices_tuples[1], SizeField):\n                form_fields['extra_units:{0}'.format(style)] = ChoiceField(\n                    label=_(\"Units for {0}:\").format(translated_style),\n                    choices=self.DISTANCE_UNITS,\n                    required=False,\n                    help_text=_(\"Allow these size units for customized {0} fields.\").format(translated_style),\n                )\n        inline_styles_fields = list(form_fields.keys())\n        form = type('PluginExtraFieldsForm', (PluginExtraFieldsForm,), form_fields)\n        form._meta.entangled_fields['inline_styles'] = inline_styles_fields\n        kwargs.setdefault('form', form)\n        return super().get_form(request, obj=None, **kwargs)\n\n    def has_add_permission(self, request):\n        \"\"\"\n        Only if at least one plugin uses the class ExtraFieldsMixin, allow to add an instance.\n        \"\"\"\n        has_permission = super().has_add_permission(request)\n        return has_permission and len(self.plugins_for_site) > 0\n\n    def module(self, obj):\n        return plugin_pool.get_plugin(obj.plugin_type).module\n    module.short_description = _(\"Module\")\n\n    def allowed_classes_styles(self, obj):\n        clsn = [cn for cn in obj.css_classes.get('class_names', '').split(',') if cn]\n        sef = [len(group) for ef, group in obj.inline_styles.items() if ef.startswith('extra_fields:')]\n        return \"{} / {}\".format(len(clsn), sum(sef))\n    allowed_classes_styles.short_description = _(\"Allowed Classes and Styles\")\n\nadmin.site.register(PluginExtraFields, PluginExtraFieldsAdmin)\n\n\nclass TextEditorConfigForm(ModelForm):\n    validation_pattern = re.compile(r'^[A-Za-z0-9_-]+$')\n\n    class Meta:\n        fields = ['name', 'element_type', 'css_classes']\n\n    def clean_css_classes(self):\n        css_classes = []\n        for val in self.cleaned_data['css_classes'].split(' '):\n            if val:\n                if self.validation_pattern.match(val.strip()):\n                    css_classes.append(val)\n                else:\n                    raise ValidationError(_(\"'%s' is not a valid CSS class name.\") % val)\n        return ' '.join(css_classes)\n\n\nclass TextEditorConfigAdmin(admin.ModelAdmin):\n    list_display = ['name', 'element_type']\n    form = TextEditorConfigForm\n\n    def get_urls(self):\n        return [\n            re_path(r'^wysiwig-config\\.js$', self.render_texteditor_config,\n                name='cascade_texteditor_config'),\n        ] + super().get_urls()\n\n    def render_texteditor_config(self, request):\n        context = {\n            'text_editor_configs': TextEditorConfigFields.objects.all(),\n        }\n        if 'cmsplugin_cascade.icon' in settings.INSTALLED_APPS:\n            context['icon_fonts'] = IconFont.objects.all()\n        javascript = render_to_string('cascade/admin/ckeditor.wysiwyg.txt', context)\n        return HttpResponse(javascript, content_type='application/javascript')\n\nadmin.site.register(TextEditorConfigFields, TextEditorConfigAdmin)\n"
  },
  {
    "path": "cmsplugin_cascade/extra_fields/config.py",
    "content": "\nclass PluginExtraFieldsConfig:\n    \"\"\"\n    Each Cascade Plugin can be configured to accept extra fields, such as an ID tag, one or more\n    CSS classes or inlines styles. It is possible to configure these fields globally using an\n    instance of this class, or to configure them on a per site base using the appropriate\n    admin's backend interface at:\n    ```\n    *Start › django CMS Cascade › Custom CSS classes and styles › PluginExtraFields*\n    ```\n    :param allow_id_tag:\n        If ``True``, allows to set the ``id`` attribute in HTML elements.\n\n    :param css_classes:\n        A dictionary containing:\n          ``class_names`` a comma separated string of allowed class names.\n          ``multiple`` a Boolean indicating if more multiple classes are allowed concurrently.\n\n    :param inline_styles:\n        A dictionary containing:\n\n    :param allow_override:\n        If ``True``, allows to override this configuration using the admin's backend interface.\n    \"\"\"\n    def __init__(self, allow_id_tag=False, css_classes=None, inline_styles=None, allow_override=True):\n        self.allow_id_tag = allow_id_tag\n        self.css_classes = dict(multiple='', class_names='') if css_classes is None else dict(css_classes)\n        self.inline_styles = {} if inline_styles is None else dict(inline_styles)\n        self.allow_override = allow_override\n"
  },
  {
    "path": "cmsplugin_cascade/extra_fields/mixins.py",
    "content": "from django.contrib.sites.shortcuts import get_current_site\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.forms import MediaDefiningClass, widgets\nfrom django.forms.fields import CharField, ChoiceField, MultipleChoiceField\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.fields import SizeField\n\n\nclass ExtraFieldsMixin(metaclass=MediaDefiningClass):\n    \"\"\"\n    If a Cascade plugin is listed in ``settings.CMSPLUGIN_CASCADE['plugins_with_extra_fields']``,\n    then this ``ExtraFieldsMixin`` class is added automatically to its plugin class in order to\n    offer extra fields for customizing CSS classes and styles.\n    \"\"\"\n\n    def __str__(self):\n        return self.plugin_class.get_identifier(self)\n\n    def get_form(self, request, obj=None, **kwargs):\n        from cmsplugin_cascade.models import PluginExtraFields\n        from .config import PluginExtraFieldsConfig\n\n        clsname = self.__class__.__name__\n        try:\n            site = get_current_site(request)\n            extra_fields = PluginExtraFields.objects.get(plugin_type=clsname, site=site)\n        except ObjectDoesNotExist:\n            extra_fields = app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_fields'].get(clsname)\n\n        if isinstance(extra_fields, (PluginExtraFields, PluginExtraFieldsConfig)):\n            form_fields = {}\n\n            # add a text input field to let the user name an ID tag for this HTML element\n            if extra_fields.allow_id_tag:\n                form_fields['extra_element_id'] = CharField(\n                    label=_(\"Named Element ID\"),\n                )\n\n            # add a select box to let the user choose one or more CSS classes\n            class_names, choices = extra_fields.css_classes.get('class_names'), None\n            if isinstance(class_names, (list, tuple)):\n                choices = [(clsname, clsname) for clsname in class_names]\n            elif isinstance(class_names, str):\n                choices = [(clsname, clsname) for clsname in class_names.replace(' ', ',').split(',') if clsname]\n            if choices:\n                if extra_fields.css_classes.get('multiple'):\n                    form_fields['extra_css_classes'] = MultipleChoiceField(\n                        label=_(\"Customized CSS Classes\"),\n                        choices=choices,\n                        required=False,\n                        widget=widgets.CheckboxSelectMultiple,\n                        help_text=_(\"Customized CSS classes to be added to this element.\"),\n                    )\n                else:\n                    choices.insert(0, (None, _(\"Select CSS\")))\n                    form_fields['extra_css_classes'] = ChoiceField(\n                        label=_(\"Customized CSS Class\"),\n                        choices=choices,\n                        required=False,\n                        help_text=_(\"Customized CSS class to be added to this element.\"),\n                    )\n\n            # add input fields to let the user enter styling information\n            for style, choices_list in app_settings.CMSPLUGIN_CASCADE['extra_inline_styles'].items():\n                inline_styles = extra_fields.inline_styles.get('extra_fields:{0}'.format(style))\n                if not inline_styles:\n                    continue\n                Field = choices_list[1]\n                for inline_style in inline_styles:\n                    key = 'extra_inline_styles:{0}'.format(inline_style)\n                    field_kwargs = {\n                        'label': '{0}: {1}'.format(style, inline_style),\n                        'required': False,\n                    }\n                    if issubclass(Field, SizeField):\n                        field_kwargs['allowed_units'] = extra_fields.inline_styles.get('extra_units:{0}'.format(style)).split(',')\n                    form_fields[key] = Field(**field_kwargs)\n\n            # extend the form with some extra fields\n            base_form = kwargs.pop('form', self.form)\n            assert issubclass(base_form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n            class Meta:\n                entangled_fields = {'glossary': list(form_fields.keys())}\n            form_fields['Meta'] = Meta\n            kwargs['form'] = type(base_form.__name__, (base_form,), form_fields)\n        return super().get_form(request, obj, **kwargs)\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        \"\"\"Enrich list of CSS classes with customized ones\"\"\"\n        css_classes = super().get_css_classes(obj)\n        extra_css_classes = obj.glossary.get('extra_css_classes')\n        if extra_css_classes:\n            if isinstance(extra_css_classes, str):\n                css_classes.append(extra_css_classes)\n            elif isinstance(extra_css_classes, (list, tuple)):\n                css_classes.extend(extra_css_classes)\n        return css_classes\n\n    @classmethod\n    def get_inline_styles(cls, obj):\n        \"\"\"Enrich inline CSS styles with customized ones\"\"\"\n        inline_styles = super().get_inline_styles(obj)\n        extra_inline_styles = app_settings.CMSPLUGIN_CASCADE['extra_inline_styles']\n        for key, eis in obj.glossary.items():\n            if key.startswith('extra_inline_styles:'):\n                _, prop = key.split(':')\n                if isinstance(eis, dict):\n                    # a multi value field, storing values as dict\n                    inline_styles.update(dict((k, v) for k, v in eis.items() if v))\n                elif isinstance(eis, (list, tuple)):\n                    # a multi value field, storing values as list\n                    for props, field_class in extra_inline_styles.values():\n                        if prop in props:\n                            inline_styles.update({prop: field_class.css_value(eis)})\n                            break\n                elif isinstance(eis, str):\n                    inline_styles.update({prop: eis})\n        return inline_styles\n\n    @classmethod\n    def get_html_tag_attributes(cls, obj):\n        attributes = super().get_html_tag_attributes(obj)\n        extra_element_id = obj.glossary.get('extra_element_id')\n        if extra_element_id:\n            attributes.update(id=extra_element_id)\n        return attributes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        identifier = super().get_identifier(obj)\n        extra_element_id = obj.glossary and obj.glossary.get('extra_element_id')\n        if extra_element_id:\n            return format_html('{0}<em>{1}:</em> ', identifier, extra_element_id)\n        return identifier\n"
  },
  {
    "path": "cmsplugin_cascade/fields.py",
    "content": "import re\nimport json\nimport warnings\n\nfrom django.apps import apps\nfrom django.core.exceptions import ValidationError\nfrom django.db.models.fields.related import ManyToOneRel\nfrom django.forms import widgets\nfrom django.forms.fields import Field, CharField, ChoiceField, BooleanField, MultiValueField\nfrom django.forms.utils import ErrorList\nfrom django.core.validators import ProhibitNullCharactersValidator\nfrom django.utils.deconstruct import deconstructible\nfrom django.utils.translation import gettext_lazy as _, pgettext\n\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.widgets import ColorPickerWidget, BorderChoiceWidget, MultipleTextInputWidget\nfrom filer.fields.image import FilerImageField, AdminImageFormField\nfrom filer.settings import settings as filer_settings\n\n\nclass GlossaryField:\n    \"\"\"\n    Deprecated.\n    Behave similar to django.forms.Field, encapsulating a partial dictionary, stored as\n    JSONField in the database.\n    \"\"\"\n    def __init__(self, widget, label=None, name=None, initial='', help_text='', error_class=ErrorList):\n        warnings.warn(\"GlossaryField is deprecated\")\n        self.name = name\n        if not isinstance(widget, widgets.Widget):\n            raise AttributeError('`widget` must inherit from django.forms.widgets.Widget')\n        if label is None:\n            label = name\n        self.widget = widget\n        self.label = label\n        self.initial = initial\n        self.help_text = help_text\n        self.error_class = error_class\n\n    def run_validators(self, value):\n        if not callable(getattr(self.widget, 'validate', None)):\n            return\n        errors = []\n        if callable(getattr(self.widget, '__iter__', None)):\n            for field_name in self.widget:\n                try:\n                    self.widget.validate(value.get(self.name), field_name)\n                except ValidationError as e:\n                    if isinstance(getattr(e, 'params', None), dict):\n                        e.params.update(label=self.label)\n                    messages = self.error_class([m for m in e.messages])\n                    errors.extend(messages)\n        else:\n            try:\n                self.widget.validate(value.get(self.name))\n            except ValidationError as e:\n                if isinstance(getattr(e, 'params', None), dict):\n                    e.params.update(label=self.label)\n                errors = self.error_class([m for m in e.messages])\n        if errors:\n            raise ValidationError(errors)\n\n    def get_element_ids(self, prefix_id):\n        \"\"\"\n        Returns a single or a list of element ids, one for each input widget of this field\n        \"\"\"\n        if isinstance(self.widget, widgets.MultiWidget):\n            ids = ['{0}_{1}_{2}'.format(prefix_id, self.name, field_name) for field_name in self.widget]\n        elif isinstance(self.widget, (widgets.SelectMultiple, widgets.RadioSelect)):\n            ids = ['{0}_{1}_{2}'.format(prefix_id, self.name, k) for k in range(len(self.widget.choices))]\n        else:\n            ids = ['{0}_{1}'.format(prefix_id, self.name)]\n        return ids\n\n\nclass BorderChoiceField(MultiValueField):\n    BORDER_STYLES = ['none', 'solid', 'dashed', 'dotted', 'double', 'groove', 'hidden',\n                     'inset', 'outset', 'ridge']\n\n    def __init__(self, *args, **kwargs):\n        choices = [(s, s) for s in self.BORDER_STYLES]\n        with_alpha = kwargs.pop('with_alpha', app_settings.CMSPLUGIN_CASCADE['color_picker_with_alpha'])\n        widget = kwargs.pop('widget', BorderChoiceWidget(choices, with_alpha))\n        fields = [\n            SizeField(),\n            ChoiceField(choices=choices),\n            CharField(),\n        ]\n        kwargs['initial'] = ['0px', 'none', '#000000']\n        super().__init__(fields=fields, widget=widget, *args, **kwargs)\n\n    def prepare_value(self, value):\n        return value\n\n    def compress(self, data_list):\n        return data_list\n\n    @classmethod\n    def css_value(self, values):\n        return ' '.join(values)\n\n\nclass SelectTextAlignField(ChoiceField):\n    CHOICES = [('left', 'left'), ('center', 'center'), ('right', 'right'), ('justify', 'justify')]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(choices=self.CHOICES, *args, **kwargs)\n\n\nclass SelectOverflowField(ChoiceField):\n    CHOICES = [('auto', 'auto'), ('scroll', 'scroll'), ('hidden', 'hidden')]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(choices=self.CHOICES, *args, **kwargs)\n\n\n@deconstructible\nclass ColorValidator():\n    message = _(\"'%(color)s' is not a valid color code.\")\n    code = 'invalid_color_code'\n\n    def __init__(self, with_alpha):\n        if with_alpha:\n            self.validation_pattern = re.compile(r'(#(?:[0-9a-fA-F]{2}){2,4}|(#[0-9a-fA-F]{3})|(rgb|hsl)a?\\((-?\\d+%?[,\\s]+){2,3}\\s*[\\d\\.]+%?\\))')\n        else:\n            self.validation_pattern = re.compile(r'(#(?:[0-9a-fA-F]{2}){2,3}|(#[0-9a-fA-F]{3})|(rgb|hsl))')\n\n    def __call__(self, value):\n        color, inherit = value\n        match = self.validation_pattern.match(color)\n        if not (inherit or match):\n            params = {'color': color}\n            raise ValidationError(self.message, code=self.code, params=params)\n\n    def __eq__(self, other):\n        return (\n            isinstance(other, self.__class__) and\n            self.validation_pattern == other.validation_pattern and\n            self.message == other.message and\n            self.code == other.code\n        )\n\n\nclass ColorField(MultiValueField):\n    DEFAULT_COLOR = '#808080'\n\n    def __init__(self, inherit_color=False, default_color=DEFAULT_COLOR, *args, **kwargs):\n        kwargs.pop('required', None)\n        with_alpha = kwargs.pop('with_alpha', app_settings.CMSPLUGIN_CASCADE['color_picker_with_alpha'])\n        widget = kwargs.pop('widget', ColorPickerWidget(with_alpha))\n        fields = [\n            CharField(initial=default_color),\n            BooleanField(initial=inherit_color, required=False),\n        ]\n        kwargs['initial'] = [default_color, inherit_color]\n        super().__init__(fields=fields, widget=widget, *args, **kwargs)\n        self.validators.append(ColorValidator(with_alpha))\n        self.validators.append(ProhibitNullCharactersValidator())\n\n    def compress(self, data_list):\n        self.run_validators(data_list)\n        return data_list\n\n    @classmethod\n    def css_value(self, values):\n        return values[0]\n\n\n@deconstructible\nclass SizeUnitValidator():\n    allowed_units = []\n    message = _(\"'%(value)s' is not a valid size unit. Allowed units are: %(allowed_units)s.\")\n    code = 'invalid_size_unit'\n\n    def __init__(self, allowed_units=None, allow_negative=True):\n        possible_units = ['rem', 'px', 'em', '%', 'auto']\n        if allowed_units is None:\n            self.allowed_units = possible_units\n        else:\n            self.allowed_units = [au for au in allowed_units if au in possible_units]\n        units_with_value = list(self.allowed_units)\n        if 'auto' in self.allowed_units:\n            self.allow_auto = True\n            units_with_value.remove('auto')\n        else:\n            self.allow_auto = False\n        if allow_negative:\n            patterns = r'^(-?\\d+(\\.\\d+)?)({})$'.format('|'.join(units_with_value))\n        else:\n            patterns = r'^(\\d+(\\.\\d+)?)({})$'.format('|'.join(units_with_value))\n        self.validation_pattern = re.compile(patterns)\n\n    def __call__(self, value):\n        if self.allow_auto and value == 'auto':\n            return\n        match = self.validation_pattern.match(value)\n        try:\n            float(match.group(1))\n        except (AttributeError, ValueError):\n            allowed_units = \" {} \".format(pgettext('allowed_units', \"or\")).join(\"'{}'\".format(u) for u in self.allowed_units)\n            params = {'value': value, 'allowed_units': allowed_units}\n            raise ValidationError(self.message, code=self.code, params=params)\n\n    def __eq__(self, other):\n        return (\n            isinstance(other, self.__class__) and\n            self.allowed_units == other.allowed_units and\n            self.message == other.message and\n            self.code == other.code\n        )\n\n\nclass SizeField(Field):\n    \"\"\"\n    Use this field for validating input containing a value ending in ``px``, ``em``, ``rem`` or ``%``.\n    Use it for values representing a size, margin, padding, width or height.\n    \"\"\"\n    def __init__(self, *, allowed_units=None, **kwargs):\n        self.empty_value = ''\n        super().__init__(**kwargs)\n        self.validators.append(SizeUnitValidator(allowed_units))\n        self.validators.append(ProhibitNullCharactersValidator())\n\n    def to_python(self, value):\n        \"\"\"Return a stripped string.\"\"\"\n        if value not in self.empty_values:\n            value = str(value).strip()\n        if value in self.empty_values:\n            return self.empty_value\n        return value\n\n\nclass MultiSizeField(MultiValueField):\n    \"\"\"\n    Some size input fields must be specified per Bootstrap breakpoint. Use this multiple\n    input field to handle this.\n    \"\"\"\n    def __init__(self, properties, sublabels=None, *args, **kwargs):\n        required = kwargs.pop('required', False)\n        require_all_fields = kwargs.pop('require_all_fields', required)\n        initial = kwargs.pop('initial', None)\n        if isinstance(initial, (list, tuple)):\n            if len(initial) != len(properties):\n                raise ValueError(\"The number of initial values must be {}.\".format(len(properties)))\n            initial = dict(zip(properties, initial))\n        elif not isinstance(initial, dict):\n            initial = {prop: initial for prop in properties}\n        allowed_units = kwargs.pop('allowed_units', None)\n        fields = [SizeField(required=required, allowed_units=allowed_units)] * len(properties)\n        if sublabels is None:\n            sublabels = properties\n        widget = MultipleTextInputWidget(sublabels)\n        super().__init__(fields=fields, widget=widget, required=required,\n                         require_all_fields=require_all_fields, initial=initial, *args, **kwargs)\n        self.properties = list(properties)\n\n    def prepare_value(self, value):\n        \"\"\"Transform dict from DB into list\"\"\"\n        if isinstance(value, dict):\n            return [value.get(prop) for prop in self.properties]\n        return value\n\n    def compress(self, data_list):\n        \"\"\"Transform list into dict for DB\"\"\"\n        return {prop: value for prop, value in zip(self.properties, data_list)}\n\n\nclass HiddenDictField(Field):\n    widget = widgets.HiddenInput\n\n    def prepare_value(self, value):\n        \"\"\"Transform dict from DB into list\"\"\"\n        if isinstance(value, dict):\n            return json.dumps(value)\n        return value\n\n    def clean(self, value):\n        try:\n            return json.loads(value)\n        except:\n            raise ValidationError(\"Invalid Field Value\")\n\n\nclass CascadeImageField(AdminImageFormField):\n    def __init__(self, *args, **kwargs):\n        model_name_tuple = filer_settings.FILER_IMAGE_MODEL.split('.')\n        Image = apps.get_model(*model_name_tuple, require_ready=False)\n        kwargs.setdefault('label', _(\"Image\"))\n        super().__init__(\n            ManyToOneRel(FilerImageField, Image, 'file_ptr'),\n            Image.objects.all(),\n            'image_file', *args, **kwargs)\n"
  },
  {
    "path": "cmsplugin_cascade/forms.py",
    "content": "from django.forms.formsets import DELETION_FIELD_NAME\nfrom django.forms.models import ModelForm\n\nfrom entangled.forms import EntangledModelFormMixin, EntangledModelForm\n\n\nclass ManageChildrenFormMixin:\n    \"\"\"\n    Classes derived from ``CascadePluginBase`` can optionally add this mixin class to their form,\n    offering the input field ``num_children`` in their plugin editor. The content of this field is\n    not persisted in the plugin's model.\n    It allows the client to modify the number of children attached to this plugin.\n    \"\"\"\n    class Meta:\n        fields = ['num_children']\n\n    def __init__(self, *args, **kwargs):\n        instance = kwargs.get('instance')\n        if instance:\n            initial = {'num_children': instance.get_num_children()}\n            kwargs.update(initial=initial)\n        super().__init__(*args, **kwargs)\n\n\nclass CascadeModelFormMixin(EntangledModelFormMixin):\n    \"\"\"\n    Classes inheriting from InlineAdmin and defining their own `form` shall use this special\n    variant of an `EntangledModelForm`, otherwise deletion of inlined elements does not work.\n    \"\"\"\n    def _clean_form(self):\n        internal_fields = ['cascade_element', 'id', DELETION_FIELD_NAME]\n        internal_cleaned_data = {key: val for key, val in self.cleaned_data.items() if key in internal_fields}\n        super()._clean_form()\n        self.cleaned_data.update(internal_cleaned_data)\n\n\nclass CascadeModelForm(CascadeModelFormMixin, ModelForm):\n    \"\"\"\n    A convenience class to create a ModelForms to be used with djangocms-cascade\n    \"\"\"\n"
  },
  {
    "path": "cmsplugin_cascade/generic/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/generic/custom_snippet.py",
    "content": "from django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.plugin_pool import plugin_pool\n\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase, TransparentContainer\n\n\nclass CustomSnippetPlugin(TransparentContainer, CascadePluginBase):\n    \"\"\"\n    Allows to add a customized template anywhere. This plugins will be registered only if the\n    project added a template using the configuration setting 'plugins_with_extra_render_templates'.\n    \"\"\"\n    name = _(\"Custom Snippet\")\n    require_parent = False\n    allow_children = True\n    alien_child_classes = True\n    render_template_choices = dict(app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_render_templates'].get('CustomSnippetPlugin', ()))\n    render_template = 'cascade/generic/does_not_exist.html'  # default in case the template could not be found\n\n    @classmethod\n    def get_identifier(cls, instance):\n        render_template = instance.glossary.get('render_template')\n        if render_template:\n            return format_html('{}', cls.render_template_choices.get(render_template))\n\n\nif CustomSnippetPlugin.render_template_choices:\n    # register only, if at least one template has been defined\n    plugin_pool.register_plugin(CustomSnippetPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/generic/heading.py",
    "content": "from django.forms import widgets, CharField, ChoiceField\nfrom django.utils.html import format_html\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.plugin_pool import plugin_pool\nfrom entangled.forms import EntangledModelFormMixin\n\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\nclass HeadingFormMixin(EntangledModelFormMixin):\n    TAG_TYPES = [('h{}'.format(k), _(\"Heading {}\").format(k)) for k in range(1, 7)]\n\n    tag_type = ChoiceField(\n        choices=TAG_TYPES,\n        label=_(\"Structure Level\"),\n    )\n\n    content = CharField(\n        label=_(\"Content\"),\n        widget=widgets.TextInput(attrs={'style': 'width: calc(100% - 12em); padding-right: 0; font-weight: bold; font-size: 125%;'}),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['tag_type', 'content']}\n\n\nclass HeadingPlugin(CascadePluginBase):\n    name = _(\"Heading\")\n    parent_classes = None\n    allow_children = False\n    form = HeadingFormMixin\n    render_template = 'cascade/generic/heading.html'\n\n    @classmethod\n    def get_identifier(cls, instance):\n        tag_type = instance.glossary.get('tag_type')\n        content = mark_safe(instance.glossary.get('content', ''))\n        if tag_type:\n            return format_html('<code>{0}</code>: {1}', tag_type, content)\n        return content\n\n    def render(self, context, instance, placeholder):\n        context = self.super(HeadingPlugin, self).render(context, instance, placeholder)\n        context.update({'content': mark_safe(instance.glossary.get('content', ''))})\n        return context\n\nplugin_pool.register_plugin(HeadingPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/generic/horizontal_rule.py",
    "content": "from django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\nclass HorizontalRulePlugin(CascadePluginBase):\n    name = _(\"Horizontal Rule\")\n    parent_classes = None\n    allow_children = False\n    tag_type = 'hr'\n    render_template = 'cascade/generic/single.html'\n\nplugin_pool.register_plugin(HorizontalRulePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/generic/mixins.py",
    "content": "from django.core.exceptions import ValidationError, ObjectDoesNotExist\nfrom django.forms.fields import CharField\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\n\nfrom entangled.forms import EntangledModelFormMixin\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.models import CascadePage\n\n\nclass SectionFormMixin(EntangledModelFormMixin):\n    element_id = CharField(\n        label=_(\"Id\"),\n        max_length=15,\n        required=False,\n        help_text=_(\"A unique identifier for this element (please don't use any special characters, punctuations, etc.) May be used as anchor-link: #id.\")\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['element_id']}\n\n    def clean_element_id(self):\n        element_id = self.cleaned_data['element_id']\n        self.check_unique_element_id(self.instance, element_id)\n        return element_id\n\n    @classmethod\n    def check_unique_element_id(cls, instance, element_id):\n        \"\"\"\n        Check for uniqueness of the given element_id for the current page.\n        Return None if instance is not yet associated with a page.\n        \"\"\"\n        if not element_id:\n            return\n        try:\n            element_ids = instance.placeholder.page.cascadepage.glossary['element_ids'][instance.language]\n        except (AttributeError, KeyError, ObjectDoesNotExist):\n            return\n        else:\n            for key, value in element_ids.items():\n                if str(key) != str(instance.pk) and element_id == value:\n                    msg = _(\"The element ID '{}' is not unique for this page.\")\n                    raise ValidationError(msg.format(element_id))\n\n\nclass SectionModelMixin:\n    def element_id(self):\n        id_attr = self.glossary.get('element_id')\n        if id_attr:\n            return '{bookmark_prefix}{0}'.format(id_attr, **app_settings.CMSPLUGIN_CASCADE)\n\n    def delete(self, *args, **kwargs):\n        try:\n            self.placeholder.page.cascadepage.glossary['element_ids'][self.language].pop(str(self.pk))\n        except (AttributeError, KeyError, ObjectDoesNotExist):\n            pass\n        else:\n            self.placeholder.page.cascadepage.save()\n        super().delete(*args, **kwargs)\n\n\nclass SectionMixin:\n    def get_form(self, request, obj=None, **kwargs):\n        form = kwargs.get('form', self.form)\n        assert issubclass(form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n        kwargs['form'] = type(form.__name__, (SectionFormMixin, form), {})\n        return super().get_form(request, obj, **kwargs)\n\n    @classmethod\n    def get_identifier(cls, instance):\n        try:\n            element_id = instance.glossary['element_id'][instance.language]\n        except (KeyError, TypeError):\n            pass\n        else:\n            if element_id:\n                return format_html('<code>id=\"{0}\"</code>', element_id)\n        return super().get_identifier(instance)\n\n    def save_model(self, request, obj, form, change):\n        super().save_model(request, obj, form, change)\n        element_id = obj.glossary['element_id']\n        if not change:\n            # when adding a new element, `element_id` can not be validated for uniqueness\n            postfix = 0\n            # check if form simplewrapper has function check_unique_element_id\n            if not 'check_unique_element_id' in dir(form):\n                form_ = SectionFormMixin\n            else:\n                form_ = form\n            while True:\n                try:\n                    form_.check_unique_element_id(obj, element_id)\n                except ValidationError:\n                    postfix += 1\n                    element_id = '{element_id}_{0}'.format(postfix, **obj.glossary)\n                else:\n                    break\n            if postfix:\n                obj.glossary['element_id'] = element_id\n                obj.save()\n\n        cms_page = obj.placeholder.page\n        if cms_page:\n            # storing the element_id on a placholder only makes sense, if it is non-static\n            CascadePage.assure_relation(cms_page)\n            cms_page.cascadepage.glossary.setdefault('element_ids', {})\n            cms_page.cascadepage.glossary['element_ids'].setdefault(obj.language, {})\n            cms_page.cascadepage.glossary['element_ids'][obj.language][str(obj.pk)] = element_id\n            cms_page.cascadepage.save()\n"
  },
  {
    "path": "cmsplugin_cascade/generic/settings.py",
    "content": "CASCADE_PLUGINS = ['custom_snippet', 'heading', 'horizontal_rule', 'simple_wrapper', 'text_image']\n\ndef set_defaults(config):\n    from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig\n\n    config.setdefault('plugins_with_extra_fields', {})\n    plugins_with_extra_fields = config['plugins_with_extra_fields']\n    plugins_with_extra_fields.setdefault('HorizontalRulePlugin', PluginExtraFieldsConfig(\n        inline_styles={\n            'extra_fields:Border': ['border-top'],\n            'extra_fields:Border Radius': ['border-radius'],\n            'extra_units:Border Radius': 'px,rem',\n        },\n        allow_override=False,\n    ))\n"
  },
  {
    "path": "cmsplugin_cascade/generic/simple_wrapper.py",
    "content": "from django.forms import ChoiceField\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.plugin_pool import plugin_pool\nfrom entangled.forms import EntangledModelFormMixin\n\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase, TransparentContainer\n\n\nclass SimpleWrapperFormMixin(EntangledModelFormMixin):\n    TAG_CHOICES = [(cls, _(\"<{}> – Element\").format(cls)) for cls in ['div', 'span', 'section', 'article']] + \\\n                  [('naked', _(\"Naked Wrapper\"))]\n\n    tag_type = ChoiceField(\n        choices=TAG_CHOICES,\n        label=_(\"HTML element tag\"),\n        help_text=_('Choose a type for this HTML element.')\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['tag_type']}\n\n\nclass SimpleWrapperPlugin(TransparentContainer, CascadePluginBase):\n    name = _(\"Simple Wrapper\")\n    parent_classes = None\n    require_parent = False\n    form = SimpleWrapperFormMixin\n    allow_children = True\n    alien_child_classes = True\n\n    @classmethod\n    def get_identifier(cls, instance):\n        identifier = super().get_identifier(instance)\n        tag_name = dict(SimpleWrapperFormMixin.TAG_CHOICES).get(instance.glossary.get('tag_type'))\n        if tag_name:\n            return format_html('{0} {1}', tag_name, identifier)\n        return identifier\n\n    def get_render_template(self, context, instance, placeholder):\n        if instance.glossary.get('tag_type') == 'naked':\n            return 'cascade/generic/naked.html'\n        return 'cascade/generic/wrapper.html'\n\nplugin_pool.register_plugin(SimpleWrapperPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/generic/text_image.py",
    "content": "from django.forms import widgets, ChoiceField, MultipleChoiceField\nfrom django.utils.html import format_html_join\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.fields import SizeField\nfrom cmsplugin_cascade.image import ImageFormMixin, ImagePropertyMixin\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\nfrom cmsplugin_cascade.utils import compute_aspect_ratio\n\n\nclass TextImageFormMixin(ImageFormMixin):\n    RESIZE_OPTIONS = [\n        ('upscale', _(\"Upscale image\")),\n        ('crop', _(\"Crop image\")),\n        ('subject_location', _(\"With subject location\")),\n        ('high_resolution', _(\"Optimized for Retina\")),\n    ]\n\n    image_width = SizeField(\n        label=_(\"Image Width\"),\n        allowed_units=['px'],\n        required=True,\n        help_text=_(\"Set the image width in pixels.\"),\n    )\n\n    image_height = SizeField(\n        label=_(\"Image Height\"),\n        allowed_units=['px'],\n        required=False,\n        help_text=_(\"Set the image height in pixels.\"),\n    )\n\n    resize_options = MultipleChoiceField(\n        label=_(\"Resize Options\"),\n        choices = RESIZE_OPTIONS,\n        required=False,\n        widget=widgets.CheckboxSelectMultiple,\n        help_text=_(\"Options to use when resizing the image.\"),\n        initial=['subject_location', 'high_resolution']\n    )\n\n    alignement = ChoiceField(\n        label=_(\"Alignement\"),\n        choices=[('', _(\"Not aligned\")), ('left', _(\"Left\")), ('right', _(\"Right\"))],\n        required=False,\n        widget=widgets.RadioSelect,\n        initial='',\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['image_width', 'image_height', 'resize_options', 'alignement']}\n\n\nclass TextImagePlugin(LinkPluginBase):\n    name = _(\"Image in text\")\n    text_enabled = True\n    ring_plugin = 'TextImagePlugin'\n    render_template = 'cascade/plugins/textimage.html'\n    parent_classes = ['TextPlugin']\n    model_mixins = (ImagePropertyMixin, LinkElementMixin)\n    allow_children = False\n    require_parent = False\n    form = type('TextImageForm', (LinkFormMixin, TextImageFormMixin), {'require_link': False})\n    html_tag_attributes = LinkPluginBase.html_tag_attributes\n    html_tag_attributes.update({'image_title': 'title', 'alt_tag': 'alt'})\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/textimageplugin.js']\n\n    @classmethod\n    def requires_parent_plugin(cls, slot, page):\n        \"\"\"\n        Workaround for `PluginPool.get_all_plugins()`, otherwise TextImagePlugin is not allowed\n        as a child of a `TextPlugin`.\n        \"\"\"\n        return False\n\n    @classmethod\n    def get_inline_styles(cls, instance):\n        inline_styles = cls.super(TextImagePlugin, cls).get_inline_styles(instance)\n        alignement = instance.glossary.get('alignement')\n        if alignement:\n            inline_styles['float'] = alignement\n        return inline_styles\n\n    def render(self, context, instance, placeholder):\n        context = self.super(TextImagePlugin, self).render(context, instance, placeholder)\n        try:\n            aspect_ratio = compute_aspect_ratio(instance.image)\n        except Exception:\n            # if accessing the image file fails, abort here\n            return context\n        resize_options = instance.glossary.get('resize_options', {})\n        crop = 'crop' in resize_options\n        upscale = 'upscale' in resize_options\n        subject_location = instance.image.subject_location if 'subject_location' in resize_options else False\n        high_resolution = 'high_resolution' in resize_options\n        image_width = instance.glossary.get('image_width', '')\n        if not image_width.endswith('px'):\n            return context\n        image_width = int(image_width.rstrip('px'))\n        image_height = instance.glossary.get('image_height', '')\n        if image_height.endswith('px'):\n            image_height = int(image_height.rstrip('px'))\n        else:\n            image_height = int(round(image_width * aspect_ratio))\n        context['src'] = {\n            'size': (image_width, image_height),\n            'size2x': (image_width * 2, image_height * 2),\n            'crop': crop,\n            'upscale': upscale,\n            'subject_location': subject_location,\n            'high_resolution': high_resolution,\n        }\n        link_attributes = LinkPluginBase.get_html_tag_attributes(instance)\n        context['link_html_tag_attributes'] = format_html_join(' ', '{0}=\"{1}\"',\n            [(attr, val) for attr, val in link_attributes.items() if val]\n        )\n        return context\n\nplugin_pool.register_plugin(TextImagePlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/hide_plugins.py",
    "content": "from django.core.exceptions import ImproperlyConfigured\nfrom django.forms.fields import BooleanField\nfrom django.utils.translation import gettext_lazy as _\nfrom django.template import engines\n\nfrom entangled.forms import EntangledModelFormMixin\n\n\nclass HidePluginFormMixin(EntangledModelFormMixin):\n    hide_plugin = BooleanField(\n        label=_(\"Hide element\"),\n        required=False,\n        help_text=_(\"Hide this element and all of it's descendants from the web-page.\")\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['hide_plugin']}\n\n\nclass HidePluginMixin:\n    \"\"\"\n    This mixin class adds a checkbox to each named plugin, which if checked hides that\n    plugin during the rendering phase.\n    \"\"\"\n    suppress_template = engines['django'].from_string('')\n    hiding_template_string = '''\n{{% load cms_tags %}}\n<div style=\"display: none;\">\n{{% for plugin in instance.child_plugin_instances %}}{{% render_plugin plugin %}}{{% endfor %}}\n</div>\n<style>\ndiv.cms .cms-structure .cms-draggable-{plugin_id} .cms-dragitem {{\ncolor: gray;\nbackground-color: lightgray;\nbackground-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, rgba(255,255,255,.5) 4px, rgba(255,255,255,.5) 8px);\nbackground-size: contain;\n}}\n</style>\n'''\n\n    def get_form(self, request, obj=None, **kwargs):\n        form = kwargs.get('form', self.form)\n        assert issubclass(form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n        kwargs['form'] = type(form.__name__, (HidePluginFormMixin, form), {})\n        return super().get_form(request, obj, **kwargs)\n\n    def get_render_template(self, context, instance, placeholder):\n        if instance.glossary.get('hide_plugin'):\n            if self.in_edit_mode(context['request'], placeholder):\n                # in edit mode we actually must render the children, otherwise they won't show\n                # up in Structure Mode\n                template_string = self.hiding_template_string.format(plugin_id=instance.pk)\n                return engines['django'].from_string(template_string)\n            else:\n                return self.suppress_template\n\n        super_self = super(HidePluginMixin, self)\n        if hasattr(super_self, 'get_render_template'):\n            template = super_self.get_render_template(context, instance, placeholder)\n        elif getattr(self, 'render_template', False):\n            template = getattr(self, 'render_template', False)\n        else:\n            template = None\n        if not template:\n            raise ImproperlyConfigured(\"Plugin {} has no render_template.\".format(self.__class__))\n        return template\n"
  },
  {
    "path": "cmsplugin_cascade/icon/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/icon/admin.py",
    "content": "from django.contrib import admin\nfrom django.core.exceptions import ValidationError\nfrom django.forms import Media\nfrom django.forms.models import ModelForm\nfrom django.utils.html import format_html, format_html_join\nfrom django.utils.text import format_lazy\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\nfrom cmsplugin_cascade.models import IconFont\nfrom cmsplugin_cascade.icon.utils import zipfile, unzip_archive\n\n\nclass UploadIconsForms(ModelForm):\n    class Meta:\n        fields = ['identifier', 'zip_file', 'is_default']\n\n    @property\n    def media(self):\n        media = super().media\n        try:\n            css_url = self.instance.get_stylesheet_url()\n            media += Media(css={'all': ['cascade/css/admin/iconfont.css', css_url]})\n        except AttributeError:\n            pass\n        return media\n\n    def clean(self):\n        fontello_url = 'http://fontello.com/'\n        cleaned_data = super().clean()\n        if 'zip_file' in self.changed_data:\n            try:\n                label = cleaned_data['zip_file'].label\n                zip_ref = zipfile.ZipFile(cleaned_data['zip_file'].file.file, 'r')\n                cleaned_data.update(zip(['font_folder', 'config_data'], unzip_archive(label, zip_ref)))\n            except Exception as exc:\n                raise ValidationError(format_lazy(_(\"Can not unzip uploaded archive {}: {}.\"), label, exc))\n            finally:\n                zip_ref.close()\n            try:\n                css_prefix_text = cleaned_data['config_data']['css_prefix_text']\n            except KeyError:\n                raise ValidationError(format_lazy(\n                    _(\"File does not seem to originate from <a href=\\\"{url}\\\" target=\\\"_blank\\\">{url}</a>.\"),\n                    url=fontello_url,\n                ))\n            other_icon_fonts = IconFont.objects.all()\n            if self.instance:\n                other_icon_fonts = other_icon_fonts.exclude(id=self.instance.id)\n            for icon_font in other_icon_fonts:\n                if icon_font.config_data['css_prefix_text'] == css_prefix_text:\n                    msg = format_lazy(_(\n                        \"Icon Font '{icon_font}' already uses CSS prefix '{css_prefix}'.<br/>\" \\\n                        \"Please reload the font from <a href=\\\"{url}\\\" target=\\\"_blank\\\">{url}</a> \" \\\n                        \"using another CSS prefix.\"),\n                        icon_font=icon_font.identifier, css_prefix=css_prefix_text, url=fontello_url)\n                    raise ValidationError(mark_safe(msg))\n        return cleaned_data\n\n\n@admin.register(IconFont)\nclass IconFontAdmin(admin.ModelAdmin):\n    form = UploadIconsForms\n    list_display = ['identifier', 'is_default', 'num_icons', 'css_prefix']\n    readonly_fields = ['css_prefix']\n\n    def save_model(self, request, obj, form, change):\n        if 'font_folder' in form.cleaned_data and 'config_data' in form.cleaned_data:\n            obj.font_folder = form.cleaned_data['font_folder']\n            obj.config_data = form.cleaned_data['config_data']\n        # at least one icon font must be set as default\n        if self.model.objects.filter(is_default=True).count() < 1:\n            obj.is_default = True\n        super().save_model(request, obj, form, change)\n        if obj.is_default is True and 'is_default' in form.changed_data:\n            # maximum one icon font can be set as default\n            self.model.objects.exclude(id=obj.id).update(is_default=False)\n\n    def get_readonly_fields(self, request, obj=None):\n        readonly_fields = list(super().get_readonly_fields(request, obj=obj))\n        if obj:\n            readonly_fields.append('preview_icons')\n        return readonly_fields\n\n    def preview_icons(self, obj):\n        families = obj.get_icon_families()\n        format_string = '<li title=\"{{0}}\"><i class=\"{css_prefix_text}{{0}}\"></i></li>'.format(**obj.config_data)\n        return format_html('<div class=\"preview-iconfont\">{}</div>',\n            format_html_join('\\n', '<h2>{}</h2><ul>{}</ul>',\n                 ((src.title(), format_html_join('', format_string, ((g,) for g in glyphs)))\n                 for src, glyphs in families.items())))\n    preview_icons.short_description = _(\"Preview Icons\")\n\n    def num_icons(self, obj):\n        try:\n            return len(obj.config_data['glyphs'])\n        except (KeyError, TypeError):\n            return '–'\n    num_icons.short_description = _(\"Number of Icons\")\n\n    def css_prefix(self, obj):\n        try:\n            return obj.config_data['css_prefix_text']\n        except (KeyError, TypeError):\n            return '–'\n    css_prefix.short_description = _(\"CSS prefix\")\n"
  },
  {
    "path": "cmsplugin_cascade/icon/forms.py",
    "content": "from django.forms import widgets, CharField, ModelChoiceField\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cmsplugin_cascade.models import IconFont\n\nfrom entangled.forms import EntangledModelFormMixin\n\n\ndef get_default_icon_font():\n    try:\n        return IconFont.objects.get(is_default=True).id\n    except IconFont.DoesNotExist:\n        return ''\n\n\nclass IconFormMixin(EntangledModelFormMixin):\n    icon_font = ModelChoiceField(\n        IconFont.objects.all(),\n        label=_(\"Font\"),\n        initial=get_default_icon_font,\n    )\n\n    symbol = CharField(\n        widget=widgets.HiddenInput(),\n        label=_(\"Select Symbol\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['icon_font', 'symbol']}\n\n    def __init__(self, *args, **kwargs):\n        if not getattr(self, 'require_icon', True):\n            self.declared_fields['icon_font'].required = False\n            self.declared_fields['icon_font'].empty_label = _(\"No Icon\")\n            self.declared_fields['icon_font'].initial = None\n            self.declared_fields['symbol'].required = False\n        super().__init__(*args, **kwargs)\n"
  },
  {
    "path": "cmsplugin_cascade/icon/plugin_base.py",
    "content": "from django.utils.safestring import mark_safe\nfrom cmsplugin_cascade.models import IconFont\nfrom cmsplugin_cascade.plugin_base import CascadePluginMixinBase\nfrom entangled.utils import get_related_object\n\n\nclass IconPluginMixin(CascadePluginMixinBase):\n    change_form_template = 'cascade/admin/change_form.html'\n    ring_plugin = 'IconPluginMixin'\n\n    class Media:\n        css = {'all': ['cascade/css/admin/iconplugin.css']}\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/iconpluginmixin.js']\n\n    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):\n        extra_context = dict(extra_context or {}, icon_fonts=IconFont.objects.all())\n        return super().changeform_view(request, object_id, form_url, extra_context)\n\n    def render(self, context, instance, placeholder):\n        context = super().render(context, instance, placeholder)\n        icon_font = get_related_object(instance.glossary, 'icon_font')\n        symbol = instance.glossary.get('symbol')\n        if icon_font and symbol:\n            prefix = icon_font.config_data.get('css_prefix_text', 'icon-')\n            context.update({\n                'stylesheet_url': icon_font.get_stylesheet_url(),\n                'icon_font_class': mark_safe('{}{}'.format(prefix, symbol)),\n            })\n        return context\n"
  },
  {
    "path": "cmsplugin_cascade/icon/settings.py",
    "content": "CASCADE_PLUGINS = ['simpleicon', 'texticon']\n"
  },
  {
    "path": "cmsplugin_cascade/icon/simpleicon.py",
    "content": "from django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\nfrom cmsplugin_cascade.icon.forms import IconFormMixin\nfrom cmsplugin_cascade.icon.plugin_base import IconPluginMixin\n\n\nclass SimpleIconPlugin(IconPluginMixin, LinkPluginBase):\n    name = _(\"Simple Icon\")\n    parent_classes = None\n    require_parent = False\n    allow_children = False\n    render_template = 'cascade/plugins/simpleicon.html'\n    form = type('SimpleIconForm', (LinkFormMixin, IconFormMixin), {'require_link': False})\n    model_mixins = (LinkElementMixin,)\n    ring_plugin = 'IconPlugin'\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/iconplugin.js']\n\nplugin_pool.register_plugin(SimpleIconPlugin)\n\n"
  },
  {
    "path": "cmsplugin_cascade/icon/texticon.py",
    "content": "from django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\nfrom cmsplugin_cascade.icon.forms import IconFormMixin\nfrom cmsplugin_cascade.icon.plugin_base import IconPluginMixin\n\n\nclass TextIconPlugin(IconPluginMixin, LinkPluginBase):\n    \"\"\"\n    This plugin is intended to be used inside the django-CMS-CKEditor.\n    \"\"\"\n    name = _(\"Icon in text\")\n    text_enabled = True\n    render_template = 'cascade/plugins/texticon.html'\n    ring_plugin = 'IconPlugin'\n    parent_classes = ['TextPlugin']\n    form = type('TextIconForm', (LinkFormMixin, IconFormMixin), {'require_link': False})\n    model_mixins = (LinkElementMixin,)\n    allow_children = False\n    require_parent = False\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/iconplugin.js']\n\n    @classmethod\n    def requires_parent_plugin(cls, slot, page):\n        return False\n\nplugin_pool.register_plugin(TextIconPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/icon/utils.py",
    "content": "import os, io, json, shutil\nfrom django.core.exceptions import SuspiciousFileOperation\nfrom cmsplugin_cascade import app_settings\n\nimport tempfile\ntry:\n    import czipfile as zipfile\nexcept ImportError:\n    import zipfile\n\n\ndef unzip_archive(label, zip_ref):\n    common_prefix = os.path.commonprefix(zip_ref.namelist())\n    if not common_prefix:\n        raise SuspiciousFileOperation(\"The zip archive {} is not packed correctly\".format(label))\n    icon_font_root = app_settings.CMSPLUGIN_CASCADE['icon_font_root']\n    try:\n        try:\n            os.makedirs(icon_font_root)\n        except os.error:\n            pass  # the directory exists already\n        temp_folder = tempfile.mkdtemp(prefix='', dir=icon_font_root)\n        for member in zip_ref.infolist():\n            zip_ref.extract(member, temp_folder)\n        font_folder = os.path.join(temp_folder, common_prefix)\n\n        # this is specific to fontello\n        with io.open(os.path.join(font_folder, 'config.json'), 'r') as fh:\n            config_data = json.load(fh)\n    except Exception as exc:\n        shutil.rmtree(temp_folder, ignore_errors=True)\n        raise SuspiciousFileOperation(\"Can not unzip uploaded archive {}: {}\".format(label, exc))\n    return os.path.relpath(font_folder, icon_font_root), config_data\n"
  },
  {
    "path": "cmsplugin_cascade/image.py",
    "content": "from django.forms.fields import CharField\nfrom django.utils.translation import gettext_lazy as _\n\nfrom entangled.forms import EntangledModelFormMixin, EntangledField\nfrom entangled.utils import get_related_object\nfrom cmsplugin_cascade.fields import CascadeImageField\n\n\nclass ImageFormMixin(EntangledModelFormMixin):\n    image_file = CascadeImageField()\n\n    image_title = CharField(\n        label=_('Image Title'),\n        required=False,\n        help_text=_(\"Caption text added to the 'title' attribute of the <img> element.\"),\n    )\n\n    alt_tag = CharField(\n        label=_('Alternative Description'),\n        required=False,\n        help_text=_(\"Textual description of the image added to the 'alt' tag of the <img> element.\"),\n    )\n\n    _image_properties = EntangledField()\n\n    class Meta:\n        entangled_fields = {'glossary': ['image_file', 'image_title', 'alt_tag', '_image_properties']}\n\n    def __init__(self, *args, **kwargs):\n        if not getattr(self, 'require_image', True):\n            self.base_fields['image_file'].required = False\n        super().__init__(*args, **kwargs)\n\n    def clean_image_file(self):\n        image_file = self.cleaned_data['image_file']\n        # _image_properties are just a cached representation, maybe useless\n        if image_file:\n            self.cleaned_data['_image_properties'] = {\n                'width': image_file._width,\n                'height': image_file._height,\n                'exif_orientation': image_file.exif.get('Orientation', 1),\n            }\n        return image_file\n\n\nclass ImagePropertyMixin:\n    \"\"\"\n    A mixin class to convert a CascadeElement into a proxy model for rendering an image element.\n    \"\"\"\n    def __str__(self):\n        try:\n            return self.plugin_class.get_identifier(self)\n        except AttributeError:\n            return str(self.image)\n\n    @property\n    def image(self):\n        if not hasattr(self, '_image_file'):\n            self._image_file = get_related_object(self.glossary, 'image_file')\n        return self._image_file\n\n    def post_copy(self, old_instance, new_old_ziplist):\n        # by saving this model after the full tree has been copied, ``<Any>ImagePlugin.sanitize_model()``\n        # is invoked a second time with the now complete information of all column siblings.\n        self.save(sanitize_only=True)\n"
  },
  {
    "path": "cmsplugin_cascade/leaflet/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/leaflet/map.py",
    "content": "import json\n\nfrom django.forms import widgets\nfrom django.forms.fields import CharField, BooleanField\nfrom django.db.models.fields.related import ManyToOneRel\nfrom django.contrib.admin import StackedInline\nfrom django.core.exceptions import ValidationError\nfrom django.utils.html import strip_tags, strip_spaces_between_tags\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import ngettext_lazy, gettext_lazy as _\nfrom filer.fields.image import FilerImageField, AdminImageFormField\nfrom filer.settings import settings as filer_settings\nfrom filer.utils.loader import load_model\nfrom cms.plugin_pool import plugin_pool\nfrom djangocms_text_ckeditor.fields import HTMLFormField\n\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.fields import HiddenDictField, SizeField, MultiSizeField\nfrom cmsplugin_cascade.forms import CascadeModelForm, CascadeModelFormMixin\nfrom cmsplugin_cascade.image import ImagePropertyMixin\nfrom cmsplugin_cascade.mixins import WithInlineElementsMixin\nfrom cmsplugin_cascade.models import InlineCascadeElement\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase, create_proxy_model\nfrom cmsplugin_cascade.utils import compute_aspect_ratio, get_image_size, parse_responsive_length\n\nImage = load_model(filer_settings.FILER_IMAGE_MODEL)\n\n\nclass MarkerModelMixin:\n    @property\n    def data(self):\n        return mark_safe(json.dumps(self.glossary))\n\n\nclass MarkerForm(CascadeModelForm):\n    title = CharField(\n        label=_(\"Marker Title\"),\n        widget=widgets.TextInput(attrs={'size': 60}),\n        help_text=_(\"Please choose a title, then go to the map to set a marker pin.\")\n    )\n\n    use_icon = BooleanField(\n        label=_(\"Use customized marker icon\"),\n        initial=False,\n        required=False,\n    )\n\n    marker_image = AdminImageFormField(\n        ManyToOneRel(FilerImageField, Image, 'file_ptr'),\n        Image.objects.all(),\n        label=_(\"Icon\"),\n        required=False,\n        to_field_name='image_file',\n    )\n\n    marker_width = SizeField(\n        label=_(\"Icon Width\"),\n        allowed_units=['px'],\n        required=False,\n        help_text=_(\"Width of the marker icon in pixels.\"),\n    )\n\n    marker_anchor = MultiSizeField(\n        ['left', 'top'],\n        label=_(\"Marker Anchor\"),\n        sublabels=[_(\"Left\"), _(\"Top\")],\n        allowed_units=['px', '%'],\n        required=False,\n        help_text=_(\"Coordinates of the icon's anchor relative to its top left corner.\"),\n    )\n\n    popup_text = HTMLFormField(\n        required=False,\n        help_text=_(\"Text to display in popup.\"),\n    )\n\n    position = HiddenDictField()\n\n    address_lookup = CharField(\n        label=_(\"Address lookup\"),\n        required=False,\n        help_text=_(\"Search for an address\"),\n        widget=widgets.TextInput(attrs={'size': 100}),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['title', 'use_icon', 'marker_image', 'marker_width', 'marker_anchor',\n                                         'popup_text', 'position']}\n\n    def clean(self):\n        cleaned_data = super().clean()\n        try:\n            position = cleaned_data['position']\n            if isinstance(position, str):\n                position = json.loads(position)\n            elif not isinstance(position, dict):\n                raise ValueError\n        except (ValueError, KeyError):\n            raise ValidationError(\"Invalid internal position data. Check your Javascript imports.\")\n        else:\n            if 'lat' not in position or 'lng' not in position:\n                # place the marker in the center of the current map\n                position = {k: v for k, v in self.instance.cascade_element.glossary['map_position'].items()\n                            if k in ['lat', 'lng']}\n                cleaned_data['position'] = position\n\n        popup_text = cleaned_data.pop('popup_text', '')\n        if strip_tags(popup_text):\n            cleaned_data['popup_text'] = strip_spaces_between_tags(popup_text)\n        return cleaned_data\n\n\nclass MarkerInline(StackedInline):\n    model = InlineCascadeElement\n    form = MarkerForm\n    raw_id_fields = ['marker_image']\n    verbose_name = _(\"Marker\")\n    verbose_name_plural = _(\"Markers\")\n    extra = 0\n\n\nclass LeafletFormMixin(CascadeModelFormMixin):\n    map_width = SizeField(\n        label=_(\"Map Width\"),\n        allowed_units=['px', '%'],\n        initial='100%',\n        help_text=_(\"Set the map width in percent relative to containing element.\"),\n    )\n\n    map_height = SizeField(\n        label=_(\"Adapt Map Height\"),\n        allowed_units=['px', '%'],\n        initial='400px',\n        help_text=_(\"Set a fixed height in pixels, or percent relative to the map width.\"),\n    )\n\n    map_min_height = SizeField(\n        label=_(\"Adapt Map Minimum Height\"),\n        allowed_units=['px'],\n        required=False,\n        help_text=_(\"Optional, set a minimum height in pixels.\"),\n    )\n\n    scroll_wheel_zoom = BooleanField(\n        label=_(\"Zoom by scrolling wheel\"),\n        initial=True,\n        required=False,\n        help_text=_(\"Zoom into map on mouse over by scrolling wheel.\"),\n    )\n\n    map_position = HiddenDictField(\n        initial=app_settings.CMSPLUGIN_CASCADE['leaflet']['default_position'],\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['map_width', 'map_height', 'map_position', 'map_min_height',\n                                         'scroll_wheel_zoom']}\n\n    def clean(self):\n        cleaned_data = super().clean()\n        try:\n            if isinstance(cleaned_data['map_position'], str):\n                cleaned_data['map_position'] = json.loads(cleaned_data['map_position'])\n            elif not isinstance(cleaned_data['map_position'], dict):\n                raise ValueError\n        except (ValueError, KeyError):\n            raise ValidationError(\"Invalid internal position data. Check your Javascript imports.\")\n        return cleaned_data\n\n\nclass LeafletModelMixin:\n    @property\n    def map_position(self):\n        return mark_safe(json.dumps(self.glossary.get('map_position', {})))\n\n\nclass LeafletPlugin(WithInlineElementsMixin, CascadePluginBase):\n    name = _(\"Map\")\n    parent_classes = None\n    require_parent = False\n    allow_children = False\n    change_form_template = 'cascade/admin/leaflet_plugin_change_form.html'\n    ring_plugin = 'LeafletPlugin'\n    form = LeafletFormMixin\n    admin_preview = False\n    render_template = 'cascade/plugins/leaflet.html'\n    inlines = [MarkerInline]\n    model_mixins = (LeafletModelMixin,)\n    settings = mark_safe(json.dumps(app_settings.CMSPLUGIN_CASCADE['leaflet']))\n\n    class Media:\n        css = {'all': [\n            'node_modules/leaflet/dist/leaflet.css',\n            'node_modules/leaflet-easybutton/src/easy-button.css',\n            'cascade/css/admin/leafletplugin.css',\n        ]}\n        js = [\n            'node_modules/leaflet/dist/leaflet.js',\n            'node_modules/leaflet-easybutton/src/easy-button.js',\n            'admin/js/jquery.init.js',\n            'cascade/js/admin/leafletplugin.js',\n        ]\n\n    def add_view(self, request, form_url='', extra_context=None):\n        extra_context = dict(extra_context or {}, settings=self.settings)\n        return super().add_view(request, form_url, extra_context)\n\n    def change_view(self, request, object_id, form_url='', extra_context=None):\n        extra_context = dict(extra_context or {}, settings=self.settings)\n        return super().change_view(request, object_id, form_url, extra_context)\n\n    def render(self, context, instance, placeholder):\n        marker_instances = []\n        for inline_element in instance.inline_elements.all():\n            try:\n                ProxyModel = create_proxy_model('LeafletMarker',\n                                                (ImagePropertyMixin, MarkerModelMixin),\n                                                InlineCascadeElement,\n                                                module=__name__)\n                marker = ProxyModel(id=inline_element.id, glossary=inline_element.glossary)\n                try:\n                    aspect_ratio = compute_aspect_ratio(marker.image)\n                    width = parse_responsive_length(marker.glossary.get('marker_width') or '25px')\n                    marker.size = list(get_image_size(width[0], (None, None), aspect_ratio))\n                    marker.size2x = 2 * marker.size[0], 2 * marker.size[1]\n                except Exception:\n                    # if accessing the image file fails, skip size computations\n                    pass\n                else:\n                    try:\n                        marker_anchor = marker.glossary['marker_anchor']\n                        top = parse_responsive_length(marker_anchor['top'])\n                        left = parse_responsive_length(marker_anchor['left'])\n                        if top[0] is None or left[0] is None:\n                            left = width[0] * left[1]\n                            top = width[0] * aspect_ratio * top[1]\n                        else:\n                            left, top = left[0], top[0]\n                        marker.anchor = [left, top]\n                    except Exception:\n                        pass\n                marker_instances.append(marker)\n            except (KeyError, AttributeError):\n                pass\n\n        context.update(dict(instance=instance,\n                            placeholder=placeholder,\n                            settings=self.settings,\n                            config=app_settings.CMSPLUGIN_CASCADE['leaflet'],\n                            markers=marker_instances))\n        return context\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        css_classes = cls.super(LeafletPlugin, cls).get_css_classes(obj)\n        css_class = obj.glossary.get('css_class')\n        if css_class:\n            css_classes.append(css_class)\n        return css_classes\n\n    @classmethod\n    def get_identifier(cls, obj):\n        num_elems = obj.inline_elements.count()\n        content = ngettext_lazy(\"with {0} marker\", \"with {0} markers\", num_elems).format(num_elems)\n        return mark_safe(content)\n\n    @classmethod\n    def get_data_representation(cls, instance):\n        data = super().get_data_representation(instance)\n        data.update(inlines=[ie.glossary for ie in instance.inline_elements.all()])\n        return data\n\nplugin_pool.register_plugin(LeafletPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/leaflet/settings.py",
    "content": "from django.utils.safestring import mark_safe\n\nCASCADE_PLUGINS = ['map']\n\ndef set_defaults(config):\n    config.setdefault('leaflet', {})\n    config['leaflet'].setdefault('tilesURL', 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'),\n    config['leaflet'].setdefault('default_position', {'lat': 30.0, 'lng': -40.0, 'zoom': 3})\n    config['leaflet'].setdefault('id', 'mapbox/streets-v11'),\n    config['leaflet'].setdefault('maxZoom', 18),\n    config['leaflet'].setdefault('tileSize', 512)\n    config['leaflet'].setdefault('zoomOffset', -1)\n    config['leaflet'].setdefault('detectRetina', True)\n    config['leaflet'].setdefault('attribution', mark_safe('Map data &copy; <a href=\"http://openstreetmap.org\">OpenStreetMap</a>')),\n    config['leaflet'].setdefault('addressLookupURL', 'https://nominatim.openstreetmap.org/search'),\n"
  },
  {
    "path": "cmsplugin_cascade/link/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/link/cms_plugins.py",
    "content": "from django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\nfrom cmsplugin_cascade.link.forms import TextLinkFormMixin\nfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\n\n\nclass TextLinkPlugin(LinkPluginBase):\n    name = _(\"Link\")\n    model_mixins = (LinkElementMixin,)\n    text_enabled = True\n    render_template = 'cascade/link/text-link.html'\n    ring_plugin = 'TextLinkPlugin'\n    form = type('TextLinkForm', (LinkFormMixin, TextLinkFormMixin), {})\n    parent_classes = ['TextPlugin']\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/textlinkplugin.js']\n\n    @classmethod\n    def get_identifier(cls, obj):\n        return mark_safe(obj.glossary.get('link_content', ''))\n\n    @classmethod\n    def requires_parent_plugin(cls, slot, page):\n        \"\"\"\n        Workaround for `PluginPool.get_all_plugins()`, otherwise TextLinkPlugin is not allowed\n        as a child of a `TextPlugin`.\n        \"\"\"\n        return False\n\nplugin_pool.register_plugin(TextLinkPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/link/config.py",
    "content": "from django.utils.module_loading import import_string\nfrom cmsplugin_cascade import app_settings\n\n\nLinkPluginBase, LinkFormMixin = (import_string(cls)\n    for cls in app_settings.CMSPLUGIN_CASCADE['link_plugin_classes'])\n"
  },
  {
    "path": "cmsplugin_cascade/link/forms.py",
    "content": "from django.core.exceptions import ObjectDoesNotExist, ValidationError\nfrom django.contrib.admin.sites import site as admin_site\nfrom django.db.models.fields.related import ManyToOneRel\nfrom django.forms import fields, Media\nfrom django.forms.models import ModelChoiceField\nfrom django.forms.widgets import RadioSelect, URLInput\nfrom django.utils.html import format_html\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\nfrom django_select2.forms import HeavySelect2Widget\n\nfrom cms.utils import get_current_site\nfrom cms.models import Page\nfrom entangled.forms import EntangledModelFormMixin\nfrom entangled.utils import get_related_object\nfrom filer.models.filemodels import File as FilerFileModel\nfrom filer.fields.file import AdminFileWidget, FilerFileField\n\ntry:\n    from phonenumber_field.formfields import PhoneNumberField\nexcept ImportError:\n    PhoneNumberField = None\n\n\ndef format_page_link(title, path):\n    html = format_html(\"{} ({})\", mark_safe(title), path)\n    return html\n\n\nclass PageSelect2Widget(HeavySelect2Widget):\n    def __init__(self, *args, **kwargs):\n        kwargs.setdefault('data_view', 'admin:get_published_pagelist')\n        super().__init__(*args, **kwargs)\n\n    @property\n    def media(self):\n        parent_media = super().media\n        # append jquery.init.js to enforce select2.js into the global 'jQuery' namespace\n        js = list(parent_media._js) + ['admin/js/jquery.init.js']\n        return Media(css=parent_media._css, js=js)\n\n    def render(self, *args, **kwargs):\n        # replace self.choices by an empty list to prevent building an optgroup for all pages\n        try:\n            page = Page.objects.get(pk=kwargs['value'])\n        except (Page.DoesNotExist, ValueError, KeyError):\n            self.choices = []\n        else:\n            self.choices = [(kwargs['value'], str(page))]\n        return super().render(*args, **kwargs)\n\n\nclass LinkSearchField(ModelChoiceField):\n    widget = PageSelect2Widget()\n\n    def __init__(self, *args, **kwargs):\n        queryset = Page.objects.public()\n        try:\n            queryset = queryset.published().on_site(get_current_site()).distinct()\n        except:\n            pass\n        kwargs.setdefault('queryset', queryset)\n        super().__init__(*args, **kwargs)\n\n    def clean(self, value):\n        self.queryset = Page.objects.public().distinct().published().on_site(get_current_site())\n        return super().clean(value)\n\n\nclass SectionChoiceField(fields.ChoiceField):\n    def __init__(self, *args, **kwargs):\n        kwargs.setdefault('choices', [('', _(\"Page Root\"))])\n        super().__init__(*args, **kwargs)\n\n    def valid_value(self, value):\n        \"\"\"\n        The optgroup is adjusted dynamically accroding to the selected cms_page, so always returns True\n        and let `LinkForm` validate this value.\n        \"\"\"\n        return True\n\n\nclass LinkForm(EntangledModelFormMixin):\n    LINK_TYPE_CHOICES = [\n        ('cmspage', _(\"CMS Page\")),\n        ('download', _(\"Download File\")),\n        ('exturl', _(\"External URL\")),\n        ('email', _(\"Mail To\")),\n    ]\n    if PhoneNumberField:\n        LINK_TYPE_CHOICES.append(('phonenumber', _(\"Phone number\")))\n\n    LINK_TARGETS = [\n        ('', _(\"Same Window\")),\n        ('_blank', _(\"New Window\")),\n        ('_parent', _(\"Parent Window\")),\n        ('_top', _(\"Topmost Frame\")),\n    ]\n\n    link_type = fields.ChoiceField(\n        label=_(\"Link\"),\n        help_text=_(\"Type of link\"),\n    )\n\n    cms_page = LinkSearchField(\n        required=False,\n        label='',\n        help_text=_(\"An internal link onto any CMS page of this site\"),\n    )\n\n    section = SectionChoiceField(\n        required=False,\n        label='',\n        help_text=_(\"Page bookmark\"),\n    )\n\n    download_file = ModelChoiceField(\n        label='',\n        queryset=FilerFileModel.objects.all(),\n        widget=AdminFileWidget(ManyToOneRel(FilerFileField, FilerFileModel, 'id'), admin_site),\n        required=False,\n        help_text=_(\"An internal link onto a file from filer\"),\n    )\n\n    ext_url = fields.URLField(\n        required=False,\n        label=_(\"URL\"),\n        help_text=_(\"Link onto external page\"),\n        widget=URLInput(attrs={'size': 100}),\n    )\n\n    mail_to = fields.EmailField(\n        required=False,\n        label=_(\"Email\"),\n        help_text=_(\"Open Email program with this address\"),\n    )\n\n    if PhoneNumberField:\n        phone_number = PhoneNumberField(\n            required=False,\n            label=_(\"Phone Number\"),\n            help_text=_(\"International phone number, ex. +1 212 555 2368.\"),\n        )\n\n    link_target = fields.ChoiceField(\n        choices=LINK_TARGETS,\n        label=_(\"Link Target\"),\n        widget=RadioSelect,\n        required=False,\n        help_text=_(\"Open Link in other target.\"),\n    )\n\n    link_title = fields.CharField(\n        label=_(\"Title\"),\n        required=False,\n        help_text=_(\"Link's Title\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['link_type', 'cms_page', 'section', 'download_file', 'ext_url', 'mail_to',\n                                         'link_target', 'link_title']}\n        if PhoneNumberField:\n            entangled_fields['glossary'].append('phone_number')\n\n    def __init__(self, *args, **kwargs):\n        link_type_choices = []\n        if not getattr(self, 'require_link', True):\n            link_type_choices.append(('', _(\"No Link\")))\n            self.declared_fields['link_type'].required = False\n        link_type_choices.extend(self.LINK_TYPE_CHOICES)\n        self.declared_fields['link_type'].choices = link_type_choices\n        self.declared_fields['link_type'].initial = link_type_choices[0][0]\n        instance = kwargs.get('instance')\n        if instance and instance.glossary.get('link_type') == 'cmspage':\n            self._preset_section(instance)\n        super().__init__(*args, **kwargs)\n\n    def _preset_section(self, instance):\n        \"\"\"\n        Field ``cms_page`` may refer onto any CMS page, which itself may contain bookmarks. This method\n        creates the list of bookmarks.\n        \"\"\"\n        self.base_fields['section'].choices = self.base_fields['section'].choices[:1]\n        try:\n            cascade_page = get_related_object(instance.glossary, 'cms_page').cascadepage\n            for key, val in cascade_page.glossary['element_ids'][instance.language].items():\n                if val:\n                    self.base_fields['section'].choices.append((key, val))\n        except (AttributeError, KeyError, ObjectDoesNotExist):\n            pass\n\n    def _post_clean(self):\n        super()._post_clean()\n        empty_fields = [None, '']\n        link_type = self.cleaned_data['glossary'].get('link_type')\n        if link_type == 'cmspage':\n            if self.cleaned_data['glossary'].get('cms_page', False) in empty_fields:\n                error = ValidationError(_(\"CMS page to link to is missing.\"), code='required')\n                self.add_error('cms_page', error)\n        elif link_type == 'download':\n            if self.cleaned_data['glossary'].get('download_file', False) in empty_fields:\n                error = ValidationError(_(\"File for download is missing.\"), code='required')\n                self.add_error('download_file', error)\n        elif link_type == 'exturl':\n            ext_url = self.cleaned_data['glossary'].get('ext_url', False)\n            if ext_url in empty_fields:\n                error = ValidationError(_(\"No valid URL provided.\"), code='required')\n                self.add_error('ext_url', error)\n        elif link_type == 'email':\n            if self.cleaned_data['glossary'].get('mail_to', False) in empty_fields:\n                error = ValidationError(_(\"No email address provided.\"), code='required')\n                self.add_error('mail_to', error)\n        elif link_type == 'phonenumber':\n            if self.cleaned_data['glossary'].get('phone_number', False) in empty_fields:\n                error = ValidationError(_(\"No phone number provided.\"), code='required')\n                self.add_error('phone_number', error)\n\n    def clean_phone_number(self):\n        return str(self.cleaned_data['phone_number'])\n\n    @classmethod\n    def unset_required_for(cls, sharable_fields):\n        \"\"\"\n        Fields borrowed by `SharedGlossaryAdmin` to build its temporary change form, only are\n        required if they are declared in `sharable_fields`. Otherwise just deactivate them.\n        \"\"\"\n        if 'link_content' in cls.base_fields and 'link_content' not in sharable_fields:\n            cls.base_fields['link_content'].required = False\n        if 'link_type' in cls.base_fields and 'link' not in sharable_fields:\n            cls.base_fields['link_type'].required = False\n\n\nclass TextLinkFormMixin(EntangledModelFormMixin):\n    link_content = fields.CharField(\n        label=_(\"Link Content\"),\n        widget=fields.TextInput(attrs={'id': 'id_name'}),  # replace auto-generated id so that CKEditor automatically transfers the text into this input field\n        help_text=_(\"Content of Link\"),\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['link_content']}\n"
  },
  {
    "path": "cmsplugin_cascade/link/plugin_base.py",
    "content": "from django.core.exceptions import ObjectDoesNotExist\nfrom django.utils.functional import cached_property\nfrom django.utils.safestring import mark_safe\n\nfrom entangled.utils import get_related_object\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\nfrom filer.models.filemodels import File as FilerFileModel\n\n\nclass LinkPluginBase(CascadePluginBase):\n    allow_children = False\n    parent_classes = []\n    require_parent = False\n    ring_plugin = 'LinkPluginBase'\n    raw_id_fields = ['download_file']\n    html_tag_attributes = {'title': 'title', 'target': 'target'}\n\n    class Media:\n        css = {'all': ['cascade/css/admin/linkplugin.css']}\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/linkplugin.js']\n\n    @classmethod\n    def get_link(cls, obj):\n        linktype = obj.glossary.get('link_type')\n        if linktype == 'exturl':\n            return '{ext_url}'.format(**obj.glossary)\n        if linktype == 'email':\n            return 'mailto:{mail_to}'.format(**obj.glossary)\n        if linktype == 'phonenumber':\n            return 'tel:{phone_number}'.format(**obj.glossary)\n\n        # otherwise resolve by model\n        if linktype == 'cmspage':\n            relobj = get_related_object(obj.glossary, 'cms_page')\n            if relobj:\n                href = relobj.get_absolute_url()\n                try:\n                    element_ids = relobj.cascadepage.glossary['element_ids'][obj.language]\n                    section = obj.glossary['section']\n                    href = '{}#{}'.format(href, element_ids[section])\n                except (KeyError, ObjectDoesNotExist):\n                    pass\n                return href\n            else:\n                return 'javascript:void(0)'\n        elif linktype == 'download':\n            relobj = get_related_object(obj.glossary, 'download_file')\n            if isinstance(relobj, FilerFileModel):\n                return relobj.url\n            else:\n                return 'javascript:void(0)'\n        return linktype\n\n\nclass DefaultLinkPluginBase(LinkPluginBase):\n    \"\"\"\n    The default `LinkPluginBase` class. It is injected by the class creator in link.config\n    \"\"\"\n    ring_plugin = 'LinkPluginBase'\n\n\nclass LinkElementMixin:\n    \"\"\"\n    A mixin class to convert a CascadeElement into a proxy model for rendering the ``<a>`` element.\n    Note that a Link inside the Text Editor Plugin is rendered using ``str(instance)`` rather\n    than ``instance.content``.\n    \"\"\"\n    def __str__(self):\n        return self.plugin_class.get_identifier(self)\n\n    @property\n    def link(self):\n        return self.plugin_class.get_link(self)\n\n    @property\n    def content(self):\n        return mark_safe(self.glossary.get('link_content', ''))\n\n    @cached_property\n    def download_name(self):\n        link_type = self.glossary.get('link_type')\n        if link_type == 'download':\n            relobj = get_related_object(self.glossary, 'download_file')\n            if isinstance(relobj, FilerFileModel):\n                return mark_safe(relobj.original_filename)\n"
  },
  {
    "path": "cmsplugin_cascade/locale/de/LC_MESSAGES/django.po",
    "content": "# djangocms-cascade\n# Copyright (C) 2021 Jacob Rief\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2022-08-09 13:24+0200\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n#: app_settings.py:125 app_settings.py:129\nmsgid \"default\"\nmsgstr \"Standard\"\n\n#: app_settings.py:126\nmsgid \"with line break\"\nmsgstr \"mit Zeilenumbruch\"\n\n#: app_settings.py:130\nmsgid \"Google Map\"\nmsgstr \"\"\n\n#: apps.py:12\nmsgid \"django CMS Cascade\"\nmsgstr \"\"\n\n#: bootstrap4/accordion.py:20\nmsgid \"Groups\"\nmsgstr \"Gruppen\"\n\n#: bootstrap4/accordion.py:21\nmsgid \"Number of groups for this accordion.\"\nmsgstr \"Anzahl der Gruppen für dieses Akkordeon.\"\n\n#: bootstrap4/accordion.py:25\nmsgid \"Close others\"\nmsgstr \"Schließe die anderen\"\n\n#: bootstrap4/accordion.py:28\nmsgid \"Open only one card at a time.\"\nmsgstr \"Öffne nur jeweils eine Kachel.\"\n\n#: bootstrap4/accordion.py:32\nmsgid \"First open\"\nmsgstr \"Erstes geöffnet\"\n\n#: bootstrap4/accordion.py:35\nmsgid \"Start with the first card open.\"\nmsgstr \"Beginne mit der ersten Kachel geöffnet.\"\n\n#: bootstrap4/accordion.py:44\nmsgid \"Accordion\"\nmsgstr \"Akkordeon\"\n\n#: bootstrap4/accordion.py:56\n#, python-brace-format\nmsgid \"with {0} card\"\nmsgid_plural \"with {0} cards\"\nmsgstr[0] \"mit {0} Kachel\"\nmsgstr[1] \"mit {0} Kacheln\"\n\n#: bootstrap4/accordion.py:77 generic/heading.py:29\nmsgid \"Heading\"\nmsgstr \"Überschrift\"\n\n#: bootstrap4/accordion.py:82\nmsgid \"Body with padding\"\nmsgstr \"Body in Innenabstand\"\n\n#: bootstrap4/accordion.py:85\nmsgid \"Add standard padding to card body.\"\nmsgstr \"Füge Innenabstand to Kachelinhalt hinzu\"\n\n#: bootstrap4/accordion.py:96\nmsgid \"Accordion Group\"\nmsgstr \"Akkordeon Gruppe\"\n\n#: bootstrap4/buttons.py:29 bootstrap4/buttons.py:38\nmsgid \"Primary\"\nmsgstr \"Primär\"\n\n#: bootstrap4/buttons.py:30 bootstrap4/buttons.py:39\nmsgid \"Secondary\"\nmsgstr \"Sekundär\"\n\n#: bootstrap4/buttons.py:31 bootstrap4/buttons.py:40\nmsgid \"Success\"\nmsgstr \"Erfolg\"\n\n#: bootstrap4/buttons.py:32 bootstrap4/buttons.py:41\nmsgid \"Danger\"\nmsgstr \"Achtung\"\n\n#: bootstrap4/buttons.py:33 bootstrap4/buttons.py:42\nmsgid \"Warning\"\nmsgstr \"Warnung\"\n\n#: bootstrap4/buttons.py:34 bootstrap4/buttons.py:43\nmsgid \"Info\"\nmsgstr \"Info\"\n\n#: bootstrap4/buttons.py:35 bootstrap4/buttons.py:44\nmsgid \"Light\"\nmsgstr \"Hell\"\n\n#: bootstrap4/buttons.py:36 bootstrap4/buttons.py:45\nmsgid \"Dark\"\nmsgstr \"Dunkel\"\n\n#: bootstrap4/buttons.py:37 bootstrap4/buttons.py:46 link/cms_plugins.py:11\n#: link/forms.py:101\nmsgid \"Link\"\nmsgstr \"Link\"\n\n#: bootstrap4/buttons.py:50\nmsgid \"Large button\"\nmsgstr \"Großer Button\"\n\n#: bootstrap4/buttons.py:51\nmsgid \"Default button\"\nmsgstr \"Standard Button\"\n\n#: bootstrap4/buttons.py:52\nmsgid \"Small button\"\nmsgstr \"Kleiner Button\"\n\n#: bootstrap4/buttons.py:57\nmsgid \"Button Content\"\nmsgstr \"Button Inhalt\"\n\n#: bootstrap4/buttons.py:62\nmsgid \"Button Type\"\nmsgstr \"Button Type\"\n\n#: bootstrap4/buttons.py:66\nmsgid \"Display Link using this Button Style\"\nmsgstr \"Anzeige des Links mit diesem Button Style\"\n\n#: bootstrap4/buttons.py:70\nmsgid \"Button Size\"\nmsgstr \"Button Größe\"\n\n#: bootstrap4/buttons.py:75\nmsgid \"Display Link using this Button Size\"\nmsgstr \"Anzeige des Links mit dieser Button Größe\"\n\n#: bootstrap4/buttons.py:79\nmsgid \"Button Options\"\nmsgstr \"Button Optionen\"\n\n#: bootstrap4/buttons.py:81\nmsgid \"Block level\"\nmsgstr \"Blockebene\"\n\n#: bootstrap4/buttons.py:82\nmsgid \"Disabled\"\nmsgstr \"Deaktiviert\"\n\n#: bootstrap4/buttons.py:89\nmsgid \"Stretched link\"\nmsgstr \"\"\n\n#: bootstrap4/buttons.py:91\nmsgid \"\"\n\"Stretched-link utility to make any anchor the size of it’s nearest position: \"\n\"relative parent, perfect for entirely clickable cards!\"\nmsgstr \"\"\n\n#: bootstrap4/buttons.py:96\nmsgid \"Icon alignment\"\nmsgstr \"Ausrichtung des Icons\"\n\n#: bootstrap4/buttons.py:98\nmsgid \"Icon placed left\"\nmsgstr \"Icon linksbündig\"\n\n#: bootstrap4/buttons.py:99\nmsgid \"Icon placed right\"\nmsgstr \"Icon rechtsbündig\"\n\n#: bootstrap4/buttons.py:103\nmsgid \"Add an Icon before or after the button content.\"\nmsgstr \"Ein Glyphicon nach dem Inhalt anfügen.\"\n\n#: bootstrap4/buttons.py:145\nmsgid \"Button\"\nmsgstr \"Button\"\n\n#: bootstrap4/buttons.py:162\nmsgid \"Empty\"\nmsgstr \"Leer\"\n\n#: bootstrap4/card.py:16\nmsgid \"Card Header\"\nmsgstr \"Kachel Kopfzeile\"\n\n#: bootstrap4/card.py:23\nmsgid \"Card Body\"\nmsgstr \"Kachelinhalt\"\n\n#: bootstrap4/card.py:30\nmsgid \"Card Footer\"\nmsgstr \"Kachel Fusszeile\"\n\n#: bootstrap4/card.py:40\nmsgid \"Card\"\nmsgstr \"Kachel\"\n\n#: bootstrap4/carousel.py:22\nmsgid \"Animate\"\nmsgstr \"Animieren\"\n\n#: bootstrap4/carousel.py:22\nmsgid \"Pause\"\nmsgstr \"Pause\"\n\n#: bootstrap4/carousel.py:22\nmsgid \"Wrap\"\nmsgstr \"Wrap\"\n\n#: bootstrap4/carousel.py:25\nmsgid \"Slides\"\nmsgstr \"Slides\"\n\n#: bootstrap4/carousel.py:26\nmsgid \"Number of slides for this carousel.\"\nmsgstr \"Anzahl von Slides für dieses Karussell.\"\n\n#: bootstrap4/carousel.py:30\nmsgid \"Interval\"\nmsgstr \"Intervall\"\n\n#: bootstrap4/carousel.py:32\nmsgid \"Change slide after this number of seconds.\"\nmsgstr \"Ändere den Slide nach Ablauf dieser Anzahl von Sekunden.\"\n\n#: bootstrap4/carousel.py:36\nmsgid \"Options\"\nmsgstr \"Optionen\"\n\n#: bootstrap4/carousel.py:40\nmsgid \"Adjust interval for the carousel.\"\nmsgstr \"Intervall für das Carousel einstellen.\"\n\n#: bootstrap4/carousel.py:44\nmsgid \"Carousel heights\"\nmsgstr \"Höhe des Carousels\"\n\n#: bootstrap4/carousel.py:47\nmsgid \"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"Höhe des Carousel in Pixeln für unterschiedliche Bootstrap Breakpoints\"\n\n#: bootstrap4/carousel.py:51 bootstrap4/image.py:54 bootstrap4/picture.py:37\n#: generic/text_image.py:36\nmsgid \"Resize Options\"\nmsgstr \"Größenanpassung\"\n\n#: bootstrap4/carousel.py:54 bootstrap4/image.py:58 bootstrap4/picture.py:41\n#: generic/text_image.py:40\nmsgid \"Options to use when resizing the image.\"\nmsgstr \"Optionen für die Skalierung von Bildern.\"\n\n#: bootstrap4/carousel.py:64\nmsgid \"Carousel\"\nmsgstr \"Carousel\"\n\n#: bootstrap4/carousel.py:76\n#, python-brace-format\nmsgid \"with {0} slide\"\nmsgid_plural \"with {0} slides\"\nmsgstr[0] \"mit {0} Slide\"\nmsgstr[1] \"mit {0} Slides\"\n\n#: bootstrap4/carousel.py:118\nmsgid \"Slide\"\nmsgstr \"Slide\"\n\n#: bootstrap4/carousel.py:175\nmsgid \"Empty Slide\"\nmsgstr \"Leerer Slide\"\n\n#: bootstrap4/container.py:37\nmsgid \"Available Breakpoints\"\nmsgstr \"Verfügbare Breakpoints\"\n\n#: bootstrap4/container.py:41\nmsgid \"Supported display widths for Bootstrap's grid system.\"\nmsgstr \"Unterstützte Bildschirm Breiten für das Bootstrap Grid System.\"\n\n#: bootstrap4/container.py:45\nmsgid \"Fluid Container\"\nmsgstr \"Fluid Container\"\n\n#: bootstrap4/container.py:48\nmsgid \"Changing your outermost '.container' to '.container-fluid'.\"\nmsgstr \"Ändert den äusersten '.container' auf '.container-fluid'.\"\n\n#: bootstrap4/container.py:57\nmsgid \"At least one breakpoint must be selected.\"\nmsgstr \"Mindestens ein Breakpoint muss ausgewählt werden.\"\n\n#: bootstrap4/container.py:76\nmsgid \"Container\"\nmsgstr \"Container\"\n\n#: bootstrap4/container.py:93\n#, python-brace-format\nmsgid \"{0}for {1}\"\nmsgstr \"{0}für {1}\"\n\n#: bootstrap4/container.py:118\nmsgid \"Columns\"\nmsgstr \"Spalten\"\n\n#: bootstrap4/container.py:119\n#, python-brace-format\nmsgid \"{0} column\"\nmsgid_plural \"{0} columns\"\nmsgstr[0] \"{0} Spalte\"\nmsgstr[1] \"{0} Spalten\"\n\n#: bootstrap4/container.py:121\nmsgid \"Number of columns to be created with this row.\"\nmsgstr \"Anzahl von Spalten die für diese Zeile erzeugt werden.\"\n\n#: bootstrap4/container.py:139\nmsgid \"Row\"\nmsgstr \"Zeile\"\n\n#: bootstrap4/container.py:148\n#, python-brace-format\nmsgid \"with {0} column\"\nmsgid_plural \"with {0} columns\"\nmsgstr[0] \"mit {0} Spalte\"\nmsgstr[1] \"mit {0} Spalten\"\n\n#: bootstrap4/container.py:181\nmsgid \"Column\"\nmsgstr \"Spalte\"\n\n#: bootstrap4/container.py:212\nmsgid \"{} unit\"\nmsgid_plural \"{} units\"\nmsgstr[0] \"{} Einheit\"\nmsgstr[1] \"{} Einheiten\"\n\n#: bootstrap4/container.py:224 bootstrap4/container.py:228\nmsgid \"Flex column\"\nmsgstr \"Flex-Spalte\"\n\n#: bootstrap4/container.py:225 bootstrap4/container.py:229\nmsgid \"{} fixed column\"\nmsgstr \"{} fixe Spalten\"\n\n#: bootstrap4/container.py:226 bootstrap4/container.py:230\nmsgid \"Auto column\"\nmsgstr \"Auto-Spalte\"\n\n#: bootstrap4/container.py:236 bootstrap4/container.py:250\nmsgid \"Column width for {}\"\nmsgstr \"Spaltenbreite für {}\"\n\n#: bootstrap4/container.py:239\nmsgid \"Column width for devices narrower than {:.1f} pixels.\"\nmsgstr \"Spaltenreihenfolge für Geräte kleiner als {} Pixel.\"\n\n#: bootstrap4/container.py:240\nmsgid \"Column width for devices wider than {:.1f} pixels.\"\nmsgstr \"Spaltenbreite für Geräte größer als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:241\nmsgid \"Column width for all devices.\"\nmsgstr \"Spaltenbreite für alle Geräte\"\n\n#: bootstrap4/container.py:246\nmsgid \"Inherit column width from {}\"\nmsgstr \"Erbe Spaltenbreite für {}\"\n\n#: bootstrap4/container.py:254\nmsgid \"Override column width for devices narrower than {:.1f} pixels.\"\nmsgstr \"Überschreibe die Spatenbreite für Geräte schmäler als {} Pixel.\"\n\n#: bootstrap4/container.py:255\nmsgid \"Override column width for devices wider than {:.1f} pixels.\"\nmsgstr \"Überschreibte Spaltenbreite für Geräte breiter als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:256\nmsgid \"Override column width for all devices.\"\nmsgstr \"Überschreibe Spaltenbreite für alle Geräte.\"\n\n#: bootstrap4/container.py:263\nmsgid \"No offset\"\nmsgstr \"Kein Offste\"\n\n#: bootstrap4/container.py:266\nmsgid \"Inherit offset from {}\"\nmsgstr \"Erbe Offset von {}\"\n\n#: bootstrap4/container.py:273\nmsgid \"Offset for {}\"\nmsgstr \"Offset für {}\"\n\n#: bootstrap4/container.py:275\nmsgid \"Offset width for devices narrower than {:.1f} pixels.\"\nmsgstr \"Offsetbreite für Geräte schmäler als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:276\nmsgid \"Offset width for devices wider than {:.1f} pixels.\"\nmsgstr \"Offsetbreite für Geräte breiter als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:277\nmsgid \"Offset width for all devices.\"\nmsgstr \"Offsetbreite für alle Geräte.\"\n\n#: bootstrap4/container.py:288\nmsgid \"No reordering\"\nmsgstr \"Keine Sortierung\"\n\n#: bootstrap4/container.py:290 bootstrap4/container.py:292\nmsgid \"Reorder by {}\"\nmsgstr \"Sortiere nach {}\"\n\n#: bootstrap4/container.py:293\nmsgid \"Reordering for {}\"\nmsgstr \"Spaltenreihenfolge für {}\"\n\n#: bootstrap4/container.py:295\nmsgid \"Reordering for devices narrower than {:.1f} pixels.\"\nmsgstr \"Spaltenreihenfolge für Geräte kleiner als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:296\nmsgid \"Reordering for devices wider than {:.1f} pixels.\"\nmsgstr \"Reihenfolge für Geräte größer als {} Pixel ändern.\"\n\n#: bootstrap4/container.py:297\nmsgid \"Reordering for all devices.\"\nmsgstr \"Spaltenreihenfolge für alle Geräte\"\n\n#: bootstrap4/container.py:308 bootstrap4/mixins.py:47\nmsgid \"Default\"\nmsgstr \"Standard\"\n\n#: bootstrap4/container.py:308\nmsgid \"Visible\"\nmsgstr \"Sichtbar\"\n\n#: bootstrap4/container.py:308\nmsgid \"Hidden\"\nmsgstr \"Versteckt\"\n\n#: bootstrap4/container.py:309\nmsgid \"Responsive utilities for {}\"\nmsgstr \"Responsive Utilities für {}\"\n\n#: bootstrap4/container.py:311\nmsgid \"\"\n\"Utility classes for showing and hiding content by devices narrower than \"\n\"{:.1f} pixels.\"\nmsgstr \"\"\n\"Utility Klassen zum Anzeigen und Verstecken von Inhalten für Geräte schmäler \"\n\"als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:312\nmsgid \"\"\n\"Utility classes for showing and hiding content by devices wider than {:.1f} \"\n\"pixels.\"\nmsgstr \"\"\n\"Utility Klassen zum Anzeigen und Verstecken von Inhalten für Geräte breiter \"\n\"als {:.1f} Pixel.\"\n\n#: bootstrap4/container.py:313\nmsgid \"Utility classes for showing and hiding content for all devices.\"\nmsgstr \"\"\n\"Utility Klassen zum Anzeigen und Verstecken von Ihnalten für alle Geräte.\"\n\n#: bootstrap4/container.py:354\nmsgid \"widths: {}\"\nmsgstr \"Breite: {}\"\n\n#: bootstrap4/container.py:356\nmsgid \"unknown width\"\nmsgstr \"Unbekannte Breite\"\n\n#: bootstrap4/embeds.py:15\nmsgid \"Responsive 21:9\"\nmsgstr \"Responsive 21:9\"\n\n#: bootstrap4/embeds.py:16\nmsgid \"Responsive 16:9\"\nmsgstr \"Responsive 16:9\"\n\n#: bootstrap4/embeds.py:17\nmsgid \"Responsive 4:3\"\nmsgstr \"Responsive 4:3\"\n\n#: bootstrap4/embeds.py:18\nmsgid \"Responsive 1:1\"\nmsgstr \"Responsive 1:1\"\n\n#: bootstrap4/embeds.py:24\nmsgid \"YouTube URL\"\nmsgstr \"\"\n\n#: bootstrap4/embeds.py:29\nmsgid \"Aspect Ratio\"\nmsgstr \"Seitenverhältnis\"\n\n#: bootstrap4/embeds.py:36\nmsgid \"Allow Fullscreen\"\nmsgstr \"Vollbildschirm erlauben\"\n\n#: bootstrap4/embeds.py:42\nmsgid \"Autoplay\"\nmsgstr \"Autoplay\"\n\n#: bootstrap4/embeds.py:47\nmsgid \"Display Controls\"\nmsgstr \"Bedienelemente anzeigen\"\n\n#: bootstrap4/embeds.py:52\nmsgid \"Enable Looping\"\nmsgstr \"Endlos wiederholen\"\n\n#: bootstrap4/embeds.py:57\nmsgid \"Show related\"\nmsgstr \"Zeige zugehörige\"\n\n#: bootstrap4/embeds.py:59\nmsgid \"Show videos suggested by YouTube at the end.\"\nmsgstr \"Von YouTube empfohlene Videos am Ende anzeigen.\"\n\n#: bootstrap4/embeds.py:90\nmsgid \"Please enter a valid YouTube URL\"\nmsgstr \"Bitte eine gültige YouTube URL eingeben\"\n\n#: bootstrap4/embeds.py:97\nmsgid \"You Tube\"\nmsgstr \"\"\n\n#: bootstrap4/grid.py:60\nmsgid \"Portrait Phones\"\nmsgstr \"\"\n\n#: bootstrap4/grid.py:61\nmsgid \"Landscape Phones\"\nmsgstr \"\"\n\n#: bootstrap4/grid.py:62\nmsgid \"Tablets\"\nmsgstr \"\"\n\n#: bootstrap4/grid.py:63\nmsgid \"Laptops\"\nmsgstr \"\"\n\n#: bootstrap4/grid.py:64\nmsgid \"Large Desktops\"\nmsgstr \"\"\n\n#: bootstrap4/icon.py:16\nmsgid \"Square\"\nmsgstr \"\"\n\n#: bootstrap4/icon.py:18 bootstrap4/utils.py:20\nmsgid \"Circle\"\nmsgstr \"Kreis\"\n\n#: bootstrap4/icon.py:21\nmsgid \"Do not align\"\nmsgstr \"Nicht ausrichten\"\n\n#: bootstrap4/icon.py:22 bootstrap4/image.py:19 generic/text_image.py:46\n#: leaflet/map.py:66\nmsgid \"Left\"\nmsgstr \"Links\"\n\n#: bootstrap4/icon.py:23 bootstrap4/image.py:21\nmsgid \"Center\"\nmsgstr \"Mittig\"\n\n#: bootstrap4/icon.py:24 bootstrap4/image.py:20 generic/text_image.py:46\nmsgid \"Right\"\nmsgstr \"Rechts\"\n\n#: bootstrap4/icon.py:28\nmsgid \"Icon size\"\nmsgstr \"Icongröße\"\n\n#: bootstrap4/icon.py:34\nmsgid \"Icon color\"\nmsgstr \"Iconfarbe\"\n\n#: bootstrap4/icon.py:38 bootstrap4/jumbotron.py:101\nmsgid \"Background color\"\nmsgstr \"Hintergrundfarbe\"\n\n#: bootstrap4/icon.py:44\nmsgid \"Text alignment\"\nmsgstr \"Textausrichtung\"\n\n#: bootstrap4/icon.py:46\nmsgid \"Align the icon inside the parent column.\"\nmsgstr \"Richte Icon innerhalb der Elternspalte aus.\"\n\n#: bootstrap4/icon.py:50\nmsgid \"Set border\"\nmsgstr \"Setze Rand\"\n\n#: bootstrap4/icon.py:55\nmsgid \"Border radius\"\nmsgstr \"Randradius\"\n\n#: bootstrap4/icon.py:65\nmsgid \"Icon with frame\"\nmsgstr \"Icon mit Rahmen\"\n\n#: bootstrap4/image.py:25 bootstrap4/picture.py:45\nmsgid \"Image Shapes\"\nmsgstr \"\"\n\n#: bootstrap4/image.py:32\nmsgid \"Responsive Image Width\"\nmsgstr \"Responsiv Bildbreite\"\n\n#: bootstrap4/image.py:36\nmsgid \"Set the image width in percent relative to containing element.\"\nmsgstr \"Setze die Bildbreite in Prozent relativ zum enthaltenen Element.\"\n\n#: bootstrap4/image.py:40\nmsgid \"Fixed Image Width\"\nmsgstr \"Fest Bildbreite\"\n\n#: bootstrap4/image.py:43\nmsgid \"Set a fixed image width in pixels.\"\nmsgstr \"Setze eine feste Bildbreite in Pixeln.\"\n\n#: bootstrap4/image.py:47\nmsgid \"Adapt Image Height\"\nmsgstr \"Anpassung der Bildhöhe\"\n\n#: bootstrap4/image.py:50\nmsgid \"Set a fixed height in pixels, or percent relative to the image width.\"\nmsgstr \"Setze eine feste Höhe in Pixeln oder Prozent relativ zur Bildbreite.\"\n\n#: bootstrap4/image.py:63\nmsgid \"Image Alignment\"\nmsgstr \"Bildausrichtung\"\n\n#: bootstrap4/image.py:67\nmsgid \"How to align a non-responsive image.\"\nmsgstr \"Ausrichtung von nicht-“responsiven” Bildern.\"\n\n#: bootstrap4/image.py:76 fields.py:288\nmsgid \"Image\"\nmsgstr \"Bild\"\n\n#: bootstrap4/image.py:122\nmsgid \"No Image\"\nmsgstr \"Kein Bild\"\n\n#: bootstrap4/jumbotron.py:86\nmsgid \"Is fluid\"\nmsgstr \"Ist “fluid”\"\n\n#: bootstrap4/jumbotron.py:89\nmsgid \"Shall this element occupy the entire horizontal space of its parent.\"\nmsgstr \"\"\n\"Soll dieses Element die gesamte horizontale Ausdehnung des Elternelementes \"\n\"beanspruchen.\"\n\n#: bootstrap4/jumbotron.py:97\nmsgid \"This property specifies the height for each Bootstrap breakpoint.\"\nmsgstr \"Diese Eigenschaft definiert die Höhe für jeden “Bootstrap breakpoint”.\"\n\n#: bootstrap4/jumbotron.py:105\nmsgid \"Background image\"\nmsgstr \"Hintergrundbild\"\n\n#: bootstrap4/jumbotron.py:110\nmsgid \"Background repeat\"\nmsgstr \"Hintergrund Wiederholung\"\n\n#: bootstrap4/jumbotron.py:115\nmsgid \"This property specifies how the background image repeates.\"\nmsgstr \"Diese Eigenschaft definiert wie sich das Hintergrundbild wiederholt.\"\n\n#: bootstrap4/jumbotron.py:119\nmsgid \"Background attachment\"\nmsgstr \"Hintergrund-Anhang\"\n\n#: bootstrap4/jumbotron.py:124\nmsgid \"\"\n\"This property specifies how to move the background image relative to the \"\n\"viewport.\"\nmsgstr \"\"\n\"Diese Eigenschaft definiert wie sich das Hintergrundbild relativ zum \"\n\"Viewport verschiebt.\"\n\n#: bootstrap4/jumbotron.py:128\nmsgid \"Background vertical position\"\nmsgstr \"Vertikale Position des Hintergrundes\"\n\n#: bootstrap4/jumbotron.py:132\nmsgid \"This property moves a background image vertically within its container.\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:136\nmsgid \"Background horizontal position\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:140\nmsgid \"\"\n\"This property moves a background image horizontally within its container.\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:144\nmsgid \"Background size\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:149\nmsgid \"This property specifies how the background image is sized.\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:154\nmsgid \"Background width/height\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:155\nmsgid \"Width\"\nmsgstr \"Breite\"\n\n#: bootstrap4/jumbotron.py:155\nmsgid \"Height\"\nmsgstr \"Höhe\"\n\n#: bootstrap4/jumbotron.py:158\nmsgid \"\"\n\"This property specifies the width and height of a background image in px or \"\n\"%.\"\nmsgstr \"Setzt die Breite und Höhe des Hintergrundbildes in px oder %.\"\n\n#: bootstrap4/jumbotron.py:186\nmsgid \"You must at least set a background width.\"\nmsgstr \"Es muss zumindest eine Breite für den Hintergrund angegeben werden.\"\n\n#: bootstrap4/jumbotron.py:193\nmsgid \"Jumbotron\"\nmsgstr \"\"\n\n#: bootstrap4/jumbotron.py:262\nmsgid \"Without background image\"\nmsgstr \"Ohne Hintergrundbild\"\n\n#: bootstrap4/mixins.py:48\nmsgid \"Primary with white text\"\nmsgstr \"Primärfarbe mit weißem Text\"\n\n#: bootstrap4/mixins.py:49\nmsgid \"Secondary with white text\"\nmsgstr \"Sekundärfarbe mit weißem Text\"\n\n#: bootstrap4/mixins.py:50\nmsgid \"Success with white text\"\nmsgstr \"Erfolg mit weißem Text\"\n\n#: bootstrap4/mixins.py:51\nmsgid \"Danger with white text\"\nmsgstr \"Gefahr mit weißem Text\"\n\n#: bootstrap4/mixins.py:52\nmsgid \"Warning with white text\"\nmsgstr \"Warnung mit weißem Text\"\n\n#: bootstrap4/mixins.py:53\nmsgid \"Info with white text\"\nmsgstr \"Info mit weißem Text\"\n\n#: bootstrap4/mixins.py:54\nmsgid \"Light with dark text\"\nmsgstr \"Hell mit dunklem Text\"\n\n#: bootstrap4/mixins.py:55\nmsgid \"Dark with white text\"\nmsgstr \"Dunkel mit weißem Text\"\n\n#: bootstrap4/mixins.py:56\nmsgid \"White with dark text\"\nmsgstr \"Weiß mit dunklem Text\"\n\n#: bootstrap4/mixins.py:57\nmsgid \"Transparent with dark text\"\nmsgstr \"Transparent mit dunklem Text\"\n\n#: bootstrap4/mixins.py:58\nmsgid \"Transparent with white text\"\nmsgstr \"Transparent mit weißem Text\"\n\n#: bootstrap4/mixins.py:61\nmsgid \"Background and color\"\nmsgstr \"Hintergrund und Farbe\"\n\n#: bootstrap4/mixins.py:71\nmsgid \"4 sided margins ({})\"\nmsgstr \"Allseitiger Abstand\"\n\n#: bootstrap4/mixins.py:72\nmsgid \"Horizontal margins ({})\"\nmsgstr \"Horizontaler Abstand ({})\"\n\n#: bootstrap4/mixins.py:73 bootstrap4/mixins.py:101\nmsgid \"Vertical margins ({})\"\nmsgstr \"Vertikaler Abstand ({})\"\n\n#: bootstrap4/mixins.py:74 bootstrap4/mixins.py:102\nmsgid \"Top margin ({})\"\nmsgstr \"Abstand oben ({})\"\n\n#: bootstrap4/mixins.py:75\nmsgid \"Right margin ({})\"\nmsgstr \"Abstand rechts ({})\"\n\n#: bootstrap4/mixins.py:76 bootstrap4/mixins.py:103\nmsgid \"Bottom margin ({})\"\nmsgstr \"Abstand unten ({})\"\n\n#: bootstrap4/mixins.py:77\nmsgid \"Left margin ({})\"\nmsgstr \"Abstand links ({})\"\n\n#: bootstrap4/mixins.py:84 bootstrap4/mixins.py:110\nmsgid \"No Margins\"\nmsgstr \"Keine Abstände\"\n\n#: bootstrap4/mixins.py:87 bootstrap4/mixins.py:113\nmsgid \"Inherit margin from {}\"\nmsgstr \"Abstand von {} erben\"\n\n#: bootstrap4/mixins.py:90 bootstrap4/mixins.py:116\n#, python-brace-format\nmsgid \"Margins for {breakpoint}\"\nmsgstr \"Abstand für {breakpoint}\"\n\n#: bootstrap4/mixins.py:127\nmsgid \"4 sided padding ({})\"\nmsgstr \"Allseitiger Innenabstand\"\n\n#: bootstrap4/mixins.py:128\nmsgid \"Horizontal padding ({})\"\nmsgstr \"Horizontaler Innenabstand ({})\"\n\n#: bootstrap4/mixins.py:129\nmsgid \"Vertical padding ({})\"\nmsgstr \"Vertikaler Innenabstand ({})\"\n\n#: bootstrap4/mixins.py:130\nmsgid \"Top padding ({})\"\nmsgstr \"Innenabstand oben ({})\"\n\n#: bootstrap4/mixins.py:131\nmsgid \"Right padding ({})\"\nmsgstr \"Innenabstand rechts ({})\"\n\n#: bootstrap4/mixins.py:132\nmsgid \"Bottom padding ({})\"\nmsgstr \"Innenabstand unten ({})\"\n\n#: bootstrap4/mixins.py:133\nmsgid \"Left padding ({})\"\nmsgstr \"Innenabstand links ({})\"\n\n#: bootstrap4/mixins.py:140\nmsgid \"No Padding\"\nmsgstr \"Kein Innenabstand\"\n\n#: bootstrap4/mixins.py:143\nmsgid \"Inherit padding from {}\"\nmsgstr \"Innenabstand von {} erben\"\n\n#: bootstrap4/mixins.py:146\n#, python-brace-format\nmsgid \"Padding for {breakpoint}\"\nmsgstr \"Innenabstand für {breakpoint}\"\n\n#: bootstrap4/mixins.py:157\nmsgid \"Do not float\"\nmsgstr \"Nicht floaten\"\n\n#: bootstrap4/mixins.py:158\nmsgid \"Float left\"\nmsgstr \"Nach links ausrichten\"\n\n#: bootstrap4/mixins.py:159\nmsgid \"Float right\"\nmsgstr \"Nach rechts ausrichten\"\n\n#: bootstrap4/mixins.py:165\nmsgid \"Unset\"\nmsgstr \"Nicht gesetzt\"\n\n#: bootstrap4/mixins.py:168\nmsgid \"Inherit float from {}\"\nmsgstr \"'float' von {} erben\"\n\n#: bootstrap4/mixins.py:171\n#, python-brace-format\nmsgid \"Floats for {breakpoint}\"\nmsgstr \"Float für {breakpoint}\"\n\n#: bootstrap4/picture.py:19\nmsgid \"Adapt Picture Heights\"\nmsgstr \"Setze Höhe des Bildes\"\n\n#: bootstrap4/picture.py:24\nmsgid \"\"\n\"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\"Höhe des Bildes in Prozent oder Pixel für gewählten Bootstrap Breakpoint.\"\n\n#: bootstrap4/picture.py:28\nmsgid \"Adapt Picture Zoom\"\nmsgstr \"\"\n\n#: bootstrap4/picture.py:33\nmsgid \"\"\n\"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap4/picture.py:56\nmsgid \"Picture\"\nmsgstr \"Bild\"\n\n#: bootstrap4/picture.py:104\nmsgid \"No Picture\"\nmsgstr \"Kein Bild\"\n\n#: bootstrap4/secondary_menu.py:12\nmsgid \"CMS Page Id\"\nmsgstr \"CMS Seiten-ID\"\n\n#: bootstrap4/secondary_menu.py:13\nmsgid \"Select a CMS page with a given unique Id (in advanced settings).\"\nmsgstr \"\"\n\"Wähle CMS Seite mit eindeutig gegebener Kennung (in Erweiterten \"\n\"Einstellungen).\"\n\n#: bootstrap4/secondary_menu.py:17\nmsgid \"Offset\"\nmsgstr \"Offset\"\n\n#: bootstrap4/secondary_menu.py:19\nmsgid \"Starting from which child menu.\"\nmsgstr \"Start von welchem Untermenü.\"\n\n#: bootstrap4/secondary_menu.py:23\nmsgid \"Limit\"\nmsgstr \"Begrenzung\"\n\n#: bootstrap4/secondary_menu.py:25\nmsgid \"Number of child menus.\"\nmsgstr \"Anzahl der Untermenüs\"\n\n#: bootstrap4/secondary_menu.py:42\nmsgid \"Secondary Menu\"\nmsgstr \"Sekundärmenü\"\n\n#: bootstrap4/settings.py:65\nmsgid \"List Group\"\nmsgstr \"Listengruppe\"\n\n#: bootstrap4/settings.py:66\nmsgid \"Unstyled List\"\nmsgstr \"Ungestylte Liste\"\n\n#: bootstrap4/tabs.py:21\nmsgid \"Number of Tabs\"\nmsgstr \"Anzahl der Registerkarten\"\n\n#: bootstrap4/tabs.py:22\nmsgid \"Number can be adjusted at any time.\"\nmsgstr \"Die Anzahl kann jederzeit angepasst werden.\"\n\n#: bootstrap4/tabs.py:26\nmsgid \"Justified tabs\"\nmsgstr \"Bündig ausrichten\"\n\n#: bootstrap4/tabs.py:36\nmsgid \"Tab Set\"\nmsgstr \"Registermenü\"\n\n#: bootstrap4/tabs.py:48\nmsgid \"with {} tab\"\nmsgid_plural \"with {} tabs\"\nmsgstr[0] \"mit {0} Registerkarte\"\nmsgstr[1] \"mit {0} Registerkarten\"\n\n#: bootstrap4/tabs.py:61\nmsgid \"Tab Title\"\nmsgstr \"Beschriftung\"\n\n#: bootstrap4/tabs.py:70\nmsgid \"Tab Pane\"\nmsgstr \"Registerkarte\"\n\n#: bootstrap4/utils.py:11 generic/text_image.py:15\nmsgid \"Upscale image\"\nmsgstr \"Upscale Bild\"\n\n#: bootstrap4/utils.py:12 generic/text_image.py:16\nmsgid \"Crop image\"\nmsgstr \"Bild zuschneiden\"\n\n#: bootstrap4/utils.py:13 generic/text_image.py:17\nmsgid \"With subject location\"\nmsgstr \"Mit Standortangabe\"\n\n#: bootstrap4/utils.py:14 generic/text_image.py:18\nmsgid \"Optimized for Retina\"\nmsgstr \"Für Retina optimiert\"\n\n#: bootstrap4/utils.py:18\nmsgid \"Responsive\"\nmsgstr \"Responsive\"\n\n#: bootstrap4/utils.py:19\nmsgid \"Rounded\"\nmsgstr \"Abgerundet\"\n\n#: bootstrap4/utils.py:21\nmsgid \"Thumbnail\"\nmsgstr \"Thumbnail\"\n\n#: clipboard/admin.py:34\nmsgid \"Copy to Clipboard\"\nmsgstr \"In den Clipboard kopieren\"\n\n#: clipboard/admin.py:35\nmsgid \"Successfully pasted JSON data\"\nmsgstr \"JSON-Inhalt eingefügt.\"\n\n#: clipboard/admin.py:36\nmsgid \"Successfully copied JSON data\"\nmsgstr \"JSON-Inhalt kopiert.\"\n\n#: clipboard/admin.py:54\nmsgid \"Insert Data\"\nmsgstr \"Daten einfügen\"\n\n#: clipboard/admin.py:55\nmsgid \"From CMS Clipboard\"\nmsgstr \"Vom CMS Clipboard\"\n\n#: clipboard/admin.py:59\nmsgid \"Restore Data\"\nmsgstr \"Daten wiederherstellen\"\n\n#: clipboard/admin.py:60\nmsgid \"To CMS Clipboard\"\nmsgstr \"Zum CMS Clipboard\"\n\n#: clipboard/admin.py:70\nmsgid \"The clipboard's content has been persisted for later.\"\nmsgstr \"Der Inhalt des Clipboards wurde für später gespeichert.\"\n\n#: clipboard/admin.py:74\nmsgid \"Persisted content has been restored to the clipboard.\"\nmsgstr \"Gespeichere Inhalte wurden im Clipboad abgelegt.\"\n\n#: clipboard/cms_plugins.py:41 clipboard/cms_plugins.py:149\nmsgid \"Export to Clipboard\"\nmsgstr \"Exportiere ins Clipboard\"\n\n#: clipboard/cms_plugins.py:50 clipboard/cms_plugins.py:87\nmsgid \"Import from Clipboard\"\nmsgstr \"Importiere von Clipboard\"\n\n#: clipboard/cms_plugins.py:92 clipboard/cms_plugins.py:103\nmsgid \"Select Clipboard Content\"\nmsgstr \"Wähle Cliboard Inhalt\"\n\n#: clipboard/forms.py:26\nmsgid \"This identifier has already been used, please choose another one.\"\nmsgstr \"Diese Kennung wurde bereits verwendet, bitte wähle eine andere.\"\n\n#: cms_toolbars.py:21\nmsgid \"Extra Page Fields\"\nmsgstr \"Extra Felder für Seite\"\n\n#: extra_fields/admin.py:28\nmsgid \"CSS class names\"\nmsgstr \"CSS-Klassennamen\"\n\n#: extra_fields/admin.py:31\nmsgid \"Freely selectable CSS classnames for this Plugin, separated by commas.\"\nmsgstr \"\"\n\"Frei wählbare CSS-Klassennamen für dieses Plugin, durch Kommas getrennt.\"\n\n#: extra_fields/admin.py:35\nmsgid \"Allow multiple\"\nmsgstr \"Mehrfachselektion ermöglichen\"\n\n#: extra_fields/admin.py:37\nmsgid \"Allow to select multiple of the above CSS classes.\"\nmsgstr \"Mehrfachselektion obiger CSS-Klassen ermöglichen.\"\n\n#: extra_fields/admin.py:52\nmsgid \"CSS class '{}' contains invalid characters.\"\nmsgstr \"CSS-Klasse '{}' enthält ungültige Zeichen.\"\n\n#: extra_fields/admin.py:60\nmsgid \"px, em, rem and %\"\nmsgstr \"px, em, rem und %\"\n\n#: extra_fields/admin.py:61\nmsgid \"px, em and %\"\nmsgstr \"px, em und %\"\n\n#: extra_fields/admin.py:62\nmsgid \"px, rem and em\"\nmsgstr \"px, rem und em\"\n\n#: extra_fields/admin.py:63\nmsgid \"px and em\"\nmsgstr \"px und em\"\n\n#: extra_fields/admin.py:64\nmsgid \"px, rem and %\"\nmsgstr \"px, rem und %\"\n\n#: extra_fields/admin.py:65\nmsgid \"px and %\"\nmsgstr \"px und %\"\n\n#: extra_fields/admin.py:66\nmsgid \"px and rem\"\nmsgstr \"px und rem\"\n\n#: extra_fields/admin.py:67\nmsgid \"px\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:68\nmsgid \"% and rem\"\nmsgstr \"% und rem\"\n\n#: extra_fields/admin.py:69\nmsgid \"%\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:70\nmsgid \"px, em, rem, % and auto\"\nmsgstr \"px, em, rem, % und auto\"\n\n#: extra_fields/admin.py:71\nmsgid \"px, em, % and auto\"\nmsgstr \"px, em, % und auto\"\n\n#: extra_fields/admin.py:72\nmsgid \"px, rem, em and auto\"\nmsgstr \"px, rem, em und auto\"\n\n#: extra_fields/admin.py:73\nmsgid \"px, em and auto\"\nmsgstr \"px, em und auto\"\n\n#: extra_fields/admin.py:74\nmsgid \"px, rem, % and auto\"\nmsgstr \"px, rem, % und auto\"\n\n#: extra_fields/admin.py:75\nmsgid \"px, % and auto\"\nmsgstr \"px, % und auto\"\n\n#: extra_fields/admin.py:76\nmsgid \"px, rem and auto\"\nmsgstr \"px, rem und auto\"\n\n#: extra_fields/admin.py:77\nmsgid \"px and auto\"\nmsgstr \"px und auto\"\n\n#: extra_fields/admin.py:78\nmsgid \"%, rem and auto\"\nmsgstr \"%, rem und auto\"\n\n#: extra_fields/admin.py:79\nmsgid \"% and auto\"\nmsgstr \"% und auto\"\n\n#: extra_fields/admin.py:106\nmsgid \"Customized {0}:\"\nmsgstr \"Benutzerdefinierte {0}:\"\n\n#: extra_fields/admin.py:110\nmsgid \"Allow these extra inlines styles for the given plugin type.\"\nmsgstr \"Extra Inline-Styles für ausgewählten Plugin-Type zulassen.\"\n\n#: extra_fields/admin.py:114\nmsgid \"Units for {0}:\"\nmsgstr \"Einheiten für {0}:\"\n\n#: extra_fields/admin.py:117\n#, python-brace-format\nmsgid \"Allow these size units for customized {0} fields.\"\nmsgstr \"Diese Maßeinheiten für benutzerdefinierte {0} Felder zulassen.\"\n\n#: extra_fields/admin.py:134\nmsgid \"Module\"\nmsgstr \"Modul\"\n\n#: extra_fields/admin.py:140\nmsgid \"Allowed Classes and Styles\"\nmsgstr \"Benutzerdefinierte CSS-Klassen und Styles\"\n\n#: extra_fields/admin.py:158\n#, python-format\nmsgid \"'%s' is not a valid CSS class name.\"\nmsgstr \"'%s' ist kein gültiger CSS Klassenname.\"\n\n#: extra_fields/mixins.py:39\nmsgid \"Named Element ID\"\nmsgstr \"Benenne Element-ID\"\n\n#: extra_fields/mixins.py:51\nmsgid \"Customized CSS Classes\"\nmsgstr \"Benutzerdefinierte CSS-Klassen\"\n\n#: extra_fields/mixins.py:55\nmsgid \"Customized CSS classes to be added to this element.\"\nmsgstr \"Füge benutzerdefinierte CSS-Klassen zu diesem Element hinzu.\"\n\n#: extra_fields/mixins.py:58\nmsgid \"Select CSS\"\nmsgstr \"Wähle CSS aus\"\n\n#: extra_fields/mixins.py:60\nmsgid \"Customized CSS Class\"\nmsgstr \"Benutzerdefinierte CSS-Klassen\"\n\n#: extra_fields/mixins.py:63\nmsgid \"Customized CSS class to be added to this element.\"\nmsgstr \"Benutzerdefinierte CSS-Klassen für dieses Element.\"\n\n#: fields.py:119\n#, python-format\nmsgid \"'%(color)s' is not a valid color code.\"\nmsgstr \"'%(color)s' ist kein gültiger Farbcode.\"\n\n#: fields.py:172\n#, python-format\nmsgid \"\"\n\"'%(value)s' is not a valid size unit. Allowed units are: %(allowed_units)s.\"\nmsgstr \"\"\n\"'%(value)s' ist keine gültige Einheit. Gültige Einheiten sind: \"\n\"%(allowed_units)s.\"\n\n#: fields.py:200\nmsgctxt \"allowed_units\"\nmsgid \"or\"\nmsgstr \"oder\"\n\n#: generic/custom_snippet.py:14\nmsgid \"Custom Snippet\"\nmsgstr \"Benutzerdefiniertes Schnipsel\"\n\n#: generic/heading.py:12\nmsgid \"Heading {}\"\nmsgstr \"Überschrift {}\"\n\n#: generic/heading.py:16\nmsgid \"Structure Level\"\nmsgstr \"Gliederungsebene\"\n\n#: generic/heading.py:20\n#: templates/cascade/admin/legacy_widgets/panel_types.html:7\n#: templates/cascade/admin/widgets/panel_types.html:7\nmsgid \"Content\"\nmsgstr \"Inhalt\"\n\n#: generic/horizontal_rule.py:8\nmsgid \"Horizontal Rule\"\nmsgstr \"Horizontale Linie\"\n\n#: generic/mixins.py:13\nmsgid \"Id\"\nmsgstr \"Id\"\n\n#: generic/mixins.py:16\nmsgid \"\"\n\"A unique identifier for this element (please don't use any special \"\n\"characters, punctuations, etc.) May be used as anchor-link: #id.\"\nmsgstr \"\"\n\"Eindeutige Bezeichnung des Elements (bitte keine Sonderzeichen, Umlaute, \"\n\"Interpunktion etc. verwenden). Kann als Ankerlink verwendet werden: #id.\"\n\n#: generic/mixins.py:42\nmsgid \"The element ID '{}' is not unique for this page.\"\nmsgstr \"Die Bezeichnung des Elements '{}' ist nicht eindeutig für diese Seite.\"\n\n#: generic/simple_wrapper.py:11\nmsgid \"<{}> – Element\"\nmsgstr \"\"\n\n#: generic/simple_wrapper.py:12\nmsgid \"Naked Wrapper\"\nmsgstr \"Wrapper ohne Hülle\"\n\n#: generic/simple_wrapper.py:16\nmsgid \"HTML element tag\"\nmsgstr \"HTML Element\"\n\n#: generic/simple_wrapper.py:17\nmsgid \"Choose a type for this HTML element.\"\nmsgstr \"Wähle Type für dieses HTML-Element\"\n\n#: generic/simple_wrapper.py:25\nmsgid \"Simple Wrapper\"\nmsgstr \"Einfacher Wrapper\"\n\n#: generic/text_image.py:22\nmsgid \"Image Width\"\nmsgstr \"Bildbreite\"\n\n#: generic/text_image.py:25\nmsgid \"Set the image width in pixels.\"\nmsgstr \"Setze eine Bildbreite in Pixeln.\"\n\n#: generic/text_image.py:29\nmsgid \"Image Height\"\nmsgstr \"Bildhöhe\"\n\n#: generic/text_image.py:32\nmsgid \"Set the image height in pixels.\"\nmsgstr \"Setze Bildhöhe in Pixeln.\"\n\n#: generic/text_image.py:45\nmsgid \"Alignement\"\nmsgstr \"Ausrichtung\"\n\n#: generic/text_image.py:46\nmsgid \"Not aligned\"\nmsgstr \"Nicht ausgerichtet\"\n\n#: generic/text_image.py:57\nmsgid \"Image in text\"\nmsgstr \"Bild in Text\"\n\n#: hide_plugins.py:11\nmsgid \"Hide element\"\nmsgstr \"Element verbergen\"\n\n#: hide_plugins.py:13\nmsgid \"Hide this element and all of it's descendants from the web-page.\"\nmsgstr \"\"\n\"Dieses Element mitsamt seiner untergeordneten Elemente auf der Webseite \"\n\"verbergen.\"\n\n#: icon/admin.py:36\nmsgid \"Can not unzip uploaded archive {}: {}.\"\nmsgstr \"Kann hochgeladenes Archiv {}: {} nicht entpacken.\"\n\n#: icon/admin.py:43\n#, python-brace-format\nmsgid \"\"\n\"File does not seem to originate from <a href=\\\"{url}\\\" target=\\\"_blank\"\n\"\\\">{url}</a>.\"\nmsgstr \"\"\n\"Diese Datei scheint nicht von <a href=\\\"{url}\\\" target=\\\"_blank\\\">{url}</a> \"\n\"zu stammen.\"\n\n#: icon/admin.py:52\n#, python-brace-format\nmsgid \"\"\n\"Icon Font '{icon_font}' already uses CSS prefix '{css_prefix}'.<br/>Please \"\n\"reload the font from <a href=\\\"{url}\\\" target=\\\"_blank\\\">{url}</a> using \"\n\"another CSS prefix.\"\nmsgstr \"\"\n\"Icon-Font '{icon_font}' verwendet bereits den CSS-Präfix '{css_prefix}'.<br/\"\n\">Bitte lade den Font erneut von <a href=\\\"{url}\\\" target=\\\"_blank\\\">{url}</\"\n\"a>, diesmal mit einem anderen CSS-Präfix.\"\n\n#: icon/admin.py:91\nmsgid \"Preview Icons\"\nmsgstr \"Icon-Voransicht\"\n\n#: icon/admin.py:98\nmsgid \"Number of Icons\"\nmsgstr \"Anzahl der Icons\"\n\n#: icon/admin.py:105\nmsgid \"CSS prefix\"\nmsgstr \"CSS-Präfix\"\n\n#: icon/forms.py:17\nmsgid \"Font\"\nmsgstr \"Font\"\n\n#: icon/forms.py:23\nmsgid \"Select Symbol\"\nmsgstr \"Wähle Symbol\"\n\n#: icon/forms.py:32\nmsgid \"No Icon\"\nmsgstr \"Kein Icon\"\n\n#: icon/simpleicon.py:11\nmsgid \"Simple Icon\"\nmsgstr \"Einfaces Icon\"\n\n#: icon/texticon.py:14\nmsgid \"Icon in text\"\nmsgstr \"Icon in Text\"\n\n#: image.py:13\nmsgid \"Image Title\"\nmsgstr \"Bildtitel\"\n\n#: image.py:15\nmsgid \"Caption text added to the 'title' attribute of the <img> element.\"\nmsgstr \"\"\n\"Beschriftung des Bildes welches zum 'title' Attribut des <img> Elementes \"\n\"hinzugefügt wird.\"\n\n#: image.py:19\nmsgid \"Alternative Description\"\nmsgstr \"Alternative Beschreibung\"\n\n#: image.py:21\nmsgid \"\"\n\"Textual description of the image added to the 'alt' tag of the <img> element.\"\nmsgstr \"\"\n\"Beschreibungstext des Bildes, das zum 'alt' Tag des <img> Elements \"\n\"hinzugefügt wird.\"\n\n#: leaflet/map.py:37\nmsgid \"Marker Title\"\nmsgstr \"Marker-Titel\"\n\n#: leaflet/map.py:39\nmsgid \"Please choose a title, then go to the map to set a marker pin.\"\nmsgstr \"Bitte Titel wählen und anschließend Marker in die Karte setzen.\"\n\n#: leaflet/map.py:43\nmsgid \"Use customized marker icon\"\nmsgstr \"Verwende eigenes Marker-Symbol\"\n\n#: leaflet/map.py:51\nmsgid \"Icon\"\nmsgstr \"Icon\"\n\n#: leaflet/map.py:57\nmsgid \"Icon Width\"\nmsgstr \"Bildbreite\"\n\n#: leaflet/map.py:60\nmsgid \"Width of the marker icon in pixels.\"\nmsgstr \"Setze Breite des Markers in Pixeln.\"\n\n#: leaflet/map.py:65\nmsgid \"Marker Anchor\"\nmsgstr \"Marker-Anker\"\n\n#: leaflet/map.py:66\nmsgid \"Top\"\nmsgstr \"Oben\"\n\n#: leaflet/map.py:69\nmsgid \"Coordinates of the icon's anchor relative to its top left corner.\"\nmsgstr \"Die Koordinaten des Icon-Ankers relativ zu seiner linken oberen Ecke.\"\n\n#: leaflet/map.py:74\nmsgid \"Text to display in popup.\"\nmsgstr \"Optionaler Text für Popup\"\n\n#: leaflet/map.py:80\nmsgid \"Address lookup\"\nmsgstr \"\"\n\n#: leaflet/map.py:82\nmsgid \"Search for an address\"\nmsgstr \"\"\n\n#: leaflet/map.py:117\nmsgid \"Marker\"\nmsgstr \"Marker\"\n\n#: leaflet/map.py:118\nmsgid \"Markers\"\nmsgstr \"Marker\"\n\n#: leaflet/map.py:124\nmsgid \"Map Width\"\nmsgstr \"Breite der Karte\"\n\n#: leaflet/map.py:127\nmsgid \"Set the map width in percent relative to containing element.\"\nmsgstr \"Setze die Kartenbreite in Prozent relativ zum enthaltenen Element.\"\n\n#: leaflet/map.py:131\nmsgid \"Adapt Map Height\"\nmsgstr \"Anpassung der Kartenhöhe\"\n\n#: leaflet/map.py:134\nmsgid \"Set a fixed height in pixels, or percent relative to the map width.\"\nmsgstr \"Setze eine feste Höhe in Pixeln oder Prozent relativ zur Bildbreite.\"\n\n#: leaflet/map.py:138\nmsgid \"Adapt Map Minimum Height\"\nmsgstr \"Anpassung der minimalen Kartenhöhe\"\n\n#: leaflet/map.py:141\nmsgid \"Optional, set a minimum height in pixels.\"\nmsgstr \"Setze minimale Höhe in Pixel (optional).\"\n\n#: leaflet/map.py:145\nmsgid \"Zoom by scrolling wheel\"\nmsgstr \"Zoomen mit Scrollrad\"\n\n#: leaflet/map.py:148\nmsgid \"Zoom into map on mouse over by scrolling wheel.\"\nmsgstr \"Mit Scrollrad hineinzoomen, falls Mauszeiger über der Karte.\"\n\n#: leaflet/map.py:178\nmsgid \"Map\"\nmsgstr \"Karte\"\n\n#: leaflet/map.py:264\n#, python-brace-format\nmsgid \"with {0} marker\"\nmsgid_plural \"with {0} markers\"\nmsgstr[0] \"mit {0} Markierung\"\nmsgstr[1] \"mit {0} Markierungen\"\n\n#: link/forms.py:72\nmsgid \"Page Root\"\nmsgstr \"Seitenanfang\"\n\n#: link/forms.py:85 sphinx/link_plugin.py:37\nmsgid \"CMS Page\"\nmsgstr \"CMS-Seite\"\n\n#: link/forms.py:86\nmsgid \"Download File\"\nmsgstr \"Datei-Download\"\n\n#: link/forms.py:87 sphinx/link_plugin.py:39\nmsgid \"External URL\"\nmsgstr \"Externe URL\"\n\n#: link/forms.py:88 sphinx/link_plugin.py:40\nmsgid \"Mail To\"\nmsgstr \"E-Mail an\"\n\n#: link/forms.py:91\nmsgid \"Phone number\"\nmsgstr \"Telefonnummer\"\n\n#: link/forms.py:94\nmsgid \"Same Window\"\nmsgstr \"Selbes Fenster\"\n\n#: link/forms.py:95\nmsgid \"New Window\"\nmsgstr \"Neues Fenster\"\n\n#: link/forms.py:96\nmsgid \"Parent Window\"\nmsgstr \"Eltern-Fenster\"\n\n#: link/forms.py:97\nmsgid \"Topmost Frame\"\nmsgstr \"Äußerster Frame\"\n\n#: link/forms.py:102\nmsgid \"Type of link\"\nmsgstr \"Art des Links\"\n\n#: link/forms.py:108\nmsgid \"An internal link onto any CMS page of this site\"\nmsgstr \"Ein interner Link auf eine beliebige Seite im CMS\"\n\n#: link/forms.py:114\nmsgid \"Page bookmark\"\nmsgstr \"Seitenlesezeichen\"\n\n#: link/forms.py:122\nmsgid \"An internal link onto a file from filer\"\nmsgstr \"Ein interner Link auf eine Datei in der Mediathek\"\n\n#: link/forms.py:127\nmsgid \"URL\"\nmsgstr \"URL\"\n\n#: link/forms.py:128\nmsgid \"Link onto external page\"\nmsgstr \"Link auf externe Seite\"\n\n#: link/forms.py:134\nmsgid \"Email\"\nmsgstr \"E-Mail\"\n\n#: link/forms.py:135\nmsgid \"Open Email program with this address\"\nmsgstr \"Öffne E-Mail Programm mit dieser Adresse\"\n\n#: link/forms.py:141\nmsgid \"Phone Number\"\nmsgstr \"Telefonnummer\"\n\n#: link/forms.py:142\nmsgid \"International phone number, ex. +1 212 555 2368.\"\nmsgstr \"Internationale Telefonnummer, z.B. +1 212 555 2368.\"\n\n#: link/forms.py:147\nmsgid \"Link Target\"\nmsgstr \"Ziel des Links\"\n\n#: link/forms.py:150\nmsgid \"Open Link in other target.\"\nmsgstr \"Öffne Link in anderem Zielfenster\"\n\n#: link/forms.py:154\nmsgid \"Title\"\nmsgstr \"Titel\"\n\n#: link/forms.py:156\nmsgid \"Link's Title\"\nmsgstr \"Titel des Links\"\n\n#: link/forms.py:168\nmsgid \"No Link\"\nmsgstr \"Kein Link\"\n\n#: link/forms.py:198\nmsgid \"CMS page to link to is missing.\"\nmsgstr \"Zu verlinkende Seite fehlt im CMS.\"\n\n#: link/forms.py:202\nmsgid \"File for download is missing.\"\nmsgstr \"Datei zum Download fehlt.\"\n\n#: link/forms.py:207\nmsgid \"No valid URL provided.\"\nmsgstr \"Keine gültige URL angegeben.\"\n\n#: link/forms.py:211\nmsgid \"No email address provided.\"\nmsgstr \"Keine E-Mail Adresse angegeben.\"\n\n#: link/forms.py:215\nmsgid \"No phone number provided.\"\nmsgstr \"Keine Telefonnummer angegeben.\"\n\n#: link/forms.py:235\nmsgid \"Link Content\"\nmsgstr \"Link-Inhalt\"\n\n#: link/forms.py:237\nmsgid \"Content of Link\"\nmsgstr \"Inhalt des Links\"\n\n#: models.py:28 models.py:156\nmsgid \"Plugin Name\"\nmsgstr \"Name des Plugins\"\n\n#: models.py:35 models.py:239 models.py:299\nmsgid \"Identifier\"\nmsgstr \"Kennung\"\n\n#: models.py:48\nmsgid \"Shared between Plugins\"\nmsgstr \"Geteilt zwischen plugins\"\n\n#: models.py:77\nmsgid \"Element\"\nmsgstr \"Element\"\n\n#: models.py:78\nmsgid \"Elements\"\nmsgstr \"Elemente\"\n\n#: models.py:138\nmsgid \"Sort by\"\nmsgstr \"Sortiere nach\"\n\n#: models.py:163\nmsgid \"Site\"\nmsgstr \"Site\"\n\n#: models.py:182\nmsgid \"Custom CSS classes and styles\"\nmsgstr \"Maßgeschneiderte CSS-Klassen und Styles\"\n\n#: models.py:199\nmsgid \"Name\"\nmsgstr \"Name\"\n\n#: models.py:204\nmsgid \"Element Type\"\nmsgstr \"Element-Typ\"\n\n#: models.py:210\nmsgid \"CSS classes\"\nmsgstr \"CSS-Klassen\"\n\n#: models.py:212\nmsgid \"\"\n\"Freely selectable CSS classnames for this Text-Editor Style, separated by \"\n\"spaces.\"\nmsgstr \"\"\n\"Frei wählbare CSS-Klassennamen für Text-Editor-Style, durch Leerzeichen \"\n\"getrennt.\"\n\n#: models.py:216\nmsgid \"Text Editor Config\"\nmsgstr \"Konfiguriere Text-Editor\"\n\n#: models.py:229 segmentation/cms_toolbars.py:13\nmsgid \"Segmentation\"\nmsgstr \"Segmentierung\"\n\n#: models.py:252\nmsgid \"Created by\"\nmsgstr \"Erstellt von\"\n\n#: models.py:259\nmsgid \"Created at\"\nmsgstr \"Erstellt am\"\n\n#: models.py:265\nmsgid \"Last accessed at\"\nmsgstr \"Letzter Zugriff am\"\n\n#: models.py:272\nmsgid \"Persisted Clipboard Content\"\nmsgstr \"Gespeicherter Clipboard-Inhalt\"\n\n#: models.py:273\nmsgid \"Persisted Clipboard Contents\"\nmsgstr \"Gespeicherte Clipboard-Inhalte\"\n\n#: models.py:302\nmsgid \"A unique identifier to distinguish this icon font.\"\nmsgstr \"Eindeutige Kennung, um Icon-Fonts zu unterscheiden.\"\n\n#: models.py:309\nmsgid \"\"\n\"Upload a zip file created on <a href=\\\"http://fontello.com/\\\" target=\\\"_blank\"\n\"\\\">Fontello</a> containing fonts.\"\nmsgstr \"\"\n\"Lade ZIP-Datei mit auf <a href=\\\"http://fontello.com/\\\" target=\\\"_blank\"\n\"\\\">Fontello</a> erstellten Fonts hoch.\"\n\n#: models.py:315\nmsgid \"Default Font\"\nmsgstr \"Standard Font\"\n\n#: models.py:317\nmsgid \"\"\n\"Use this font as default, unless an icon font is set for the current page.\"\nmsgstr \"Verwende diesen Font als Standard\"\n\n#: models.py:321\nmsgid \"Uploaded Icon Font\"\nmsgstr \"Abgelegter Icon-Font\"\n\n#: models.py:322\nmsgid \"Uploaded Icon Fonts\"\nmsgstr \"Abgelegte Icon-Fonts\"\n\n#: models.py:373\nmsgid \"User editable settings for this page.\"\nmsgstr \"Benutzer-Einstellungen für diese Seite.\"\n\n#: models.py:379\nmsgid \"Store for arbitrary page data.\"\nmsgstr \"Ablage für beliebige Seiten-Einstellungen.\"\n\n#: models.py:387\nmsgid \"Icon Font\"\nmsgstr \"Icon-Font\"\n\n#: models.py:391\nmsgid \"Menu Symbol\"\nmsgstr \"Menü-Symbol\"\n\n#: models.py:395\nmsgid \"Symbol to be used with the menu title for this page.\"\nmsgstr \"Symbol, um den Menü-Titel der Seite anzuzeigen.\"\n\n#: models.py:400\nmsgid \"Cascade Page Settings\"\nmsgstr \"Cascade Seiten-Einstellungen\"\n\n#: render_template.py:11\nmsgid \"Render template\"\nmsgstr \"Render Template\"\n\n#: render_template.py:12\nmsgid \"Use alternative template for rendering this plugin.\"\nmsgstr \"Verwende alternative Template zum Rendern dieses Plugins\"\n\n#: segmentation/cms_plugins.py:26\nmsgid \"Condition evaluation\"\nmsgstr \"Bedingungsauswertung\"\n\n#: segmentation/cms_plugins.py:29\nmsgid \"Evaluation as used in Django's template tags for conditions\"\nmsgstr \"Auswertung, wie sie in Django Templates für Bedingungen verwendet wird\"\n\n#: segmentation/cms_plugins.py:44\nmsgid \"The evaluation condition is missing or empty.\"\nmsgstr \"Die Auswertung der Bedingung fehlt oder ist leer.\"\n\n#: segmentation/cms_plugins.py:49\nmsgid \"Unable to evaluate condition: {}\"\nmsgstr \"Kann Bedingung nich auswerten: {}\"\n\n#: segmentation/cms_plugins.py:61\nmsgid \"Segment\"\nmsgstr \"Segment\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"if\"\nmsgstr \"if\"\n\n#: segmentation/cms_plugins.py:149\nmsgid \"elif\"\nmsgstr \"elif\"\n\n#: segmentation/cms_plugins.py:149\nmsgid \"else\"\nmsgstr \"else\"\n\n#: segmentation/cms_plugins.py:151\nmsgid \"Condition tag\"\nmsgstr \"Fallunterscheidung\"\n\n#: segmentation/cms_plugins.py:153\nmsgid \"Django's condition tag\"\nmsgstr \"Django's Tag zur Fallunterscheidung\"\n\n#: segmentation/mixins.py:62\nmsgid \"Emulate User\"\nmsgstr \"Emuliere User\"\n\n#: segmentation/mixins.py:68\nmsgid \"Clear emulations\"\nmsgstr \"Lösche Emulation\"\n\n#: segmentation/mixins.py:137\n#, python-format\nmsgid \"%(total_count)s selected\"\nmsgid_plural \"All %(total_count)s selected\"\nmsgstr[0] \"%(total_count)s ausgewählt\"\nmsgstr[1] \"Alle %(total_count)s ausgewählt\"\n\n#: segmentation/mixins.py:142\n#, python-format\nmsgid \"0 of %(cnt)s selected\"\nmsgstr \"0 von %(cnt)s ausgewählt\"\n\n#: segmentation/mixins.py:144\n#, python-format\nmsgid \"Select %(user_model)s to emulate\"\nmsgstr \"Wähle %(user_model)s zum Emulieren\"\n\n#: sharable/admin.py:51\nmsgid \"Change shared settings of '{}' plugin\"\nmsgstr \"Ändere gemeinsame Einstellungen von Plugin '{}'\"\n\n#: sharable/admin.py:60\nmsgid \"Used by plugins\"\nmsgstr \"Von Plugins verwendet\"\n\n#: sharable/admin.py:77\nmsgid \"Plugin Type\"\nmsgstr \"Plugin-Typ\"\n\n#: sharable/fields.py:35\nmsgid \"An identifier is required to remember these settings.\"\nmsgstr \"Um diese Einstellungen zu speichern wird eine Kennung benötigt.\"\n\n#: sharable/forms.py:53\nmsgid \"Shared Settings\"\nmsgstr \"Gemeinsame Einstellungen\"\n\n#: sharable/forms.py:56\nmsgid \"Use individual settings\"\nmsgstr \"Verwende individuelle Einstellungen\"\n\n#: sharable/forms.py:57\nmsgid \"Use settings shared with other plugins of this type\"\nmsgstr \"Verwende gemeinsame Einstellungen mit anderen Plugins dieses Typs\"\n\n#: sharable/forms.py:66\nmsgid \"Remember these settings as\"\nmsgstr \"Merke diese Einstellungen als\"\n\n#: sharable/forms.py:75\nmsgid \"The identifier '{}' has already been used, please choose another name.\"\nmsgstr \"\"\n\"Die Kennung '{}' wurde bereits verwendet, bitte einen anderen Namen wählen. \"\n\n#: sphinx/cms_apps.py:45\nmsgid \"Sphinx Documentation\"\nmsgstr \"Dokumentation mittels Sphinx\"\n\n#: sphinx/cms_menus.py:13\nmsgid \"Documentation Menu\"\nmsgstr \"Dokumentation-Menü\"\n\n#: sphinx/link_plugin.py:38\nmsgid \"Documentation\"\nmsgstr \"Dokumentation\"\n\n#: sphinx/link_plugin.py:48\nmsgid \"An internal link onto a documentation page\"\nmsgstr \"Ein interner Link auf die Dokumentations-Seite\"\n\n#: templates/cascade/admin/change_form.html:8\nmsgid \"There are no further settings for this plugin\"\nmsgstr \"Es gibt keine weitere Einstellungen für dieses Plugin\"\n\n#: templates/cascade/admin/change_form.html:9\nmsgid \"Please hit OK to save.\"\nmsgstr \"Zum Speichern bitte auf OK klicken.\"\n\n#: templates/cascade/admin/leaflet_plugin_change_form.html:9\nmsgid \"no results\"\nmsgstr \"keine Ergebnisse\"\n\n#: templates/cascade/admin/segmentation_list.html:43\nmsgid \"Home\"\nmsgstr \"Home\"\n\n#: templates/cascade/admin/segmentation_list.html:46\nmsgid \"Add\"\nmsgstr \"Hinzufügen\"\n\n#: templates/cascade/admin/widgets/colorpicker.html:6\n#: templates/cascade/admin/widgets/inherit_color.html:2\nmsgid \"Inherit\"\nmsgstr \"Erben\"\n\n#: utils.py:41\n#, python-brace-format\nmsgid \"Unable to link onto '{0}'.\"\nmsgstr \"Kann nicht auf '{0}' verlinken.\"\n\n#: utils.py:139\nmsgid \"Extra Styles\"\nmsgstr \"Extra Style\"\n\n#: utils.py:156\nmsgid \"Margins\"\nmsgstr \"Abstände\"\n\n#: utils.py:157\nmsgid \"Paddings\"\nmsgstr \"Innenabstände\"\n\n#: utils.py:158\nmsgid \"Widths\"\nmsgstr \"Breiten\"\n\n#: utils.py:159\nmsgid \"Heights\"\nmsgstr \"Höhen\"\n\n#: utils.py:160\nmsgid \"Text Alignement\"\nmsgstr \"Textausrichtung\"\n\n#: utils.py:161\nmsgid \"Font Size\"\nmsgstr \"Font-Größe\"\n\n#: utils.py:162\nmsgid \"Line Height\"\nmsgstr \"Zeilenabstand\"\n\n#: utils.py:163\nmsgid \"Colors\"\nmsgstr \"Farben\"\n\n#: utils.py:164\nmsgid \"Border\"\nmsgstr \"Rand\"\n\n#: utils.py:165\nmsgid \"Border Radius\"\nmsgstr \"Randradien\"\n\n#: utils.py:166\nmsgid \"Overflow\"\nmsgstr \"Überlaufe\"\n\n#: widgets.py:95\n#, python-format\nmsgid \"In '%(label)s': This field is required.\"\nmsgstr \"In '%(label)s': Dieses Feld wird benötigt.\"\n\n#: widgets.py:96\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid decimal number.\"\nmsgstr \"In '%(label)s': Wert '%(value)s' muss numerisch sein.\"\n"
  },
  {
    "path": "cmsplugin_cascade/locale/fr/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n# Nicolas P <np>, 2017.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2017-10-18 21:56+0200\\n\"\n\"PO-Revision-Date: 2017-10-18 21:57+0200\\n\"\n\"Last-Translator: Nicolas P <np>\\n\"\n\"Language-Team: français <>\\n\"\n\"Language: French\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Generator: Gtranslator 2.91.7\\n\"\n\n#: app_settings.py:128 app_settings.py:132\nmsgid \"default\"\nmsgstr \"standard\"\n\n#: app_settings.py:129\nmsgid \"with line break\"\nmsgstr \"Avec rupture de ligne\"\n\n#: app_settings.py:133\nmsgid \"Google Map\"\nmsgstr \"\"\n\n#: apps.py:16\nmsgid \"django CMS Cascade\"\nmsgstr \"django CMS Cascade\"\n\n#: bootstrap3/accordion.py:28\nmsgid \"Panels\"\nmsgstr \"Panneaux\"\n\n#: bootstrap3/accordion.py:29\nmsgid \"Number of panels for this panel group.\"\nmsgstr \"Nombre de panneaux pour ce groupe de panneaux.\"\n\n#: bootstrap3/accordion.py:33\nmsgid \"Accordion\"\nmsgstr \"Accordéon\"\n\n#: bootstrap3/accordion.py:45\nmsgid \"Close others\"\nmsgstr \"Fermer les autres\"\n\n#: bootstrap3/accordion.py:47\nmsgid \"Open only one panel at a time.\"\nmsgstr \"Ouvrir un seul panneau à la fois.\"\n\n#: bootstrap3/accordion.py:52\nmsgid \"First panel open\"\nmsgstr \"Premier panneau ouvert\"\n\n#: bootstrap3/accordion.py:54\nmsgid \"Start with the first panel open.\"\nmsgstr \"Commencez par ouvrir le premier panneau.\"\n\n#: bootstrap3/accordion.py:61\n#, python-brace-format\nmsgid \"with {0} panel\"\nmsgid_plural \"with {0} panels\"\nmsgstr[0] \"Avec {0} panneau \"\nmsgstr[1] \"Avec {0} panneaux\"\n\n#: bootstrap3/accordion.py:73\nmsgid \"Accordion Panel\"\nmsgstr \"Panneau d'accordéon\"\n\n#: bootstrap3/accordion.py:83 bootstrap3/panel.py:59\nmsgid \"Panel type\"\nmsgstr \"Type de panneau\"\n\n#: bootstrap3/accordion.py:84 bootstrap3/panel.py:60\nmsgid \"Display Panel using this style.\"\nmsgstr \"Panneau d'affichage utilisant ce style.\"\n\n#: bootstrap3/accordion.py:90 bootstrap3/panel.py:66\nmsgid \"Heading Size\"\nmsgstr \"Taille du titre\"\n\n#: bootstrap3/accordion.py:95\nmsgid \"Panel Title\"\nmsgstr \"Titre du panneau\"\n\n#: bootstrap3/buttons.py:22 bootstrap3/buttons.py:43\n#: bootstrap3/container.py:243 bootstrap3/panel.py:24\nmsgid \"Default\"\nmsgstr \"Par défaut\"\n\n#: bootstrap3/buttons.py:22 bootstrap3/panel.py:24\nmsgid \"Primary\"\nmsgstr \"Primaire\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Success\"\nmsgstr \"Succès\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Info\"\nmsgstr \"Info\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Warning\"\nmsgstr \"Attention\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Danger\"\nmsgstr \"Danger\"\n\n#: bootstrap3/buttons.py:24 link/cms_plugins.py:16 link/forms.py:50\nmsgid \"Link\"\nmsgstr \"Lien\"\n\n#: bootstrap3/buttons.py:43\nmsgid \"Large\"\nmsgstr \"Grand\"\n\n#: bootstrap3/buttons.py:43\nmsgid \"Small\"\nmsgstr \"Petit\"\n\n#: bootstrap3/buttons.py:44\nmsgid \"Extra small\"\nmsgstr \"Super petit\"\n\n#: bootstrap3/buttons.py:74\nmsgid \"Button Type\"\nmsgstr \"Type de bouton\"\n\n#: bootstrap3/buttons.py:76\nmsgid \"Display Link using this Button Style\"\nmsgstr \"Afficher le lien en utilisant ce style de bouton\"\n\n#: bootstrap3/buttons.py:81\nmsgid \"Button Size\"\nmsgstr \"Taille du bouton\"\n\n#: bootstrap3/buttons.py:83\nmsgid \"Display Link using this Button Size\"\nmsgstr \"Afficher le lien à l'aide de cette taille de bouton\"\n\n#: bootstrap3/buttons.py:87\nmsgid \"Block level\"\nmsgstr \"Niveau de blocage\"\n\n#: bootstrap3/buttons.py:87 widgets.py:193\nmsgid \"Disabled\"\nmsgstr \"Désactivé\"\n\n#: bootstrap3/buttons.py:88\nmsgid \"Button Options\"\nmsgstr \"Options des boutons\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Do not float\"\nmsgstr \"Ne pas flotter\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull left\"\nmsgstr \"Tire vers la gauche\"\n\n#: bootstrap3/buttons.py:93\nmsgid \"Pull right\"\nmsgstr \"Tire à droite\"\n\n#: bootstrap3/buttons.py:94\nmsgid \"Quick Float\"\nmsgstr \"Flot rapide\"\n\n#: bootstrap3/buttons.py:96\nmsgid \"Float the button to the left or right.\"\nmsgstr \"Mettre en place le bouton à droite ou à gauche.\"\n\n#: bootstrap3/buttons.py:100\nmsgid \"No Icon\"\nmsgstr \"Pas d'icône\"\n\n#: bootstrap3/buttons.py:100\nmsgid \"Icon placed left\"\nmsgstr \"Icône placé à gauche\"\n\n#: bootstrap3/buttons.py:101\nmsgid \"Icon placed right\"\nmsgstr \"Icône placée à droite\"\n\n#: bootstrap3/buttons.py:102\nmsgid \"Icon alignment\"\nmsgstr \"Alignement des icônes\"\n\n#: bootstrap3/buttons.py:104\n#, fuzzy\n#| msgid \"Append a Glyphicon after the content.\"\nmsgid \"Add an Icon before or after the button content.\"\nmsgstr \"Ajouter une icône avant ou après le contenu du bouton.\"\n\n#: bootstrap3/buttons.py:109 icon/cms_plugins.py:35 icon/cms_plugins.py:131\nmsgid \"Font\"\nmsgstr \"Fonte\"\n\n#: bootstrap3/buttons.py:114 icon/cms_plugins.py:40 icon/cms_plugins.py:136\n#, fuzzy\n#| msgid \"Select CSS\"\nmsgid \"Select Symbol\"\nmsgstr \"Sélectionnez le symbole\"\n\n#: bootstrap3/buttons.py:137\nmsgid \"Button\"\nmsgstr \"Bouton\"\n\n#: bootstrap3/buttons.py:158\nmsgid \"Empty\"\nmsgstr \"Vide\"\n\n#: bootstrap3/buttons.py:162\nmsgid \"Button Content\"\nmsgstr \"Contenu du bouton\"\n\n#: bootstrap3/carousel.py:32\nmsgid \"Slides\"\nmsgstr \"Slides\"\n\n#: bootstrap3/carousel.py:33\nmsgid \"Number of slides for this carousel.\"\nmsgstr \"Nombre de slides pour ce carrousel.\"\n\n#: bootstrap3/carousel.py:38\nmsgid \"Carousel\"\nmsgstr \"Carrousel\"\n\n#: bootstrap3/carousel.py:47\nmsgid \"Animate\"\nmsgstr \"Animer\"\n\n#: bootstrap3/carousel.py:47\nmsgid \"Pause\"\nmsgstr \"Pause\"\n\n#: bootstrap3/carousel.py:47\nmsgid \"Wrap\"\nmsgstr \"Emballage\"\n\n#: bootstrap3/carousel.py:51\nmsgid \"Interval\"\nmsgstr \"Intervalle\"\n\n#: bootstrap3/carousel.py:53\nmsgid \"Change slide after this number of seconds.\"\nmsgstr \"Changer le slide après ce nombre de secondes.\"\n\n#: bootstrap3/carousel.py:58\nmsgid \"Options\"\nmsgstr \"Options\"\n\n#: bootstrap3/carousel.py:60\nmsgid \"Adjust interval for the carousel.\"\nmsgstr \"Ajuster l'intervalle pour le carrousel.\"\n\n#: bootstrap3/carousel.py:66\nmsgid \"Carousel heights\"\nmsgstr \"Hauteur du carrousel\"\n\n#: bootstrap3/carousel.py:69\nmsgid \"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\"Hauteurs du carrousel en pixels pour les points d'arrêt distincts de \"\n\"Bootstrap.\"\n\n#: bootstrap3/carousel.py:74 bootstrap3/gallery.py:141 bootstrap3/image.py:117\n#: bootstrap3/jumbotron.py:115 bootstrap3/picture.py:54\nmsgid \"Resize Options\"\nmsgstr \"Options de redimensionnement\"\n\n#: bootstrap3/carousel.py:75 bootstrap3/gallery.py:142 bootstrap3/image.py:118\n#: bootstrap3/jumbotron.py:118 bootstrap3/picture.py:55\nmsgid \"Options to use when resizing the image.\"\nmsgstr \"Options à utiliser lors du redimensionnement de l'image.\"\n\n#: bootstrap3/carousel.py:87\n#, python-brace-format\nmsgid \"with {0} slide\"\nmsgid_plural \"with {0} slides\"\nmsgstr[0] \"Avec {0} Slide\"\nmsgstr[1] \"Avec {0} Slides\"\n\n#: bootstrap3/carousel.py:129\nmsgid \"Slide\"\nmsgstr \"Slide\"\n\n#: bootstrap3/carousel.py:167\nmsgid \"Empty Slide\"\nmsgstr \"Slide Vide\"\n\n#: bootstrap3/container.py:42\nmsgid \"At least one breakpoint must be selected.\"\nmsgstr \"Au moins un point d'arrêt doit être sélectionné.\"\n\n#: bootstrap3/container.py:47\nmsgid \"Container\"\nmsgstr \"Conteneur\"\n\n#: bootstrap3/container.py:56 bootstrap3/jumbotron.py:100\nmsgid \"Available Breakpoints\"\nmsgstr \"Points d'arrêt disponibles\"\n\n#: bootstrap3/container.py:58 bootstrap3/jumbotron.py:103\nmsgid \"Supported display widths for Bootstrap's grid system.\"\nmsgstr \"\"\n\"Largeurs d'affichage prises en charge pour le système de grille de Bootstrap.\"\n\n#: bootstrap3/container.py:63\nmsgid \"Fluid Container\"\nmsgstr \"Fluid Container\"\n\n#: bootstrap3/container.py:64\nmsgid \"Changing your outermost '.container' to '.container-fluid'.\"\nmsgstr \"Modification de votre \\\".container\\\" externe vers '.container-fluid'.\"\n\n#: bootstrap3/container.py:74\n#, python-brace-format\nmsgid \"{0}for {1}\"\nmsgstr \"{0}pour {1}\"\n\n#: bootstrap3/container.py:104\n#, python-brace-format\nmsgid \"{0} column\"\nmsgid_plural \"{0} columns\"\nmsgstr[0] \"{0} colonne\"\nmsgstr[1] \"{0} colonnes\"\n\n#: bootstrap3/container.py:105\nmsgid \"Columns\"\nmsgstr \"Colonnes\"\n\n#: bootstrap3/container.py:106\nmsgid \"Number of columns to be created with this row.\"\nmsgstr \"Nombre de colonnes à créer avec cette ligne.\"\n\n#: bootstrap3/container.py:110\nmsgid \"Row\"\nmsgstr \"Rangée\"\n\n#: bootstrap3/container.py:120\n#, python-brace-format\nmsgid \"with {0} column\"\nmsgid_plural \"with {0} columns\"\nmsgstr[0] \"Avec {0} colonne\"\nmsgstr[1] \"Avec {0} colonnes\"\n\n#: bootstrap3/container.py:138\nmsgid \"Column\"\nmsgstr \"Colonne\"\n\n#: bootstrap3/container.py:161\nmsgid \"{} unit\"\nmsgid_plural \"{} units\"\nmsgstr[0] \"{} unité\"\nmsgstr[1] \"{} Unités\"\n\n#: bootstrap3/container.py:176 bootstrap3/container.py:191\nmsgid \"Column width for {}\"\nmsgstr \"Largeur de colonne pour {}\"\n\n#: bootstrap3/container.py:178\nmsgid \"Number of column units for devices narrower than {} pixels.\"\nmsgstr \"Nombre d'unités de colonne pour appareils plus étroits que {} pixels.\"\n\n#: bootstrap3/container.py:179\nmsgid \"Number of column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\"Nombre d'unités de colonne pour les périphériques plus larges que {} pixels.\"\n\n#: bootstrap3/container.py:180\nmsgid \"Number of column units for all devices.\"\nmsgstr \"Nombre d'unités de colonne pour tous les appareils.\"\n\n#: bootstrap3/container.py:189 bootstrap3/container.py:209\nmsgid \"Inherit from above\"\nmsgstr \"Hériter d'en haut\"\n\n#: bootstrap3/container.py:193\nmsgid \"Override column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\"Annuler les unités de colonne pour les périphériques plus étroits que {} \"\n\"pixels.\"\n\n#: bootstrap3/container.py:194\nmsgid \"Override column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\"Annuler les unités de colonne pour des périphériques plus larges que {} \"\n\"pixels.\"\n\n#: bootstrap3/container.py:195\nmsgid \"Override column units for all devices.\"\nmsgstr \"Annuler les unités de colonne pour tous les périphériques.\"\n\n#: bootstrap3/container.py:206\nmsgid \"No offset\"\nmsgstr \"Pas décalage\"\n\n#: bootstrap3/container.py:214\nmsgid \"Offset for {}\"\nmsgstr \"Décalage pour {}\"\n\n#: bootstrap3/container.py:216\nmsgid \"Number of offset units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\"Nombre d'unités de décalage pour les appareils plus petits que {} pixels.\"\n\n#: bootstrap3/container.py:217\nmsgid \"Number of offset units for devices wider than {} pixels.\"\nmsgstr \"Nombre d'unités de décalage pour les appareils que résister {} pixels.\"\n\n#: bootstrap3/container.py:218\nmsgid \"Number of offset units for all devices.\"\nmsgstr \"Nombre d'unités de décalage pour tous les appareils.\"\n\n#: bootstrap3/container.py:227\nmsgid \"No reordering\"\nmsgstr \"Pas de réorganisation\"\n\n#: bootstrap3/container.py:228\nmsgid \"Push {}\"\nmsgstr \"presser {}\"\n\n#: bootstrap3/container.py:229\nmsgid \"Pull {}\"\nmsgstr \"Intégration {}\"\n\n#: bootstrap3/container.py:230\n#, python-brace-format\nmsgid \"Column ordering for {0}\"\nmsgstr \"Ordre de colonne pour {0}\"\n\n#: bootstrap3/container.py:232\nmsgid \"Column ordering for devices narrower than {} pixels.\"\nmsgstr \"Ordre des colonnes pour les périphériques plus étroits que {} pixels.\"\n\n#: bootstrap3/container.py:233\nmsgid \"Column ordering for devices wider than {} pixels.\"\nmsgstr \"Ordre des colonnes pour les périphériques plus larges que {} pixels.\"\n\n#: bootstrap3/container.py:234\nmsgid \"Column ordering for all devices.\"\nmsgstr \"Ordre des colonnes pour tous les périphériques.\"\n\n#: bootstrap3/container.py:243\nmsgid \"Visible\"\nmsgstr \"Visible\"\n\n#: bootstrap3/container.py:243\nmsgid \"Hidden\"\nmsgstr \"Caché\"\n\n#: bootstrap3/container.py:244\nmsgid \"Responsive utilities for {}\"\nmsgstr \"Responsive utilitaires pour {}\"\n\n#: bootstrap3/container.py:246\nmsgid \"\"\n\"Utility classes for showing and hiding content by devices narrower than {} \"\n\"pixels.\"\nmsgstr \"\"\n\"Classes utilitaire pour afficher et cacher du contenu pour des appareils \"\n\"plus étroits que {} pixels\"\n\n#: bootstrap3/container.py:247\nmsgid \"\"\n\"Utility classes for showing and hiding content by devices wider than {} \"\n\"pixels.\"\nmsgstr \"\"\n\"Classes utilitaire pour afficher et cacher le contenu pour des appareils \"\n\"plus larges que {} pixels.\"\n\n#: bootstrap3/container.py:248\nmsgid \"Utility classes for showing and hiding content for all devices.\"\nmsgstr \"\"\n\"Classes utilitaire pour afficher et cacher du contenu pour tous les \"\n\"périphériques.\"\n\n#: bootstrap3/container.py:312\n#, python-brace-format\nmsgid \"widths: {0} units\"\nmsgstr \"Largeurs: {0} unités\"\n\n#: bootstrap3/container.py:315\n#, python-brace-format\nmsgid \"default width: {0} unit\"\nmsgid_plural \"default width: {0} units\"\nmsgstr[0] \"Largeur par défaut: {0} unité\"\nmsgstr[1] \"Largeur par défaut: {0} unités\"\n\n#: bootstrap3/container.py:317\nmsgid \"unknown width\"\nmsgstr \"Largeur inconnue\"\n\n#: bootstrap3/gallery.py:32 bootstrap3/image.py:49 bootstrap3/image.py:71\n#: bootstrap3/image.py:127 bootstrap3/picture.py:64\nmsgid \"Image\"\nmsgstr \"Image\"\n\n#: bootstrap3/gallery.py:33 bootstrap3/image.py:59\nmsgid \"Image Title\"\nmsgstr \"Image Title\"\n\n#: bootstrap3/gallery.py:35 bootstrap3/image.py:60\nmsgid \"Caption text added to the 'title' attribute of the <img> element.\"\nmsgstr \"Texte de légende ajouté à l'attribut 'title' de l'élément <img>.\"\n\n#: bootstrap3/gallery.py:36 bootstrap3/image.py:65\nmsgid \"Alternative Description\"\nmsgstr \"Description alternative\"\n\n#: bootstrap3/gallery.py:38 bootstrap3/image.py:66\nmsgid \"\"\n\"Textual description of the image added to the 'alt' tag of the <img> element.\"\nmsgstr \"\"\n\"Description textuelle de l'image ajoutée à la balise 'alt' de l'élément \"\n\"<img>.\"\n\n#: bootstrap3/gallery.py:85\nmsgid \"Gallery\"\nmsgstr \"Galerie\"\n\n#: bootstrap3/gallery.py:87\nmsgid \"\"\n\"<p>Add images, which make up a set to be used as gallery.</p><p>All \"\n\"thumbnails for these images are resized to the same widths and heights.</p>\"\nmsgstr \"\"\n\"<p> Ajoutez des images, qui constituent un ensemble à utiliser comme \"\n\"galerie. </p><p> Tous les miniatures de ces images sont redimensionnées avec \"\n\"les mêmes largeurs et hauteurs.</p>\"\n\n#: bootstrap3/gallery.py:97 bootstrap3/image.py:84\nmsgid \"Responsive\"\nmsgstr \"Responsive\"\n\n#: bootstrap3/gallery.py:98 bootstrap3/image.py:86 bootstrap3/picture.py:34\nmsgid \"Upscale image\"\nmsgstr \"Image haut de gamme\"\n\n#: bootstrap3/gallery.py:98 bootstrap3/image.py:86 bootstrap3/picture.py:34\nmsgid \"Crop image\"\nmsgstr \"Recadrer l'image\"\n\n#: bootstrap3/gallery.py:99 bootstrap3/image.py:87 bootstrap3/picture.py:35\nmsgid \"With subject location\"\nmsgstr \"Avec l'emplacement du sujet\"\n\n#: bootstrap3/gallery.py:100 bootstrap3/image.py:88 bootstrap3/picture.py:36\nmsgid \"Optimized for Retina\"\nmsgstr \"Optimisé pour Retina\"\n\n#: bootstrap3/gallery.py:104\nmsgid \"Image Responsiveness\"\nmsgstr \"Réactivité de l'image\"\n\n#: bootstrap3/gallery.py:110 bootstrap3/image.py:98\nmsgid \"Responsive Image Width\"\nmsgstr \"Largeur de l'image réactive\"\n\n#: bootstrap3/gallery.py:112 bootstrap3/image.py:100\nmsgid \"Set the image width in percent relative to containing element.\"\nmsgstr \"\"\n\"Réglez la largeur de l'image en pour cent par rapport à l'élément contenant.\"\n\n#: bootstrap3/gallery.py:117 bootstrap3/image.py:105\nmsgid \"Fixed Image Width\"\nmsgstr \"Largeur de l'image fixe\"\n\n#: bootstrap3/gallery.py:118 bootstrap3/image.py:106\nmsgid \"Set a fixed image width in pixels.\"\nmsgstr \"Définir une largeur de l'image fixe en pixels.\"\n\n#: bootstrap3/gallery.py:123 bootstrap3/image.py:111\nmsgid \"Adapt Image Height\"\nmsgstr \"Adapter l'hauteur de l'image\"\n\n#: bootstrap3/gallery.py:124 bootstrap3/image.py:112\nmsgid \"Set a fixed height in pixels, or percent relative to the image width.\"\nmsgstr \"\"\n\"Définir une hauteur fixe en pixels, ou pour cent par rapport à la largeur de \"\n\"l'image.\"\n\n#: bootstrap3/gallery.py:129\nmsgid \"Thumbnail Width\"\nmsgstr \"Largeur de vignette\"\n\n#: bootstrap3/gallery.py:130\nmsgid \"Set a fixed thumbnail width in pixels.\"\nmsgstr \"Définir une largeur fixe de la vignette en pixels.\"\n\n#: bootstrap3/gallery.py:135\nmsgid \"Thumbnail Height\"\nmsgstr \"Hauteur de la vignette\"\n\n#: bootstrap3/gallery.py:136\nmsgid \"\"\n\"Set a fixed height in pixels, or percent relative to the thumbnail width.\"\nmsgstr \"\"\n\"Définissez une hauteur fixe en pixels, ou un pourcentage par rapport à la \"\n\"largeur des vignettes.\"\n\n#: bootstrap3/gallery.py:190\n#, python-brace-format\nmsgid \"with {0} image\"\nmsgid_plural \"with {0} images\"\nmsgstr[0] \"Avec {0} image\"\nmsgstr[1] \"Avec {0} images\"\n\n#: bootstrap3/image.py:23\nmsgid \"No Link\"\nmsgstr \"Pas de lien\"\n\n#: bootstrap3/image.py:84\nmsgid \"Rounded\"\nmsgstr \"Arrondi\"\n\n#: bootstrap3/image.py:85 icon/cms_plugins.py:31\nmsgid \"Circle\"\nmsgstr \"Cercle\"\n\n#: bootstrap3/image.py:85\nmsgid \"Thumbnail\"\nmsgstr \"Vignette\"\n\n#: bootstrap3/image.py:92\nmsgid \"Image Shapes\"\nmsgstr \"Formes d'image\"\n\n#: bootstrap3/image.py:159\nmsgid \"No Image\"\nmsgstr \"Pas d'image\"\n\n#: bootstrap3/jumbotron.py:75\nmsgid \"You must at least set a background width.\"\nmsgstr \"Vous devez au moins définir une largeur de fond.\"\n\n#: bootstrap3/jumbotron.py:80\nmsgid \"Jumbotron\"\nmsgstr \"Écran géant\"\n\n#: bootstrap3/jumbotron.py:108 bootstrap3/picture.py:40\nmsgid \"Adapt Picture Heights\"\nmsgstr \"Adapter les hauteurs d'image\"\n\n#: bootstrap3/jumbotron.py:111 bootstrap3/picture.py:42\nmsgid \"\"\n\"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\"Hauteurs de l'image en pourcentage ou en pixels pour les points d'arrêt \"\n\"distincts de Bootstrap.\"\n\n#: bootstrap3/jumbotron.py:124 icon/cms_plugins.py:56\nmsgid \"Background color\"\nmsgstr \"Couleur de fond\"\n\n#: bootstrap3/jumbotron.py:130\nmsgid \"This property specifies how an image repeates.\"\nmsgstr \"Cette propriété spécifie comment une image se répète.\"\n\n#: bootstrap3/jumbotron.py:136\nmsgid \"\"\n\"This property specifies how to move the background relative to the viewport.\"\nmsgstr \"\"\n\"Cette propriété spécifie comment déplacer l'arrière-plan par rapport à la \"\n\"fenêtre.\"\n\n#: bootstrap3/jumbotron.py:142\nmsgid \"This property moves a background image vertically within its container.\"\nmsgstr \"\"\n\"Cette propriété déplace une image d'arrière-plan verticalement dans son \"\n\"conteneur.\"\n\n#: bootstrap3/jumbotron.py:148\nmsgid \"\"\n\"This property moves a background image horizontally within its container.\"\nmsgstr \"\"\n\"Cette propriété déplace une image de fond horizontalement dans son conteneur.\"\n\n#: bootstrap3/jumbotron.py:154\nmsgid \"Background size\"\nmsgstr \"Taille de l'arrière-plan\"\n\n#: bootstrap3/jumbotron.py:155\nmsgid \"This property specifies how an image is sized.\"\nmsgstr \"Cette propriété spécifie comment une image est dimensionnée.\"\n\n#: bootstrap3/jumbotron.py:161\nmsgid \"Background width and height\"\nmsgstr \"Largeur et hauteur du fond\"\n\n#: bootstrap3/jumbotron.py:162\nmsgid \"This property specifies the width and height of a background image.\"\nmsgstr \"\"\n\"Cette propriété spécifie la largeur et la hauteur d'une image d'arrière-plan.\"\n\n#: bootstrap3/jumbotron.py:202\nmsgid \"Without background image\"\nmsgstr \"Sans image de fond\"\n\n#: bootstrap3/panel.py:17\nmsgid \"normal\"\nmsgstr \"normal\"\n\n#: bootstrap3/panel.py:17 generic/cms_plugins.py:62\nmsgid \"Heading {}\"\nmsgstr \"Titre {}\"\n\n#: bootstrap3/panel.py:39\nmsgid \"Content\"\nmsgstr \"Contenu\"\n\n#: bootstrap3/panel.py:48\nmsgid \"Panel\"\nmsgstr \"Panneau\"\n\n#: bootstrap3/panel.py:71\nmsgid \"Panel Heading\"\nmsgstr \"Panneau de titre\"\n\n#: bootstrap3/panel.py:76\nmsgid \"Panel Footer\"\nmsgstr \"Panneau bas de page\"\n\n#: bootstrap3/picture.py:20\nmsgid \"Picture\"\nmsgstr \"Photo\"\n\n#: bootstrap3/picture.py:47\nmsgid \"Adapt Picture Zoom\"\nmsgstr \"Adapter le zoom d'image\"\n\n#: bootstrap3/picture.py:49\nmsgid \"\"\n\"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\"Grossissement de l'image en pourcentage pour les points d'arrêt distincts de \"\n\"Bootstrap.\"\n\n#: bootstrap3/picture.py:97\nmsgid \"No Picture\"\nmsgstr \"Pas de photo\"\n\n#: bootstrap3/secondary_menu.py:17\nmsgid \"Secondary Menu\"\nmsgstr \"Menu secondaire\"\n\n#: bootstrap3/secondary_menu.py:26\nmsgid \"CMS Page Id\"\nmsgstr \"ID de la page CMS\"\n\n#: bootstrap3/secondary_menu.py:27\nmsgid \"Select a CMS page with a given unique Id (in advanced settings).\"\nmsgstr \"\"\n\"Sélectionnez une page CMS avec un identifiant unique donné (dans les \"\n\"paramètres avancés).\"\n\n#: bootstrap3/secondary_menu.py:32\n#, fuzzy\n#| msgid \"No offset\"\nmsgid \"Offset\"\nmsgstr \"Pas décalage\"\n\n#: bootstrap3/secondary_menu.py:34\nmsgid \"Starting from which child menu.\"\nmsgstr \"A partir de quel menu enfant\"\n\n#: bootstrap3/secondary_menu.py:39\nmsgid \"Limit\"\nmsgstr \"Limite\"\n\n#: bootstrap3/secondary_menu.py:41\nmsgid \"Number of child menus.\"\nmsgstr \"Nombre de menus enfants\"\n\n#: bootstrap3/settings.py:21\nmsgid \"mobile phones\"\nmsgstr \"téléphones portables\"\n\n#: bootstrap3/settings.py:22\nmsgid \"tablets\"\nmsgstr \"tablette\"\n\n#: bootstrap3/settings.py:23\nmsgid \"laptops\"\nmsgstr \"Ordinateurs portables\"\n\n#: bootstrap3/settings.py:24\nmsgid \"large desktops\"\nmsgstr \"Ordinateurs de bureau\"\n\n#: bootstrap3/settings.py:33\nmsgid \"List Group\"\nmsgstr \"groupe de liste\"\n\n#: bootstrap3/settings.py:34\nmsgid \"Unstyled List\"\nmsgstr \"Pas de style\"\n\n#: bootstrap3/tabs.py:21\nmsgid \"Tabs\"\nmsgstr \"Onglets\"\n\n#: bootstrap3/tabs.py:22\nmsgid \"Number of tabs.\"\nmsgstr \"Nombre d'onglets.\"\n\n#: bootstrap3/tabs.py:26\nmsgid \"Tab Set\"\nmsgstr \"Ensemble d'onglets\"\n\n#: bootstrap3/tabs.py:36\nmsgid \"Justified tabs\"\nmsgstr \"Onglets justifiés\"\n\n#: bootstrap3/tabs.py:43\nmsgid \"with {} tab\"\nmsgid_plural \"with {} tabs\"\nmsgstr[0] \"avec l'onglet {}\"\nmsgstr[1] \"avec {} onglets\"\n\n#: bootstrap3/tabs.py:55\nmsgid \"Tab Pane\"\nmsgstr \"Tableau d'onglets\"\n\n#: bootstrap3/tabs.py:63\nmsgid \"Tab Title\"\nmsgstr \"Titre de l'onglet\"\n\n#: clipboard/admin.py:42\nmsgid \"Copy to Clipboard\"\nmsgstr \"Copier dans le presse-papier\"\n\n#: clipboard/admin.py:43\nmsgid \"Successfully pasted JSON data\"\nmsgstr \"Les données JSON ont été collées avec succès\"\n\n#: clipboard/admin.py:44\nmsgid \"Successfully copied JSON data\"\nmsgstr \"Les données JSON ont été copiées avec succès\"\n\n#: clipboard/admin.py:61\nmsgid \"Insert Data\"\nmsgstr \"Insérer des données\"\n\n#: clipboard/admin.py:62\nmsgid \"From CMS Clipboard\"\nmsgstr \"Du Presse-papiers CMS\"\n\n#: clipboard/admin.py:66\nmsgid \"Restore Data\"\nmsgstr \"Restaurer les données\"\n\n#: clipboard/admin.py:67\nmsgid \"To CMS Clipboard\"\nmsgstr \"Au Presse-papiers CMS\"\n\n#: extra_fields/admin.py:26 widgets.py:161 widgets.py:296\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' is not a valid color.\"\nmsgstr \"Dans '%(label)s': Valeur '%(value)s' n'est pas une couleur valide.\"\n\n#: extra_fields/admin.py:35\n#, python-format\nmsgid \"'%s' is not a valid CSS class name.\"\nmsgstr \"'%s' n'est pas un nom de classe CSS valide.\"\n\n#: extra_fields/admin.py:40\nmsgid \"px, em and %\"\nmsgstr \"px, em et %\"\n\n#: extra_fields/admin.py:40\nmsgid \"px and em\"\nmsgstr \"px et em\"\n\n#: extra_fields/admin.py:41\nmsgid \"px and %\"\nmsgstr \"px et %\"\n\n#: extra_fields/admin.py:41\nmsgid \"px\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:41\nmsgid \"%\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:45\nmsgid \"CSS class names\"\nmsgstr \"Nom de classe CSS\"\n\n#: extra_fields/admin.py:47\nmsgid \"Freely selectable CSS classnames for this Plugin, separated by commas.\"\nmsgstr \"\"\n\"Noms de classe CSS sélectionnables pour ce plugin, séparés par des virgules.\"\n\n#: extra_fields/admin.py:51\nmsgid \"Allow multiple\"\nmsgstr \"Autoriser plusieurs\"\n\n#: extra_fields/admin.py:65\n#, python-brace-format\nmsgid \"Customized {0} Fields:\"\nmsgstr \"Champs Personnalisés {0}:\"\n\n#: extra_fields/admin.py:74\n#, python-brace-format\nmsgid \"Units for {0} Fields:\"\nmsgstr \"Unités pour {0} Champs:\"\n\n#: extra_fields/admin.py:120\nmsgid \"Module\"\nmsgstr \"Module\"\n\n#: extra_fields/admin.py:126\nmsgid \"Allowed Classes and Styles\"\nmsgstr \"Classes et styles autorisés\"\n\n#: extra_fields/mixins.py:45\nmsgid \"Named Element ID\"\nmsgstr \"ID d'élément nommé\"\n\n#: extra_fields/mixins.py:56\nmsgid \"Select CSS\"\nmsgstr \"Sélectionnez CSS\"\n\n#: extra_fields/mixins.py:59\nmsgid \"Customized CSS Classes\"\nmsgstr \"Classes CSS personnalisées\"\n\n#: extra_fields/mixins.py:61\nmsgid \"Customized CSS classes to be added to this element.\"\nmsgstr \"Classes CSS personnalisées à ajouter à cet élément.\"\n\n#: generic/cms_plugins.py:17\nmsgid \"Simple Wrapper\"\nmsgstr \"Enveloppe simple\"\n\n#: generic/cms_plugins.py:22\nmsgid \"<{}> – Element\"\nmsgstr \"<{}> – Élément\"\n\n#: generic/cms_plugins.py:23\nmsgid \"Naked Wrapper\"\nmsgstr \"Enveloppe Nu\"\n\n#: generic/cms_plugins.py:27\nmsgid \"HTML element tag\"\nmsgstr \"Tag d'élément HTML\"\n\n#: generic/cms_plugins.py:28\nmsgid \"Choose a tag type for this HTML element.\"\nmsgstr \"Choisissez un type de tag pour cet élément HTML.\"\n\n#: generic/cms_plugins.py:48\nmsgid \"Horizontal Rule\"\nmsgstr \"Règle horizontale\"\n\n#: generic/cms_plugins.py:59\nmsgid \"Heading\"\nmsgstr \"Titre\"\n\n#: generic/cms_plugins.py:68\nmsgid \"Heading content\"\nmsgstr \"Titre de la rubrique\"\n\n#: generic/cms_plugins.py:97\nmsgid \"Custom Snippet\"\nmsgstr \"Extrait personnalisé\"\n\n#: generic/mixins.py:33\nmsgid \"The element ID '{}' is not unique for this page.\"\nmsgstr \"L'élément ID '{}' n'est pas unique pour cette page.\"\n\n#: generic/mixins.py:58\nmsgid \"Element ID\"\nmsgstr \"ID de l'élément\"\n\n#: generic/mixins.py:60\nmsgid \"A unique identifier for this element.\"\nmsgstr \"Un identifiant unique pour cet élément.\"\n\n#: hide_plugins.py:37\nmsgid \"Hide plugin\"\nmsgstr \"Masquer le plugin\"\n\n#: hide_plugins.py:39\nmsgid \"Hide this plugin and all of it's children.\"\nmsgstr \"Masquer ce plugin et tous ses enfants.\"\n\n#: icon/admin.py:38\nmsgid \"The uploaded zip archive is not packed correctly\"\nmsgstr \"L'archive zip téléchargée n'est pas correctement chargée\"\n\n#: icon/admin.py:55\nmsgid \"Can not unzip uploaded archive. Reason: {}\"\nmsgstr \"Impossible de décompresser les archives téléchargées. Raison: {}\"\n\n#: icon/admin.py:92\nmsgid \"Preview Icons\"\nmsgstr \"Aperçu des icônes\"\n\n#: icon/cms_plugins.py:21\nmsgid \"Icon with frame\"\nmsgstr \"Icône avec cadre\"\n\n#: icon/cms_plugins.py:29\nmsgid \"Square\"\nmsgstr \"Carré\"\n\n#: icon/cms_plugins.py:45\nmsgid \"Icon size\"\nmsgstr \"Taille de l'icône\"\n\n#: icon/cms_plugins.py:51\nmsgid \"Icon color\"\nmsgstr \"Couleur de l'icône\"\n\n#: icon/cms_plugins.py:61\nmsgid \"Do not align\"\nmsgstr \"Ne pas aligner\"\n\n#: icon/cms_plugins.py:62\nmsgid \"Left\"\nmsgstr \"Gauche\"\n\n#: icon/cms_plugins.py:63\nmsgid \"Center\"\nmsgstr \"Centre\"\n\n#: icon/cms_plugins.py:64\nmsgid \"Right\"\nmsgstr \"Droite\"\n\n#: icon/cms_plugins.py:66\nmsgid \"Text alignment\"\nmsgstr \"Alignement du texte\"\n\n#: icon/cms_plugins.py:68\nmsgid \"Align the icon inside the parent column.\"\nmsgstr \"Alignez l'icône à l'intérieur de la colonne parent.\"\n\n#: icon/cms_plugins.py:73\nmsgid \"Set border\"\nmsgstr \"Définir une bordure\"\n\n#: icon/cms_plugins.py:78\nmsgid \"Border radius\"\nmsgstr \"Rayon de bordure\"\n\n#: icon/cms_plugins.py:120\nmsgid \"Icon in text\"\nmsgstr \"Icône dans le texte\"\n\n#: leaflet/map.py:74\nmsgid \"Marker Title\"\nmsgstr \"Titre du marqueur\"\n\n#: leaflet/map.py:76\nmsgid \"Please choose a title, then go to the map to set a marker pin\"\nmsgstr \"\"\n\"Veuillez choisir un titre, puis allez sur la carte pour définir un repère\"\n\n#: leaflet/map.py:80\nmsgid \"Use customized marker icon\"\nmsgstr \"Utiliser l'icône de marqueur personnalisé\"\n\n#: leaflet/map.py:87\nmsgid \"Marker Image\"\nmsgstr \"Image du marqueur\"\n\n#: leaflet/map.py:93\nmsgid \"Marker Width\"\nmsgstr \"Largeur du marqueur\"\n\n#: leaflet/map.py:95\nmsgid \"Width of the marker icon in pixels.\"\nmsgstr \"Largeur de l'image fixe en pixels.\"\n\n#: leaflet/map.py:101\nmsgid \"Marker Anchor\"\nmsgstr \"Ancre du marqueur\"\n\n#: leaflet/map.py:102\nmsgid \"The coordinates of the icon's anchor (relative to its top left corner).\"\nmsgstr \"\"\n\"Les coordonnées de l'ancre de l'icône (par rapport à son coin supérieur \"\n\"gauche).\"\n\n#: leaflet/map.py:107\nmsgid \"Optional rich text to display in popup.\"\nmsgstr \"Texte facultatif à afficher dans la fenêtre contextuelle.\"\n\n#: leaflet/map.py:176\nmsgid \"Marker\"\nmsgstr \"Marqueur\"\n\n#: leaflet/map.py:177\nmsgid \"Markers\"\nmsgstr \"Marqueurs\"\n\n#: leaflet/map.py:217\nmsgid \"Map\"\nmsgstr \"Carte geo\"\n\n#: leaflet/map.py:233\nmsgid \"Map Width\"\nmsgstr \"Largeur de la carte\"\n\n#: leaflet/map.py:235\nmsgid \"Set the map width in percent relative to containing element.\"\nmsgstr \"\"\n\"Définissez la largeur de la carte en pourcentage par rapport à l'élément \"\n\"contenant.\"\n\n#: leaflet/map.py:240\nmsgid \"Adapt Map Height\"\nmsgstr \"Adaptez la hauteur de la carte\"\n\n#: leaflet/map.py:242\nmsgid \"Set a fixed height in pixels, or percent relative to the map width.\"\nmsgstr \"\"\n\"Définissez une hauteur fixe en pixels, ou un pourcentage par rapport à la \"\n\"largeur de la carte.\"\n\n#: leaflet/map.py:247\n#| msgid \"Adapt Map Height\"\nmsgid \"Adapt Map Minimum Height\"\nmsgstr \"Adaptez la hauteur minimale de la carte\"\n\n#: leaflet/map.py:248\nmsgid \"Optional, set a minimum height in pixels.\"\nmsgstr \"Facultatif, définissez une hauteur minimale en pixels.\"\n\n#: leaflet/map.py:325\n#, python-brace-format\nmsgid \"with {0} marker\"\nmsgid_plural \"with {0} markers\"\nmsgstr[0] \"Avec {0} marqueur\"\nmsgstr[1] \"Avec {0} marqueurs\"\n\n#: link/cms_plugins.py:32\nmsgid \"Link Content\"\nmsgstr \"Lien du contenu\"\n\n#: link/cms_plugins.py:34\nmsgid \"Content of Link\"\nmsgstr \"Contenu du lien\"\n\n#: link/forms.py:49\nmsgid \"CMS Page\"\nmsgstr \"Page CMS\"\n\n#: link/forms.py:49\nmsgid \"External URL\"\nmsgstr \"URL externe\"\n\n#: link/forms.py:49\nmsgid \"Mail To\"\nmsgstr \"Envoyer à\"\n\n#: link/forms.py:50\nmsgid \"Type of link\"\nmsgstr \"Type de lien\"\n\n#: link/forms.py:52\nmsgid \"An internal link onto CMS pages of this site\"\nmsgstr \"Un lien interne sur les pages CMS de ce site\"\n\n#: link/forms.py:54\nmsgid \"Page bookmark\"\nmsgstr \"Marqueur de page\"\n\n#: link/forms.py:55\nmsgid \"Link onto external page\"\nmsgstr \"Lien vers une page externe\"\n\n#: link/forms.py:56\nmsgid \"Open Email program with this address\"\nmsgstr \"Ouvrez le programme de messagerie électronique avec cette adresse\"\n\n#: link/forms.py:90\nmsgid \"Page root\"\nmsgstr \"Racine de la page\"\n\n#: link/plugin_base.py:27\nmsgid \"Same Window\"\nmsgstr \"Même fenêtre\"\n\n#: link/plugin_base.py:27\nmsgid \"New Window\"\nmsgstr \"Nouvelle fenetre\"\n\n#: link/plugin_base.py:28\nmsgid \"Parent Window\"\nmsgstr \"Fenêtre parentale\"\n\n#: link/plugin_base.py:28\nmsgid \"Topmost Frame\"\nmsgstr \"Cadre le plus haut\"\n\n#: link/plugin_base.py:30\nmsgid \"Link Target\"\nmsgstr \"Cible du lien\"\n\n#: link/plugin_base.py:31\nmsgid \"Open Link in other target.\"\nmsgstr \"Ouvrir le lien dans une autre cible.\"\n\n#: link/plugin_base.py:36\nmsgid \"Title\"\nmsgstr \"Titre\"\n\n#: link/plugin_base.py:37\nmsgid \"Link's Title\"\nmsgstr \"Titre du lien\"\n\n#: models.py:30 models.py:118\nmsgid \"Plugin Name\"\nmsgstr \"Nom du plugin\"\n\n#: models.py:31 models.py:148 models.py:213\nmsgid \"Identifier\"\nmsgstr \"identificateurs\"\n\n#: models.py:36\nmsgid \"Shared between Plugins\"\nmsgstr \"Partagé entre plugins\"\n\n#: models.py:60\nmsgid \"Element\"\nmsgstr \"Élément\"\n\n#: models.py:102\nmsgid \"Sort by\"\nmsgstr \"Trier par\"\n\n#: models.py:119\nmsgid \"Site\"\nmsgstr \"Site\"\n\n#: models.py:125\nmsgid \"Custom CSS classes and styles\"\nmsgstr \"Personnalisés les classes CSS et les styles\"\n\n#: models.py:138 segmentation/cms_toolbars.py:15\nmsgid \"Segmentation\"\nmsgstr \"Segmentation\"\n\n#: models.py:152\nmsgid \"Persited Clipboard Content\"\nmsgstr \"Contenu de presse-papiers persistant\"\n\n#: models.py:162\nmsgid \"User editable settings for this page.\"\nmsgstr \"Paramètres éditables par l'utilisateur pour cette page.\"\n\n#: models.py:163\nmsgid \"Store for arbitrary page data.\"\nmsgstr \"Stockez pour des données de page arbitraires.\"\n\n#: models.py:167\nmsgid \"Cascade Page Settings\"\nmsgstr \"Paramètres de la page Cascade\"\n\n#: models.py:214\nmsgid \"A unique identifier to distinguish this icon font.\"\nmsgstr \"Un identifiant unique pour distinguer cette police d'icône.\"\n\n#: models.py:218\nmsgid \"\"\n\"Upload a zip file created on <a href=\\\"http://fontello.com/\\\" target=\\\"_blank\"\n\"\\\">Fontello</a> containing fonts.\"\nmsgstr \"\"\n\"Téléchargez un fichier zip créé sur <a href=\\\"http://fontello.com/\\\" target=\"\n\"\\\"_blank\\\">Fontello</a> contenant les fontes. \"\n\n#: models.py:223\nmsgid \"Uploaded Icon Font\"\nmsgstr \"Police d'icone téléchargée\"\n\n#: models.py:224\nmsgid \"Uploaded Icon Fonts\"\nmsgstr \"Polices d'icônes téléchargées\"\n\n#: render_template.py:27\nmsgid \"Render template\"\nmsgstr \"Modèle de rendu\"\n\n#: render_template.py:29\nmsgid \"Use alternative template for rendering this plugin.\"\nmsgstr \"Utilisez un autre modèle pour rendre ce plugin.\"\n\n#: segmentation/cms_plugins.py:34\nmsgid \"Segment\"\nmsgstr \"Segment\"\n\n#: segmentation/cms_plugins.py:51\nmsgid \"Condition tag\"\nmsgstr \"Condition tag\"\n\n#: segmentation/cms_plugins.py:52\nmsgid \"Django's condition tag\"\nmsgstr \"Django's condition tag\"\n\n#: segmentation/cms_plugins.py:57\nmsgid \"Condition evaluation\"\nmsgstr \"Évaluation de la condition\"\n\n#: segmentation/cms_plugins.py:58\nmsgid \"Evaluation as used in Django's template tags for conditions\"\nmsgstr \"Évaluation utilisée dans les tag de modèle Django pour les conditions\"\n\n#: segmentation/cms_plugins.py:142\n#, python-brace-format\nmsgid \"Unable to evaluate condition: {err}\"\nmsgstr \"Impossible d'évaluer la condition: {err}\"\n\n#: segmentation/cms_plugins.py:173 segmentation/cms_plugins.py:174\nmsgid \"if\"\nmsgstr \"si\"\n\n#: segmentation/cms_plugins.py:173\nmsgid \"elif\"\nmsgstr \"sinon si\"\n\n#: segmentation/cms_plugins.py:173\nmsgid \"else\"\nmsgstr \"sinon\"\n\n#: segmentation/mixins.py:68\nmsgid \"Emulate User\"\nmsgstr \"Emuler l'utilisateur\"\n\n#: segmentation/mixins.py:70\nmsgid \"Clear emulations\"\nmsgstr \"Annuler les emulations \"\n\n#: segmentation/mixins.py:136\n#, python-format\nmsgid \"%(total_count)s selected\"\nmsgid_plural \"All %(total_count)s selected\"\nmsgstr[0] \"%(total_count)s selectionnés\"\nmsgstr[1] \"All %(total_count)s selectionnés\"\n\n#: segmentation/mixins.py:141\n#, python-format\nmsgid \"0 of %(cnt)s selected\"\nmsgstr \"0 of %(cnt)s selectionné\"\n\n#: segmentation/mixins.py:143\n#, python-format\nmsgid \"Select %(user_model)s to emulate\"\nmsgstr \"Sélectionnez %(user_model)s à emuler\"\n\n#: sharable/admin.py:36\nmsgid \"Shared Fields\"\nmsgstr \"Champs partagés\"\n\n#: sharable/admin.py:73\nmsgid \"Change shared settings of {} plugin\"\nmsgstr \"Modifier les paramètres partagés de {} plugin\"\n\n#: sharable/admin.py:83\nmsgid \"Used by plugins\"\nmsgstr \"Utilisé par des plugins\"\n\n#: sharable/admin.py:100\nmsgid \"Plugin Type\"\nmsgstr \"Type de plugin\"\n\n#: sharable/forms.py:64\nmsgid \"Shared Settings\"\nmsgstr \"Paramètres partagés\"\n\n#: sharable/forms.py:68\nmsgid \"Use individual settings\"\nmsgstr \"Utilisez les paramètres individuels\"\n\n#: sharable/forms.py:69\nmsgid \"Use settings shared with other plugins of this type\"\nmsgstr \"Utilisez les paramètres partagés avec d'autres plugins de ce type\"\n\n#: sharable/forms.py:72\nmsgid \"Remember these settings as:\"\nmsgstr \"Rappelez-vous ces paramètres comme suit:\"\n\n#: sharable/forms.py:87\n#, python-brace-format\nmsgid \"The identifier '{0}' has already been used, please choose another name.\"\nmsgstr \"L'identifiant '{0}' a déjà été utilisé, choisissez un autre nom.\"\n\n#: templates/cascade/admin/change_form.html:8\nmsgid \"There are no further settings for this plugin\"\nmsgstr \"Il n'y a pas d'autres paramètres pour ce plugin\"\n\n#: templates/cascade/admin/change_form.html:9\nmsgid \"Please hit OK to save.\"\nmsgstr \"Cliquez sur OK pour enregistrer.\"\n\n#: templates/cascade/admin/segmentation_list.html:39\nmsgid \"Home\"\nmsgstr \"Accueil\"\n\n#: templates/cascade/admin/segmentation_list.html:42\nmsgid \"Add\"\nmsgstr \"Ajouter\"\n\n#: utils.py:115\n#, python-brace-format\nmsgid \"Unable to link onto '{0}'.\"\nmsgstr \"Impossible de lier '{0}'.\"\n\n#: widgets.py:94 widgets.py:104\n#, python-format\nmsgid \"In '%(label)s': This field is required.\"\nmsgstr \"Dans '%(label)s':Ce champ est requis.\"\n\n#: widgets.py:95\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid decimal number.\"\nmsgstr \"\"\n\"Dans '%(label)s': Valeur '%(value)s' doit contenir un nombre décimal valide.\"\n\n#: widgets.py:105\n#, python-format\nmsgid \"\"\n\"In '%(label)s': Value '%(value)s' shall contain a valid number, ending in \"\n\"%(endings)s.\"\nmsgstr \"\"\n\"Dans '%(label)s': Valeur '%(value)s' doit contenir un numéro valide, se \"\n\"terminant en %(endings)s.\"\n\n#: widgets.py:120\nmsgid \"or\"\nmsgstr \"ou\"\n\n#: widgets.py:276\n#, python-format\nmsgid \"\"\n\"In '%(label)s': Value '%(value)s' for field '%(field)s' shall contain a \"\n\"valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\"Dans '%(label)s': Valeur '%(value)s' pour le champ '%(field)s' contiendra un \"\n\"numéro valide, se terminant en %(endings)s.\"\n\n#: widgets.py:294\n#, fuzzy, python-format\n#| msgid \"In '%(label)s': Value '%(value)s' is not a valid color.\"\nmsgid \"In '%(label)s': Value '%(value)s' is not a valid border style.\"\nmsgstr \"\"\n\"Dans '%(label)s': Valeur '%(value)s' n'est pas un style de bordure valide.\"\n"
  },
  {
    "path": "cmsplugin_cascade/locale/it/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-06 15:36+0200\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n#: models.py:16 models.py:81\nmsgid \"Plugin Name\"\nmsgstr \"\"\n\n#: models.py:17\nmsgid \"Identifier\"\nmsgstr \"\"\n\n#: models.py:23\nmsgid \"Shared between Plugins\"\nmsgstr \"\"\n\n#: models.py:82\nmsgid \"Site\"\nmsgstr \"\"\n\n#: models.py:89\nmsgid \"Custom CSS classes and styles\"\nmsgstr \"\"\n\n#: models.py:96 segmentation/cms_toolbar.py:13\nmsgid \"Segmentation\"\nmsgstr \"\"\n\n#: render_template.py:24\nmsgid \"Render template\"\nmsgstr \"\"\n\n#: render_template.py:25\nmsgid \"Use alternative template for rendering this plugin.\"\nmsgstr \"\"\n\n#: settings.py:54 bootstrap3/settings.py:29 bootstrap3/settings.py:33\n#: bootstrap3/settings.py:37\nmsgid \"default\"\nmsgstr \"\"\n\n#: settings.py:55\nmsgid \"with line break\"\nmsgstr \"\"\n\n#: widgets.py:94 widgets.py:123\n#, python-format\nmsgid \"In '%(label)s': This field is required.\"\nmsgstr \"\"\n\n#: widgets.py:95\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number.\"\nmsgstr \"\"\n\n#: widgets.py:111\nmsgid \"or\"\nmsgstr \"\"\n\n#: widgets.py:124\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: widgets.py:150 extra_fields/admin.py:24\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' is not a valid color.\"\nmsgstr \"\"\n\n#: widgets.py:178 bootstrap3/buttons.py:88\nmsgid \"Disabled\"\nmsgstr \"\"\n\n#: widgets.py:261\n#, python-format\nmsgid \"In '%(label)s': Field '%(field)s' is required.\"\nmsgstr \"\"\n\n#: widgets.py:262\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' for field '%(field)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:25\nmsgid \"Panels\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:26\nmsgid \"Number of panels for this panel group.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:30\nmsgid \"Accordion\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:42\nmsgid \"Close others\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:44\nmsgid \"Open only one panel at a time.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:48\nmsgid \"First panel open\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:50\nmsgid \"Start with the first panel open.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:58\n#, python-brace-format\nmsgid \"with {0} panel\"\nmsgid_plural \"with {0} panels\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/accordion.py:70\nmsgid \"Accordion Panel\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:78 bootstrap3/panel.py:57\nmsgid \"Panel type\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:79 bootstrap3/panel.py:58\nmsgid \"Display Panel using this style.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:84 bootstrap3/panel.py:63\nmsgid \"Heading Size\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:88\nmsgid \"Panel Title\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/buttons.py:45\n#: bootstrap3/container.py:246 bootstrap3/panel.py:25\nmsgid \"Default\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Primary\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Success\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Info\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Warning\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 bootstrap3/panel.py:27\nmsgid \"Danger\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 link/cms_plugins.py:14\nmsgid \"Link\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Large\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:46\nmsgid \"Extra small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:77\nmsgid \"Button Type\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:79\nmsgid \"Display Link using this Button Style\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:83\nmsgid \"Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:85\nmsgid \"Display Link using this Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:88\nmsgid \"Block level\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:89\nmsgid \"Button Options\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Do not float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull left\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull right\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:93\nmsgid \"Quick Float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:95\nmsgid \"Float the button to the left or right.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:99\nmsgid \"Prepend icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:101\nmsgid \"Prepend a Glyphicon before the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:105\nmsgid \"Append icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:107\nmsgid \"Append a Glyphicon after the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:126\nmsgid \"Button\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:142\nmsgid \"Empty\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:146\nmsgid \"Button Content\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:31\nmsgid \"Slides\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:32\nmsgid \"Number of slides for this carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:37\nmsgid \"Carousel\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Animate\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Pause\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Wrap\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:50\nmsgid \"Interval\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:52\nmsgid \"Change slide after this number of seconds.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:56\nmsgid \"Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:58\nmsgid \"Adjust interval for the carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:62\nmsgid \"Carousel heights\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:64\nmsgid \"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:68 bootstrap3/gallery.py:122 bootstrap3/image.py:103\n#: bootstrap3/picture.py:65\nmsgid \"Resize Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:69 bootstrap3/gallery.py:123 bootstrap3/image.py:104\n#: bootstrap3/picture.py:66\nmsgid \"Options to use when resizing the image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:82\n#, python-brace-format\nmsgid \"with {0} slide\"\nmsgid_plural \"with {0} slides\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/carousel.py:123\nmsgid \"Slide\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:142\nmsgid \"Slide Caption\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:143\nmsgid \"Caption text to be laid over the backgroud image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:174\nmsgid \"Empty Slide\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:33\nmsgid \"At least one breakpoint must be selected.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:38\nmsgid \"Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:42\n#, python-brace-format\nmsgid \"Tiny (<{sm[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:43\n#, python-brace-format\nmsgid \"Small (≥{sm[0]}px and <{md[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:44\n#, python-brace-format\nmsgid \"Medium (≥{md[0]}px and <{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:45\n#, python-brace-format\nmsgid \"Large (≥{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:50\nmsgid \"Available Breakpoints\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:52\nmsgid \"Supported display widths for Bootstrap's grid system.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:56\nmsgid \"Fluid Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:57\nmsgid \"Changing your outermost '.container' to '.container-fluid'.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:73\n#, python-brace-format\nmsgid \"{0}for {1}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:129\n#, python-brace-format\nmsgid \"{0} column\"\nmsgid_plural \"{0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:130\nmsgid \"Columns\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:131\nmsgid \"Number of columns to be created with this row.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:135\nmsgid \"Row\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:145\n#, python-brace-format\nmsgid \"with {0} column\"\nmsgid_plural \"with {0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:163\nmsgid \"Column\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:182\nmsgid \"{} unit\"\nmsgid_plural \"{} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:198 bootstrap3/container.py:210\nmsgid \"Column width for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:200\nmsgid \"Number of column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:201\nmsgid \"Number of column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:202\nmsgid \"Number of column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:208\nmsgid \"Inherit from above\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:212\nmsgid \"Override column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:213\nmsgid \"Override column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:214\nmsgid \"Override column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:221\nmsgid \"No offset\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:223\nmsgid \"Offset for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:225\nmsgid \"Number of offset units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:226\nmsgid \"Number of offset units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:227\nmsgid \"Number of offset units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:233\nmsgid \"No reordering\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:234\nmsgid \"Push {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:235\nmsgid \"Pull {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:236\n#, python-brace-format\nmsgid \"Column ordering for {0}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:238\nmsgid \"Column ordering for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:239\nmsgid \"Column ordering for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:240\nmsgid \"Column ordering for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Visible\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Hidden\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:247\nmsgid \"Responsive utilities for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:249\nmsgid \"Utility classes for showing and hiding content by devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:250\nmsgid \"Utility classes for showing and hiding content by devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:251\nmsgid \"Utility classes for showing and hiding content for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:310\n#, python-brace-format\nmsgid \"widths: {0} units\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:313\n#, python-brace-format\nmsgid \"default width: {0} unit\"\nmsgid_plural \"default width: {0} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:315\nmsgid \"unknown width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:23 bootstrap3/image.py:44 bootstrap3/image.py:48\n#: bootstrap3/image.py:114 bootstrap3/picture.py:76\nmsgid \"Image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:24 bootstrap3/image.py:71 bootstrap3/picture.py:42\nmsgid \"Image Title\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:26 bootstrap3/image.py:72 bootstrap3/picture.py:43\nmsgid \"Caption text added to the 'title' attribute of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:27 bootstrap3/image.py:76 bootstrap3/picture.py:47\nmsgid \"Alternative Description\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:29 bootstrap3/image.py:77 bootstrap3/picture.py:48\nmsgid \"Textual description of the image added to the 'alt' tag of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:72\nmsgid \"Gallery\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:84 bootstrap3/image.py:63\nmsgid \"Responsive\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Upscale image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Crop image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:86 bootstrap3/image.py:66 bootstrap3/picture.py:37\nmsgid \"With subject location\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:87 bootstrap3/image.py:67 bootstrap3/picture.py:38\nmsgid \"Optimized for Retina\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:91\nmsgid \"Image Responsiveness\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:96 bootstrap3/image.py:87\nmsgid \"Responsive Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:98 bootstrap3/image.py:89\nmsgid \"Set the image width in percent relative to containing element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:102 bootstrap3/image.py:93\nmsgid \"Fixed Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:103 bootstrap3/image.py:94\nmsgid \"Set a fixed image width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:107 bootstrap3/image.py:98\nmsgid \"Adapt Image Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:108 bootstrap3/image.py:99\nmsgid \"Set a fixed height in pixels, or percent relative to the image width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:112\nmsgid \"Thumbnail Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:113\nmsgid \"Set a fixed thumbnail width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:117\nmsgid \"Thumbnail Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:118\nmsgid \"Set a fixed height in pixels, or percent relative to the thumbnail width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:174\n#, python-brace-format\nmsgid \"with {0} image\"\nmsgid_plural \"with {0} images\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/image.py:61 bootstrap3/picture.py:34\nmsgid \"No Link\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:63\nmsgid \"Rounded\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Circle\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Thumbnail\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:82\nmsgid \"Image Shapes\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:146\nmsgid \"No Image\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"normal\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"Heading {}\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:39\nmsgid \"Content\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:47\nmsgid \"Panel\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:67\nmsgid \"Panel Heading\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:71\nmsgid \"Panel Footer\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:20\nmsgid \"Picture\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:53\nmsgid \"Adapt Picture Heights\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:55\nmsgid \"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:59\nmsgid \"Adapt Picture Zoom\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:61\nmsgid \"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:107\nmsgid \"No Picture\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:17\nmsgid \"Secondary Menu\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:26\nmsgid \"CMS Page Id\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:27\nmsgid \"Select a CMS page with a given unique Id (in advanced settings).\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:12\nmsgid \"mobile phones\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:13\nmsgid \"tablets\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:14\nmsgid \"laptops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:15\nmsgid \"large desktops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:38\nmsgid \"unstyled\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:33\n#, python-format\nmsgid \"'%s' is not a valid CSS class name.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px, em and %\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px and em\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"%\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:42\nmsgid \"CSS class names\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:43\nmsgid \"Freely selectable CSS classnames for this Plugin, separated by commas.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:47\nmsgid \"Allow multiple\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:60\n#, python-brace-format\nmsgid \"Customized {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:68\n#, python-brace-format\nmsgid \"Units for {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:43\nmsgid \"Named Element ID\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:53\nmsgid \"Select CSS\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:56\nmsgid \"Customized CSS Classes\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:57\nmsgid \"Customized CSS classes to be added to this element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:13\nmsgid \"Simple Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:17\nmsgid \"<{}> – Element\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:18\nmsgid \"Naked Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:22\nmsgid \"HTML element tag\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:23\nmsgid \"Choose a tag type for this HTML element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:44\nmsgid \"Horizontal Rule\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:27\nmsgid \"Link Content\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:29\nmsgid \"Content of Link\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"CMS Page\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"External URL\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"Mail To\"\nmsgstr \"\"\n\n#: link/forms.py:27\nmsgid \"An internal link onto CMS pages of this site\"\nmsgstr \"\"\n\n#: link/forms.py:28\nmsgid \"Link onto external page\"\nmsgstr \"\"\n\n#: link/forms.py:29\nmsgid \"Open Email program with this address\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"Same Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"New Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Parent Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Topmost Frame\"\nmsgstr \"\"\n\n#: link/plugin_base.py:24\nmsgid \"Link Target\"\nmsgstr \"\"\n\n#: link/plugin_base.py:25\nmsgid \"Open Link in other target.\"\nmsgstr \"\"\n\n#: link/plugin_base.py:29\nmsgid \"Title\"\nmsgstr \"\"\n\n#: link/plugin_base.py:30\nmsgid \"Link's Title\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:23\nmsgid \"Segment\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:27\nmsgid \"Condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:28\nmsgid \"Django's condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:32\nmsgid \"Condition evaluation\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:33\nmsgid \"Evaluation as used in Django's template tags for conditions\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:114\n#, python-brace-format\nmsgid \"Unable to evaluate condition: {err}\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145 segmentation/cms_plugins.py:146\nmsgid \"if\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"elif\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"else\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:52\nmsgid \"Emulate User\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:54\nmsgid \"Clear emulations\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:112\n#, python-format\nmsgid \"%(total_count)s selected\"\nmsgid_plural \"All %(total_count)s selected\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: segmentation/mixins.py:117\n#, python-format\nmsgid \"0 of %(cnt)s selected\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:119\n#, python-format\nmsgid \"Select %(user_model)s to emulate\"\nmsgstr \"\"\n\n#: sharable/admin.py:28\nmsgid \"Shared Fields\"\nmsgstr \"\"\n\n#: sharable/admin.py:60\n#, python-format\nmsgid \"Change %s\"\nmsgstr \"\"\n\n#: sharable/admin.py:79\nmsgid \"Used by plugins\"\nmsgstr \"\"\n\n#: sharable/forms.py:48\nmsgid \"Remember these settings as:\"\nmsgstr \"\"\n\n#: sharable/forms.py:54\n#, python-brace-format\nmsgid \"The identifier '{0}' has already been used, please choose another name.\"\nmsgstr \"\"\n\n#: sharable/forms.py:70\nmsgid \"Shared Settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:73\nmsgid \"Use individual settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:74\nmsgid \"Use settings shared with other plugins of this type\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:7\nmsgid \"There are no further settings for this plugin\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:8\nmsgid \"Please hit OK to save.\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:39\nmsgid \"Home\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:42\nmsgid \"Add\"\nmsgstr \"\"\n"
  },
  {
    "path": "cmsplugin_cascade/locale/ru/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-06 15:36+0200\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\\n\"\n\n#: models.py:16 models.py:81\nmsgid \"Plugin Name\"\nmsgstr \"\"\n\n#: models.py:17\nmsgid \"Identifier\"\nmsgstr \"\"\n\n#: models.py:23\nmsgid \"Shared between Plugins\"\nmsgstr \"\"\n\n#: models.py:82\nmsgid \"Site\"\nmsgstr \"\"\n\n#: models.py:89\nmsgid \"Custom CSS classes and styles\"\nmsgstr \"\"\n\n#: models.py:96 segmentation/cms_toolbar.py:13\nmsgid \"Segmentation\"\nmsgstr \"\"\n\n#: render_template.py:24\nmsgid \"Render template\"\nmsgstr \"\"\n\n#: render_template.py:25\nmsgid \"Use alternative template for rendering this plugin.\"\nmsgstr \"\"\n\n#: settings.py:54 bootstrap3/settings.py:29 bootstrap3/settings.py:33\n#: bootstrap3/settings.py:37\nmsgid \"default\"\nmsgstr \"\"\n\n#: settings.py:55\nmsgid \"with line break\"\nmsgstr \"\"\n\n#: widgets.py:94 widgets.py:123\n#, python-format\nmsgid \"In '%(label)s': This field is required.\"\nmsgstr \"\"\n\n#: widgets.py:95\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number.\"\nmsgstr \"\"\n\n#: widgets.py:111\nmsgid \"or\"\nmsgstr \"\"\n\n#: widgets.py:124\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: widgets.py:150 extra_fields/admin.py:24\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' is not a valid color.\"\nmsgstr \"\"\n\n#: widgets.py:178 bootstrap3/buttons.py:88\nmsgid \"Disabled\"\nmsgstr \"\"\n\n#: widgets.py:261\n#, python-format\nmsgid \"In '%(label)s': Field '%(field)s' is required.\"\nmsgstr \"\"\n\n#: widgets.py:262\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' for field '%(field)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:25\nmsgid \"Panels\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:26\nmsgid \"Number of panels for this panel group.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:30\nmsgid \"Accordion\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:42\nmsgid \"Close others\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:44\nmsgid \"Open only one panel at a time.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:48\nmsgid \"First panel open\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:50\nmsgid \"Start with the first panel open.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:58\n#, python-brace-format\nmsgid \"with {0} panel\"\nmsgid_plural \"with {0} panels\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/accordion.py:70\nmsgid \"Accordion Panel\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:78 bootstrap3/panel.py:57\nmsgid \"Panel type\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:79 bootstrap3/panel.py:58\nmsgid \"Display Panel using this style.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:84 bootstrap3/panel.py:63\nmsgid \"Heading Size\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:88\nmsgid \"Panel Title\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/buttons.py:45\n#: bootstrap3/container.py:246 bootstrap3/panel.py:25\nmsgid \"Default\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Primary\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Success\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Info\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Warning\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 bootstrap3/panel.py:27\nmsgid \"Danger\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 link/cms_plugins.py:14\nmsgid \"Link\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Large\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:46\nmsgid \"Extra small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:77\nmsgid \"Button Type\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:79\nmsgid \"Display Link using this Button Style\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:83\nmsgid \"Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:85\nmsgid \"Display Link using this Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:88\nmsgid \"Block level\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:89\nmsgid \"Button Options\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Do not float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull left\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull right\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:93\nmsgid \"Quick Float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:95\nmsgid \"Float the button to the left or right.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:99\nmsgid \"Prepend icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:101\nmsgid \"Prepend a Glyphicon before the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:105\nmsgid \"Append icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:107\nmsgid \"Append a Glyphicon after the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:126\nmsgid \"Button\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:142\nmsgid \"Empty\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:146\nmsgid \"Button Content\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:31\nmsgid \"Slides\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:32\nmsgid \"Number of slides for this carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:37\nmsgid \"Carousel\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Animate\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Pause\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Wrap\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:50\nmsgid \"Interval\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:52\nmsgid \"Change slide after this number of seconds.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:56\nmsgid \"Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:58\nmsgid \"Adjust interval for the carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:62\nmsgid \"Carousel heights\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:64\nmsgid \"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:68 bootstrap3/gallery.py:122 bootstrap3/image.py:103\n#: bootstrap3/picture.py:65\nmsgid \"Resize Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:69 bootstrap3/gallery.py:123 bootstrap3/image.py:104\n#: bootstrap3/picture.py:66\nmsgid \"Options to use when resizing the image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:82\n#, python-brace-format\nmsgid \"with {0} slide\"\nmsgid_plural \"with {0} slides\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/carousel.py:123\nmsgid \"Slide\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:142\nmsgid \"Slide Caption\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:143\nmsgid \"Caption text to be laid over the backgroud image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:174\nmsgid \"Empty Slide\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:33\nmsgid \"At least one breakpoint must be selected.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:38\nmsgid \"Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:42\n#, python-brace-format\nmsgid \"Tiny (<{sm[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:43\n#, python-brace-format\nmsgid \"Small (≥{sm[0]}px and <{md[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:44\n#, python-brace-format\nmsgid \"Medium (≥{md[0]}px and <{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:45\n#, python-brace-format\nmsgid \"Large (≥{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:50\nmsgid \"Available Breakpoints\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:52\nmsgid \"Supported display widths for Bootstrap's grid system.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:56\nmsgid \"Fluid Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:57\nmsgid \"Changing your outermost '.container' to '.container-fluid'.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:73\n#, python-brace-format\nmsgid \"{0}for {1}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:129\n#, python-brace-format\nmsgid \"{0} column\"\nmsgid_plural \"{0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:130\nmsgid \"Columns\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:131\nmsgid \"Number of columns to be created with this row.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:135\nmsgid \"Row\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:145\n#, python-brace-format\nmsgid \"with {0} column\"\nmsgid_plural \"with {0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:163\nmsgid \"Column\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:182\nmsgid \"{} unit\"\nmsgid_plural \"{} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:198 bootstrap3/container.py:210\nmsgid \"Column width for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:200\nmsgid \"Number of column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:201\nmsgid \"Number of column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:202\nmsgid \"Number of column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:208\nmsgid \"Inherit from above\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:212\nmsgid \"Override column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:213\nmsgid \"Override column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:214\nmsgid \"Override column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:221\nmsgid \"No offset\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:223\nmsgid \"Offset for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:225\nmsgid \"Number of offset units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:226\nmsgid \"Number of offset units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:227\nmsgid \"Number of offset units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:233\nmsgid \"No reordering\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:234\nmsgid \"Push {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:235\nmsgid \"Pull {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:236\n#, python-brace-format\nmsgid \"Column ordering for {0}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:238\nmsgid \"Column ordering for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:239\nmsgid \"Column ordering for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:240\nmsgid \"Column ordering for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Visible\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Hidden\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:247\nmsgid \"Responsive utilities for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:249\nmsgid \"Utility classes for showing and hiding content by devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:250\nmsgid \"Utility classes for showing and hiding content by devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:251\nmsgid \"Utility classes for showing and hiding content for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:310\n#, python-brace-format\nmsgid \"widths: {0} units\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:313\n#, python-brace-format\nmsgid \"default width: {0} unit\"\nmsgid_plural \"default width: {0} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:315\nmsgid \"unknown width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:23 bootstrap3/image.py:44 bootstrap3/image.py:48\n#: bootstrap3/image.py:114 bootstrap3/picture.py:76\nmsgid \"Image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:24 bootstrap3/image.py:71 bootstrap3/picture.py:42\nmsgid \"Image Title\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:26 bootstrap3/image.py:72 bootstrap3/picture.py:43\nmsgid \"Caption text added to the 'title' attribute of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:27 bootstrap3/image.py:76 bootstrap3/picture.py:47\nmsgid \"Alternative Description\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:29 bootstrap3/image.py:77 bootstrap3/picture.py:48\nmsgid \"Textual description of the image added to the 'alt' tag of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:72\nmsgid \"Gallery\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:84 bootstrap3/image.py:63\nmsgid \"Responsive\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Upscale image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Crop image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:86 bootstrap3/image.py:66 bootstrap3/picture.py:37\nmsgid \"With subject location\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:87 bootstrap3/image.py:67 bootstrap3/picture.py:38\nmsgid \"Optimized for Retina\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:91\nmsgid \"Image Responsiveness\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:96 bootstrap3/image.py:87\nmsgid \"Responsive Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:98 bootstrap3/image.py:89\nmsgid \"Set the image width in percent relative to containing element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:102 bootstrap3/image.py:93\nmsgid \"Fixed Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:103 bootstrap3/image.py:94\nmsgid \"Set a fixed image width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:107 bootstrap3/image.py:98\nmsgid \"Adapt Image Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:108 bootstrap3/image.py:99\nmsgid \"Set a fixed height in pixels, or percent relative to the image width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:112\nmsgid \"Thumbnail Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:113\nmsgid \"Set a fixed thumbnail width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:117\nmsgid \"Thumbnail Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:118\nmsgid \"Set a fixed height in pixels, or percent relative to the thumbnail width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:174\n#, python-brace-format\nmsgid \"with {0} image\"\nmsgid_plural \"with {0} images\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/image.py:61 bootstrap3/picture.py:34\nmsgid \"No Link\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:63\nmsgid \"Rounded\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Circle\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Thumbnail\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:82\nmsgid \"Image Shapes\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:146\nmsgid \"No Image\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"normal\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"Heading {}\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:39\nmsgid \"Content\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:47\nmsgid \"Panel\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:67\nmsgid \"Panel Heading\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:71\nmsgid \"Panel Footer\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:20\nmsgid \"Picture\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:53\nmsgid \"Adapt Picture Heights\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:55\nmsgid \"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:59\nmsgid \"Adapt Picture Zoom\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:61\nmsgid \"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:107\nmsgid \"No Picture\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:17\nmsgid \"Secondary Menu\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:26\nmsgid \"CMS Page Id\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:27\nmsgid \"Select a CMS page with a given unique Id (in advanced settings).\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:12\nmsgid \"mobile phones\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:13\nmsgid \"tablets\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:14\nmsgid \"laptops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:15\nmsgid \"large desktops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:38\nmsgid \"unstyled\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:33\n#, python-format\nmsgid \"'%s' is not a valid CSS class name.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px, em and %\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px and em\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"%\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:42\nmsgid \"CSS class names\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:43\nmsgid \"Freely selectable CSS classnames for this Plugin, separated by commas.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:47\nmsgid \"Allow multiple\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:60\n#, python-brace-format\nmsgid \"Customized {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:68\n#, python-brace-format\nmsgid \"Units for {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:43\nmsgid \"Named Element ID\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:53\nmsgid \"Select CSS\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:56\nmsgid \"Customized CSS Classes\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:57\nmsgid \"Customized CSS classes to be added to this element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:13\nmsgid \"Simple Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:17\nmsgid \"<{}> – Element\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:18\nmsgid \"Naked Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:22\nmsgid \"HTML element tag\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:23\nmsgid \"Choose a tag type for this HTML element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:44\nmsgid \"Horizontal Rule\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:27\nmsgid \"Link Content\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:29\nmsgid \"Content of Link\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"CMS Page\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"External URL\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"Mail To\"\nmsgstr \"\"\n\n#: link/forms.py:27\nmsgid \"An internal link onto CMS pages of this site\"\nmsgstr \"\"\n\n#: link/forms.py:28\nmsgid \"Link onto external page\"\nmsgstr \"\"\n\n#: link/forms.py:29\nmsgid \"Open Email program with this address\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"Same Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"New Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Parent Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Topmost Frame\"\nmsgstr \"\"\n\n#: link/plugin_base.py:24\nmsgid \"Link Target\"\nmsgstr \"\"\n\n#: link/plugin_base.py:25\nmsgid \"Open Link in other target.\"\nmsgstr \"\"\n\n#: link/plugin_base.py:29\nmsgid \"Title\"\nmsgstr \"\"\n\n#: link/plugin_base.py:30\nmsgid \"Link's Title\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:23\nmsgid \"Segment\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:27\nmsgid \"Condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:28\nmsgid \"Django's condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:32\nmsgid \"Condition evaluation\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:33\nmsgid \"Evaluation as used in Django's template tags for conditions\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:114\n#, python-brace-format\nmsgid \"Unable to evaluate condition: {err}\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145 segmentation/cms_plugins.py:146\nmsgid \"if\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"elif\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"else\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:52\nmsgid \"Emulate User\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:54\nmsgid \"Clear emulations\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:112\n#, python-format\nmsgid \"%(total_count)s selected\"\nmsgid_plural \"All %(total_count)s selected\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: segmentation/mixins.py:117\n#, python-format\nmsgid \"0 of %(cnt)s selected\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:119\n#, python-format\nmsgid \"Select %(user_model)s to emulate\"\nmsgstr \"\"\n\n#: sharable/admin.py:28\nmsgid \"Shared Fields\"\nmsgstr \"\"\n\n#: sharable/admin.py:60\n#, python-format\nmsgid \"Change %s\"\nmsgstr \"\"\n\n#: sharable/admin.py:79\nmsgid \"Used by plugins\"\nmsgstr \"\"\n\n#: sharable/forms.py:48\nmsgid \"Remember these settings as:\"\nmsgstr \"\"\n\n#: sharable/forms.py:54\n#, python-brace-format\nmsgid \"The identifier '{0}' has already been used, please choose another name.\"\nmsgstr \"\"\n\n#: sharable/forms.py:70\nmsgid \"Shared Settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:73\nmsgid \"Use individual settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:74\nmsgid \"Use settings shared with other plugins of this type\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:7\nmsgid \"There are no further settings for this plugin\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:8\nmsgid \"Please hit OK to save.\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:39\nmsgid \"Home\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:42\nmsgid \"Add\"\nmsgstr \"\"\n"
  },
  {
    "path": "cmsplugin_cascade/locale/uk/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-06 15:36+0200\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\\n\"\n\n#: models.py:16 models.py:81\nmsgid \"Plugin Name\"\nmsgstr \"\"\n\n#: models.py:17\nmsgid \"Identifier\"\nmsgstr \"\"\n\n#: models.py:23\nmsgid \"Shared between Plugins\"\nmsgstr \"\"\n\n#: models.py:82\nmsgid \"Site\"\nmsgstr \"\"\n\n#: models.py:89\nmsgid \"Custom CSS classes and styles\"\nmsgstr \"\"\n\n#: models.py:96 segmentation/cms_toolbar.py:13\nmsgid \"Segmentation\"\nmsgstr \"\"\n\n#: render_template.py:24\nmsgid \"Render template\"\nmsgstr \"\"\n\n#: render_template.py:25\nmsgid \"Use alternative template for rendering this plugin.\"\nmsgstr \"\"\n\n#: settings.py:54 bootstrap3/settings.py:29 bootstrap3/settings.py:33\n#: bootstrap3/settings.py:37\nmsgid \"default\"\nmsgstr \"\"\n\n#: settings.py:55\nmsgid \"with line break\"\nmsgstr \"\"\n\n#: widgets.py:94 widgets.py:123\n#, python-format\nmsgid \"In '%(label)s': This field is required.\"\nmsgstr \"\"\n\n#: widgets.py:95\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number.\"\nmsgstr \"\"\n\n#: widgets.py:111\nmsgid \"or\"\nmsgstr \"\"\n\n#: widgets.py:124\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: widgets.py:150 extra_fields/admin.py:24\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' is not a valid color.\"\nmsgstr \"\"\n\n#: widgets.py:178 bootstrap3/buttons.py:88\nmsgid \"Disabled\"\nmsgstr \"\"\n\n#: widgets.py:261\n#, python-format\nmsgid \"In '%(label)s': Field '%(field)s' is required.\"\nmsgstr \"\"\n\n#: widgets.py:262\n#, python-format\nmsgid \"In '%(label)s': Value '%(value)s' for field '%(field)s' shall contain a valid number, ending in %(endings)s.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:25\nmsgid \"Panels\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:26\nmsgid \"Number of panels for this panel group.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:30\nmsgid \"Accordion\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:42\nmsgid \"Close others\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:44\nmsgid \"Open only one panel at a time.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:48\nmsgid \"First panel open\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:50\nmsgid \"Start with the first panel open.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:58\n#, python-brace-format\nmsgid \"with {0} panel\"\nmsgid_plural \"with {0} panels\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/accordion.py:70\nmsgid \"Accordion Panel\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:78 bootstrap3/panel.py:57\nmsgid \"Panel type\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:79 bootstrap3/panel.py:58\nmsgid \"Display Panel using this style.\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:84 bootstrap3/panel.py:63\nmsgid \"Heading Size\"\nmsgstr \"\"\n\n#: bootstrap3/accordion.py:88\nmsgid \"Panel Title\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/buttons.py:45\n#: bootstrap3/container.py:246 bootstrap3/panel.py:25\nmsgid \"Default\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:23 bootstrap3/panel.py:25\nmsgid \"Primary\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Success\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Info\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:24 bootstrap3/panel.py:26\nmsgid \"Warning\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 bootstrap3/panel.py:27\nmsgid \"Danger\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:25 link/cms_plugins.py:14\nmsgid \"Link\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Large\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:45\nmsgid \"Small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:46\nmsgid \"Extra small\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:77\nmsgid \"Button Type\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:79\nmsgid \"Display Link using this Button Style\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:83\nmsgid \"Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:85\nmsgid \"Display Link using this Button Size\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:88\nmsgid \"Block level\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:89\nmsgid \"Button Options\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Do not float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull left\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:92\nmsgid \"Pull right\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:93\nmsgid \"Quick Float\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:95\nmsgid \"Float the button to the left or right.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:99\nmsgid \"Prepend icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:101\nmsgid \"Prepend a Glyphicon before the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:105\nmsgid \"Append icon\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:107\nmsgid \"Append a Glyphicon after the content.\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:126\nmsgid \"Button\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:142\nmsgid \"Empty\"\nmsgstr \"\"\n\n#: bootstrap3/buttons.py:146\nmsgid \"Button Content\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:31\nmsgid \"Slides\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:32\nmsgid \"Number of slides for this carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:37\nmsgid \"Carousel\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Animate\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Pause\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:46\nmsgid \"Wrap\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:50\nmsgid \"Interval\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:52\nmsgid \"Change slide after this number of seconds.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:56\nmsgid \"Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:58\nmsgid \"Adjust interval for the carousel.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:62\nmsgid \"Carousel heights\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:64\nmsgid \"Heights of Carousel in pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:68 bootstrap3/gallery.py:122 bootstrap3/image.py:103\n#: bootstrap3/picture.py:65\nmsgid \"Resize Options\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:69 bootstrap3/gallery.py:123 bootstrap3/image.py:104\n#: bootstrap3/picture.py:66\nmsgid \"Options to use when resizing the image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:82\n#, python-brace-format\nmsgid \"with {0} slide\"\nmsgid_plural \"with {0} slides\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/carousel.py:123\nmsgid \"Slide\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:142\nmsgid \"Slide Caption\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:143\nmsgid \"Caption text to be laid over the backgroud image.\"\nmsgstr \"\"\n\n#: bootstrap3/carousel.py:174\nmsgid \"Empty Slide\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:33\nmsgid \"At least one breakpoint must be selected.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:38\nmsgid \"Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:42\n#, python-brace-format\nmsgid \"Tiny (<{sm[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:43\n#, python-brace-format\nmsgid \"Small (≥{sm[0]}px and <{md[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:44\n#, python-brace-format\nmsgid \"Medium (≥{md[0]}px and <{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:45\n#, python-brace-format\nmsgid \"Large (≥{lg[0]}px)\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:50\nmsgid \"Available Breakpoints\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:52\nmsgid \"Supported display widths for Bootstrap's grid system.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:56\nmsgid \"Fluid Container\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:57\nmsgid \"Changing your outermost '.container' to '.container-fluid'.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:73\n#, python-brace-format\nmsgid \"{0}for {1}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:129\n#, python-brace-format\nmsgid \"{0} column\"\nmsgid_plural \"{0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:130\nmsgid \"Columns\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:131\nmsgid \"Number of columns to be created with this row.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:135\nmsgid \"Row\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:145\n#, python-brace-format\nmsgid \"with {0} column\"\nmsgid_plural \"with {0} columns\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:163\nmsgid \"Column\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:182\nmsgid \"{} unit\"\nmsgid_plural \"{} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:198 bootstrap3/container.py:210\nmsgid \"Column width for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:200\nmsgid \"Number of column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:201\nmsgid \"Number of column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:202\nmsgid \"Number of column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:208\nmsgid \"Inherit from above\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:212\nmsgid \"Override column units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:213\nmsgid \"Override column units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:214\nmsgid \"Override column units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:221\nmsgid \"No offset\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:223\nmsgid \"Offset for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:225\nmsgid \"Number of offset units for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:226\nmsgid \"Number of offset units for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:227\nmsgid \"Number of offset units for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:233\nmsgid \"No reordering\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:234\nmsgid \"Push {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:235\nmsgid \"Pull {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:236\n#, python-brace-format\nmsgid \"Column ordering for {0}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:238\nmsgid \"Column ordering for devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:239\nmsgid \"Column ordering for devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:240\nmsgid \"Column ordering for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Visible\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:246\nmsgid \"Hidden\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:247\nmsgid \"Responsive utilities for {}\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:249\nmsgid \"Utility classes for showing and hiding content by devices narrower than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:250\nmsgid \"Utility classes for showing and hiding content by devices wider than {} pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:251\nmsgid \"Utility classes for showing and hiding content for all devices.\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:310\n#, python-brace-format\nmsgid \"widths: {0} units\"\nmsgstr \"\"\n\n#: bootstrap3/container.py:313\n#, python-brace-format\nmsgid \"default width: {0} unit\"\nmsgid_plural \"default width: {0} units\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/container.py:315\nmsgid \"unknown width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:23 bootstrap3/image.py:44 bootstrap3/image.py:48\n#: bootstrap3/image.py:114 bootstrap3/picture.py:76\nmsgid \"Image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:24 bootstrap3/image.py:71 bootstrap3/picture.py:42\nmsgid \"Image Title\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:26 bootstrap3/image.py:72 bootstrap3/picture.py:43\nmsgid \"Caption text added to the 'title' attribute of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:27 bootstrap3/image.py:76 bootstrap3/picture.py:47\nmsgid \"Alternative Description\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:29 bootstrap3/image.py:77 bootstrap3/picture.py:48\nmsgid \"Textual description of the image added to the 'alt' tag of the <img> element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:72\nmsgid \"Gallery\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:84 bootstrap3/image.py:63\nmsgid \"Responsive\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Upscale image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:85 bootstrap3/image.py:65 bootstrap3/picture.py:36\nmsgid \"Crop image\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:86 bootstrap3/image.py:66 bootstrap3/picture.py:37\nmsgid \"With subject location\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:87 bootstrap3/image.py:67 bootstrap3/picture.py:38\nmsgid \"Optimized for Retina\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:91\nmsgid \"Image Responsiveness\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:96 bootstrap3/image.py:87\nmsgid \"Responsive Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:98 bootstrap3/image.py:89\nmsgid \"Set the image width in percent relative to containing element.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:102 bootstrap3/image.py:93\nmsgid \"Fixed Image Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:103 bootstrap3/image.py:94\nmsgid \"Set a fixed image width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:107 bootstrap3/image.py:98\nmsgid \"Adapt Image Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:108 bootstrap3/image.py:99\nmsgid \"Set a fixed height in pixels, or percent relative to the image width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:112\nmsgid \"Thumbnail Width\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:113\nmsgid \"Set a fixed thumbnail width in pixels.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:117\nmsgid \"Thumbnail Height\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:118\nmsgid \"Set a fixed height in pixels, or percent relative to the thumbnail width.\"\nmsgstr \"\"\n\n#: bootstrap3/gallery.py:174\n#, python-brace-format\nmsgid \"with {0} image\"\nmsgid_plural \"with {0} images\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: bootstrap3/image.py:61 bootstrap3/picture.py:34\nmsgid \"No Link\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:63\nmsgid \"Rounded\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Circle\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:64\nmsgid \"Thumbnail\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:82\nmsgid \"Image Shapes\"\nmsgstr \"\"\n\n#: bootstrap3/image.py:146\nmsgid \"No Image\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"normal\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:18\nmsgid \"Heading {}\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:39\nmsgid \"Content\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:47\nmsgid \"Panel\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:67\nmsgid \"Panel Heading\"\nmsgstr \"\"\n\n#: bootstrap3/panel.py:71\nmsgid \"Panel Footer\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:20\nmsgid \"Picture\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:53\nmsgid \"Adapt Picture Heights\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:55\nmsgid \"Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:59\nmsgid \"Adapt Picture Zoom\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:61\nmsgid \"Magnification of picture in percent for distinct Bootstrap's breakpoints.\"\nmsgstr \"\"\n\n#: bootstrap3/picture.py:107\nmsgid \"No Picture\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:17\nmsgid \"Secondary Menu\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:26\nmsgid \"CMS Page Id\"\nmsgstr \"\"\n\n#: bootstrap3/secondary_menu.py:27\nmsgid \"Select a CMS page with a given unique Id (in advanced settings).\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:12\nmsgid \"mobile phones\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:13\nmsgid \"tablets\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:14\nmsgid \"laptops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:15\nmsgid \"large desktops\"\nmsgstr \"\"\n\n#: bootstrap3/settings.py:38\nmsgid \"unstyled\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:33\n#, python-format\nmsgid \"'%s' is not a valid CSS class name.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px, em and %\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px and em\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"px\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:38\nmsgid \"%\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:42\nmsgid \"CSS class names\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:43\nmsgid \"Freely selectable CSS classnames for this Plugin, separated by commas.\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:47\nmsgid \"Allow multiple\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:60\n#, python-brace-format\nmsgid \"Customized {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/admin.py:68\n#, python-brace-format\nmsgid \"Units for {0} Fields:\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:43\nmsgid \"Named Element ID\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:53\nmsgid \"Select CSS\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:56\nmsgid \"Customized CSS Classes\"\nmsgstr \"\"\n\n#: extra_fields/mixins.py:57\nmsgid \"Customized CSS classes to be added to this element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:13\nmsgid \"Simple Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:17\nmsgid \"<{}> – Element\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:18\nmsgid \"Naked Wrapper\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:22\nmsgid \"HTML element tag\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:23\nmsgid \"Choose a tag type for this HTML element.\"\nmsgstr \"\"\n\n#: generic/cms_plugins.py:44\nmsgid \"Horizontal Rule\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:27\nmsgid \"Link Content\"\nmsgstr \"\"\n\n#: link/cms_plugins.py:29\nmsgid \"Content of Link\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"CMS Page\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"External URL\"\nmsgstr \"\"\n\n#: link/forms.py:24\nmsgid \"Mail To\"\nmsgstr \"\"\n\n#: link/forms.py:27\nmsgid \"An internal link onto CMS pages of this site\"\nmsgstr \"\"\n\n#: link/forms.py:28\nmsgid \"Link onto external page\"\nmsgstr \"\"\n\n#: link/forms.py:29\nmsgid \"Open Email program with this address\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"Same Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:21\nmsgid \"New Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Parent Window\"\nmsgstr \"\"\n\n#: link/plugin_base.py:22\nmsgid \"Topmost Frame\"\nmsgstr \"\"\n\n#: link/plugin_base.py:24\nmsgid \"Link Target\"\nmsgstr \"\"\n\n#: link/plugin_base.py:25\nmsgid \"Open Link in other target.\"\nmsgstr \"\"\n\n#: link/plugin_base.py:29\nmsgid \"Title\"\nmsgstr \"\"\n\n#: link/plugin_base.py:30\nmsgid \"Link's Title\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:23\nmsgid \"Segment\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:27\nmsgid \"Condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:28\nmsgid \"Django's condition tag\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:32\nmsgid \"Condition evaluation\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:33\nmsgid \"Evaluation as used in Django's template tags for conditions\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:114\n#, python-brace-format\nmsgid \"Unable to evaluate condition: {err}\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145 segmentation/cms_plugins.py:146\nmsgid \"if\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"elif\"\nmsgstr \"\"\n\n#: segmentation/cms_plugins.py:145\nmsgid \"else\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:52\nmsgid \"Emulate User\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:54\nmsgid \"Clear emulations\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:112\n#, python-format\nmsgid \"%(total_count)s selected\"\nmsgid_plural \"All %(total_count)s selected\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: segmentation/mixins.py:117\n#, python-format\nmsgid \"0 of %(cnt)s selected\"\nmsgstr \"\"\n\n#: segmentation/mixins.py:119\n#, python-format\nmsgid \"Select %(user_model)s to emulate\"\nmsgstr \"\"\n\n#: sharable/admin.py:28\nmsgid \"Shared Fields\"\nmsgstr \"\"\n\n#: sharable/admin.py:60\n#, python-format\nmsgid \"Change %s\"\nmsgstr \"\"\n\n#: sharable/admin.py:79\nmsgid \"Used by plugins\"\nmsgstr \"\"\n\n#: sharable/forms.py:48\nmsgid \"Remember these settings as:\"\nmsgstr \"\"\n\n#: sharable/forms.py:54\n#, python-brace-format\nmsgid \"The identifier '{0}' has already been used, please choose another name.\"\nmsgstr \"\"\n\n#: sharable/forms.py:70\nmsgid \"Shared Settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:73\nmsgid \"Use individual settings\"\nmsgstr \"\"\n\n#: sharable/forms.py:74\nmsgid \"Use settings shared with other plugins of this type\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:7\nmsgid \"There are no further settings for this plugin\"\nmsgstr \"\"\n\n#: templates/cascade/admin/change_form.html:8\nmsgid \"Please hit OK to save.\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:39\nmsgid \"Home\"\nmsgstr \"\"\n\n#: templates/cascade/admin/segmentation_list.html:42\nmsgid \"Add\"\nmsgstr \"\"\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0001_initial.py",
    "content": "from django.db import models, migrations\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('sites', '0001_initial'),\n        ('cms', '0003_auto_20140926_2347'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='CascadeElement',\n            fields=[\n                ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='+', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)),\n                ('glossary', models.JSONField(default={}, null=True, blank=True)),\n            ],\n            options={\n                'db_table': 'cmsplugin_cascade_element',\n            },\n            bases=('cms.cmsplugin',),\n        ),\n        migrations.CreateModel(\n            name='PluginExtraFields',\n            fields=[\n                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n                ('plugin_type', models.CharField(db_index=True, max_length=50, verbose_name='Plugin Name', choices=[(b'BootstrapButtonPlugin', b'Bootstrap Button'), (b'SimpleWrapperPlugin', b'Bootstrap Simple Wrapper'), (b'BootstrapRowPlugin', b'Bootstrap Row'), (b'BootstrapPicturePlugin', b'Bootstrap Picture'), (b'BootstrapContainerPlugin', b'Bootstrap Container'), (b'BootstrapColumnPlugin', b'Bootstrap Column')])),\n                ('allow_id_tag', models.BooleanField(default=False)),\n                ('css_classes', models.JSONField(default={}, null=True, blank=True)),\n                ('inline_styles', models.JSONField(default={}, null=True, blank=True)),\n                ('site', models.ForeignKey(verbose_name='Site', to='sites.Site', on_delete=models.CASCADE)),\n            ],\n            options={\n                'verbose_name': 'Custom CSS classes and styles',\n                'verbose_name_plural': 'Custom CSS classes and styles',\n            },\n            bases=(models.Model,),\n        ),\n        migrations.CreateModel(\n            name='SharableCascadeElement',\n            fields=[\n                ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='+', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)),\n                ('glossary', models.JSONField(default={}, null=True, blank=True)),\n            ],\n            options={\n                'db_table': 'cmsplugin_cascade_sharableelement',\n            },\n            bases=('cms.cmsplugin',),\n        ),\n        migrations.CreateModel(\n            name='SharedGlossary',\n            fields=[\n                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n                ('plugin_type', models.CharField(verbose_name='Plugin Name', max_length=50, editable=False, db_index=True)),\n                ('identifier', models.CharField(unique=True, max_length=50, verbose_name='Identifier')),\n                ('glossary', models.JSONField(default={}, null=True, blank=True)),\n            ],\n            options={\n                'verbose_name': 'Shared between Plugins',\n                'verbose_name_plural': 'Shared between Plugins',\n            },\n            bases=(models.Model,),\n        ),\n        migrations.AlterUniqueTogether(\n            name='sharedglossary',\n            unique_together=set([('plugin_type', 'identifier')]),\n        ),\n        migrations.AddField(\n            model_name='sharablecascadeelement',\n            name='shared_glossary',\n            field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='cmsplugin_cascade.SharedGlossary', null=True),\n            preserve_default=True,\n        ),\n        migrations.AlterUniqueTogether(\n            name='pluginextrafields',\n            unique_together=set([('plugin_type', 'site')]),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0002_auto_20150530_1018.py",
    "content": "from django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Segmentation',\n            fields=[\n                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n            ],\n            options={\n                'verbose_name': 'Segmentation',\n                'managed': False,\n                'verbose_name_plural': 'Segmentation',\n            },\n            bases=(models.Model,),\n        ),\n        migrations.AlterField(\n            model_name='cascadeelement',\n            name='glossary',\n            field=models.JSONField(default={}, blank=True),\n            preserve_default=True,\n        ),\n        migrations.AlterField(\n            model_name='pluginextrafields',\n            name='plugin_type',\n            field=models.CharField(max_length=50, verbose_name='Plugin Name', db_index=True),\n            preserve_default=True,\n        ),\n        migrations.AlterField(\n            model_name='sharablecascadeelement',\n            name='glossary',\n            field=models.JSONField(default={}, blank=True),\n            preserve_default=True,\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0003_inlinecascadeelement.py",
    "content": "from django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0002_auto_20150530_1018'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='InlineCascadeElement',\n            fields=[\n                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n                ('glossary', models.JSONField(default={}, blank=True)),\n                ('cascade_element', models.ForeignKey(related_name='inline_elements', to='cmsplugin_cascade.CascadeElement', on_delete=models.CASCADE)),\n            ],\n            options={\n                'db_table': 'cmsplugin_cascade_inline',\n            },\n            bases=(models.Model,),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0004_auto_20151112_0147.py",
    "content": "from django.db import models, migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0003_inlinecascadeelement'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0005_tabset_and_clipboard.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0004_auto_20151112_0147'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='CascadeClipboard',\n            fields=[\n                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n                ('identifier', models.CharField(unique=True, max_length=50, verbose_name='Identifier')),\n                ('data', models.JSONField(default={}, null=True, blank=True)),\n            ],\n            options={\n                'verbose_name': 'Persited Clipboard Content',\n                'verbose_name_plural': 'Persited Clipboard Content',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0006_bootstrapgallerypluginmodel.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0005_tabset_and_clipboard'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0007_add_proxy_models.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0006_bootstrapgallerypluginmodel'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0008_sortableinlinecascadeelement.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0007_add_proxy_models'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='SortableInlineCascadeElement',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('glossary', models.JSONField(blank=True, default={})),\n                ('order', models.PositiveIntegerField(db_index=True, verbose_name='Sort by')),\n                ('cascade_element', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sortinline_elements', to='cmsplugin_cascade.CascadeElement')),\n            ],\n            options={\n                'ordering': ('order',),\n                'db_table': 'cmsplugin_cascade_sortinline',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0009_cascadepage.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cms', '0013_urlconfrevision'),\n        ('cmsplugin_cascade', '0008_sortableinlinecascadeelement'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='CascadePage',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('settings', models.JSONField(blank=True, default={}, help_text='User editable settings for this page.')),\n                ('glossary', models.JSONField(blank=True, default={}, help_text='Store for arbitrary page data.')),\n                ('extended_object', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='cms.Page')),\n                ('public_extension', models.OneToOneField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='draft_extension', to='cmsplugin_cascade.CascadePage')),\n            ],\n            options={\n                'db_table': 'cmsplugin_cascade_page',\n                'verbose_name': 'Cascade Page Settings',\n                'verbose_name_plural': 'Cascade Page Settings',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0010_refactor_heading.py",
    "content": "from django.db import migrations\n\ndef forwards(apps, schema_editor):\n    CascadeElement = apps.get_model('cmsplugin_cascade', 'CascadeElement')\n    for element in CascadeElement.objects.using(schema_editor.connection.alias).filter(plugin_type='HeadingPlugin'):\n        head_size = element.glossary.pop('head_size', None)\n        if head_size:\n            element.glossary['tag_type'] = 'h{}'.format(head_size)\n            element.save()\n\n\ndef backwards(apps, schema_editor):\n    CascadeElement = apps.get_model('cmsplugin_cascade', 'CascadeElement')\n    for element in CascadeElement.objects.using(schema_editor.connection.alias).filter(plugin_type='HeadingPlugin'):\n        tag_type = element.glossary.pop('tag_type', None)\n        if tag_type and len(tag_type) == 2:\n            element.glossary['head_size'] = tag_type[1]\n            element.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0009_cascadepage'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0011_merge_sharable_with_cascadeelement.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\nfrom cmsplugin_cascade import app_settings\n\nplugins_with_sharables = app_settings.CMSPLUGIN_CASCADE['plugins_with_sharables'].keys()\nplugins_with_sharables = ','.join([\"'{}'\".format(p) for p in plugins_with_sharables])\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cms', '0015_auto_20160421_0000'),\n        ('cmsplugin_cascade', '0010_refactor_heading'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='cascadeelement',\n            name='shared_glossary',\n            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.SharedGlossary'),\n        ),\n        migrations.RunSQL(\n            [(\"INSERT INTO cmsplugin_cascade_element (cmsplugin_ptr_id, glossary, shared_glossary_id) SELECT cmsplugin_ptr_id, glossary, shared_glossary_id FROM cmsplugin_cascade_sharableelement\", None)],\n            reverse_sql=[\n                (\"INSERT INTO cmsplugin_cascade_sharableelement (cmsplugin_ptr_id, glossary) SELECT cmsplugin_ptr_id, glossary FROM cmsplugin_cascade_element INNER JOIN cms_cmsplugin ON cmsplugin_ptr_id=cms_cmsplugin.id WHERE plugin_type IN ({})\".format(plugins_with_sharables), None),\n                (\"DELETE FROM cmsplugin_cascade_element WHERE cmsplugin_ptr_id IN (SELECT cmsplugin_ptr_id FROM cmsplugin_cascade_sharableelement)\", None)\n            ]\n        ),\n        migrations.RemoveField(\n            model_name='sharablecascadeelement',\n            name='cmsplugin_ptr',\n        ),\n        migrations.RemoveField(\n            model_name='sharablecascadeelement',\n            name='shared_glossary',\n        ),\n        migrations.DeleteModel(\n            name='SharableCascadeElement',\n        ),\n        migrations.CreateModel(\n            name='SharableCascadeElement',\n            fields=[\n            ],\n            options={\n                'proxy': True,\n            },\n            bases=('cmsplugin_cascade.cascadeelement',),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0012_auto_20160619_1854.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0011_merge_sharable_with_cascadeelement'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='cascadeelement',\n            name='shared_glossary',\n            field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.SharedGlossary'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0013_iconfont.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\nimport filer.fields.file\nimport cmsplugin_cascade.models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('filer', '0006_auto_20160623_1627'),\n        ('cmsplugin_cascade', '0012_auto_20160619_1854'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='IconFont',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('identifier', models.CharField(help_text='A unique identifier to distinguish this icon font.', max_length=50, unique=True, verbose_name='Identifier')),\n                ('config_data', models.JSONField()),\n                ('font_folder', cmsplugin_cascade.models.FilePathField(allow_files=False, allow_folders=True)),\n                ('zip_file', filer.fields.file.FilerFileField(help_text='Upload a zip file created on <a href=\"http://fontello.com/\" target=\"_blank\">Fontello</a> containing fonts.', on_delete=django.db.models.deletion.CASCADE, to='filer.File')),\n            ],\n            options={\n                'verbose_name': 'Uploaded Icon Font',\n                'verbose_name_plural': 'Uploaded Icon Fonts',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0014_glossary_field.py",
    "content": "from django.db import migrations\n\nFIELD_MAPPINGS = {\n    'BootstrapButtonPlugin': [\n        ('button-type', 'button_type'),\n        ('button-size', 'button_size'),\n        ('button-options', 'button_options'),\n        ('quick-float', 'quick_float'),\n        ('icon-left', 'icon_left'),\n        ('icon-right', 'icon_right'),\n    ],\n    'CarouselPlugin': [\n        ('resize-options', 'resize_options'),\n    ],\n    'BootstrapGalleryPlugin': [\n        ('image-shapes', 'image_shapes'),\n        ('image-width-responsive', 'image_width_responsive'),\n        ('image-width-fixed', 'image_width_fixed'),\n        ('image-height', 'image_height'),\n        ('thumbnail-width', 'thumbnail_width'),\n        ('thumbnail-height', 'thumbnail_height'),\n        ('resize-options', 'resize_options'),\n    ],\n    'BootstrapImagePlugin': [\n        ('image-title', 'image_title'),\n        ('alt-tag', 'alt_tag'),\n        ('image-shapes', 'image_shapes'),\n        ('image-width-responsive', 'image_width_responsive'),\n        ('image-width-fixed', 'image_width_fixed'),\n        ('image-height', 'image_height'),\n        ('resize-options', 'resize_options'),\n    ],\n    'BootstrapJumbotronPlugin': [\n        ('background-color', 'background_color'),\n        ('background-repeat', 'background_repeat'),\n        ('background-attachment', 'background_attachment'),\n        ('background-vertical-position', 'background_vertical_position'),\n        ('background-horizontal-position', 'background_horizontal_position'),\n        ('background-size', 'background_size'),\n        ('background-width-height', 'background_width_height'),\n    ],\n    'BootstrapPicturePlugin': [\n        ('image-title', 'image_title'),\n        ('alt-tag', 'alt_tag'),\n        ('responsive-heights', 'responsive_heights'),\n        ('responsive-zoom', 'responsive_zoom'),\n        ('resize-options', 'resize_options'),\n    ]\n}\n\n\ndef forwards(apps, schema_editor):\n    field_mappings = {}\n    for key, maps in FIELD_MAPPINGS.items():\n        field_mappings[key] = dict(maps)\n    migrate_glossary(apps, field_mappings)\n\n\ndef backwards(apps, schema_editor):\n    field_mappings = {}\n    for key, maps in FIELD_MAPPINGS.items():\n        field_mappings[key] = dict((m[1], m[0]) for m in maps)\n    migrate_glossary(apps, field_mappings)\n\n\ndef migrate_glossary(apps, field_mappings):\n    CascadeElement = apps.get_model('cmsplugin_cascade', 'CascadeElement')\n    for element in CascadeElement.objects.all():\n        if element.plugin_type not in field_mappings:\n            continue\n        glossary = dict(element.glossary)\n        for srckey, value in element.glossary.items():\n            dstkey = field_mappings[element.plugin_type].get(srckey)\n            if dstkey and srckey in glossary:\n                glossary[dstkey] = glossary.pop(srckey)\n        element.glossary = glossary\n        element.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0013_iconfont'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0015_carousel_slide.py",
    "content": "import re\nimport warnings\nfrom html.parser import HTMLParser\nfrom django.db import migrations\nfrom cms.api import add_plugin\nfrom cms.models.pluginmodel import CMSPlugin\nfrom djangocms_text_ckeditor.cms_plugins import TextPlugin\nfrom cmsplugin_cascade.models import CascadeElement\n\n\ndef _replace_text_body(old_body, input_pattern, output_tag, id_format):\n    regex = re.compile(input_pattern)\n\n    def _do_replace(match):\n        before_id, old_plugin_id, after_id = match.groups()\n\n        if not old_plugin_id:\n            return ''\n\n        bits = []\n\n        if before_id:\n            bits.append(before_id.strip())\n\n        bits.append(id_format.format(old_plugin_id))\n\n        if after_id:\n            bits.append(after_id.strip())\n\n        # By using .join() we ensure the correct\n        # amount of spaces are used to separate the different\n        # attributes.\n        tag_attrs = ' '.join(bits)\n        return output_tag.format(tag_attrs)\n\n    new_body, count = regex.subn(_do_replace, old_body)\n    return new_body, count\n\n\ndef forwards(apps, schema_editor):\n    html_parser = HTMLParser()\n\n    for cascade_element in CascadeElement.objects.all():\n        if cascade_element.plugin_type != 'CarouselSlidePlugin':\n            continue\n\n        caption = cascade_element.glossary.get('caption')\n        if not caption:\n            continue\n\n        text_element = add_plugin(cascade_element.placeholder, TextPlugin, cascade_element.language,\n                                  target=cascade_element)\n\n        old_body = html_parser.unescape(caption)\n        new_body, count = _replace_text_body(\n            old_body,\n            input_pattern=r'<img ([^>]*)\\bid=\"plugin_obj_(?P<pk>\\d+)\"([^>]*)/?>',\n            output_tag='<cms-plugin {}></cms-plugin>',\n            id_format='id=\"{}\"',\n        )\n        text_element.body = new_body\n        text_element.save()\n\n        # TODO: need to be re-tested\n        if False and count > 0:\n            for link_element in CMSPlugin.objects.filter(parent_id__in=(cascade_element.id, cascade_element.parent_id), plugin_type='TextLinkPlugin'):\n                # print(\"Move Link {} from {} -> {}\".format(link_element.id, link_element.parent_id, text_element.id))\n                link_element.move(text_element, pos='last-child')\n                link_element.save()\n\n\ndef backwards(apps, schema_editor):\n    warnings.warn(\"Backward migration for Carousel Slide Plugins is not supported. Please check them manually.\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0014_glossary_field'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0016_shared_glossary_uneditable.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0015_carousel_slide'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='cascadeelement',\n            name='shared_glossary',\n            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.SharedGlossary'),\n        ),\n        migrations.AlterModelOptions(\n            name='cascadeelement',\n            options={'verbose_name': 'Element'},\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0017_fake_proxy_models.py",
    "content": "from cmsplugin_cascade.models import CascadeModelBase\nfrom cmsplugin_cascade.plugin_base import fake_proxy_models\nfrom django.db import migrations\nfrom django.utils.functional import classproperty\n\n\nclass Migration(migrations.Migration):\n    \"\"\"\n    This migration is a noop. It pretends that the proxy models created by Cascade do already\n    exists, so that ``./manage.py makemigrations`` does not create any migration file for you.\n    \"\"\"\n\n    dependencies = [\n        ('cmsplugin_cascade', '0016_shared_glossary_uneditable'),\n    ]\n\n    @classproperty\n    def operations(self):\n        for name, bases in sorted(fake_proxy_models.items()):\n            bases = tuple(\n                b._meta.app_label + '.' + b._meta.model_name if issubclass(b, CascadeModelBase) else b\n                for b in bases\n            )\n            yield migrations.CreateModel(\n                name=name,\n                fields=[],\n                options={'proxy': True},\n                bases=bases,\n            )\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0018_iconfont_color.py",
    "content": "from django.db import migrations\nfrom cmsplugin_cascade.models import CascadeElement\n\n\ndef forwards(apps, schema_editor):\n    for cascade_element in CascadeElement.objects.all():\n        if cascade_element.plugin_type != 'FramedIconPlugin':\n            continue\n\n        color = cascade_element.glossary.get('color')\n        if isinstance(color, str):\n            cascade_element.glossary['color'] = ('', color)\n            cascade_element.save()\n        shared = cascade_element.shared_glossary\n        if shared:\n            color = shared.glossary.get('color')\n            if isinstance(color, str):\n                shared.glossary['color'] = ('', color)\n                shared.save()\n\n\ndef backwards(apps, schema_editor):\n    for cascade_element in CascadeElement.objects.all():\n        if cascade_element.plugin_type != 'FramedIconPlugin':\n            continue\n\n        color = cascade_element.glossary.get('color')\n        if isinstance(color, (list, tuple)):\n            cascade_element.glossary['color'] = color[1]\n            cascade_element.save()\n        shared = cascade_element.shared_glossary\n        if shared:\n            color = shared.glossary.get('color')\n            if isinstance(color, (list, tuple)):\n                shared.glossary['color'] = color[1]\n                shared.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0017_fake_proxy_models'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0019_verbose_table_names.py",
    "content": "from django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0018_iconfont_color'),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name='cascadeclipboard',\n            options={'verbose_name': 'Persisted Clipboard Content', 'verbose_name_plural': 'Persisted Clipboard Contents'},\n        ),\n        migrations.AlterModelOptions(\n            name='cascadeelement',\n            options={'verbose_name': 'Element', 'verbose_name_plural': 'Elements'},\n        ),\n        migrations.AlterModelOptions(\n            name='cascadepage',\n            options={'verbose_name': 'Cascade Page Settings'},\n        ),\n        migrations.AlterModelOptions(\n            name='segmentation',\n            options={'managed': False, 'verbose_name': 'Segmentation'},\n        ),\n        migrations.AlterModelOptions(\n            name='sortableinlinecascadeelement',\n            options={'ordering': ['order']},\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0020_page_icon_font.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\ndef forwards(apps, schema_editor):\n    print(\"Skipping this migration.\")\n\n\ndef backwards(apps, schema_editor):\n    print(\"Backward migration not implemented.\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0019_verbose_table_names'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='cascadepage',\n            name='icon_font',\n            field=models.ForeignKey(blank=True, help_text='Set Icon Font globally for this page', null=True, on_delete=django.db.models.deletion.CASCADE, to='cmsplugin_cascade.IconFont', verbose_name='Icon Font'),\n        ),\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0021_cascadepage_verbose_name.py",
    "content": "from django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0020_page_icon_font'),\n    ]\n\n    operations = [\n        migrations.AlterModelOptions(\n            name='cascadepage',\n            options={'verbose_name': 'Cascade Page Settings', 'verbose_name_plural': 'Cascade Page Settings'},\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0022_auto_20181202_1055.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0021_cascadepage_verbose_name'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='cascadepage',\n            name='icon_font',\n            field=models.ForeignKey(blank=True, help_text='Set Icon Font globally for this page', null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.IconFont', verbose_name='Icon Font'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0023_iconfont_is_default.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0022_auto_20181202_1055'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='iconfont',\n            name='is_default',\n            field=models.BooleanField(default=False, help_text='Use this font as default, unless an icon font is set for the current page.', verbose_name='Default Font'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0024_page_icon_font.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\nfrom cmsplugin_cascade.models import CascadeElement, CascadePage, IconFont\n\n\ndef forwards(apps, schema_editor):\n    for cascade_element in CascadeElement.objects.all():\n        if cascade_element.plugin_type not in ['FramedIconPlugin', 'TextIconPlugin', 'BootstrapButtonPlugin']:\n            continue\n        try:\n            cms_page = cascade_element.page.get_public_object()\n            icon_font = cms_page.cascadepage.icon_font\n            if not icon_font:\n                continue\n        except:\n            continue\n        if 'icon_font' not in cascade_element.glossary:\n            cascade_element.glossary['icon_font'] = icon_font.pk\n            cascade_element.save()\n\n\ndef backwards(apps, schema_editor):\n    print(\"Backward migration not implemented\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0023_iconfont_is_default'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='cascadepage',\n            name='icon_font',\n            field=models.ForeignKey(blank=True, help_text='Deprecated', null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.IconFont', verbose_name='Icon Font'),\n        ),\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0025_texteditorconfigfields.py",
    "content": "from django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0024_page_icon_font'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='TextEditorConfigFields',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=50, verbose_name='Name')),\n                ('element_type', models.CharField(choices=[('p', 'p'), ('h1', 'h1'), ('h2', 'h2'), ('h3', 'h3'), ('h4', 'h4'), ('h5', 'h5'), ('h6', 'h6'), ('pre', 'pre'), ('address', 'address'), ('div', 'div'), ('span', 'span')], max_length=12, verbose_name='Element Type')),\n                ('css_classes', models.CharField(help_text='Freely selectable CSS classnames for this Text-Editor Style, separated by spaces.', max_length=250, verbose_name='CSS classes')),\n            ],\n            options={\n                'verbose_name': 'Text Editor Config',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0026_cascadepage_menu_symbol.py",
    "content": "from django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0025_texteditorconfigfields'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='cascadepage',\n            name='menu_symbol',\n            field=models.CharField(blank=True, help_text='Symbol to be used with the menu title for this page.', max_length=32, null=True, verbose_name='Menu Symbol'),\n        ),\n        migrations.AlterField(\n            model_name='cascadepage',\n            name='icon_font',\n            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cmsplugin_cascade.IconFont', verbose_name='Icon Font'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0027_version_1.py",
    "content": "from django.db import migrations\nfrom cmsplugin_cascade.models import CascadeElement\n\n\ndef migrate_link(glossary):\n    def foreign_key():\n        return {'model': link['model'].lower(), 'pk': link['pk']}\n\n    link = glossary.pop('link', None)\n    if link:\n        if link['type'] == 'cmspage':\n            glossary.update({\n                'link_type': 'cmspage',\n                'cms_page': foreign_key(),\n                'section': link['section'],\n            })\n        elif link['type'] == 'download':\n            glossary.update({\n                'link_type': 'download',\n                'download_file': foreign_key(),\n            })\n        elif link['type'] == 'exturl':\n            glossary.update({\n                'link_type': 'exturl',\n                'ext_url': link['url'],\n            })\n        elif link['type'] == 'email':\n            glossary.update({\n                'link_type': 'email',\n                'mail_to': link['mail_to'],\n            })\n        elif link['type'] and link['type'] != 'none':\n            glossary.update({\n                'link_type': link['type'],\n            })\n        else:\n            glossary.update({\n                'link_type': '',\n            })\n        return True\n\n\ndef migrate_icon(glossary):\n    icon_font = glossary.pop('icon_font', None)\n    if icon_font and not isinstance(icon_font, dict):\n        glossary.update({\n            'icon_font': {'model': 'cmsplugin_cascade.iconfont', 'pk': int(icon_font)},\n        })\n        return True\n\n\ndef migrate_image(glossary):\n    image = glossary.pop('image', None)\n    if image:\n        glossary.update({\n            'image_file': {'model': 'filer.image', 'pk': image['pk']},\n        })\n        if 'width' in image and 'height' in image and 'exif_orientation' in image:\n            glossary.update({\n                '_image_properties': {'width': image['width'], 'height': image['height'],\n                                      'exif_orientation': image['exif_orientation']},\n            })\n        return True\n\n\ndef forwards(apps, schema_editor):\n    LINK_PLUGINS = ['TextLinkPlugin', 'LinkPlugin', 'BootstrapButtonPlugin', 'SimpleIconPlugin', 'FramedIconPlugin',\n                    'BootstrapImagePlugin', 'BootstrapPicturePlugin', 'TextImagePlugin', 'TextIconPlugin']\n    ICON_PLUGINS = ['BootstrapButtonPlugin', 'SimpleIconPlugin', 'FramedIconPlugin', 'TextIconPlugin']\n    IMAGE_PLUGINS = ['BootstrapImagePlugin', 'BootstrapPicturePlugin', 'TextImagePlugin',\n                     'BootstrapCarouselSlidePlugin', 'BootstrapJumbotronPlugin']\n    for cascade_element in CascadeElement.objects.all():\n        changed = False\n        if cascade_element.plugin_type in LINK_PLUGINS:\n            changed = migrate_link(cascade_element.glossary) or changed\n        if cascade_element.plugin_type in ICON_PLUGINS:\n            changed = migrate_icon(cascade_element.glossary) or changed\n        if cascade_element.plugin_type in IMAGE_PLUGINS:\n            changed = migrate_image(cascade_element.glossary) or changed\n        if changed:\n            cascade_element.save()\n\n\ndef backwards(apps, schema_editor):\n    print(\"Backward migration not implemented\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0026_cascadepage_menu_symbol'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0028_cascade_clipboard.py",
    "content": "from django.conf import settings\nfrom django.db import migrations, models\nimport django.utils.timezone\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        migrations.swappable_dependency(settings.AUTH_USER_MODEL),\n        ('cmsplugin_cascade', '0027_version_1'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='cascadeclipboard',\n            name='created_at',\n            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'),\n            preserve_default=False,\n        ),\n        migrations.AddField(\n            model_name='cascadeclipboard',\n            name='created_by',\n            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created by'),\n            preserve_default=False,\n        ),\n        migrations.AddField(\n            model_name='cascadeclipboard',\n            name='last_accessed_at',\n            field=models.DateTimeField(default=None, editable=False, null=True, verbose_name='Last accessed at'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0029_json_field.py",
    "content": "# Generated by Django 3.1.5 on 2021-01-28 15:52\n\nfrom django.db import migrations, models\n\n\ndef backwards(apps, schema_editor):\n    print(\"Migration backward will not restore your `JSONField`s to `CharField`s.\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0028_cascade_clipboard'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='cascadeelement',\n            name='glossary',\n            field=models.JSONField(blank=True, default=dict),\n        ),\n        migrations.AlterField(\n            model_name='cascadeclipboard',\n            name='data',\n            field=models.JSONField(blank=True, default=dict, null=True),\n        ),\n        migrations.AlterField(\n            model_name='cascadepage',\n            name='glossary',\n            field=models.JSONField(blank=True, default=dict, help_text='Store for arbitrary page data.'),\n        ),\n        migrations.AlterField(\n            model_name='cascadepage',\n            name='settings',\n            field=models.JSONField(blank=True, default=dict, help_text='User editable settings for this page.'),\n        ),\n        migrations.AlterField(\n            model_name='iconfont',\n            name='config_data',\n            field=models.JSONField(),\n        ),\n        migrations.AlterField(\n            model_name='inlinecascadeelement',\n            name='glossary',\n            field=models.JSONField(blank=True, default=dict),\n        ),\n        migrations.AlterField(\n            model_name='pluginextrafields',\n            name='css_classes',\n            field=models.JSONField(blank=True, default=dict, null=True),\n        ),\n        migrations.AlterField(\n            model_name='pluginextrafields',\n            name='inline_styles',\n            field=models.JSONField(blank=True, default=dict, null=True),\n        ),\n        migrations.AlterField(\n            model_name='sharedglossary',\n            name='glossary',\n            field=models.JSONField(blank=True, default=dict, null=True),\n        ),\n        migrations.AlterField(\n            model_name='sortableinlinecascadeelement',\n            name='glossary',\n            field=models.JSONField(blank=True, default=dict),\n        ),\n        migrations.RunPython(migrations.RunPython.noop, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0030_separate_ids_per_language.py",
    "content": "from django.db import migrations\nfrom django.conf import settings\n\n\ndef forwards(apps, schema_editor):\n    CascadePage = apps.get_model('cmsplugin_cascade', 'CascadePage')\n\n    for cascade_page in CascadePage.objects.all():\n        try:\n            element_ids = cascade_page.glossary.pop('element_ids')\n        except:\n            continue\n        cascade_page.glossary['element_ids'] = {settings.LANGUAGE_CODE: element_ids}\n        cascade_page.save()\n\n\ndef backwards(apps, schema_editor):\n    CascadePage = apps.get_model('cmsplugin_cascade', 'CascadePage')\n\n    for cascade_page in CascadePage.objects.all():\n        try:\n            element_ids = cascade_page.glossary.pop('element_ids')\n        except:\n            continue\n        cascade_page.glossary['element_ids'] = element_ids[settings.LANGUAGE_CODE]\n        cascade_page.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0029_json_field'),\n    ]\n\n    operations = [\n        migrations.RunPython(forwards, reverse_code=backwards),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/0031_alter_texteditorconfigfields_element_type.py",
    "content": "# Generated by Django 4.0.6.dev20220601124058 on 2022-08-02 08:54\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('cmsplugin_cascade', '0030_separate_ids_per_language'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='texteditorconfigfields',\n            name='element_type',\n            field=models.CharField(choices=[('p', 'p'), ('h1', 'h1'), ('h2', 'h2'), ('h3', 'h3'), ('h4', 'h4'), ('h5', 'h5'), ('h6', 'h6'), ('pre', 'pre'), ('address', 'address'), ('div', 'div'), ('span', 'span'), ('ol', 'ol'), ('ul', 'ul')], max_length=12, verbose_name='Element Type'),\n        ),\n    ]\n"
  },
  {
    "path": "cmsplugin_cascade/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/mixins.py",
    "content": "from cmsplugin_cascade.models import InlineCascadeElement, SortableInlineCascadeElement\n\n\nclass CascadePluginMixin:\n    \"\"\"\n    This is the common mixin class used for both, the :class:`cmsplugin_cascade.plugin_base.CascadePluginBase`\n    and the :class:`cmsplugin_cascade.strides.StridePluginBase`.\n    \"\"\"\n\n    @classmethod\n    def get_tag_type(self, instance):\n        \"\"\"\n        Return the tag_type used to render this plugin.\n        \"\"\"\n        return instance.glossary.get('tag_type', getattr(self, 'tag_type', 'div'))\n\n    @classmethod\n    def get_css_classes(cls, instance):\n        \"\"\"\n        Returns a list of CSS classes to be added as class=\"...\" to the current HTML tag.\n        \"\"\"\n        css_classes = []\n        if hasattr(cls, 'default_css_class'):\n            css_classes.append(cls.default_css_class)\n        default_css_attributes = getattr(cls, 'default_css_attributes', [])\n        for attr in default_css_attributes:\n            css_class = instance.glossary.get(attr)\n            if isinstance(css_class, str):\n                css_classes.append(css_class)\n            elif isinstance(css_class, list):\n                css_classes.extend(css_class)\n        return css_classes\n\n    @classmethod\n    def get_inline_styles(cls, instance):\n        \"\"\"\n        Returns a dictionary of CSS attributes to be added as style=\"...\" to the current HTML tag.\n        \"\"\"\n        inline_styles = getattr(cls, 'default_inline_styles', {})\n        css_style = instance.glossary.get('inline_styles')\n        if css_style:\n            inline_styles.update(css_style)\n        return inline_styles\n\n    @classmethod\n    def get_html_tag_attributes(cls, instance):\n        \"\"\"\n        Returns a dictionary of attributes, which shall be added to the current HTML tag.\n        This method normally is called by the models's property method ``html_tag_ attributes``,\n        which enriches the HTML tag with those attributes converted to a list as\n        ``attr1=\"val1\" attr2=\"val2\" ...``.\n        \"\"\"\n        attributes = getattr(cls, 'html_tag_attributes', {})\n        return dict((attr, instance.glossary.get(key, '')) for key, attr in attributes.items())\n\n\nclass WithInlineElementsMixin:\n    \"\"\"\n    Plugins wishing to allow child elements as inlines, shall inherit from this\n    mixin class, in order to override the serialize and deserialize methods.\n    \"\"\"\n    @classmethod\n    def get_data_representation(cls, instance):\n        data = super().get_data_representation(instance)\n        data.update(inlines=[ie.glossary for ie in instance.inline_elements.all()])\n        return data\n\n    @classmethod\n    def add_inline_elements(cls, instance, inlines):\n        for inline_glossary in inlines:\n            InlineCascadeElement.objects.create(\n                cascade_element=instance, glossary=inline_glossary)\n\n\nclass WithSortableInlineElementsMixin:\n    \"\"\"\n    Plugins wishing to allow child elements as sortable inlines, shall inherit from this\n    mixin class, in order to override the serialize and deserialize methods.\n    \"\"\"\n    @classmethod\n    def get_data_representation(cls, instance):\n        data = super().get_data_representation(instance)\n        data.update(inlines=[ie.glossary for ie in instance.sortinline_elements.all()])\n        return data\n\n    @classmethod\n    def add_inline_elements(cls, instance, inlines):\n        for order, inline_glossary in enumerate(inlines, 1):\n            SortableInlineCascadeElement.objects.create(\n                cascade_element=instance, glossary=inline_glossary, order=order)\n"
  },
  {
    "path": "cmsplugin_cascade/models.py",
    "content": "import json\nimport os\nimport shutil\nfrom collections import OrderedDict\nfrom urllib.parse import urljoin\nfrom pathlib import Path\n\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.sites.models import Site\nfrom django.db import models\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\n\nfrom filer.fields.file import FilerFileField\nfrom cms.extensions import PageExtension\nfrom cms.extensions.extension_pool import extension_pool\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.models_base import CascadeModelBase\nfrom cmsplugin_cascade import app_settings\n\n\nclass SharedGlossary(models.Model):\n    \"\"\"\n    A model class to hold glossary data shared among different plugins.\n    \"\"\"\n    plugin_type = models.CharField(\n        _(\"Plugin Name\"),\n        max_length=50,\n        db_index=True,\n        editable=False,\n    )\n\n    identifier = models.CharField(\n        _(\"Identifier\"),\n        max_length=50,\n        unique=True,\n    )\n\n    glossary = models.JSONField(\n        null=True,\n        blank=True,\n        default=dict,\n    )\n\n    class Meta:\n        unique_together = ['plugin_type', 'identifier']\n        verbose_name_plural = verbose_name = _(\"Shared between Plugins\")\n\n    def __str__(self):\n        return self.identifier\n\n    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):\n        \"\"\"\n        Only entries which are declared as sharable, shall be stored in the sharable glossary.\n        \"\"\"\n        plugin_instance = plugin_pool.get_plugin(self.plugin_type)\n        glossary = dict((key, value) for key, value in self.glossary.items()\n                        if key in plugin_instance.sharable_fields)\n        self.glossary = glossary\n        super().save(force_insert, force_update, using, update_fields)\n\n\nclass CascadeElement(CascadeModelBase):\n    \"\"\"\n    The concrete model class to store arbitrary data for plugins derived from CascadePluginBase.\n    \"\"\"\n    shared_glossary = models.ForeignKey(\n        SharedGlossary,\n        blank=True,\n        null=True,\n        on_delete=models.SET_NULL,\n    )\n\n    class Meta:\n        db_table = 'cmsplugin_cascade_element'\n        verbose_name = _(\"Element\")\n        verbose_name_plural = _(\"Elements\")\n\n    def copy_relations(self, oldinstance):\n        def init_element(inline_element):\n            inline_element.pk = None\n            inline_element.cascade_element = self\n            inline_element.save()\n\n        for inline_element in oldinstance.inline_elements.all():\n            init_element(inline_element)\n        for sortinline_element in oldinstance.sortinline_elements.all():\n            init_element(sortinline_element)\n\n\nclass SharableCascadeElement(CascadeElement):\n    \"\"\"\n    A proxy model which takes care of merging the glossary with its shared instance.\n    \"\"\"\n    class Meta:\n        proxy = True\n\n    def __getattribute__(self, name):\n        \"\"\"\n        Update glossary with content from SharedGlossary model if that exists.\n        \"\"\"\n        attribute = object.__getattribute__(self, name)\n        if name == 'glossary' and self.shared_glossary:\n            attribute.update(self.shared_glossary.glossary)\n        return attribute\n\n\nclass InlineCascadeElement(models.Model):\n    cascade_element = models.ForeignKey(\n        CascadeElement,\n        related_name='inline_elements',\n        on_delete=models.CASCADE,\n    )\n\n    glossary = models.JSONField(\n        blank=True,\n        default=dict,\n    )\n\n    class Meta:\n        db_table = 'cmsplugin_cascade_inline'\n\n\nclass SortableInlineCascadeElement(models.Model):\n    cascade_element = models.ForeignKey(\n        CascadeElement,\n        related_name='sortinline_elements',\n        on_delete=models.CASCADE,\n    )\n\n    glossary = models.JSONField(\n        blank=True,\n        default=dict,\n    )\n\n    order = models.PositiveIntegerField(\n        verbose_name=_(\"Sort by\"),\n        db_index=True,\n    )\n\n    class Meta:\n        db_table = 'cmsplugin_cascade_sortinline'\n        ordering = ['order']\n\n    def __str__(self):\n        return \"\"\n\n\nclass PluginExtraFields(models.Model):\n    \"\"\"\n    Store a set of allowed extra CSS classes and inline styles to be used for Cascade plugins\n    inheriting from `ExtraFieldsMixin`. Also store if individual ``id=\"\"`` tags are allowed.\n    \"\"\"\n    plugin_type = models.CharField(\n        _(\"Plugin Name\"),\n        max_length=50,\n        db_index=True,\n    )\n\n    site = models.ForeignKey(\n        Site,\n        verbose_name=_(\"Site\"),\n        on_delete=models.CASCADE,\n    )\n\n    allow_id_tag = models.BooleanField(default=False)\n\n    css_classes = models.JSONField(\n        null=True,\n        blank=True,\n        default=dict,\n    )\n\n    inline_styles = models.JSONField(\n        null=True,\n        blank=True,\n        default=dict,\n    )\n\n    class Meta:\n        verbose_name = verbose_name_plural = _(\"Custom CSS classes and styles\")\n        unique_together = ['plugin_type', 'site']\n\n    def __str__(self):\n        return str(self.name)\n\n    @cached_property\n    def name(self):\n        return plugin_pool.get_plugin(self.plugin_type).name\n\n\nclass TextEditorConfigFields(models.Model):\n    ELEMENT_CHOICES = [(c, c) for c in [\n        'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'address', 'div', 'span', 'ol', 'ul']\n    ]\n\n    name = models.CharField(\n        _(\"Name\"),\n        max_length=50,\n    )\n\n    element_type = models.CharField(\n        _(\"Element Type\"),\n        choices=ELEMENT_CHOICES,\n        max_length=12,\n    )\n\n    css_classes = models.CharField(\n        _(\"CSS classes\"),\n        max_length=250,\n        help_text=_(\"Freely selectable CSS classnames for this Text-Editor Style, separated by spaces.\"),\n    )\n\n    class Meta:\n        verbose_name = _(\"Text Editor Config\")\n\n    def get_config(self):\n        config = {\n            'name': self.name,\n            'element': self.element_type,\n            'attributes': {'class': self.css_classes},\n        }\n        return json.dumps(config)\n\n\nclass Segmentation(models.Model):\n    class Meta:\n        verbose_name = _(\"Segmentation\")\n        managed = False  # it's a dummy model\n        db_table = None\n\n\nclass CascadeClipboard(models.Model):\n    \"\"\"\n    A model class to persist, export and re-import the clipboard's content.\n    \"\"\"\n    identifier = models.CharField(\n        _(\"Identifier\"),\n        max_length=50,\n        unique=True,\n    )\n\n    data = models.JSONField(\n        null=True,\n        blank=True,\n        default=dict,\n    )\n\n    created_by = models.ForeignKey(\n        get_user_model(),\n        verbose_name=_(\"Created by\"),\n        on_delete=models.SET_NULL,\n        editable=False,\n        null=True,\n    )\n\n    created_at = models.DateTimeField(\n        _(\"Created at\"),\n        auto_now_add=True,\n        editable=False,\n    )\n\n    last_accessed_at = models.DateTimeField(\n        _(\"Last accessed at\"),\n        null=True,\n        default=None,\n        editable=False,\n    )\n\n    class Meta:\n        verbose_name = _(\"Persisted Clipboard Content\")\n        verbose_name_plural = _(\"Persisted Clipboard Contents\")\n\n    def __str__(self):\n        return self.identifier\n\n\nclass FilePathField(models.FilePathField):\n    \"\"\"\n    Implementation of `models.FilePathField` which configures the `path` argument by default\n    to avoid the creation of a migration file for each change in local settings.\n    \"\"\"\n    def __init__(self, **kwargs):\n        kwargs.setdefault('path', app_settings.CMSPLUGIN_CASCADE['icon_font_root'])\n        super().__init__(**kwargs)\n\n    def deconstruct(self):\n        name, path, args, kwargs = super().deconstruct()\n        del kwargs['path']\n        return name, path, args, kwargs\n\n\nclass IconFont(models.Model):\n    \"\"\"\n    Instances of uploaded icon fonts, such as FontAwesone, MaterialIcons, etc.\n    \"\"\"\n    identifier = models.CharField(\n        _(\"Identifier\"),\n        max_length=50,\n        unique=True,\n        help_text=_(\"A unique identifier to distinguish this icon font.\"),\n    )\n\n    config_data = models.JSONField()\n\n    zip_file = FilerFileField(\n        on_delete=models.CASCADE,\n        help_text=_('Upload a zip file created on <a href=\"http://fontello.com/\" target=\"_blank\">Fontello</a> containing fonts.')\n    )\n\n    font_folder = FilePathField(allow_files=False, allow_folders=True)\n\n    is_default = models.BooleanField(\n        _(\"Default Font\"),\n        default=False,\n        help_text=_(\"Use this font as default, unless an icon font is set for the current page.\"),\n    )\n\n    class Meta:\n        verbose_name = _(\"Uploaded Icon Font\")\n        verbose_name_plural = _(\"Uploaded Icon Fonts\")\n\n    def __str__(self):\n        return self.identifier\n\n    def get_icon_families(self):\n        \"\"\"\n        Return an ordered dict of css classes required to render these icons\n        \"\"\"\n        families = OrderedDict()\n        for glyph in self.config_data['glyphs']:\n            src = glyph.pop('src', 'default')\n            families.setdefault(src, [])\n            css = glyph.get('css')\n            if css:\n                families[src].append(css)\n        return families\n\n    def get_stylesheet_url(self):\n        icon_font_url = os.path.relpath(app_settings.CMSPLUGIN_CASCADE['icon_font_root'], settings.MEDIA_ROOT)\n        name = self.config_data.get('name') or 'fontello'\n        parts = (icon_font_url, Path(self.font_folder).as_posix(), 'css/{}.css'.format(name))\n        return urljoin(settings.MEDIA_URL, '/'.join(parts))\n\n    def config_data_as_json(self):\n        data = dict(self.config_data)\n        data.pop('glyphs', None)\n        data['families'] = self.get_icon_families()\n        return json.dumps(data)\n\n    @classmethod\n    def delete_icon_font(cls, instance=None, **kwargs):\n        if isinstance(instance, cls):\n            font_folder = os.path.join(app_settings.CMSPLUGIN_CASCADE['icon_font_root'], instance.font_folder)\n            shutil.rmtree(font_folder, ignore_errors=True)\n            try:\n                temp_folder = os.path.abspath(os.path.join(font_folder, os.path.pardir))\n                os.rmdir(temp_folder)\n            except FileNotFoundError:\n                pass\n\nmodels.signals.pre_delete.connect(IconFont.delete_icon_font, dispatch_uid='delete_icon_font')\n\n\nclass CascadePage(PageExtension):\n    \"\"\"\n    Keep arbitrary data tightly coupled to the CMS page.\n    \"\"\"\n    settings = models.JSONField(\n        blank=True,\n        default=dict,\n        help_text=_(\"User editable settings for this page.\"),\n    )\n\n    glossary = models.JSONField(\n        blank=True,\n        default=dict,\n        help_text=_(\"Store for arbitrary page data.\"),\n    )\n\n    icon_font = models.ForeignKey(\n        IconFont,\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        verbose_name=_(\"Icon Font\"),\n    )\n\n    menu_symbol = models.CharField(\n        _(\"Menu Symbol\"),\n        blank=True,\n        null=True,\n        max_length=32,\n        help_text=_(\"Symbol to be used with the menu title for this page.\"),\n    )\n\n    class Meta:\n        db_table = 'cmsplugin_cascade_page'\n        verbose_name = verbose_name_plural = _(\"Cascade Page Settings\")\n\n    def __str__(self):\n        return self.get_page().get_title()\n\n    @classmethod\n    def assure_relation(cls, cms_page):\n        \"\"\"\n        Assure that we have a foreign key relation, pointing from CascadePage onto CMSPage.\n        \"\"\"\n        try:\n            cms_page.cascadepage\n        except cls.DoesNotExist:\n            cls.objects.create(extended_object=cms_page)\n\n    @classmethod\n    def delete_cascade_element(cls, instance=None, **kwargs):\n        if isinstance(instance, CascadeModelBase):\n            try:\n                instance.placeholder.page.cascadepage.glossary['element_ids'][instance.language].pop(str(instance.pk))\n                instance.placeholder.page.cascadepage.save()\n            except (AttributeError, KeyError):\n                pass\n\nextension_pool.register(CascadePage)\nmodels.signals.pre_delete.connect(CascadePage.delete_cascade_element, dispatch_uid='delete_cascade_element')\n"
  },
  {
    "path": "cmsplugin_cascade/models_base.py",
    "content": "from django.db import models\nfrom django.utils.html import mark_safe, format_html_join\nfrom django.utils.functional import cached_property\nimport json\n\nfrom cms.models import CMSPlugin\nfrom cms.plugin_pool import plugin_pool\nfrom cms.utils.placeholder import get_placeholder_conf\n\n\nclass CascadeModelBase(CMSPlugin):\n    \"\"\"\n    The container to hold additional HTML element tags.\n    \"\"\"\n    class Meta:\n        abstract = True\n\n    cmsplugin_ptr = models.OneToOneField(\n        CMSPlugin,\n        related_name='+',\n        on_delete=models.CASCADE,\n        parent_link=True,\n    )\n\n    glossary = models.JSONField(blank=True, default=dict)\n\n    def __str__(self):\n        return self.plugin_class.get_identifier(self)\n\n    @cached_property\n    def plugin_class(self):\n        return self.get_plugin_class()\n\n    @property\n    def tag_type(self):\n        return self.plugin_class.get_tag_type(self)\n\n    @property\n    def css_classes(self):\n        css_classes = self.plugin_class.get_css_classes(self)\n        return mark_safe(' '.join(c for c in css_classes if c))\n\n    @property\n    def inline_styles(self):\n        inline_styles = self.plugin_class.get_inline_styles(self)\n        return format_html_join(' ', '{0}: {1};', (s for s in inline_styles.items() if s[1]))\n\n    @property\n    def html_tag_attributes(self):\n        attributes = self.plugin_class.get_html_tag_attributes(self)\n        joined = format_html_join(' ', '{0}=\"{1}\"', ((attr, val) for attr, val in attributes.items() if val))\n        if joined:\n            return mark_safe(' ' + joined)\n        return ''\n\n    def get_parent_instance(self):\n        for model in CascadeModelBase._get_cascade_elements():\n            try:\n                return model.objects.get(id=self.parent_id)\n            except model.DoesNotExist:\n                continue\n        # in case our plugin is the child of a TextPlugin, return its grandparent\n        parent = self.get_parent()\n        if parent and parent.plugin_type == 'TextPlugin':\n            grandparent_id = self.get_parent().parent_id\n            for model in CascadeModelBase._get_cascade_elements():\n                try:\n                    return model.objects.get(id=grandparent_id)\n                except model.DoesNotExist:\n                    continue\n\n    def get_parent_glossary(self):\n        \"\"\"\n        Return the glossary from the parent of this object. If there is no parent, retrieve\n        the glossary from the placeholder settings, if configured.\n        \"\"\"\n        parent = self.get_parent_instance()\n        if parent:\n            return parent.get_complete_glossary()\n        # otherwise use self.placeholder.glossary as the starting dictionary\n        template = self.placeholder.page.template if self.placeholder.page else None\n        return get_placeholder_conf('glossary', self.placeholder.slot, template=template, default={})\n\n    def get_complete_glossary(self):\n        \"\"\"\n        Return the parent glossary for this model object merged with the current object.\n        This is done by starting from the root element down to the current element and enriching\n        the glossary with each models's own glossary.\n        \"\"\"\n        if not hasattr(self, '_complete_glossary_cache'):\n            self._complete_glossary_cache = self.get_parent_glossary().copy()\n            self._complete_glossary_cache.update(self.glossary or {})\n        return self._complete_glossary_cache\n\n    def get_num_children(self):\n        \"\"\"\n        Returns the number of children for this plugin instance.\n        \"\"\"\n        return self.get_children().count()\n\n    def sanitize_children(self):\n        \"\"\"\n        Recursively walk down the plugin tree and invoke method ``save(sanitize_only=True)`` for\n        each child.\n        \"\"\"\n        for model in CascadeModelBase._get_cascade_elements():\n            # execute query to not iterate over SELECT ... FROM while updating other models\n            children = list(model.objects.filter(parent_id=self.id))\n            for child in children:\n                child.save(sanitize_only=True)\n                child.sanitize_children()\n\n    @classmethod\n    def from_db(cls, db, field_names, values):\n        instance = cls(*values)\n        if isinstance(instance.glossary, str):\n           instance.glossary = json.loads(instance.glossary)\n        return instance\n\n    def save(self, sanitize_only=False, *args, **kwargs):\n        \"\"\"\n        A hook which let the plugin instance sanitize the current object model while saving it.\n        With ``sanitize_only=True``, the current model object only is saved when the method\n        ``sanitize_model()`` from the corresponding plugin actually changed the glossary.\n        \"\"\"\n        sanitized = self.plugin_class.sanitize_model(self)\n        if sanitize_only:\n            if sanitized:\n                super().save(no_signals=True)\n        else:\n            super().save(*args, **kwargs)\n\n    @classmethod\n    def _get_cascade_elements(cls):\n        \"\"\"\n        Returns a set of models which are derived from ``CascadeModelBase``. This set shall be used\n        for traversing the plugin tree of interconnected Cascade models. Currently, Cascade itself\n        offers only one model, namely ``CascadeElement``, but a third party library may extend\n        ``CascadeModelBase`` and add arbitrary model fields.\n        \"\"\"\n        if not hasattr(cls, '_cached_cascade_elements'):\n            cce = set([p.model._meta.concrete_model for p in plugin_pool.get_all_plugins()\n                       if issubclass(p.model, cls)])\n            cls._cached_cascade_elements = cce\n        return cls._cached_cascade_elements\n"
  },
  {
    "path": "cmsplugin_cascade/plugin_base.py",
    "content": "from django.core.exceptions import ImproperlyConfigured\nfrom django.forms import MediaDefiningClass, ModelForm\nfrom django.utils.functional import lazy\nfrom django.utils.module_loading import import_string\nfrom django.utils.text import format_lazy\nfrom django.utils.safestring import SafeText, mark_safe\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_base import CMSPluginBaseMetaclass, CMSPluginBase\nfrom cms.utils.compat.dj import is_installed\nfrom cmsplugin_cascade import app_settings\nfrom .mixins import CascadePluginMixin\nfrom .models_base import CascadeModelBase\nfrom .models import CascadeElement, SharableCascadeElement\nfrom .generic.mixins import SectionMixin, SectionModelMixin\nfrom .sharable.forms import SharableGlossaryMixin\nfrom .strides import register_stride\nfrom .extra_fields.mixins import ExtraFieldsMixin\nfrom .hide_plugins import HidePluginMixin\nfrom .render_template import RenderTemplateMixin\nfrom .utils import remove_duplicates\n\nmark_safe_lazy = lazy(mark_safe, str)\n\nfake_proxy_models = {}\n\n\ndef create_proxy_model(name, model_mixins, base_model, attrs=None, module=None):\n    \"\"\"\n    Create a Django Proxy Model on the fly, to be used by any Cascade Plugin.\n    \"\"\"\n    from django.apps import apps\n\n    class Meta:\n        proxy = True\n        app_label = 'cmsplugin_cascade'\n\n    name = str(name + 'Model')\n    try:\n        Model = apps.get_registered_model(Meta.app_label, name)\n    except LookupError:\n        bases = model_mixins + (base_model,)\n        attrs = dict(attrs or {}, Meta=Meta, __module__=module)\n        Model = type(name, bases, attrs)\n        fake_proxy_models[name] = bases\n    return Model\n\n\nclass CascadePluginMixinMetaclass(MediaDefiningClass):\n    ring_plugin_bases = {}\n\n    def __new__(cls, name, bases, attrs):\n        ring_plugin = attrs.get('ring_plugin')\n        if ring_plugin:\n            ring_plugin_bases = [b.ring_plugin for b in bases\n                                 if hasattr(b, 'ring_plugin') and b.ring_plugin != ring_plugin]\n\n            # remember the dependencies\n            cls.ring_plugin_bases.setdefault(ring_plugin, [])\n            cls.ring_plugin_bases[ring_plugin].extend(ring_plugin_bases)\n            cls.ring_plugin_bases[ring_plugin] = remove_duplicates(cls.ring_plugin_bases[ring_plugin])\n\n        new_class = super().__new__(cls, name, bases, attrs)\n        return new_class\n\n\nclass CascadePluginMixinBase(metaclass=CascadePluginMixinMetaclass):\n    \"\"\"\n    Use this as a base for mixin classes used by other CascadePlugins\n    \"\"\"\n\n\nclass CascadePluginBaseMetaclass(CascadePluginMixinMetaclass, CMSPluginBaseMetaclass):\n    \"\"\"\n    All plugins from djangocms-cascade can be instantiated in different ways. In order to allow this\n    by a user defined configuration, this meta-class conditionally inherits from additional mixin\n    classes.\n    \"\"\"\n    plugins_with_extra_fields = dict(app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_fields'])\n    plugins_with_extra_mixins = dict(app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_mixins'])\n    plugins_with_bookmark = list(app_settings.CMSPLUGIN_CASCADE['plugins_with_bookmark'])\n    plugins_with_sharables = dict(app_settings.CMSPLUGIN_CASCADE['plugins_with_sharables'])\n    plugins_with_extra_render_templates = app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_render_templates'].keys()\n    allow_plugin_hiding = app_settings.CMSPLUGIN_CASCADE['allow_plugin_hiding']\n    exclude_hiding_plugin = list(app_settings.CMSPLUGIN_CASCADE['exclude_hiding_plugin'])\n\n    def __new__(cls, name, bases, attrs):\n        model_mixins = attrs.pop('model_mixins', ())\n        if (cls.allow_plugin_hiding and name not in cls.exclude_hiding_plugin and 'name' in attrs and\n            not attrs.get('text_enabled')):\n            bases = (HidePluginMixin,) + bases\n        if name in cls.plugins_with_extra_fields:\n            bases = (ExtraFieldsMixin,) + bases\n        if name in cls.plugins_with_extra_mixins:\n            if isinstance(cls.plugins_with_extra_mixins[name], tuple):\n                bases = cls.plugins_with_extra_mixins[name] + bases\n            else:\n                bases = (cls.plugins_with_extra_mixins[name],) + bases\n        if name in cls.plugins_with_bookmark:\n            bases = (SectionMixin,) + bases\n            model_mixins = (SectionModelMixin,) + model_mixins\n        if name in cls.plugins_with_sharables:\n            bases = (SharableGlossaryMixin,) + bases\n            attrs['sharable_fields'] = cls.plugins_with_sharables[name]\n            base_model = SharableCascadeElement\n        else:\n            base_model = CascadeElement\n        if name in cls.plugins_with_extra_render_templates:\n            bases = (RenderTemplateMixin,) + bases\n        if name == 'SegmentPlugin':\n            # SegmentPlugin shall additionally inherit from configured mixin classes\n            model_mixins += tuple(import_string(mc[0]) for mc in app_settings.CMSPLUGIN_CASCADE['segmentation_mixins'])\n        if 'model' in attrs:\n            # the plugin overrides the CascadeModel\n            if not issubclass(attrs['model'], CascadeModelBase):\n                msg = \"Cascade Plugins, overriding the model, must inherit from `CascadeModelBase`.\"\n                raise ImproperlyConfigured(msg)\n        else:\n            attrs['model'] = create_proxy_model(name, model_mixins, base_model, module=attrs.get('__module__'))\n        if is_installed('reversion'):\n            import reversion.revisions\n            if not reversion.revisions.is_registered(base_model):\n                reversion.revisions.register(base_model)\n        # handle ambiguous plugin names by appending a symbol\n        if 'name' in attrs and app_settings.CMSPLUGIN_CASCADE['plugin_prefix']:\n            attrs['name'] = format_lazy('{}&nbsp;{}', app_settings.CMSPLUGIN_CASCADE['plugin_prefix'], attrs['name'])\n\n        register_stride(name, bases, attrs, model_mixins)\n        if name == 'CascadePluginBase':\n            bases += (CascadePluginMixin, CMSPluginBase,)\n        return super().__new__(cls, name, bases, attrs)\n\n\nclass TransparentWrapper:\n    \"\"\"\n    Add this mixin class to other Cascade plugins, wishing to be added transparently between other\n    plugins restricting parent-children relationships.\n    For instance: A BootstrapColumnPlugin can only be added as a child to a RowPlugin. This means\n    that no other wrapper can be added between those two plugins. By adding this mixin class we can\n    allow any plugin to behave transparently, just as if it would not have be inserted into the DOM\n    tree. When moving plugins in- and out of transparent wrapper plugins, always reload the page, so\n    that the parent-children relationships can be updated.\n    \"\"\"\n    child_plugins_cache = False\n    parent_plugins_cache = False\n\n    @classmethod\n    def get_child_classes(cls, slot, page, instance=None):\n        if hasattr(cls, 'direct_child_classes'):\n            return cls.direct_child_classes\n        child_classes = set(super().get_child_classes(slot, page, instance))\n        while True:\n            instance = instance.get_parent_instance() if instance and instance.parent else None\n            if instance is None:\n                child_classes.update(super().get_child_classes(slot, page, instance))\n                return list(child_classes)\n            if not issubclass(instance.plugin_class, TransparentWrapper):\n                child_classes.update(instance.plugin_class.get_child_classes(slot, page, instance))\n                return list(child_classes)\n\n    @classmethod\n    def get_parent_classes(cls, slot, page, instance=None):\n        if hasattr(cls, 'direct_parent_classes'):\n            return cls.direct_parent_classes\n        parent_classes = set(super().get_parent_classes(slot, page, instance) or [])\n        if isinstance(instance, CascadeElement):\n            instance = instance.get_parent_instance() if instance and instance.parent else None\n            if instance is not None:\n                parent_classes.add(instance.plugin_type)\n        return list(parent_classes)\n\n\nclass TransparentContainer(TransparentWrapper):\n    \"\"\"\n    This mixin class marks each plugin inheriting from it, as a transparent container.\n    Such a plugin is added to the global list of entitled parent plugins, which is required if we\n    want to place and move all other Cascade plugins below this container.\n\n    Often, transparent wrapping classes come in pairs. For instance the `AccordionPlugin` containing\n    one or more `PanelPlugin`. Here the `AccordionPlugin` must inherit from `TransparentWrapper`,\n    whereas the `AccordionPlugin` must inherit from the `TransparentContainer`.\n    \"\"\"\n    @staticmethod\n    def get_plugins():\n        from cms.plugin_pool import plugin_pool\n        global _leaf_transparent_plugins\n\n        try:\n            return _leaf_transparent_plugins\n        except NameError:\n            _leaf_transparent_plugins = [\n                plugin.__name__ for plugin in plugin_pool.get_all_plugins()\n                    if issubclass(plugin, TransparentContainer)\n            ]\n            return _leaf_transparent_plugins\n\n\nclass CascadeFormMixin(EntangledModelFormMixin):\n    class Meta:\n        entangled_fields = {'glossary': []}\n\n\nclass CascadePluginBase(metaclass=CascadePluginBaseMetaclass):\n    change_form_template = 'cascade/admin/change_form.html'\n    model_mixins = ()  # model mixins added to the final Django model\n    parent_classes = None\n    alien_child_classes = False\n    form = CascadeFormMixin  # safety fallback for plugins without any form\n\n    class Media:\n        css = {'all': ['cascade/css/admin/partialfields.css', 'cascade/css/admin/editplugin.css']}\n        js = ['cascade/js/underscore.js', 'cascade/js/ring.js']\n\n    def __init__(self, model=None, admin_site=None):\n        super().__init__(model, admin_site)\n\n    def __repr__(self):\n        return \"<class '{}'>\".format(self.__class__.__name__)\n\n    @classmethod\n    def super(cls, klass, instance):\n        \"\"\"\n        Plugins inheriting from CascadePluginBaseMetaclass can have two different base classes,\n        :class:`cmsplugin_cascade.plugin_base.CMSPluginBase` and :class:`cmsplugin_cascade.strides.StridePluginBase`.\n        Therefore in order to call a method from an inherited class, use this \"super\" wrapping method.\n        >>> cls.super(MyPlugin, self).a_method()\n        \"\"\"\n        return super(klass, instance)\n\n    @classmethod\n    def _get_parent_classes_transparent(cls, slot, page, instance=None):\n        \"\"\"\n        Return all parent classes including those marked as \"transparent\".\n        \"\"\"\n        parent_classes = super().get_parent_classes(slot, page, instance)\n        if parent_classes is None:\n            if cls.get_require_parent(slot, page) is False:\n                return\n            parent_classes = []\n\n        # add all plugins marked as 'transparent', since they all are potential parents\n        parent_classes = set(parent_classes)\n        parent_classes.update(TransparentContainer.get_plugins())\n        return list(parent_classes)\n\n    @classmethod\n    def get_child_classes(cls, slot, page, instance=None):\n        plugin_type = cls.__name__\n        child_classes = set()\n        for child_class in cls.get_child_plugin_candidates(slot, page):\n            if issubclass(child_class, CascadePluginBase):\n                own_child_classes = getattr(cls, 'child_classes', None) or []\n                child_parent_classes = child_class._get_parent_classes_transparent(slot, page, instance)\n                if isinstance(child_parent_classes, (list, tuple)) and plugin_type in child_parent_classes:\n                    child_classes.add(child_class)\n                elif plugin_type in own_child_classes:\n                    child_classes.add(child_class)\n                elif child_parent_classes is None:\n                    child_classes.add(child_class)\n            else:\n                if cls.alien_child_classes and child_class.__name__ in app_settings.CMSPLUGIN_CASCADE['alien_plugins']:\n                    child_classes.add(child_class)\n\n        return list(cc.__name__ for cc in child_classes)\n\n    @classmethod\n    def get_parent_classes(cls, slot, page, instance=None):\n        return cls._get_parent_classes_transparent(slot, page, instance)\n\n    @classmethod\n    def get_identifier(cls, instance):\n        \"\"\"\n        Hook to return a description for the current model.\n        \"\"\"\n        return SafeText()\n\n    @classmethod\n    def sanitize_model(cls, instance):\n        \"\"\"\n        This method is called, before the model is written to the database. It can be overloaded\n        to sanitize the current models, in case a parent model changed in a way, which might\n        affect this plugin.\n        This method shall return `True`, in case a model change was necessary, otherwise it shall\n        return `False` to prevent a useless database update.\n        \"\"\"\n        if instance.glossary is None:\n            instance.glossary = {}\n        return False\n\n    @classmethod\n    def get_data_representation(cls, instance):\n        \"\"\"\n        Return a representation of the given instance suitable for a serialized representation.\n        \"\"\"\n        return {'glossary': instance.glossary, 'pk': instance.pk}\n\n    @classmethod\n    def add_inline_elements(cls, instance, inlines):\n        \"\"\"\n        Hook to create (sortable) inline elements for the given instance.\n        \"\"\"\n\n    @classmethod\n    def add_shared_reference(cls, instance, shared_glossary):\n        \"\"\"\n        Hook to add a reference pointing onto an existing SharedGlossary instance.\n        \"\"\"\n\n    def extend_children(self, parent, wanted_children, child_class, child_glossary=None):\n        \"\"\"\n        Extend the number of children so that the parent object contains wanted children.\n        No child will be removed if wanted_children is smaller than the current number of children.\n        \"\"\"\n        from cms.api import add_plugin\n        current_children = parent.get_num_children()\n        for _ in range(current_children, wanted_children):\n            child = add_plugin(parent.placeholder, child_class, parent.language, target=parent)\n            if isinstance(child_glossary, dict):\n                child.glossary.update(child_glossary)\n            child.save()\n\n    def get_form(self, request, obj=None, **kwargs):\n        form = kwargs.get('form', self.form)\n        assert issubclass(form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n        bases = (form,)\n        if not issubclass(form, CascadeFormMixin):\n            bases = (CascadeFormMixin,) + bases\n        if not issubclass(form, ModelForm):\n            bases += (ModelForm,)\n        kwargs['form'] = type(form.__name__, bases, {})\n        return super().get_form(request, obj, **kwargs)\n\n    def get_parent_instance(self, request=None, obj=None):\n        \"\"\"\n        Get the parent model instance corresponding to this plugin. When adding a new plugin, the\n        parent might not be available. Therefore as fallback, pass in the request object.\n        \"\"\"\n        try:\n            parent_id = obj.parent_id\n        except AttributeError:\n            try:\n                # TODO: self.parent presumably is not used anymore in CMS-3.4, because it doesn't\n                # make sense anyway, since the plugin instances shall know their parents, not the\n                # plugins.\n                parent_id = self.parent.id\n            except AttributeError:\n                if request:\n                    parent_id = request.GET.get('plugin_parent', None)\n                    if parent_id is None:\n                        from cms.models import CMSPlugin\n                        try:\n                            parent_id = CMSPlugin.objects.filter(id=request.resolver_match.args[0]\n                                                                 ).only(\"parent_id\").order_by('?').first().parent_id\n                        except (AttributeError, IndexError):\n                            parent_id = None\n                else:\n                    parent_id = None\n        for model in CascadeModelBase._get_cascade_elements():\n            try:\n                return model.objects.get(id=parent_id)\n            except model.DoesNotExist:\n                continue\n\n    def get_previous_instance(self, obj):\n        \"\"\"\n        Return the previous plugin instance for the given object.\n        This differs from `obj.get_prev_sibling()` which returns an unsorted sibling.\n        \"\"\"\n        ordered_siblings = obj.get_siblings().filter(placeholder=obj.placeholder).order_by('position')\n        pos = list(ordered_siblings).index(obj.cmsplugin_ptr)\n        if pos > 0:\n            prev_sibling = ordered_siblings[pos - 1]\n            return prev_sibling.get_bound_plugin()\n\n    def get_next_instance(self, obj):\n        \"\"\"\n        Return the next plugin instance for the given object.\n        This differs from `obj.get_next_sibling()` which returns an unsorted sibling.\n        \"\"\"\n        ordered_siblings = obj.get_siblings().filter(placeholder=obj.placeholder).order_by('position')\n        pos = list(ordered_siblings).index(obj.cmsplugin_ptr)\n        if pos < ordered_siblings.count() - 1:\n            next_sibling = ordered_siblings[pos + 1]\n            return next_sibling.get_bound_plugin()\n\n    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):\n        ring_plugin_bases = {ring_plugin: ['django.cascade.{}'.format(b) for b in bases]\n                             for ring_plugin, bases in CascadePluginMixinMetaclass.ring_plugin_bases.items()}\n        context.update(\n            ring_plugin_bases=ring_plugin_bases,\n            plugin_title=format_lazy(\"{} {} Plugin\", self.module, self.name),\n            plugin_intro=mark_safe(getattr(self, 'intro_html', '')),\n            plugin_footnote=mark_safe(getattr(self, 'footnote_html', '')),\n        )\n        if hasattr(self, 'ring_plugin'):\n            context.update(\n                ring_plugin=self.ring_plugin,\n            )\n        context['empty_form'] = not (context['adminform'].form._meta.entangled_fields.get('glossary') or\n                                     context['adminform'].form._meta.untangled_fields)\n        return super().render_change_form(request, context, add, change, form_url, obj)\n\n    def in_edit_mode(self, request, placeholder):\n        \"\"\"\n        Returns True, if the plugin is in \"edit mode\".\n        \"\"\"\n        toolbar = getattr(request, 'toolbar', None)\n        edit_mode = getattr(toolbar, 'edit_mode_active', False) and getattr(placeholder, 'is_editable', True)\n        if edit_mode:\n            edit_mode = placeholder.has_change_permission(request.user)\n        return edit_mode\n"
  },
  {
    "path": "cmsplugin_cascade/render_template.py",
    "content": "from django.forms import MediaDefiningClass\nfrom django.forms.fields import ChoiceField\nfrom django.utils.translation import gettext_lazy as _\nfrom django.template.loader import get_template, TemplateDoesNotExist\nfrom entangled.forms import EntangledModelFormMixin\nfrom cmsplugin_cascade import app_settings\n\n\nclass RenderTemplateFormMixin(EntangledModelFormMixin):\n    render_template = ChoiceField(\n        label=_(\"Render template\"),\n        help_text=_(\"Use alternative template for rendering this plugin.\"),\n        required=False,\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['render_template']}\n\n\nclass RenderTemplateMixin(metaclass=MediaDefiningClass):\n    \"\"\"\n    If a Cascade plugin is listed in ``settings.CMSPLUGIN_CASCADE['plugins_with_extra_render_templates']``,\n    then this ``RenderTemplateMixin`` class is added automatically to its plugin class in order\n    to add an additional select box used for choosing an alternative render template.\n    \"\"\"\n    @classmethod\n    def get_template_choices(cls):\n        return app_settings.CMSPLUGIN_CASCADE['plugins_with_extra_render_templates'][cls.__name__]\n\n    def get_form(self, request, obj=None, **kwargs):\n        form = kwargs.get('form', self.form)\n        assert issubclass(form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n        choices = self.get_template_choices()\n        if isinstance(choices, (list, tuple)):\n            form = type(form.__name__, (RenderTemplateFormMixin, form), {})\n            form.base_fields['render_template'].choices = choices\n            kwargs['form'] = form\n        return super().get_form(request, obj, **kwargs)\n\n    def get_render_template(self, context, instance, placeholder):\n        try:\n            template = instance.glossary.get('render_template', self.get_template_choices()[0][0])\n            get_template(template)  # check if template exists\n        except (KeyError, IndexError, TemplateDoesNotExist, TypeError):\n            template = self.render_template\n        return template\n"
  },
  {
    "path": "cmsplugin_cascade/segmentation/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/segmentation/admin.py",
    "content": "from django.forms import MediaDefiningClass\nfrom django.contrib import admin\nfrom django.utils.module_loading import import_string\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.models import Segmentation\n\n\nclass SegmentationAdminMetaclass(MediaDefiningClass):\n    def __new__(cls, name, bases, attrs):\n        bases = tuple(import_string(sgm[1]) for sgm in app_settings.CMSPLUGIN_CASCADE['segmentation_mixins']) + bases\n        new_class = super().__new__(cls, name, bases, attrs)\n        return new_class\n\n\nclass SegmentationAdmin(admin.ModelAdmin, metaclass=SegmentationAdminMetaclass):\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/segmentation.js']\n\n    def get_model_perms(self, request):\n        \"\"\"\n        Return empty perms dict to hide the model from admin index.\n        \"\"\"\n        return {}\n\n    def get_queryset(self, request):\n        \"\"\"\n        Returns the QuerySet for `_lookup_model`, instead of dummy model `Segmentation`.\n        \"\"\"\n        model = getattr(request, '_lookup_model', self.model)\n        qs = model._default_manager.get_queryset()\n        # TODO: this should be handled by some parameter to the ChangeList.\n        ordering = self.get_ordering(request)\n        if ordering:\n            qs = qs.order_by(*ordering)\n        return qs\n\nadmin.site.register(Segmentation, SegmentationAdmin)\n"
  },
  {
    "path": "cmsplugin_cascade/segmentation/cms_plugins.py",
    "content": "import html\n\nfrom django.core.exceptions import ValidationError\nfrom django.forms.fields import CharField, ChoiceField\nfrom django.utils.translation import gettext_lazy as _\nfrom django.utils.html import format_html\nfrom django.template import engines, TemplateSyntaxError, Template as DjangoTemplate, Context as TemplateContext\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase, TransparentContainer\n\n\nclass Template(DjangoTemplate):\n    def render(self, context):\n        if isinstance(context, dict):\n            context = TemplateContext(context)\n        return super().render(context)\n\n\nclass SegmentFormMixin(EntangledModelFormMixin):\n    eval_template_string = '{{% if {} %}}True{{% endif %}}'\n\n    open_tag = ChoiceField()  # redeclared in get_form\n\n    condition = CharField(\n        label=_(\"Condition evaluation\"),\n        required=False,\n        # widget=widgets.TextInput(attrs={'width': '100%'}),\n        help_text=_(\"Evaluation as used in Django's template tags for conditions\")\n    )\n\n    class Meta:\n        entangled_fields = {'glossary': ['open_tag', 'condition']}\n\n    def __init__(self, *args, **kwargs):\n        if 'instance' in kwargs and kwargs['instance']:\n            pass\n        super().__init__(*args, **kwargs)\n\n    def clean(self):\n        cleaned_data = super().clean()\n        if cleaned_data['open_tag'] in ('if', 'elif'):\n            if not cleaned_data['condition']:\n                raise ValidationError(_(\"The evaluation condition is missing or empty.\"))\n            try:\n                condition = html.unescape(cleaned_data['condition'])\n                engines['django'].from_string(self.eval_template_string.format(condition))\n            except TemplateSyntaxError as err:\n                raise ValidationError(_(\"Unable to evaluate condition: {}\").format(str(err)))\n        elif cleaned_data['open_tag'] == 'else':\n            cleaned_data['condition'] = ''  # empty condition for else-block\n        return cleaned_data\n\n\nclass SegmentPlugin(TransparentContainer, CascadePluginBase):\n    \"\"\"\n    A Segment is a part of the DOM which is rendered or not, depending on a condition.\n    As condition you may use any expression which is valid inside a Django's template tag,\n    such as ``{% if ... %}``.\n    \"\"\"\n    name = _(\"Segment\")\n    default_template = engines['django'].from_string('{% load cms_tags %}{% for plugin in instance.child_plugin_instances %}{% render_plugin plugin %}{% endfor %}')\n    hiding_template_string = '{% load cms_tags %}<div style=\"display: none;\">{% for plugin in instance.child_plugin_instances %}{% render_plugin plugin %}{% endfor %}</div>'\n    hiding_template = engines['django'].from_string(hiding_template_string)\n    debug_error_template = '<!-- segment condition \"{condition}\" for plugin: {instance_id} failed: \"{message}\" -->{template_string}'\n    empty_template = engines['django'].from_string('{% load l10n %}<!-- segment condition for plugin: {{ instance.id|unlocalize }} did not evaluate -->')\n    ring_plugin = 'SegmentPlugin'\n    require_parent = False\n    direct_parent_classes = None\n    allow_children = True\n    child_classes = None\n    cache = False\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/segmentplugin.js']\n\n    @classmethod\n    def get_identifier(cls, obj):\n        try:\n            return format_html(\"<strong><em>{open_tag}</em></strong> {condition}\", **obj.glossary)\n        except KeyError:\n            return ''\n\n    def get_render_template(self, context, instance, placeholder):\n        def conditionally_eval():\n            condition = html.unescape(instance.glossary['condition'])\n            evaluated_to = False\n            template_error_message = None\n            try:\n                template_string = SegmentFormMixin.eval_template_string.format(condition)\n                eval_template = engines['django'].from_string(template_string)\n                evaluated_to = eval_template.render(context) == 'True'\n            except TemplateSyntaxError as err:\n                template_error_message = str(err)\n            finally:\n                if evaluated_to:\n                    request._evaluated_segments[instance.pk] = True\n                    template = self.default_template\n                else:\n                    request._evaluated_segments[instance.pk] = False\n                    if edit_mode:\n                        # In edit mode, hidden plugins have to be rendered nevertheless. Therefore\n                        # we use `style=\"display: none\"`, otherwise the plugin would be invisible\n                        # in structure mode, while editing.\n                        if template_error_message:\n                            template = self.debug_error_template.format(condition=condition,\n                                instance_id=instance.pk, message=template_error_message,\n                                template_string=self.hiding_template_string)\n                            template = engines['django'].from_string(template)\n                        else:\n                            template = self.hiding_template\n                    else:\n                        template = self.empty_template\n            return template\n\n        request = context['request']\n        edit_mode = self.in_edit_mode(request, placeholder)\n        open_tag = instance.glossary.get('open_tag')\n        if open_tag == 'if':\n            template = conditionally_eval()\n        else:\n            assert open_tag in ('elif', 'else'), \"Expected openening templatetag to be 'elif' or 'else'.\"\n            prev_instance = self.get_previous_instance(instance)\n            if prev_instance is None:\n                # this can happen, if one moved an `else`- or `elif`-segment in front of an `if`-segment\n                template = edit_mode and self.hiding_template or self.empty_template\n            elif request._evaluated_segments.get(prev_instance.pk):\n                request._evaluated_segments[instance.pk] = (open_tag == 'elif')\n                # in edit mode hidden plugins have to be rendered nevertheless\n                template = edit_mode and self.hiding_template or self.empty_template\n            elif open_tag == 'elif':\n                template = conditionally_eval()\n            else:\n                template = self.default_template\n        return template\n\n    def render(self, context, instance, placeholder):\n        request = context['request']\n        if not hasattr(request, '_evaluated_segments'):\n            request._evaluated_segments = {}\n        context.update(instance.get_context_override(request))\n        return self.super(SegmentPlugin, self).render(context, instance, placeholder)\n\n    def get_form(self, request, obj=None, **kwargs):\n        choices = [('if', _(\"if\"))]\n        if obj is None or self._get_previous_open_tag(obj) in ('if', 'elif'):\n            # if obj is None, we are adding a SegmentPlugin, hence we must offer all conditional\n            # tags, which however may be rectified during the save() operation\n            choices.extend([('elif', _(\"elif\")), ('else', _(\"else\"))])\n        open_tag = ChoiceField(\n            label=_(\"Condition tag\"),\n            choices=choices,\n            help_text=_(\"Django's condition tag\")\n        )\n        form = type('SegmentForm', (SegmentFormMixin,), {'open_tag': open_tag})\n        kwargs.setdefault('form', form)\n        return super().get_form(request, obj, **kwargs)\n\n    def save_model(self, request, obj, form, change):\n        super().save_model(request, obj, form, change)\n        if obj.glossary['open_tag'] != 'if' and self._get_previous_open_tag(obj) not in ('if', 'elif'):\n           # rectify to an `if`-segment, when plugin is not saved next to an `if`- or `elif`-segment\n           obj.glossary['open_tag'] = 'if'\n           obj.save(update_fields=['glossary'])\n\n    def _get_previous_open_tag(self, obj):\n        \"\"\"\n        Return the open tag of the previous sibling\n        \"\"\"\n        prev_instance = self.get_previous_instance(obj)\n        if prev_instance and prev_instance.plugin_type == self.__class__.__name__:\n            return prev_instance.glossary.get('open_tag')\n\nplugin_pool.register_plugin(SegmentPlugin)\n"
  },
  {
    "path": "cmsplugin_cascade/segmentation/cms_toolbars.py",
    "content": "from django.utils.module_loading import import_string\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.toolbar_pool import toolbar_pool\nfrom cms.toolbar_base import CMSToolbar\nfrom cmsplugin_cascade import app_settings\n\n\n@toolbar_pool.register\nclass SegmentationToolbar(CMSToolbar):\n    def populate(self):\n        if not self.request.user.has_perm('cmsplugin_cascade.view_segmentation'):\n            return\n        menu = self.toolbar.get_or_create_menu('segmentation', _(\"Segmentation\"))\n        for sgm in app_settings.CMSPLUGIN_CASCADE['segmentation_mixins']:\n            SegmentationMixin = import_string(sgm[1])\n            populate_handler = getattr(SegmentationMixin, 'populate_toolbar', None)\n            if callable(populate_handler):\n                populate_handler(menu, self.request)\n"
  },
  {
    "path": "cmsplugin_cascade/segmentation/mixins.py",
    "content": "from django.contrib import admin\nfrom django.contrib.auth import get_user_model\nfrom django.http import HttpResponse, HttpResponseBadRequest\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse, re_path\nfrom django.utils.translation import gettext_lazy as _, ngettext\nfrom django.utils.html import format_html\n\nfrom cms.constants import REFRESH_PAGE\n\n\nclass SegmentPluginModelMixin:\n    \"\"\"\n    TODO: whenever cmsplugin_cascade drops support for django-CMS < 3.4, this mixin class\n    shall be added to the plugin rather than to the model\n    \"\"\"\n\n    def get_context_override(self, request):\n        \"\"\"\n        Return a dictionary to override the request context object during evaluation with\n        alternative values. Normally this is an empty dict. However, when a staff user overrides\n        the segmentation, then update the context with this returned dict.\n        \"\"\"\n        return {}\n\n    def render_plugin(self, context=None, placeholder=None, admin=False, processors=None):\n        context.update(self.get_context_override(context['request']))\n        content = super().render_plugin(context, placeholder, admin, processors)\n        context.pop()\n        return content\n\n\nclass EmulateUserModelMixin(SegmentPluginModelMixin):\n    UserModel = get_user_model()\n\n    def get_context_override(self, request):\n        \"\"\"\n        Override the request object with an emulated user.\n        \"\"\"\n        context_override = super().get_context_override(request)\n        try:\n            if request.user.is_staff:\n                user = self.UserModel.objects.get(pk=request.session['emulate_user_id'])\n                context_override.update(user=user)\n        except (self.UserModel.DoesNotExist, KeyError):\n            pass\n        return context_override\n\n\nclass EmulateUserAdminMixin:\n    UserModel = get_user_model()\n\n    @staticmethod\n    def populate_toolbar(segmentation_menu, request):\n        active = 'emulate_user_id' in request.session\n        disabled = not request.user.has_perms([\n            'cmsplugin_cascade.add_segmentation',\n            'cmsplugin_cascade.change_segmentation',\n            'cmsplugin_cascade.delete_segmentation',\n        ])\n        segmentation_menu.add_sideframe_item(\n            _(\"Emulate User\"),\n            url=reverse('admin:emulate-users'),\n            active=active,\n            disabled=disabled,\n        )\n        segmentation_menu.add_ajax_item(\n            _(\"Clear emulations\"),\n            action=reverse('admin:clear-emulations'),\n            on_success=REFRESH_PAGE,\n            disabled=disabled or not active,\n        )\n\n    def get_urls(self):\n        return [\n            re_path(r'^emulate_users/$', self.admin_site.admin_view(self.emulate_users), name='emulate-users'),\n            re_path(r'^emulate_user/(?P<user_id>\\d+)/$', self.admin_site.admin_view(self.emulate_user), name='emulate-user'),\n            re_path(r'^clear_emulations/$', self.admin_site.admin_view(self.clear_emulations), name='clear-emulations'),\n        ] + super().get_urls()\n\n    def emulate_user(self, request, user_id):\n        try:\n            request.session['emulate_user_id'] = int(user_id)\n            return HttpResponse('OK')\n        except TypeError as err:\n            return HttpResponseBadRequest(err.message)\n\n    def emulate_users(self, request):\n        \"\"\"\n        The list view\n        \"\"\"\n        def display_as_link(obj):\n            try:\n                identifier = getattr(user_model_admin, list_display_link)(obj)\n            except AttributeError:\n                identifier = admin.utils.lookup_field(list_display_link, obj, model_admin=self)[2]\n            emulate_user_id = request.session.get('emulate_user_id')\n            if emulate_user_id == obj.id:\n                return format_html('<strong>{}</strong>', identifier)\n            fmtargs = {\n                'href': reverse('admin:emulate-user', kwargs={'user_id': obj.id}),\n                'identifier': identifier,\n            }\n            return format_html('<a href=\"{href}\" class=\"emulate-user\">{identifier}</a>', **fmtargs)\n\n        opts = self.UserModel._meta\n        app_label = opts.app_label\n        user_model_admin = self.admin_site._registry[self.UserModel]\n        request._lookup_model = self.UserModel\n        list_display_links = user_model_admin.get_list_display_links(request, user_model_admin.list_display)\n        # replace first entry in list_display_links by customized method display_as_link\n        list_display_link = list_display_links[0]\n        try:\n            list_display = list(user_model_admin.segmentation_list_display)\n        except AttributeError:\n            list_display = list(user_model_admin.list_display)\n        list_display.remove(list_display_link)\n        list_display.insert(0, 'display_as_link')\n        display_as_link.allow_tags = True  # TODO: presumably not required anymore since Django-1.9\n        try:\n            display_as_link.short_description = user_model_admin.identifier.short_description\n        except AttributeError:\n            display_as_link.short_description = admin.utils.label_for_field(list_display_link, self.UserModel)\n        self.display_as_link = display_as_link\n\n        ChangeList = self.get_changelist(request)\n        cl = ChangeList(request, self.UserModel, list_display,\n            (None,),  # disable list_display_links in ChangeList, instead override that field\n            user_model_admin.list_filter,\n            user_model_admin.date_hierarchy, user_model_admin.search_fields,\n            user_model_admin.list_select_related, user_model_admin.list_per_page,\n            user_model_admin.list_max_show_all,\n            (),  # disable list_editable\n            self,\n            None)\n        cl.formset = None\n        selection_note_all = ngettext('%(total_count)s selected',\n            'All %(total_count)s selected', cl.result_count)\n\n        context = {\n            'module_name': str(opts.verbose_name_plural),\n            'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},\n            'selection_note_all': selection_note_all % {'total_count': cl.result_count},\n            'title': _(\"Select %(user_model)s to emulate\") % {'user_model': opts.verbose_name},\n            'is_popup': cl.is_popup,\n            'cl': cl,\n            'media': self.media,\n            'has_add_permission': False,\n            'opts': cl.opts,\n            'app_label': app_label,\n            'actions_on_top': self.actions_on_top,\n            'actions_on_bottom': self.actions_on_bottom,\n            'actions_selection_counter': self.actions_selection_counter,\n            'preserved_filters': self.get_preserved_filters(request),\n        }\n        return TemplateResponse(request, self.change_list_template or [\n            'admin/%s/%s/change_list.html' % (app_label, opts.model_name),\n            'admin/%s/change_list.html' % app_label,\n            'admin/change_list.html'\n        ], context)\n\n    def clear_emulations(self, request):\n        request.session.pop('emulate_user_id', None)\n        return HttpResponse('OK')\n"
  },
  {
    "path": "cmsplugin_cascade/sharable/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "cmsplugin_cascade/sharable/admin.py",
    "content": "from django.contrib import admin\nfrom django.utils.translation import gettext as _\nfrom django.utils.html import format_html\nfrom entangled.forms import EntangledModelForm\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.models import IconFont\nfrom cmsplugin_cascade.plugin_base import CascadePluginMixinMetaclass\nfrom cmsplugin_cascade.models import SharedGlossary, SharableCascadeElement\n\n\n@admin.register(SharedGlossary)\nclass SharedGlossaryAdmin(admin.ModelAdmin):\n    change_form_template = 'cascade/admin/sharedglossary_change_form.html'\n    list_display = ['identifier', 'plugin_name', 'used_by']\n    readonly_fields = ['plugin_name']\n    list_filter = ['plugin_type']\n\n    class Media:\n        css = {'all': ['cascade/css/admin/partialfields.css', 'cascade/css/admin/editplugin.css']}\n\n    def get_form(self, request, obj=None, **kwargs):\n        \"\"\"\n        Creates a temporary form with an identifier and the fields declared as sharables for the\n        corresponding plugin model. Additionally it adds dynamic fields to edit the content inside\n        the model field `glossary`. The layout, validation and media files for these dynamic fields\n        are borrowed from the corresponding plugin.\n        \"\"\"\n        sharable_fields = getattr(self.plugin_instance, 'sharable_fields', [])\n        form_mixin = self.plugin_instance().get_form(request)\n        attrs = {name: field for name, field in form_mixin.base_fields.items() if name in sharable_fields}\n\n        class Meta:\n            untangled_fields = ['identifier']\n            entangled_fields = {'glossary': list(attrs.keys())}\n\n        attrs['Meta'] = Meta\n        kwargs['form'] = type('SharedFieldsForm', (EntangledModelForm,), attrs)\n        return super().get_form(request, obj, **kwargs)\n\n    def has_add_permission(self, request):\n        # always False, since a SharedGlossary can only be added by a plugin\n        return False\n\n    def add_view(self, request, form_url='', extra_context=None):\n        raise AssertionError(\"This method shall never be called\")\n\n    def change_view(self, request, object_id, form_url='', extra_context=None):\n        obj = self.get_object(request, object_id)\n        self.plugin_instance = plugin_pool.get_plugin(obj.plugin_type)\n        extra_context = dict(extra_context or {},\n                             title=format_html(_(\"Change shared settings of '{}' plugin\"), self.plugin_instance.name),\n                             icon_fonts=IconFont.objects.all())\n        return super().change_view(request, object_id, form_url, extra_context=extra_context)\n\n    def used_by(self, obj):\n        \"\"\"\n        Returns the number of plugins using this shared glossary\n        \"\"\"\n        return SharableCascadeElement.objects.filter(shared_glossary=obj).count()\n    used_by.short_description = _(\"Used by plugins\")\n\n    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):\n        try:\n            context['media'] += self.plugin_instance().media\n        except (AttributeError, KeyError):\n            pass\n        context.update(\n            ring_plugin=self.plugin_instance().ring_plugin,\n            ring_plugin_bases=dict((ring_plugin, ['django.cascade.{}'.format(b) for b in bases if b != 'SharableGlossaryMixin'])\n                                   for ring_plugin, bases in CascadePluginMixinMetaclass.ring_plugin_bases.items())\n        )\n        return super().render_change_form(request, context, add, change, form_url, obj)\n\n    def plugin_name(self, obj):\n        plugin_instance = plugin_pool.get_plugin(obj.plugin_type)\n        return plugin_instance.name\n    plugin_name.short_description = _(\"Plugin Type\")\n"
  },
  {
    "path": "cmsplugin_cascade/sharable/fields.py",
    "content": "from django.core.exceptions import ValidationError\nfrom django.forms import widgets\nfrom django.forms.fields import CharField, BooleanField, MultiValueField\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass SharedSettingsWidget(widgets.MultiWidget):\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/sharedsettingsfield.js']\n\n    def __init__(self):\n        widget_list = [\n            widgets.CheckboxInput(),\n            widgets.TextInput(),\n        ]\n        super().__init__(widget_list)\n\n    def decompress(self, value):\n        return value\n\n\nclass SharedSettingsField(MultiValueField):\n    def __init__(self, *args, **kwargs):\n        kwargs.pop('required', None)\n        fields = [\n            BooleanField(required=False),\n            CharField(required=False),\n        ]\n        widget = kwargs.pop('widget', SharedSettingsWidget)\n        initial = [False, '']\n        super().__init__(fields=fields, widget=widget, initial=initial, required=False, *args, **kwargs)\n\n    def clean(self, value):\n        if value[0] and not value[1]:\n            msg = _(\"An identifier is required to remember these settings.\")\n            raise ValidationError(msg)\n        return value[1]\n"
  },
  {
    "path": "cmsplugin_cascade/sharable/forms.py",
    "content": "import json\nfrom copy import deepcopy\nfrom django import forms\nfrom django.contrib.admin.helpers import AdminForm\nfrom django.core.exceptions import ValidationError, ObjectDoesNotExist\nfrom django.apps import apps\nfrom django.utils.translation import gettext_lazy as _\nfrom entangled.forms import EntangledModelFormMixin\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.models import SharedGlossary, CascadeElement\nfrom .fields import SharedSettingsField\n\n\nclass SelectSharedGlossary(forms.Select):\n    option_inherits_attrs = True\n\n    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):\n        if value:\n            attrs = {'data-glossary': json.dumps(self._get_data_glossary(value))}\n        else:\n            attrs = {}\n        return super().create_option(name, value, label, selected, index, subindex, attrs)\n\n    def _get_data_glossary(self, option_value):\n        shared_instance = self.choices.queryset.get(pk=option_value)\n        plugin_instance = plugin_pool.get_plugin(shared_instance.plugin_type)\n        # use the saved glossary and filter it by fields marked as sharable\n        glossary = dict((key, value) for key, value in shared_instance.glossary.items()\n                        if key in plugin_instance.sharable_fields)\n        self._enrich_link(glossary)\n        return glossary\n\n    def _enrich_link(self, glossary):\n        \"\"\"\n        Enrich the dict glossary['link'] with an identifier onto the model\n        \"\"\"\n        try:\n            Model = apps.get_model(*glossary['link']['model'].split('.'))\n            obj = Model.objects.get(pk=glossary['link']['pk'])\n            glossary['link'].update(identifier=str(obj))\n        except (KeyError, ObjectDoesNotExist):\n            pass\n\n\nclass SharedFormMixin(EntangledModelFormMixin):\n    \"\"\"\n    The change editor of plugins marked as sharable, are enriched by three fields:\n        - a select box named 'shared_glossary',\n        - a checkbox and a text input field\n    these additional form fields are added during runtime.\n    \"\"\"\n    shared_glossary = forms.ModelChoiceField(\n        label=_(\"Shared Settings\"),\n        required=False,\n        queryset=SharedGlossary.objects.all(),\n        empty_label=_(\"Use individual settings\"),\n        help_text=_(\"Use settings shared with other plugins of this type\"),\n    )\n\n    class Meta:\n        untangled_fields = ['shared_glossary']\n\n\nclass SharableFormMixin(SharedFormMixin):\n    save_settings_as = SharedSettingsField(\n        label=_(\"Remember these settings as\"),\n    )\n\n    class Meta:\n        untangled_fields = ['save_settings_as']\n\n    def clean_save_settings_as(self):\n        identifier = self.cleaned_data['save_settings_as']\n        if SharedGlossary.objects.filter(identifier=identifier).exclude(pk=self.instance.pk).exists():\n            msg = _(\"The identifier '{}' has already been used, please choose another name.\")\n            raise ValidationError(msg.format(identifier))\n        return identifier\n\n\n\nclass SharableGlossaryMixin(metaclass=forms.MediaDefiningClass):\n    \"\"\"\n    Every plugin class of type ``CascadePluginBase`` additionally inherits from this mixin,\n    if the plugin is marked as sharable.\n    This class adds the appropriate methods to the plugin class in order to store\n    an assortment of glossary values as a glossary reusable by other plugin instances.\n    \"\"\"\n    ring_plugin = 'SharableGlossaryMixin'\n\n    class Media:\n        js = ['admin/js/jquery.init.js', 'cascade/js/admin/sharableglossary.js']\n\n    def changeform_view(self, request, object_id=None, form_url='', extra_context=None):\n        if request.method == 'GET' and request.GET.get('glossary'):\n            # Reached after the user changed the shared settings select field.\n            # This recreates a form for the given plugin using the selected shared fields\n            # as values for a faked object.\n            extra_context = dict(extra_context or {})\n            sharable_fields = {}\n            try:\n                shared_glossary = SharedGlossary.objects.get(id=request.GET.get('glossary'))\n            except (ValueError, SharedGlossary.DoesNotExist):\n                pass\n            else:\n                for field_name in self.sharable_fields:\n                    shared_val = shared_glossary.glossary.get(field_name)\n                    if shared_val:\n                        sharable_fields[field_name] = shared_val\n\n            if object_id:\n                obj = self.get_object(request, object_id)\n                glossary = deepcopy(obj.glossary)\n                glossary.update(sharable_fields)\n                # create fake object using values from shared fields to mimic the expected behaviour\n                fake_obj = CascadeElement(glossary=glossary)\n                ModelForm = self.get_form(request, fake_obj)\n                form = ModelForm(instance=fake_obj)\n            else:\n                ModelForm = self.get_form(request)\n                initial = self.get_changeform_initial_data(request)\n                initial.update(sharable_fields)\n                form = ModelForm(initial=initial)\n\n            extra_context['adminform'] = AdminForm(\n                form,\n                list(self.get_fieldsets(request, obj)),\n                {},\n                [],\n                model_admin=self)\n\n        return super().changeform_view(request, object_id, form_url, extra_context)\n\n    def get_form(self, request, obj=None, **kwargs):\n        base_form = kwargs.pop('form', self.form)\n        assert issubclass(base_form, EntangledModelFormMixin), \"The Form class must inherit from EntangledModelFormMixin\"\n        if request.method == 'GET' and 'glossary' in request.GET:\n            FormMixin = SharedFormMixin if request.GET['glossary'] else SharableFormMixin\n        else:\n            FormMixin = SharedFormMixin if obj and obj.shared_glossary else SharableFormMixin\n        kwargs['form'] = type(base_form.__name__, (FormMixin, base_form), {})\n        kwargs['form'].base_fields['shared_glossary'].limit_choices_to = dict(plugin_type=self.__class__.__name__)\n        try:\n            shared_glossary = SharedGlossary.objects.get(id=request.GET.get('glossary'))\n        except ValueError:\n            shared_glossary = None\n        except SharedGlossary.DoesNotExist:\n            shared_glossary = obj.shared_glossary if obj else None\n        for field_name in self.sharable_fields:\n            kwargs['form'].base_fields[field_name].disabled = bool(shared_glossary)\n        return super().get_form(request, obj, **kwargs)\n\n    def save_model(self, request, obj, form, change):\n        super().save_model(request, obj, form, change)\n        # in case checkbox for `save_settings_as` is checked, then we create a new entry in `models.SharedGlossary`\n        # transferring the fields declared as sharable from this this plugin to that newly created object\n        save_settings_as = form.cleaned_data.get('save_settings_as')\n        if save_settings_as:\n            # move data from form glossary to a SharedGlossary and refer to it\n            shared_glossary, created = SharedGlossary.objects.get_or_create(\n                plugin_type=self.__class__.__name__,\n                identifier=save_settings_as,\n            )\n            assert created, \"SharableCascadeForm.clean_save_settings_as() erroneously validated identifier '{}' \" \\\n                            \"as unique\".format(save_settings_as)\n            glry = form.cleaned_data['glossary']\n            shared_glossary.glossary = dict((key, glry[key]) for key in self.sharable_fields if key in glry)\n            shared_glossary.save()\n            obj.shared_glossary = shared_glossary\n            obj.save()\n\n    @classmethod\n    def get_data_representation(cls, instance):\n        data = super().get_data_representation(instance)\n        if instance.shared_glossary:\n            data.setdefault('glossary', {})\n            data['glossary'].update(instance.shared_glossary.glossary)\n            data.update(shared_glossary=instance.shared_glossary.identifier)\n        return data\n\n    @classmethod\n    def add_shared_reference(cls, instance, shared_glossary_identifier):\n        try:\n            shared_glossary = SharedGlossary.objects.get(plugin_type=instance.plugin_type,\n                                                         identifier=shared_glossary_identifier)\n        except SharedGlossary.DoesNotExist:\n            pass\n        else:\n            instance.shared_glossary = shared_glossary\n            instance.save(update_fields=['shared_glossary'])\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/sphinx/cms_apps.py",
    "content": "import io\nimport mimetypes\nimport os\nfrom django.conf import settings\nfrom django.core.exceptions import ViewDoesNotExist\nfrom django.http.response import HttpResponse\nfrom django.views.generic import TemplateView\nfrom django.urls import re_path\nfrom django.utils.cache import patch_cache_control\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext_lazy as _\n\nfrom cms.app_base import CMSApp\nfrom cms.apphook_pool import apphook_pool\n\n\nclass SphinxDocsView(TemplateView):\n    def get(self, request, *args, **kwargs):\n        slug = kwargs.get('slug', '')\n        _, extension = os.path.splitext(slug)\n        if extension in ['.png', '.jpg', '.jpeg', '.gif']:\n            filename = os.path.join(settings.SPHINX_DOCS_ROOT, slug)\n            content_type, _ = mimetypes.guess_type(filename)\n            with io.open(filename, 'rb') as fd:\n                response = HttpResponse(content=fd.read(), content_type=content_type)\n                patch_cache_control(response, cache_control='max-age=86400')\n                return response\n        return super().get(request, page=slug, *args, **kwargs)\n\n    def get_template_names(self):\n        return [self.request.current_page.get_template()]\n\n    def get_context_data(self, page='index.html', **kwargs):\n        context = super().get_context_data(**kwargs)\n        filename = os.path.join(settings.SPHINX_DOCS_ROOT, page, 'index.html')\n        if not os.path.exists(filename):\n            raise ViewDoesNotExist(\"{} does not exist\".format(page))\n        with io.open(filename, encoding='utf-8') as fd:\n            context.update(page_content=mark_safe(fd.read()))\n        return context\n\n\n@apphook_pool.register\nclass SphinxDocsApp(CMSApp):\n    name = _(\"Sphinx Documentation\")\n\n    def get_urls(self, page=None, language=None, **kwargs):\n        return [\n            re_path(r'^(?P<slug>\\S+)/$', SphinxDocsView.as_view(), name='sphinx-documentation'),\n        ]\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/cms_menus.py",
    "content": "import io\nimport json\nimport os\nfrom django.conf import settings\nfrom django.urls import reverse_lazy\nfrom django.utils.translation import gettext_lazy as _\nfrom cms.menu_bases import CMSAttachMenu\nfrom menus.base import NavigationNode\nfrom menus.menu_pool import menu_pool\n\n\nclass DocumentationMenu(CMSAttachMenu):\n    name = _(\"Documentation Menu\")  # give the menu a name this is required.\n\n    def get_nodes(self, request, root_page):\n        \"\"\"\n        This method is used to build the menu tree.\n        \"\"\"\n        nodes = []\n        docsmap_file = os.path.join(settings.SPHINX_DOCS_ROOT, 'docsmap.json')\n        if not os.path.exists(docsmap_file):\n            return nodes\n        with io.open(docsmap_file) as fh:\n            docs_map = json.load(fh, encoding='utf-8')\n\n        for counter, items in enumerate(docs_map.items(), 1):\n            bits = items[0].split('/')\n            if len(bits) == 1 and bits[0] == 'index' or len(bits) == 2 and bits[1] != 'index':\n                continue\n            node = NavigationNode(\n                title=items[1],\n                url=reverse_lazy('sphinx-documentation', args=(bits[0],)),\n                id=counter,\n            )\n            nodes.append(node)\n        return nodes\n\nmenu_pool.register_menu(DocumentationMenu)\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/fragmentsbuilder.py",
    "content": "import json, os\nfrom docutils import nodes\nfrom sphinx.builders.dirhtml import DirectoryHTMLBuilder\nfrom sphinx.environment.adapters.toctree import TocTree\n\n\nclass FragmentsBuilder(DirectoryHTMLBuilder):\n    name = 'fragments'\n\n    def __init__(self, app):\n        super().__init__(app)\n        self.config.html_theme = 'bootstrap-fragments'\n        self.config.html_theme_path.append(os.path.abspath(os.path.join(__file__, os.pardir, 'theme')))\n        self.config.html_sidebars = {\n            '**': ['globaltoc.html'],\n        }\n        self.docsmap_file = os.path.join(self.outdir, 'docsmap.json')\n        if os.path.exists(self.docsmap_file):\n            with open(self.docsmap_file, 'r') as fh:\n                self.docs_map = json.load(fh, encoding='utf-8')\n        else:\n            self.docs_map = {}\n\n    def prepare_writing(self, docnames):\n        super().prepare_writing(docnames)\n        for docname in docnames:\n            doctree = self.env.get_doctree(docname)\n            idx = doctree.first_child_matching_class(nodes.section)\n            if idx is None or idx == -1:\n                continue\n\n            first_section = doctree[idx]\n            idx = first_section.first_child_matching_class(nodes.title)\n            if idx is None or idx == -1:\n                continue\n\n            doctitle = first_section[idx].astext()\n            if doctitle:\n                self.docs_map[docname] = doctitle\n            self.globalcontext['toctree'] = lambda **kw: self._get_local_toctree(docname, **kw)\n\n    def _get_local_toctree(self, docname, collapse=True, **kwds):\n        # type: (str, bool, Any) -> str\n        if 'includehidden' not in kwds:\n            kwds['includehidden'] = False\n        partials = TocTree(self.env).get_toctree_for(docname, self, collapse, **kwds)\n        return self.render_partial(partials)['fragment']\n\n    def finish(self):\n        super().finish()\n        with open(self.docsmap_file, 'w') as fh:\n            json.dump(self.docs_map, fh)\n\n\ndef setup(app):\n    app.require_sphinx('1.0')\n    app.add_builder(FragmentsBuilder)\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/link_plugin.py",
    "content": "import io\nimport json\nimport os\nfrom django.conf import settings\nfrom django.forms import fields\nfrom django.utils.translation import gettext_lazy as _\nfrom django_select2.forms import Select2Widget\nfrom cms.models.pagemodel import Page\nfrom cmsplugin_cascade.link.plugin_base import LinkPluginBase\nfrom cmsplugin_cascade.link.forms import LinkForm\n\n\nclass DocumentationSelect2Widget(Select2Widget):\n    def render(self, name, value, attrs=None, renderer=None):\n        html = super().render(name, value, attrs=attrs, renderer=None)\n        return html\n\n\ndef get_documents_map():\n    docsmap_file = os.path.join(settings.SPHINX_DOCS_ROOT, 'docsmap.json')\n    if not os.path.exists(docsmap_file):\n        return ()\n    with io.open(docsmap_file) as fh:\n        docs_map = json.load(fh, encoding='utf-8')\n    result = []\n    for path, title in docs_map.items():\n        bits = path.split('/')\n        if len(bits) == 2 and bits[1] == 'index':\n            result.append((bits[0], title))\n        elif bits[0] != 'index':\n            result.append((path, title))\n    return result\n\n\nclass SphinxDocsLinkForm(LinkForm):\n    LINK_TYPE_CHOICES = [\n        ('cmspage', _(\"CMS Page\")),\n        ('documentation', _(\"Documentation\")),\n        ('exturl', _(\"External URL\")),\n        ('email', _(\"Mail To\")),\n    ]\n\n    documentation = fields.ChoiceField(\n        required=False,\n        label='',\n        choices=get_documents_map(),\n        widget=Select2Widget,\n        help_text=_(\"An internal link onto a documentation page\"),\n    )\n\n    def clean_documentation(self):\n        if self.cleaned_data.get('link_type') == 'documentation':\n            self.cleaned_data['link_data'] = {\n                'type': 'documentation',\n                'value': self.cleaned_data.get('documentation'),\n            }\n\n    def set_initial_documentation(self, initial):\n        try:\n            initial['documentation'] = initial['link']['value']\n        except KeyError:\n            pass\n\n\nclass SphinxDocsLinkPlugin(LinkPluginBase):\n    fields = [\n        ('link_type', 'cms_page', 'section', 'documentation', 'ext_url', 'mail_to'),\n        'glossary',\n    ]\n    ring_plugin = 'SphinxDocsLinkPlugin'\n\n    class Media:\n        js = ['cascade/sphinx/js/link_plugin.js']\n\n    @classmethod\n    def get_link(cls, obj):\n        link = obj.glossary.get('link', {})\n        if link.get('type') == 'documentation':\n            page = Page.objects.filter(navigation_extenders='DocumentationMenu', publisher_is_draft=False).first()\n            if page:\n                return page.get_public_url()\n        return super().get_link(obj)\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/static/cascade/sphinx/css/bootstrap-sphinx.css",
    "content": "/*\n * bootstrap-sphinx.css\n * ~~~~~~~~~~~~~~~~~~~~\n *\n * Sphinx stylesheet -- Bootstrap theme.\n */\n\n/*\n * Imports to aggregate everything together.\n */\n\n@import url(\"./documentation.css\");\n\n\n/*\n * Styles\n */\n\n.navbar-inverse .brand {\n  color: #FFF;\n}\n\n/*\n * Reset navbar styles from overrides in:\n * https://bitbucket.org/birkenfeld/sphinx/commits/78d8ebf76b630ab4073a7328af9d91e8123b8d96\n */\n.navbar .container {\n  padding-top: 0;\n}\n\n/*\n * Reset the logo image dimensions. Sites like RTD can override with bad\n * results on mobile (mega-huge logo...)\n *\n * https://github.com/ryan-roemer/sphinx-bootstrap-theme/issues/142\n */\n.navbar-brand img {\n  width: auto;\n  height: 100%;\n}\n\n.page-top {\n  top: 0px;\n}\n\n\n\n\n\n  body {\n\n      padding-top: 40px;\n\n  }\n  .page-top {\n\n      top: 40px;\n\n  }\n\n\n\n.navbar-inner {\n  padding-left:  12px !important;\n  padding-right: 12px !important;\n}\n\n\ntable {\n  border: 0;\n}\n\n.highlighttable .code pre {\n    font-size: 12px;\n}\n\n.highlighttable .linenos pre {\n    word-break: normal;\n    font-size: 12px;\n}\n\ndiv.highlight {\n  background: none;\n}\n\na.footnote-reference {\n  vertical-align: super;\n  font-size: 75%;\n}\n\ntable.footnote td.label {\n  color: inherit;\n  font-size: 100%;\n  display: block;\n  line-height: normal;\n  background: inherit;\n}\n\ntable.footnote {\n  width: auto;\n  margin-bottom: 0px;\n}\n\ntable.field-list {\n  width: auto;\n}\n\n.footer {\n  width: 100%;\n  border-top: 1px solid #ccc;\n  padding-top: 10px;\n}\n\n.bs-sidenav form, .bs-sidenav #sourcelink {\n  padding: 5px 20px;\n}\n\n\n\n/* The code below is based on the bootstrap website sidebar */\n\n.bs-sidenav.affix {\n  position: static;\n}\n\n/* First level of nav */\n.bs-sidenav {\n  margin-top: 30px;\n  margin-bottom: 30px;\n  padding-top:    10px;\n  padding-bottom: 10px;\n  text-shadow: 0 1px 0 #fff;\n  background-color: #f7f5fa;\n  border-radius: 5px;\n}\n\n/* All levels of nav */\n.bs-sidenav .nav > li > a {\n  display: block;\n  color: #716b7a;\n  padding: 5px 20px;\n}\n.bs-sidenav .nav > li > a:hover,\n.bs-sidenav .nav > li > a:focus {\n  text-decoration: none;\n  background-color: #e5e3e9;\n  border-right: 1px solid #dbd8e0;\n}\n.bs-sidenav .nav > .active > a,\n.bs-sidenav .nav > .active:hover > a,\n.bs-sidenav .nav > .active:focus > a {\n  font-weight: bold;\n  color: #563d7c;\n  background-color: transparent;\n  border-right: 1px solid #563d7c;\n}\n\n.bs-sidenav .nav .nav > li > a {\n  padding-top:    3px;\n  padding-bottom: 3px;\n  padding-left: 30px;\n  font-size: 90%;\n}\n\n.bs-sidenav .nav .nav .nav > li > a {\n  padding-top:    3px;\n  padding-bottom: 3px;\n  padding-left: 40px;\n  font-size: 90%;\n}\n\n.bs-sidenav .nav .nav .nav .nav > li > a {\n  padding-top:    3px;\n  padding-bottom: 3px;\n  padding-left: 50px;\n  font-size: 90%;\n}\n\n/* Show and affix the side nav when space allows it */\n@media screen and (min-width: 992px) {\n  .bs-sidenav .nav > .active > ul {\n    display: block;\n  }\n  /* Widen the fixed sidenav */\n  .bs-sidenav.affix,\n  .bs-sidenav.affix-bottom {\n    width: 213px;\n  }\n  .bs-sidenav.affix {\n    position: fixed; /* Undo the static from mobile first approach */\n  }\n  .bs-sidenav.affix-bottom {\n    position: absolute; /* Undo the static from mobile first approach */\n  }\n  .bs-sidenav.affix-bottom .bs-sidenav,\n  .bs-sidenav.affix .bs-sidenav {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n}\n@media screen and (min-width: 1200px) {\n  /* Widen the fixed sidenav again */\n  .bs-sidenav.affix-bottom,\n  .bs-sidenav.affix {\n    width: 263px;\n  }\n}\n\n\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/static/cascade/sphinx/css/documentation.css",
    "content": "/*\n * basic.css\n * ~~~~~~~~~\n *\n * Sphinx stylesheet -- basic theme.\n *\n * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.\n * :license: BSD, see LICENSE for details.\n *\n */\n\n/* -- main layout ----------------------------------------------------------- */\n\ndiv.clearer {\n    clear: both;\n}\n\n/* -- relbar ---------------------------------------------------------------- */\n\ndiv.related {\n    width: 100%;\n    font-size: 90%;\n}\n\ndiv.related h3 {\n    display: none;\n}\n\ndiv.related ul {\n    margin: 0;\n    padding: 0 0 0 10px;\n    list-style: none;\n}\n\ndiv.related li {\n    display: inline;\n}\n\ndiv.related li.right {\n    float: right;\n    margin-right: 5px;\n}\n\n/* -- sidebar --------------------------------------------------------------- */\n\ndiv.sphinxsidebarwrapper {\n    padding: 10px 5px 0 10px;\n}\n\ndiv.sphinxsidebar {\n    float: left;\n    width: 230px;\n    margin-left: -100%;\n    font-size: 90%;\n    word-wrap: break-word;\n    overflow-wrap : break-word;\n}\n\ndiv.sphinxsidebar ul {\n    list-style: none;\n}\n\ndiv.sphinxsidebar ul ul,\ndiv.sphinxsidebar ul.want-points {\n    margin-left: 20px;\n    list-style: square;\n}\n\ndiv.sphinxsidebar ul ul {\n    margin-top: 0;\n    margin-bottom: 0;\n}\n\ndiv.sphinxsidebar form {\n    margin-top: 10px;\n}\n\ndiv.sphinxsidebar input {\n    border: 1px solid #98dbcc;\n    font-family: sans-serif;\n    font-size: 1em;\n}\n\ndiv.sphinxsidebar #searchbox input[type=\"text\"] {\n    width: 170px;\n}\n\nimg {\n    border: 0;\n    max-width: 100%;\n}\n\n/* -- search page ----------------------------------------------------------- */\n\nul.search {\n    margin: 10px 0 0 20px;\n    padding: 0;\n}\n\nul.search li {\n    padding: 5px 0 5px 20px;\n    background-image: url(file.png);\n    background-repeat: no-repeat;\n    background-position: 0 7px;\n}\n\nul.search li a {\n    font-weight: bold;\n}\n\nul.search li div.context {\n    color: #888;\n    margin: 2px 0 0 30px;\n    text-align: left;\n}\n\nul.keywordmatches li.goodmatch a {\n    font-weight: bold;\n}\n\n/* -- index page ------------------------------------------------------------ */\n\ntable.contentstable {\n    width: 90%;\n    margin-left: auto;\n    margin-right: auto;\n}\n\ntable.contentstable p.biglink {\n    line-height: 150%;\n}\n\na.biglink {\n    font-size: 1.3em;\n}\n\nspan.linkdescr {\n    font-style: italic;\n    padding-top: 5px;\n    font-size: 90%;\n}\n\n/* -- general index --------------------------------------------------------- */\n\ntable.indextable {\n    width: 100%;\n}\n\ntable.indextable td {\n    text-align: left;\n    vertical-align: top;\n}\n\ntable.indextable ul {\n    margin-top: 0;\n    margin-bottom: 0;\n    list-style-type: none;\n}\n\ntable.indextable > tbody > tr > td > ul {\n    padding-left: 0em;\n}\n\ntable.indextable tr.pcap {\n    height: 10px;\n}\n\ntable.indextable tr.cap {\n    margin-top: 10px;\n    background-color: #f2f2f2;\n}\n\nimg.toggler {\n    margin-right: 3px;\n    margin-top: 3px;\n    cursor: pointer;\n}\n\ndiv.modindex-jumpbox {\n    border-top: 1px solid #ddd;\n    border-bottom: 1px solid #ddd;\n    margin: 1em 0 1em 0;\n    padding: 0.4em;\n}\n\ndiv.genindex-jumpbox {\n    border-top: 1px solid #ddd;\n    border-bottom: 1px solid #ddd;\n    margin: 1em 0 1em 0;\n    padding: 0.4em;\n}\n\n/* -- domain module index --------------------------------------------------- */\n\ntable.modindextable td {\n    padding: 2px;\n    border-collapse: collapse;\n}\n\n/* -- general body styles --------------------------------------------------- */\n\ndiv.body p, div.body dd, div.body li, div.body blockquote {\n    -moz-hyphens: auto;\n    -ms-hyphens: auto;\n    -webkit-hyphens: auto;\n    hyphens: auto;\n}\n\na.headerlink {\n    visibility: hidden;\n}\n\nh1:hover > a.headerlink,\nh2:hover > a.headerlink,\nh3:hover > a.headerlink,\nh4:hover > a.headerlink,\nh5:hover > a.headerlink,\nh6:hover > a.headerlink,\ndt:hover > a.headerlink,\ncaption:hover > a.headerlink,\np.caption:hover > a.headerlink,\ndiv.code-block-caption:hover > a.headerlink {\n    visibility: visible;\n}\n\ndiv.body p.caption {\n    text-align: inherit;\n}\n\ndiv.body td {\n    text-align: left;\n}\n\n.first {\n    margin-top: 0 !important;\n}\n\np.rubric {\n    margin-top: 30px;\n    font-weight: bold;\n}\n\nimg.align-left, .figure.align-left, object.align-left {\n    clear: left;\n    float: left;\n    margin-right: 1em;\n}\n\nimg.align-right, .figure.align-right, object.align-right {\n    clear: right;\n    float: right;\n    margin-left: 1em;\n}\n\nimg.align-center, .figure.align-center, object.align-center {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.align-left {\n    text-align: left;\n}\n\n.align-center {\n    text-align: center;\n}\n\n.align-right {\n    text-align: right;\n}\n\n/* -- sidebars -------------------------------------------------------------- */\n\ndiv.sidebar {\n    margin: 0 0 0.5em 1em;\n    border: 1px solid #ddb;\n    padding: 7px 7px 0 7px;\n    background-color: #ffe;\n    width: 40%;\n    float: right;\n}\n\np.sidebar-title {\n    font-weight: bold;\n}\n\n/* -- topics ---------------------------------------------------------------- */\n\ndiv.topic {\n    border: 1px solid #ccc;\n    padding: 7px 7px 0 7px;\n    margin: 10px 0 10px 0;\n}\n\np.topic-title {\n    font-size: 1.1em;\n    font-weight: bold;\n    margin-top: 10px;\n}\n\n/* -- admonitions ----------------------------------------------------------- */\n\ndiv.admonition {\n    margin-top: 10px;\n    margin-bottom: 10px;\n    padding: 7px;\n}\n\ndiv.admonition dt {\n    font-weight: bold;\n}\n\ndiv.admonition dl {\n    margin-bottom: 0;\n}\n\ndiv.admonition.note p {\n    margin-left: 2em;\n}\n\ndiv.admonition p.admonition-title {\n    margin: 0px 10px 5px 0px;\n    font-weight: bold;\n}\n\ndiv.body p.centered {\n    text-align: center;\n    margin-top: 25px;\n}\n\n/* -- tables ---------------------------------------------------------------- */\n\ntable.docutils {\n    border: 0;\n    border-collapse: collapse;\n}\n\ntable caption span.caption-number {\n    font-style: italic;\n}\n\ntable caption span.caption-text {\n}\n\ntable.docutils td, table.docutils th {\n    padding: 1px 8px 1px 5px;\n    border-top: 0;\n    border-left: 0;\n    border-right: 0;\n    border-bottom: 1px solid #aaa;\n}\n\ntable.footnote td, table.footnote th {\n    border: 0 !important;\n}\n\nth {\n    text-align: left;\n    padding-right: 5px;\n}\n\ntable.citation {\n    border-left: solid 1px gray;\n    margin-left: 1px;\n}\n\ntable.citation td {\n    border-bottom: none;\n}\n\n/* -- figures --------------------------------------------------------------- */\n\ndiv.figure {\n    margin: 0.5em;\n    padding: 0.5em;\n}\n\ndiv.figure p.caption {\n    padding: 0.3em;\n}\n\ndiv.figure p.caption span.caption-number {\n    font-style: italic;\n}\n\ndiv.figure p.caption span.caption-text {\n}\n\n/* -- field list styles ----------------------------------------------------- */\n\ntable.field-list td, table.field-list th {\n    border: 0 !important;\n}\n\n.field-list ul {\n    margin: 0;\n    padding-left: 1em;\n}\n\n.field-list p {\n    margin: 0;\n}\n\n.field-name {\n    -moz-hyphens: manual;\n    -ms-hyphens: manual;\n    -webkit-hyphens: manual;\n    hyphens: manual;\n}\n\n/* -- other body styles ----------------------------------------------------- */\n\nol.arabic {\n    list-style: decimal;\n}\n\nol.loweralpha {\n    list-style: lower-alpha;\n}\n\nol.upperalpha {\n    list-style: upper-alpha;\n}\n\nol.lowerroman {\n    list-style: lower-roman;\n}\n\nol.upperroman {\n    list-style: upper-roman;\n}\n\ndl {\n    margin-bottom: 15px;\n}\n\ndd p {\n    margin-top: 0px;\n}\n\ndd ul, dd table {\n    margin-bottom: 10px;\n}\n\ndd {\n    margin-top: 3px;\n    margin-bottom: 10px;\n    margin-left: 30px;\n}\n\ndt:target, span.highlighted {\n    background-color: #fbe54e;\n}\n\nrect.highlighted {\n    fill: #fbe54e;\n}\n\ndl.glossary dt {\n    font-weight: bold;\n    font-size: 1.1em;\n}\n\n.optional {\n    font-size: 1.3em;\n}\n\n.sig-paren {\n    font-size: larger;\n}\n\n.versionmodified {\n    font-style: italic;\n}\n\n.system-message {\n    background-color: #fda;\n    padding: 5px;\n    border: 3px solid red;\n}\n\n.footnote:target  {\n    background-color: #ffa;\n}\n\n.line-block {\n    display: block;\n    margin-top: 1em;\n    margin-bottom: 1em;\n}\n\n.line-block .line-block {\n    margin-top: 0;\n    margin-bottom: 0;\n    margin-left: 1.5em;\n}\n\n.guilabel, .menuselection {\n    font-family: sans-serif;\n}\n\n.accelerator {\n    text-decoration: underline;\n}\n\n.classifier {\n    font-style: oblique;\n}\n\nabbr, acronym {\n    border-bottom: dotted 1px;\n    cursor: help;\n}\n\n/* -- code displays --------------------------------------------------------- */\n\npre {\n    overflow: auto;\n    overflow-y: hidden;  /* fixes display issues on Chrome browsers */\n}\n\nspan.pre {\n    -moz-hyphens: none;\n    -ms-hyphens: none;\n    -webkit-hyphens: none;\n    hyphens: none;\n}\n\ntd.linenos pre {\n    padding: 5px 0px;\n    border: 0;\n    background-color: transparent;\n    color: #aaa;\n}\n\ntable.highlighttable {\n    margin-left: 0.5em;\n}\n\ntable.highlighttable td {\n    padding: 0 0.5em 0 0.5em;\n}\n\ndiv.code-block-caption {\n    padding: 2px 5px;\n    font-size: small;\n}\n\ndiv.code-block-caption code {\n    background-color: transparent;\n}\n\ndiv.code-block-caption + div > div.highlight > pre {\n    margin-top: 0;\n}\n\ndiv.code-block-caption span.caption-number {\n    padding: 0.1em 0.3em;\n    font-style: italic;\n}\n\ndiv.code-block-caption span.caption-text {\n}\n\ndiv.literal-block-wrapper {\n    padding: 0;\n}\n\ndiv.literal-block-wrapper div.highlight {\n    margin: 0;\n}\n\ncode.descname {\n    background-color: transparent;\n    font-weight: bold;\n    font-size: 1.2em;\n}\n\ncode.descclassname {\n    background-color: transparent;\n}\n\ncode.xref, a code {\n    background-color: transparent;\n    font-weight: bold;\n}\n\nh1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n    background-color: transparent;\n}\n\n.viewcode-link {\n    float: right;\n}\n\n.viewcode-back {\n    float: right;\n    font-family: sans-serif;\n}\n\ndiv.viewcode-block:target {\n    margin: -1px -10px;\n    padding: 0 10px;\n}\n\n/* -- math display ---------------------------------------------------------- */\n\nimg.math {\n    vertical-align: middle;\n}\n\ndiv.body div.math p {\n    text-align: center;\n}\n\nspan.eqno {\n    float: right;\n}\n\nspan.eqno a.headerlink {\n    position: relative;\n    left: 0px;\n    z-index: 1;\n}\n\ndiv.math:hover a.headerlink {\n    visibility: visible;\n}\n\n/* -- printout stylesheet --------------------------------------------------- */\n\n@media print {\n    div.document,\n    div.documentwrapper,\n    div.bodywrapper {\n        margin: 0 !important;\n        width: 100%;\n    }\n\n    div.sphinxsidebar,\n    div.related,\n    div.footer,\n    #top-link {\n        display: none;\n    }\n}\n\n.content[role=\"main\"] .container {\n  width: initial;\n}\n\n.section {\n  margin-top: 30px;\n  margin-bottom: 30px;\n}\n\n#sidebar {\n  margin: 50px 0;\n  position: fixed;\n}\n\n#sidebar ul {\n  list-style: none;\n  padding-left: 20px;\n}\n\n#sidebar > ul {\n  padding-left: 0;\n}\n\n#sidebar ul>li>a {\n    padding-top: 1px;\n    padding-bottom: 1px;\n    padding-left: 30px;\n    font-size: 12px;\n    font-weight: 400;\n    color: rgb(118, 118, 118);\n}\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/static/cascade/sphinx/js/link_plugin.js",
    "content": "\ndjango.jQuery(function($) {\n\t'use strict';\n\n\tdjango.cascade.SphinxDocsLinkPlugin = ring.create(eval(django.cascade.ring_plugin_bases.SphinxDocsLinkPlugin), {\n\t\tconstructor: function() {\n\t\t\tthis.$super();\n\n\t\t\t// be more intuitive, reorganize layout by moving 'Link Target'\n\t\t\t$('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));\n\t\t},\n\t\tinitializeLinkTypes: function() {\n\t\t\tthis.$super();\n\t\t\t// after dropping support for django-1.11, remove the first jQuery-selector\n\t\t\tthis.linkTypes['documentation'] = new this.LinkType('.form-row .field-box.field-documentation, .form-row .fieldBox.field-documentation', true);\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/theme/bootstrap-fragments/globaltoc.html",
    "content": "{{ toctree(maxdepth=0, collapse=True, includehidden=False) }}\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/theme/bootstrap-fragments/layout.html",
    "content": "{% set bootstrap_version, navbar_version = \"3.3.7\", \"\" %}\n\n{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and sidebars %}\n\n{%- set bs_content_width = render_sidebar and \"9\" or \"12\"%}\n\n{%- block doctype -%}{%- endblock %}\n\n{%- macro bsidebar() %}\n  {%- if render_sidebar %}\n  <div class=\"col-md-3\" role=\"complementary\">\n    <nav id=\"sidebar\" class=\"hidden-print hidden-sm hidden-xs\">\n      {%- for sidebartemplate in sidebars %}\n        {%- include sidebartemplate %}\n      {%- endfor %}\n    </nav>\n  </div>\n  {%- endif %}\n{%- endmacro %}\n\n{%- block extrahead %}{%- endblock %}\n\n{# Silence the sidebar's, relbar's #}\n{% block header %}{% endblock %}\n{% block relbar1 %}{% endblock %}\n{% block relbar2 %}{% endblock %}\n{% block sidebarsourcelink %}{% endblock %}\n\n{%- block content %}\n<div class=\"row\">\n\t{%- block sidebar1 %}{%- endblock %}\n\t<div class=\"col-md-{{ bs_content_width }} content\" role=\"main\">\n\t{% block body %}{% endblock %}\n\t</div>\n\t{%- block sidebar2 %}{{ bsidebar() }}{%- endblock %}\n</div>\n{%- endblock %}\n\n{%- block footer %}{%- endblock %}\n"
  },
  {
    "path": "cmsplugin_cascade/sphinx/theme/bootstrap-fragments/theme.conf",
    "content": "# Bootstrap Theme\n[theme]\ninherit = basic\nstylesheet = bootstrap-sphinx.css\npygments_style = tango\n\n# Configurable options.\n[options]\n# Navigation bar title. (Default: ``project`` value)\nnavbar_title =\n\n# Tab name for entire site. (Default: \"Site\")\nnavbar_site_name = Site\n\n# A list of tuples containting pages to link to.  The value should be\n# in the form [(name, page), ..]\nnavbar_links =\n\n# Render the next and previous page links in navbar. (Default: true)\nnavbar_sidebarrel = true\n\n# Render the current pages TOC in the navbar. (Default: true)\nnavbar_pagenav = true\n\n# Tab name for the current pages TOC. (Default: \"Page\")\nnavbar_pagenav_name = Page\n\n# Global TOC depth for \"site\" navbar tab. (Default: 1)\n# Switching to -1 shows all levels.\nglobaltoc_depth = 1\n\n# Include hidden TOCs in Site navbar?\n#\n# Note: If this is \"false\", you cannot have mixed ``:hidden:`` and\n# non-hidden ``toctree`` directives in the same page, or else the build\n# will break.\n#\n# Values: \"true\" (default) or \"false\"\nglobaltoc_includehidden = true\n\n# HTML navbar class (Default: \"navbar\") to attach to <div> element.\n# For black navbar, do \"navbar navbar-inverse\"\nnavbar_class = navbar\n\n# Fix navigation bar to top of page?\n# Values: \"true\" (default) or \"false\"\nnavbar_fixed_top = true\n\n# Location of link to source.\n# Options are \"nav\" (default), \"footer\" or anything else to exclude.\nsource_link_position = nav\n\n# Bootswatch (http://bootswatch.com/) theme.\n#\n# Options are nothing (default) or the name of a valid theme such as\n# \"cosmo\" or \"sandstone\".\nbootswatch_theme =\n\n# Switch Bootstrap version?\n# Values: \"3\" (default) or \"2\"\nbootstrap_version = 3\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/LICENSE.md",
    "content": "fallback.svg is licensed according to the Creative Commons BY 4.0.\nIt is available from here: https://www.shareicon.net/cascade-water-nature-river-falls-cataract-810506"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/bootstrap4-buttons.css",
    "content": ".form-row.field-button_type {\n  max-width: 630px;\n}\nform .field-button_type span.btn,\n  form .field-button_size span.btn { display: inline-block; }\nform .field-button_type .label,\n  form .field-button_size .label { display: inline; }\n.btn {\n  margin-bottom: 0.25rem;\n  display: inline-block;\n  font-weight: 400;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  user-select: none;\n  border: 1px solid transparent;\n  padding: 0.375rem 0.75rem;\n  font-size: 1rem;\n  line-height: 1.5;\n  border-radius: 0.25rem;\n  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; }\n  @media screen and (prefers-reduced-motion: reduce) {\n    .btn {\n      transition: none; } }\n  .btn:hover, .btn:focus {\n    text-decoration: none; }\n  .btn:focus, .btn.focus {\n    outline: 0;\n    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); }\n  .btn.disabled, .btn:disabled {\n    opacity: 0.65; }\n  .btn:not(:disabled):not(.disabled) {\n    cursor: pointer; }\n\na.btn.disabled,\nfieldset:disabled a.btn {\n  pointer-events: none; }\n\n.btn-primary {\n  color: #fff;\n  background-color: #007bff;\n  border-color: #007bff; }\n  .btn-primary:hover {\n    color: #fff;\n    background-color: #0069d9;\n    border-color: #0062cc; }\n  .btn-primary:focus, .btn-primary.focus {\n    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); }\n  .btn-primary.disabled, .btn-primary:disabled {\n    color: #fff;\n    background-color: #007bff;\n    border-color: #007bff; }\n  .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n  .show > .btn-primary.dropdown-toggle {\n    color: #fff;\n    background-color: #0062cc;\n    border-color: #005cbf; }\n    .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-primary.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); }\n\n.btn-secondary {\n  color: #fff;\n  background-color: #6c757d;\n  border-color: #6c757d; }\n  .btn-secondary:hover {\n    color: #fff;\n    background-color: #5a6268;\n    border-color: #545b62; }\n  .btn-secondary:focus, .btn-secondary.focus {\n    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }\n  .btn-secondary.disabled, .btn-secondary:disabled {\n    color: #fff;\n    background-color: #6c757d;\n    border-color: #6c757d; }\n  .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n  .show > .btn-secondary.dropdown-toggle {\n    color: #fff;\n    background-color: #545b62;\n    border-color: #4e555b; }\n    .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-secondary.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }\n\n.btn-success {\n  color: #fff;\n  background-color: #28a745;\n  border-color: #28a745; }\n  .btn-success:hover {\n    color: #fff;\n    background-color: #218838;\n    border-color: #1e7e34; }\n  .btn-success:focus, .btn-success.focus {\n    box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); }\n  .btn-success.disabled, .btn-success:disabled {\n    color: #fff;\n    background-color: #28a745;\n    border-color: #28a745; }\n  .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n  .show > .btn-success.dropdown-toggle {\n    color: #fff;\n    background-color: #1e7e34;\n    border-color: #1c7430; }\n    .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-success.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); }\n\n.btn-info {\n  color: #fff;\n  background-color: #17a2b8;\n  border-color: #17a2b8; }\n  .btn-info:hover {\n    color: #fff;\n    background-color: #138496;\n    border-color: #117a8b; }\n  .btn-info:focus, .btn-info.focus {\n    box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }\n  .btn-info.disabled, .btn-info:disabled {\n    color: #fff;\n    background-color: #17a2b8;\n    border-color: #17a2b8; }\n  .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n  .show > .btn-info.dropdown-toggle {\n    color: #fff;\n    background-color: #117a8b;\n    border-color: #10707f; }\n    .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-info.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }\n\n.btn-warning {\n  color: #212529;\n  background-color: #ffc107;\n  border-color: #ffc107; }\n  .btn-warning:hover {\n    color: #212529;\n    background-color: #e0a800;\n    border-color: #d39e00; }\n  .btn-warning:focus, .btn-warning.focus {\n    box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); }\n  .btn-warning.disabled, .btn-warning:disabled {\n    color: #212529;\n    background-color: #ffc107;\n    border-color: #ffc107; }\n  .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n  .show > .btn-warning.dropdown-toggle {\n    color: #212529;\n    background-color: #d39e00;\n    border-color: #c69500; }\n    .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-warning.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); }\n\n.btn-danger {\n  color: #fff;\n  background-color: #dc3545;\n  border-color: #dc3545; }\n  .btn-danger:hover {\n    color: #fff;\n    background-color: #c82333;\n    border-color: #bd2130; }\n  .btn-danger:focus, .btn-danger.focus {\n    box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); }\n  .btn-danger.disabled, .btn-danger:disabled {\n    color: #fff;\n    background-color: #dc3545;\n    border-color: #dc3545; }\n  .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n  .show > .btn-danger.dropdown-toggle {\n    color: #fff;\n    background-color: #bd2130;\n    border-color: #b21f2d; }\n    .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-danger.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); }\n\n.btn-light {\n  color: #212529;\n  background-color: #f8f9fa;\n  border-color: #f8f9fa; }\n  .btn-light:hover {\n    color: #212529;\n    background-color: #e2e6ea;\n    border-color: #dae0e5; }\n  .btn-light:focus, .btn-light.focus {\n    box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }\n  .btn-light.disabled, .btn-light:disabled {\n    color: #212529;\n    background-color: #f8f9fa;\n    border-color: #f8f9fa; }\n  .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n  .show > .btn-light.dropdown-toggle {\n    color: #212529;\n    background-color: #dae0e5;\n    border-color: #d3d9df; }\n    .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-light.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }\n\n.btn-dark {\n  color: #fff;\n  background-color: #343a40;\n  border-color: #343a40; }\n  .btn-dark:hover {\n    color: #fff;\n    background-color: #23272b;\n    border-color: #1d2124; }\n  .btn-dark:focus, .btn-dark.focus {\n    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }\n  .btn-dark.disabled, .btn-dark:disabled {\n    color: #fff;\n    background-color: #343a40;\n    border-color: #343a40; }\n  .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n  .show > .btn-dark.dropdown-toggle {\n    color: #fff;\n    background-color: #1d2124;\n    border-color: #171a1d; }\n    .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-dark.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }\n\n.btn-outline-primary {\n  color: #007bff;\n  background-color: transparent;\n  background-image: none;\n  border-color: #007bff; }\n  .btn-outline-primary:hover {\n    color: #fff;\n    background-color: #007bff;\n    border-color: #007bff; }\n  .btn-outline-primary:focus, .btn-outline-primary.focus {\n    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); }\n  .btn-outline-primary.disabled, .btn-outline-primary:disabled {\n    color: #007bff;\n    background-color: transparent; }\n  .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-primary.dropdown-toggle {\n    color: #fff;\n    background-color: #007bff;\n    border-color: #007bff; }\n    .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-primary.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); }\n\n.btn-outline-secondary {\n  color: #6c757d;\n  background-color: transparent;\n  background-image: none;\n  border-color: #6c757d; }\n  .btn-outline-secondary:hover {\n    color: #fff;\n    background-color: #6c757d;\n    border-color: #6c757d; }\n  .btn-outline-secondary:focus, .btn-outline-secondary.focus {\n    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }\n  .btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n    color: #6c757d;\n    background-color: transparent; }\n  .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-secondary.dropdown-toggle {\n    color: #fff;\n    background-color: #6c757d;\n    border-color: #6c757d; }\n    .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-secondary.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }\n\n.btn-outline-success {\n  color: #28a745;\n  background-color: transparent;\n  background-image: none;\n  border-color: #28a745; }\n  .btn-outline-success:hover {\n    color: #fff;\n    background-color: #28a745;\n    border-color: #28a745; }\n  .btn-outline-success:focus, .btn-outline-success.focus {\n    box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); }\n  .btn-outline-success.disabled, .btn-outline-success:disabled {\n    color: #28a745;\n    background-color: transparent; }\n  .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-success.dropdown-toggle {\n    color: #fff;\n    background-color: #28a745;\n    border-color: #28a745; }\n    .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-success.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); }\n\n.btn-outline-info {\n  color: #17a2b8;\n  background-color: transparent;\n  background-image: none;\n  border-color: #17a2b8; }\n  .btn-outline-info:hover {\n    color: #fff;\n    background-color: #17a2b8;\n    border-color: #17a2b8; }\n  .btn-outline-info:focus, .btn-outline-info.focus {\n    box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }\n  .btn-outline-info.disabled, .btn-outline-info:disabled {\n    color: #17a2b8;\n    background-color: transparent; }\n  .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-info.dropdown-toggle {\n    color: #fff;\n    background-color: #17a2b8;\n    border-color: #17a2b8; }\n    .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-info.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }\n\n.btn-outline-warning {\n  color: #ffc107;\n  background-color: transparent;\n  background-image: none;\n  border-color: #ffc107; }\n  .btn-outline-warning:hover {\n    color: #212529;\n    background-color: #ffc107;\n    border-color: #ffc107; }\n  .btn-outline-warning:focus, .btn-outline-warning.focus {\n    box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); }\n  .btn-outline-warning.disabled, .btn-outline-warning:disabled {\n    color: #ffc107;\n    background-color: transparent; }\n  .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-warning.dropdown-toggle {\n    color: #212529;\n    background-color: #ffc107;\n    border-color: #ffc107; }\n    .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-warning.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); }\n\n.btn-outline-danger {\n  color: #dc3545;\n  background-color: transparent;\n  background-image: none;\n  border-color: #dc3545; }\n  .btn-outline-danger:hover {\n    color: #fff;\n    background-color: #dc3545;\n    border-color: #dc3545; }\n  .btn-outline-danger:focus, .btn-outline-danger.focus {\n    box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); }\n  .btn-outline-danger.disabled, .btn-outline-danger:disabled {\n    color: #dc3545;\n    background-color: transparent; }\n  .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-danger.dropdown-toggle {\n    color: #fff;\n    background-color: #dc3545;\n    border-color: #dc3545; }\n    .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-danger.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); }\n\n.btn-outline-light {\n  color: #f8f9fa;\n  background-color: transparent;\n  background-image: none;\n  border-color: #f8f9fa; }\n  .btn-outline-light:hover {\n    color: #212529;\n    background-color: #f8f9fa;\n    border-color: #f8f9fa; }\n  .btn-outline-light:focus, .btn-outline-light.focus {\n    box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }\n  .btn-outline-light.disabled, .btn-outline-light:disabled {\n    color: #f8f9fa;\n    background-color: transparent; }\n  .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-light.dropdown-toggle {\n    color: #212529;\n    background-color: #f8f9fa;\n    border-color: #f8f9fa; }\n    .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-light.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }\n\n.btn-outline-dark {\n  color: #343a40;\n  background-color: transparent;\n  background-image: none;\n  border-color: #343a40; }\n  .btn-outline-dark:hover {\n    color: #fff;\n    background-color: #343a40;\n    border-color: #343a40; }\n  .btn-outline-dark:focus, .btn-outline-dark.focus {\n    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }\n  .btn-outline-dark.disabled, .btn-outline-dark:disabled {\n    color: #343a40;\n    background-color: transparent; }\n  .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n  .show > .btn-outline-dark.dropdown-toggle {\n    color: #fff;\n    background-color: #343a40;\n    border-color: #343a40; }\n    .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n    .show > .btn-outline-dark.dropdown-toggle:focus {\n      box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }\n\n.btn-link {\n  font-weight: 400;\n  color: #007bff;\n  background-color: transparent; }\n  .btn-link:hover {\n    color: #0056b3;\n    text-decoration: underline;\n    background-color: transparent;\n    border-color: transparent; }\n  .btn-link:focus, .btn-link.focus {\n    text-decoration: underline;\n    border-color: transparent;\n    box-shadow: none; }\n  .btn-link:disabled, .btn-link.disabled {\n    color: #6c757d;\n    pointer-events: none; }\n\n.btn-lg {\n  padding: 0.5rem 1rem;\n  font-size: 1.25rem;\n  line-height: 1.5;\n  border-radius: 0.3rem; }\n\n.btn-sm {\n  padding: 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  line-height: 1.5;\n  border-radius: 0.2rem; }\n\n.btn-block {\n  display: block;\n  width: 100%; }\n  .btn-block + .btn-block {\n    margin-top: 0.5rem; }\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%; }\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/borderchoice.css",
    "content": ".cascade-border-choice em {\n  margin-left: 0.5em;\n  margin-right: 0.5em;\n}\n.cascade-border-choice .a-color-picker {\n  position: absolute;\n  margin-top: -20px;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/cascadepage.css",
    "content": ".field-menu_symbol h2 {\n\tmargin: 16px 0 8px;\n}\n.field-menu_symbol ul.font-family {\n\toverflow: hidden;\n\tline-height: 42px;\n\tpadding-top: 8px;\n\tpadding-bottom: 8px;\n}\n.field-menu_symbol ul.font-family li {\n\tfont-size: 24px;\n\tbox-sizing: border-box;\n\tdisplay: inline;\n\tmin-width: initial;\n\theight: 38px;\n\twidth: auto;\n\tmargin: 0;\n\tborder: 2px solid transparent;\n\tborder-radius: 50%;\n\tpadding: 2px;\n}\n.field-menu_symbol ul.font-family li.selected {\n\tbackground-color: #cccc00;\n}\n.field-menu_symbol ul.font-family li:hover {\n\tborder-color: #880000;\n}\n.field-menu_symbol ul.font-family li > i {\n\ttext-align: center;\n\tvertical-align: middle;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/clipboard.css",
    "content": ".pull-left {\n\tfloat: left !important;\n}\n.clip-btn img {\n\t height: 20px;\n}\n.status-line {\n\theight: 30px;\n}\n.status-line strong {\n\tbackground-color: #dff0d8;\n\tpadding: 5px 10px;\n}\n\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=import]:before,\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=export]:before {\n\tposition: absolute;\n\tcontent: '';\n\tdisplay: block !important;\n\twidth: 16px !important;\n\theight: 16px !important;\n\tbackground-image: url('../../admin/import-export.png') !important;\n\tbackground-size: 16px 64px !important;\n\ttop: 15px !important;\n\toverflow: hidden !important;\n\tline-height: 16px !important;\n\tmin-height: 16px !important;\n}\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=import]:before {\n\tbackground-position: 0 -32px;\n}\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=export]:hover:before,\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=export]:focus:before {\n\tbackground-position: 0 -16px;\n}\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=import]:hover:before,\ndiv.cms .cms-structure .cms-submenu-item a[data-icon=import]:focus:before {\n\tbackground-position: 0 -48px;\n}\ndiv.cms .cms-structure.cms-structure-condensed .cms-submenu-item a[data-icon=export]:before,\ndiv.cms .cms-structure.cms-structure-condensed .cms-submenu-item a[data-icon=import]:before {\n\ttop: 12px !important;\n}"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/colorpicker.css",
    "content": ".cascade-colorpicker .a-color-picker label {\n  width: initial;\n}\n.cascade-colorpicker input[type=\"color\"] {\n  padding: 0;\n  margin-left: -160px;\n  margin-right: 130px;\n  width: 30px;\n  height: 30px;\n  border: none;\n}\n.cascade-colorpicker input[type=\"text\"] {\n  text-align: right;\n}\n.cascade-colorpicker input[type=\"text\"].disabled {\n  color: #888;\n  border-style: dashed;\n}\n\n.cascade-color-picker em {\n  margin-left: 1em;\n  margin-right: 1em;\n}\n.cascade-color-picker .a-color-picker {\n  position: absolute;\n  margin-top: -20px;\n  margin-left: -60px;\n  z-index: 1;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/editplugin.css",
    "content": "form input:disabled, form textarea:disabled, form select:disabled { color: #888; border-style: dashed; }\nform .form-row.field-link_type { min-height: 30px; }\nform .form-row.field-link_type { border-bottom: none; }\nform .form-row.field-link_type.no-link { border-bottom: 1px solid #eee; }\nform .form-row.field-link_type input { margin-top: 0; }\nform .form-row.field-link_type .field-link_type .help { margin-left: 160px; }\nform .form-row.field-cms_page { height: 75px; width: 565px; float: left; }\nform .form-row.field-section { height: 75px; }\nform .form-row.field-section::after { content: \".\"; display: block; clear: both; visibility: hidden; height: 0; }\nform .field-download_file { width: 100%; }\nform  label.inline { text-align: right; }\nform .field-glossary { padding: 0 10px; }\n\nform .container-thumbnail img { display: inline-block; }\nform .container-thumbnail .label { display: inline; }\n\nform .form-row.field-save_settings_as input[type=\"checkbox\"] {\n  margin-right: 2em;\n}\n\nform label[for=id_section] { width: 0; }\n#id_section + .help { margin-left: 0; }\n#id_ext_url, #id_mail_to { min-width: 350px !important;}\n.field-box > .select2-container, .fieldBox > .select2-container { min-width: 350px; }\n.field-box.field-link_type, .fieldBox.field-link_type { float: none; margin-bottom: 15px; }\n\n#id_condition, #id_link_content { width: calc(100% - 20px); }\n\nform .form-row .select2-selection { height: 36px; }\nform .form-row .django-select2 {\n  width: calc(100% - 200px) !important;\n}\n\nform .form-row .select2.select2-container {\n  min-width: 150px;\n}\n\nform .aligned .vCheckboxLabel {\n  float: left;\n  width: 160px;\n  margin-right: 10px;\n  padding-left: 0;\n}\nform .aligned label + div.help {\n  margin-left: 160px;\n  padding-left: 10px;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/iconfont.css",
    "content": ".preview-iconfont h2 {\n\tmargin: 16px 0 8px;\n}\n.preview-iconfont ul {\n\tline-height: 42px;\n}\n.preview-iconfont ul li {\n\tbox-sizing: border-box;\n\tdisplay: inline;\n\tfont-size: 28px;\n\theight: 48px;\n\tmargin: 0 8px 8px 0;\n\tborder: 2px solid transparent;\n\tborder-radius: 50%;\n}\n.preview-iconfont ul li:hover {\n\tborder-color: #880000;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/iconplugin.css",
    "content": ".form-row.field-symbol h2 {\n\tmargin: 16px 0 8px;\n}\n.form-row.field-symbol ul.font-family {\n\toverflow: hidden;\n\tline-height: 42px;\n}\n.form-row.field-symbol ul.font-family li {\n\tfont-size: 24px;\n\tbox-sizing: border-box;\n\tdisplay: inline;\n\tmin-width: initial;\n\theight: 38px;\n\twidth: auto;\n\tmargin: 0;\n\tborder: 2px solid transparent;\n\tborder-radius: 50%;\n\tpadding: 2px;\n}\n.form-row.field-symbol ul.font-family li.selected {\n\tbackground-color: #cccc00;\n}\n.form-row.field-symbol ul.font-family li:hover {\n\tborder-color: #880000;\n}\n.form-row.field-symbol ul.font-family li > i {\n\ttext-align: center;\n\tvertical-align: middle;\n}\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/leafletplugin.css",
    "content": "#leaflet_edit_map {\n\twidth: 100%;\n\theight: 500px;\n\tmargin-bottom: 20px;\n}\n.map-button {\n\tfont-size: 18px;\n\tline-height: 18px;\n\tmargin-left: -3px;\n}\n.disable-click {\n\tpointer-events: none;\n\topacity: 0.3;\n}\n.field-marker_anchor ul { margin-left: 0; padding-left: 0; }\n.field-marker_anchor ul li { list-style: none; float: left; min-width: 95px; }\n.field-marker_anchor ul.errorlist li { float: none; }\n.field-marker_anchor div.sibling-field { float: left;  margin-left: 20px; width: 9em; }\n.field-marker_anchor div.sibling-field:first-child { margin-left: 0; }\n.field-marker_anchor div.sibling-field label { padding-left: 5px; }\n.field-marker_anchor div.sibling-field input[type=\"text\"] { display: block; width: 8em; margin: 0; }\n.field-marker_anchor h4 { margin-top: 10px !important; margin-bottom: 10px !important; }\n.field-address_lookup ul li { cursor: pointer; color: var(--link-fg); }\n.field-address_lookup ul li:hover { text-decoration: underline; background-color: var(--darkened-bg); }"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/linkplugin.css",
    "content": "#id_ext_url {\n  background-position-x: calc(100% - 8px);\n  background-position-y: 50%;\n  background-clip: padding-box;\n  background-size: 24px;\n  background-repeat: no-repeat;\n}\n#id_ext_url.valid {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"green\" width=\"16\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z\"/></svg>') !important;\n}\n#id_ext_url.invalid {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"darkorange\" width=\"16\" height=\"16\"><path d=\"M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.146.146 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.163.163 0 0 1-.054.06.116.116 0 0 1-.066.017H1.146a.115.115 0 0 1-.066-.017.163.163 0 0 1-.054-.06.176.176 0 0 1 .002-.183L7.884 2.073a.147.147 0 0 1 .054-.057zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z\"/><path d=\"M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z\"/></svg>');\n}"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/css/admin/partialfields.css",
    "content": "/*\nform .form-row { border-bottom: 1px solid #f3f3f3; padding: 8px 0; }\nform .form-row .form-row { border-bottom: none; }\nform .form-row:after { content: \".\"; clear: both; display: block; visibility: hidden; height: 0px; }\nform .form-row:last-child { border-bottom: none; }\nform .form-row h1 { clear: both; margin: 0 0 4px; font-size: 120%; }\nform .form-row label { width: inherit; padding-left: 10px; }\nform .form-row label.inline { height: 20px; }\nform .form-row small { margin-left: 0; }\nform .form-row .disabled { color: #aaa; }\nform .form-row ul { margin-left: 0; padding-left: 0; }\nform .form-row ul li { list-style: none; float: left; min-width: 95px; }\nform .form-row ul.errorlist li { float: none; }\n*/\nform .form-row div.sibling-field { float: left;  margin-left: 20px; width: 8em; }\nform .form-row div.sibling-field:first-child { margin-left: 0; }\nform .form-row div.sibling-field label { padding-left: 5px; }\n/*form .form-row div.sibling-field.inherit { margin-left: 60px; }*/\n.form-row div.sibling-field input[type=\"text\"], .form-row div.sibling-field input[type=\"color\"] { display: block; width: 7.5em; margin: 0; }\n/*\nform .form-row h4 { margin-top: 10px !important; margin-bottom: 10px !important; }\nform .form-row input[type=\"radio\"], form .form-row input[type=\"checkbox\"] { margin: 0 4px 0 0; display: inline-block; width: 2em; }\nform .glossary-field { float: left; margin-right: 10px; }\nform .glossary-box { zoom:1; }\nform .glossary-box:after { content: \"\"; display: table; clear: both; }\nform .glossary-field.glossary_content { width: 100%; }\nform .aligned label[for=\"id_css_classes_0\"] { display: none; }\nform .aligned label[for=\"id_inline_styles_0\"] { display: none; }\n*/"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/buttonmixin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\t// create class handling the client-side part of ButtonPlugin\n\tvar $glossary_icon_align = $('#id_glossary_icon_align').find('input[name=\"icon_align\"]'),\n\t    $glossary_icon_font = $('.glossary_icon_font').parent('.glossary-widget'),\n\t    $glossary_symbol = $('.glossary_symbol').parent('.glossary-widget');\n\n\tdjango.cascade.ButtonMixin = ring.create(eval(django.cascade.ring_plugin_bases.ButtonMixin), {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.$super();\n\t\t\tif (django.cascade.hasOwnProperty('IconPluginMixin')) {\n\t\t\t\t$glossary_icon_align.change(function(evt) {\n\t\t\t\t\tself.toggleAlignIcon(evt.target.value);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t$glossary_icon_align.prop('disabled', true);\n\t\t\t}\n\t\t\tthis.refreshChangeForm();\n\t\t},\n\t\ttoggleAlignIcon: function(iconAlign) {\n\t\t\tif (iconAlign === '') {\n\t\t\t\t$glossary_icon_font.hide();\n\t\t\t\t$glossary_symbol.hide();\n\t\t\t} else {\n\t\t\t\t$glossary_icon_font.show();\n\t\t\t\t$glossary_symbol.show();\n\t\t\t}\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tvar value;\n\t\t\t$.each($glossary_icon_align, function(index, field) {\n\t\t\t\tif (field.checked) {\n\t\t\t\t\tvalue = field.value;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.toggleAlignIcon(value);\n\t\t\tthis.$super && this.$super();\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/buttonplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\tdjango.cascade.ButtonPlugin = ring.create(eval(django.cascade.ring_plugin_bases.ButtonPlugin), {});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/cascadepage.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\tvar $selectIconFont = $('#id_icon_font'),\n\t    $symbol = $('#id_menu_symbol'),\n\t    $box = $symbol.closest('.field-menu_symbol');\n\n\t$selectIconFont.change(fontChanged);\n\tfontChanged();\n\t$('label[for=\"id_menu_symbol\"]').remove();\n\t$box.removeClass('hidden');\n\t$box.on('click', 'ul.font-family li', selectIcon);\n\n\tfunction fontChanged() {\n\t\tvar link;\n\t\tif ($selectIconFont.length === 0)\n\t\t\treturn;\n\t\tlink = {\n\t\t\tid: \"id_iconfont_link\",\n\t\t\trel: \"stylesheet\",\n\t\t\ttype: \"text/css\",\n\t\t\thref: django.cascade.iconfont_stylesheet_urls[$selectIconFont.val()]\n\t\t};\n\t\t$('#id_iconfont_link').remove();\n\t\t$(\"<link/>\", link).appendTo(\"head\");\n\n\t\tif ($selectIconFont.val()) {\n\t\t\t$.get(django.cascade.fetch_fonticons_url + $selectIconFont.val()).done(renderIcons);\n\t\t}\n\t}\n\n\tfunction selectIcon() {\n\t\t$box.find('ul.font-family li.selected').removeClass('selected');\n\t\t$(this).addClass('selected');\n\t\t$symbol.val($(this).attr('title'));\n\t}\n\n\tfunction renderIcons(response) {\n\t\tvar css_prefix_text = response.css_prefix_text;\n\t\t$box.find('label[for=\"query\"], h2, ul').remove();\n\t\t$('#fonticon_search_query').remove();\n\t\t$.each(response.families, function(key, icons) {\n\t\t\tvar lis = [];\n\t\t\t$symbol.before('<h2>' + key + '</h2>');\n\t\t\t$symbol.before('<label for=\"query\">Search Icon:</label><input id=\"fonticon_search_query\" type=\"text\" name=\"query\">');\n\t\t\t$.each(icons, function(idx, icon) {\n\t\t\t\tlis.push('<li title=\"' + icon + '\"><i class=\"' + css_prefix_text + icon + '\"></i></li>');\n\t\t\t});\n\t\t\t$symbol.before('<ul id=\"fonticon_symbols\" class=\"font-family\">' + lis.join('') + '</ul>');\n\t\t\t$('#fonticon_search_query').on('keyup paste', function(event) {\n\t\t\t\tvar fonticon_symbols = $('#fonticon_symbols').find('li'), re;\n\t\t\t\tif (event.target.value) {\n\t\t\t\t\tre = new RegExp(event.target.value, 'i');\n\t\t\t\t\tfonticon_symbols.each(function() {\n\t\t\t\t\t\tvar cssClass = $(this).children('i').attr('class');\n\t\t\t\t\t\tif (cssClass.substring(css_prefix_text.length).match(re)) {\n\t\t\t\t\t\t\t$(this).show();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$(this).hide();\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tfonticon_symbols.show();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\t// mark selected icon\n\t\t$box.find('ul.font-family li.selected').removeClass('selected');\n\t\tif ($symbol.val()) {\n\t\t\t$box.find('ul.font-family li[title=' + $symbol.val() + ']').addClass('selected');\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/clipboard.js",
    "content": "/*!\n * clipboard.js v1.5.5\n * https://zenorocha.github.io/clipboard.js\n *\n * Licensed MIT © Zeno Rocha\n */\n!function(t){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=t();else if(\"function\"==typeof define&&define.amd)define([],t);else{var e;e=\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s=\"function\"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error(\"Cannot find module '\"+a+\"'\");throw u.code=\"MODULE_NOT_FOUND\",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i=\"function\"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,e,n){var r=t(\"matches-selector\");e.exports=function(t,e,n){for(var o=n?t:t.parentNode;o&&o!==document;){if(r(o,e))return o;o=o.parentNode}}},{\"matches-selector\":2}],2:[function(t,e,n){function r(t,e){if(i)return i.call(t,e);for(var n=t.parentNode.querySelectorAll(e),r=0;r<n.length;++r)if(n[r]==t)return!0;return!1}var o=Element.prototype,i=o.matchesSelector||o.webkitMatchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector;e.exports=r},{}],3:[function(t,e,n){function r(t,e,n,r){var i=o.apply(this,arguments);return t.addEventListener(n,i),{destroy:function(){t.removeEventListener(n,i)}}}function o(t,e,n,r){return function(n){n.delegateTarget=i(n.target,e,!0),n.delegateTarget&&r.call(t,n)}}var i=t(\"closest\");e.exports=r},{closest:1}],4:[function(t,e,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&(\"[object NodeList]\"===e||\"[object HTMLCollection]\"===e)&&\"length\"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return\"string\"==typeof t||t instanceof String},n.function=function(t){var e=Object.prototype.toString.call(t);return\"[object Function]\"===e}},{}],5:[function(t,e,n){function r(t,e,n){if(!t&&!e&&!n)throw new Error(\"Missing required arguments\");if(!c.string(e))throw new TypeError(\"Second argument must be a String\");if(!c.function(n))throw new TypeError(\"Third argument must be a Function\");if(c.node(t))return o(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError(\"First argument must be a String, HTMLElement, HTMLCollection, or NodeList\")}function o(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return s(document.body,t,e,n)}var c=t(\"./is\"),s=t(\"delegate\");e.exports=r},{\"./is\":4,delegate:3}],6:[function(t,e,n){function r(t){var e;if(\"INPUT\"===t.nodeName||\"TEXTAREA\"===t.nodeName)t.focus(),t.setSelectionRange(0,t.value.length),e=t.value;else{t.hasAttribute(\"contenteditable\")&&t.focus();var n=window.getSelection(),r=document.createRange();r.selectNodeContents(t),n.removeAllRanges(),n.addRange(r),e=n.toString()}return e}e.exports=r},{}],7:[function(t,e,n){function r(){}r.prototype={on:function(t,e,n){var r=this.e||(this.e={});return(r[t]||(r[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function r(){o.off(t,r),e.apply(n,arguments)}var o=this;return r._=e,this.on(t,r,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),r=0,o=n.length;for(r;o>r;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),a=t(\"select\"),c=r(a),s=function(){function t(e){o(this,t),this.resolveOptions(e),this.initSelection()}return t.prototype.resolveOptions=function t(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action=e.action,this.emitter=e.emitter,this.target=e.target,this.text=e.text,this.trigger=e.trigger,this.selectedText=\"\"},t.prototype.initSelection=function t(){if(this.text&&this.target)throw new Error('Multiple attributes declared, use either \"target\" or \"text\"');if(this.text)this.selectFake();else{if(!this.target)throw new Error('Missing required attributes, use either \"target\" or \"text\"');this.selectTarget()}},t.prototype.selectFake=function t(){var e=this;this.removeFake(),this.fakeHandler=document.body.addEventListener(\"click\",function(){return e.removeFake()}),this.fakeElem=document.createElement(\"textarea\"),this.fakeElem.style.position=\"absolute\",this.fakeElem.style.left=\"-9999px\",this.fakeElem.style.top=(window.pageYOffset||document.documentElement.scrollTop)+\"px\",this.fakeElem.setAttribute(\"readonly\",\"\"),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=c.default(this.fakeElem),this.copyText()},t.prototype.removeFake=function t(){this.fakeHandler&&(document.body.removeEventListener(\"click\"),this.fakeHandler=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)},t.prototype.selectTarget=function t(){this.selectedText=c.default(this.target),this.copyText()},t.prototype.copyText=function t(){var e=void 0;try{e=document.execCommand(this.action)}catch(n){e=!1}this.handleResult(e)},t.prototype.handleResult=function t(e){e?this.emitter.emit(\"success\",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)}):this.emitter.emit(\"error\",{action:this.action,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})},t.prototype.clearSelection=function t(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()},t.prototype.destroy=function t(){this.removeFake()},i(t,[{key:\"action\",set:function t(){var e=arguments.length<=0||void 0===arguments[0]?\"copy\":arguments[0];if(this._action=e,\"copy\"!==this._action&&\"cut\"!==this._action)throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"')},get:function t(){return this._action}},{key:\"target\",set:function t(e){if(void 0!==e){if(!e||\"object\"!=typeof e||1!==e.nodeType)throw new Error('Invalid \"target\" value, use a valid Element');this._target=e}},get:function t(){return this._target}}]),t}();n.default=s,e.exports=n.default},{select:6}],9:[function(t,e,n){\"use strict\";function r(t){return t&&t.__esModule?t:{\"default\":t}}function o(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}function i(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){var n=\"data-clipboard-\"+t;if(e.hasAttribute(n))return e.getAttribute(n)}n.__esModule=!0;var c=t(\"./clipboard-action\"),s=r(c),u=t(\"tiny-emitter\"),l=r(u),f=t(\"good-listener\"),d=r(f),h=function(t){function e(n,r){o(this,e),t.call(this),this.resolveOptions(r),this.listenClick(n)}return i(e,t),e.prototype.resolveOptions=function t(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action=\"function\"==typeof e.action?e.action:this.defaultAction,this.target=\"function\"==typeof e.target?e.target:this.defaultTarget,this.text=\"function\"==typeof e.text?e.text:this.defaultText},e.prototype.listenClick=function t(e){var n=this;this.listener=d.default(e,\"click\",function(t){return n.onClick(t)})},e.prototype.onClick=function t(e){var n=e.delegateTarget||e.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})},e.prototype.defaultAction=function t(e){return a(\"action\",e)},e.prototype.defaultTarget=function t(e){var n=a(\"target\",e);return n?document.querySelector(n):void 0},e.prototype.defaultText=function t(e){return a(\"text\",e)},e.prototype.destroy=function t(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)},e}(l.default);n.default=h,e.exports=n.default},{\"./clipboard-action\":8,\"good-listener\":5,\"tiny-emitter\":7}]},{},[9])(9)});\n\ndjango.jQuery(function($) {\n\t'use strict';\n\n\t// initialize paste to clipboard\n\tnew Clipboard('.clip-btn');\n\n\t// handle paste from clipboard\n\tvar $pasted_success = $('#pasted_success'), $copied_success = $('#copied_success');\n\t$pasted_success.fadeOut(0);\n\t$copied_success.fadeOut(0);\n\n\t$('#id_data').on('paste', function(e) {\n\t\tvar json_data;\n\t\te.preventDefault();\n\t\tjson_data = (e.originalEvent || e).clipboardData.getData('text/plain');\n\t\ttry {\n\t\t\tjson_data = JSON.parse(json_data);\n\t\t} catch (err) {\n\t\t\talert(\"Pasted data does not seem to be valid JSON\");\n\t\t\treturn;\n\t\t}\n\t\t$(this).val(JSON.stringify(json_data, null, 2));\n\t\t$pasted_success.fadeIn(100).delay(2500).fadeOut(400);\n\t});\n\n\t$('.clip-btn').on('click', function(e) {\n\t\t$copied_success.fadeIn(100).delay(2500).fadeOut(400);\n\t});\n\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/colorpicker.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\tvar picker = null, AColorPicker = window.AColorPicker;\n\n\tfunction checkboxChanged(checkboxInput) {\n\t\tvar $rgbaInput = $(checkboxInput).siblings('input.cascade-rgba');\n\t\tvar $colorInput = $(checkboxInput).siblings('input[type=\"color\"]');\n\t\tif (checkboxInput.checked || checkboxInput.disabled) {\n\t\t\t$rgbaInput.addClass('disabled');\n\t\t\tif (!AColorPicker) {\n\t\t\t\t$colorInput.addClass('disabled');\n\t\t\t\t}\n\t\t} else {\n\t\t\t$rgbaInput.removeClass('disabled');\n\t\t\tif (!AColorPicker) {\n\t\t\t\t$colorInput.removeClass('disabled');\n\t\t\t\t}\n\t\t}\n\t}\n\n\tif (AColorPicker) {\n\t\t$('.cascade-colorpicker > input.cascade-rgba').on('click', function(evt) {\n\t\t\tvar $this = $(this);\n\t\t\tvar $colorInput = $this.siblings('input[type=\"color\"]');\n\t\t\tvar options = {color: $this.val(), showAlpha: true};\n\t\t\tif (picker) {\n\t\t\t\tpicker.destroy();\n\t\t\t}\n\t\t\tif ($this.hasClass('disabled')) {\n\t\t\t\t$this.blur();\n\t\t\t} else {\n\t\t\t\tpicker = AColorPicker.createPicker($this.parent(), options);\n\t\t\t \t$('.a-color-picker input[type=\"number\"]').css(\"cssText\", \"padding: 2px 0 !important;\");\n\t\t\t\tpicker.on('change', function(picker, color) {\n\t\t\t\t\t$colorInput.val(AColorPicker.parseColor(color, 'hex'));\n\t\t\t\t\t$this.val(color);\n\t\t\t\t});\n\t\t\t}\n\t\t\tevt.preventDefault();\n\t\t});\n\t\t$('.cascade-colorpicker > input[type=\"color\"]').on('click', function(evt) {\n\t\t\tevt.preventDefault();\n\t\t});\n\t} else {\n\t\t$('.cascade-colorpicker > input.cascade-rgba').on('focus', function(evt) {\n\t\t\tvar $this = $(this);\n\t\t\t$this.blur();\n\t\t\tif (!$this.hasClass('disabled')) {\n\t\t\t\t$this.siblings('input[type=\"color\"]').trigger('click');\n\t\t\t}\n\t\t});\n\t\t$('.cascade-colorpicker > input[type=\"color\"]').on('change', function(evt) {\n\t\t\tvar $rgbaInput = $(this).siblings('input.cascade-rgba');\n\t\t\t$rgbaInput.val($(this).val());\n\t\t});\n\t}\n\n\t$(document).on('click', function(evt) {\n\t\tif (picker && $(evt.target).closest('.cascade-colorpicker').length === 0) {\n\t\t\tpicker.destroy();\n\t\t\tpicker = null;\n\t\t}\n\t});\n\n\t$('.cascade-colorpicker > input[type=\"checkbox\"]').on('change', function(evt) {\n\t\tcheckboxChanged(evt.target);\n\t});\n\t$.each($('.cascade-colorpicker > input[type=\"checkbox\"]'), function() {\n\t\tcheckboxChanged(this);\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/framediconplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\tvar $glossary_icon_align = $('#id_glossary_text_align').find('input[name=\"text_align\"]'),\n\t    $glossary_font_size = $('#id_glossary_font_size').closest('.glossary-widget');\n\n\t// create class handling the client-side part of a FramedIconPlugin\n\tdjango.cascade.FramedIconPlugin = ring.create(eval(django.cascade.ring_plugin_bases.FramedIconPlugin), {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.$super();\n\n\t\t\t$glossary_icon_align.change(function(evt) {\n\t\t\t\tself.toggleAlignIcon(evt.target.value);\n\t\t\t});\n\t\t},\n\t\ttoggleAlignIcon: function(iconAlign) {\n\t\t\tif (iconAlign === '') {\n\t\t\t\t$glossary_font_size.hide();\n\t\t\t} else {\n\t\t\t\t$glossary_font_size.show();\n\t\t\t}\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tvar value;\n\t\t\t$.each($glossary_icon_align, function(index, field) {\n\t\t\t\tif (field.checked) {\n\t\t\t\t\tvalue = field.value;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.toggleAlignIcon(value);\n\t\t\tthis.$super && this.$super();\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/iconplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\tdjango.cascade.IconPlugin = ring.create(eval(django.cascade.ring_plugin_bases.IconPlugin), {});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/iconpluginmixin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\t// create class handling the client-side part of plugins inheriting from IconPluginMixin\n\tvar $selectIconFont = $('#id_icon_font'),\n\t    $symbol = $('#id_symbol'),\n\t    $box = $symbol.closest('.form-row.field-symbol');\n\n\tdjango.cascade.IconPluginMixin = ring.create(eval(django.cascade.ring_plugin_bases.IconPluginMixin), {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tif ($selectIconFont.length === 0)\n\t\t\t\treturn;\n\t\t\tthis.$super();\n\n\t\t\t// install event handlers\n\t\t\t$selectIconFont.on('change', function() {\n\t\t\t\tself.fontChanged();\n\t\t\t});\n\t\t\t$box.removeClass('hidden');\n\t\t\t$box.on('click', 'ul.font-family li', this.selectIcon);\n\n\t\t\t// set defaults\n\t\t\tthis.refreshChangeForm();\n\t\t},\n\t\tfontChanged: function() {\n\t\t\tvar link;\n\t\t\tif ($selectIconFont.length === 0)\n\t\t\t\treturn;\n\t\t\t$('#id_iconfont_link').remove();\n\t\t\tif ($selectIconFont.val()) {\n\t\t\t\tlink = {\n\t\t\t\t\tid: \"id_iconfont_link\",\n\t\t\t\t\trel: \"stylesheet\",\n\t\t\t\t\ttype: \"text/css\",\n\t\t\t\t\thref: django.cascade.iconfont_stylesheet_urls[$selectIconFont.val()]\n\t\t\t\t};\n\t\t\t\t$(\"<link/>\", link).appendTo(\"head\");\n\n\t\t\t\t$('.form-row.field-symbol').show();\n\t\t\t\t$.get(django.cascade.fetch_fonticons_url + $selectIconFont.val()).done(this.renderIcons);\n\t\t\t} else {\n\t\t\t\t$('.form-row.field-symbol').hide();\n\t\t\t}\n\t\t},\n\t\tselectIcon: function() {\n\t\t\t$box.find('ul.font-family li.selected').removeClass('selected');\n\t\t\t$(this).addClass('selected');\n\t\t\t$symbol.val($(this).attr('title'));\n\t\t},\n\t\trenderIcons: function(response) {\n\t\t\tvar css_prefix_text = response.css_prefix_text;\n\t\t\t$box.find('label[for=\"query\"], h2, ul:not(.errorlist)').remove();\n\t\t\t$('#fonticon_search_query').remove();\n\t\t\t$.each(response.families, function(key, icons) {\n\t\t\t\tvar lis = [];\n\t\t\t\t$symbol.before('<h2>' + key + '</h2>');\n\t\t\t\t$symbol.before('<label for=\"query\">Search Icon:</label><input id=\"fonticon_search_query\" type=\"text\" name=\"query\">');\n\t\t\t\t$.each(icons, function(idx, icon) {\n\t\t\t\t\tlis.push('<li title=\"' + icon + '\"><i class=\"' + css_prefix_text + icon + '\"></i></li>');\n\t\t\t\t});\n\t\t\t\t$symbol.before('<ul id=\"fonticon_symbols\" class=\"font-family\">' + lis.join('') + '</ul>');\n\t\t\t\t$('#fonticon_search_query').on('keyup paste', function(event) {\n\t\t\t\t\tvar fonticon_symbols = $('#fonticon_symbols').find('li'), re;\n\t\t\t\t\tif (event.target.value) {\n\t\t\t\t\t\tre = new RegExp(event.target.value, 'i');\n\t\t\t\t\t\tfonticon_symbols.each(function() {\n\t\t\t\t\t\t\tvar cssClass = $(this).children('i').attr('class');\n\t\t\t\t\t\t\tif (cssClass.substring(css_prefix_text.length).match(re)) {\n\t\t\t\t\t\t\t\t$(this).show();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$(this).hide();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfonticon_symbols.show();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// mark selected icon\n\t\t\t$box.find('ul.font-family li.selected').removeClass('selected');\n\t\t\tif ($symbol.val()) {\n\t\t\t\t$box.find('ul.font-family li[title=' + $symbol.val() + ']').addClass('selected');\n\t\t\t}\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tthis.fontChanged();\n\t\t\tthis.$super && this.$super();\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/imageplugin.js",
    "content": "\ndjango.jQuery(function($) {\n\t'use strict';\n\n\tvar $image_responsive = $('#id_image_shapes_0');\n\n\t// create class handling the client-side part of ImagePlugin\n\tdjango.cascade.ImagePlugin = ring.create(eval(django.cascade.ring_plugin_bases.ImagePlugin), {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.$super();\n\n\t\t\t// be more intuitive, reorganize layout by moving 'Link Target'\n\t\t\t// $('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));\n\n\t\t\t// install event handlers\n\t\t\t$image_responsive.change(function(evt) {\n\t\t\t\tself.toggleResponsive(evt.target.checked);\n\t\t\t});\n\t\t\tthis.refreshChangeForm();\n\t\t},\n\t\ttoggleResponsive: function(checked) {\n\t\t\tvar $image_width_responsive = $('.form-row.field-image_width_responsive'),\n\t\t\t\t$image_width_fixed = $('.form-row.field-image_width_fixed'),\n\t\t\t\t$image_alignment = $('.form-row.field-image_alignment');\n\n\t\t\t// if checkbox Image Shapes: Responsive is active, show adaptive width and heights\n\t\t\tif (checked) {\n\t\t\t\t$image_width_responsive.show();\n\t\t\t\t$image_width_fixed.hide();\n\t\t\t\t$image_alignment.hide();\n\t\t\t} else {\n\t\t\t\t$image_width_responsive.hide();\n\t\t\t\t$image_width_fixed.show();\n\t\t\t\t$image_alignment.show();\n\t\t\t}\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tthis.toggleResponsive($image_responsive.prop('checked'));\n\t\t\tthis.$super && this.$super();\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/jumbotronplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\t// create class handling the client-side part of JumbotronPlugin\n\tvar base_plugins = eval(django.cascade.ring_plugin_bases.JumbotronPlugin),\n\t    $fileIdInputSelector = $('#id_image_file'),\n\t    $backgroundInputSize = $('input[name=\"background_size\"]'),\n\t    $backgroundWidthHeight = $('.form-row.field-background_width_height');\n\n\tdjango.cascade.JumbotronPlugin = ring.create(base_plugins, {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.$super();\n\n\t\t\t// install event handlers\n\t\t\t$fileIdInputSelector.on('change', function() {\n\t\t\t\twindow.setTimeout(self.fileIdInputChanged);\n\t\t\t});\n\t\t\t$backgroundInputSize.on('change', self.backgroundInputSizeChanged);\n\n\t\t\t// set defaults\n\t\t\tthis.refreshChangeForm();\n\t\t},\n\t\tfileIdInputChanged: function () {\n\t\t\tvar $backgroundHorizontalPosition = $('#id_background_horizontal_position'),\n\t\t\t    $backgroundVerticalPosition = $('#id_background_vertical_position'),\n\t\t\t    $backgroundAttachment = $('input[name=\"background_attachment\"]'),\n\t\t\t    $backgroundRepeat = $('input[name=\"background_repeat\"]');\n\t\t\tif ($fileIdInputSelector.val()) {\n\t\t\t\t$backgroundHorizontalPosition.prop('disabled', false);\n\t\t\t\t$backgroundVerticalPosition.prop('disabled', false);\n\t\t\t\t$backgroundAttachment.prop('disabled', false);\n\t\t\t\t$backgroundRepeat.prop('disabled', false);\n\t\t\t\t$backgroundInputSize.prop('disabled', false);\n\t\t\t\t$backgroundWidthHeight.find('input').prop('disabled', false);\n\t\t\t} else {\n\t\t\t\t$backgroundHorizontalPosition.prop('disabled', true);\n\t\t\t\t$backgroundVerticalPosition.prop('disabled', true);\n\t\t\t\t$backgroundAttachment.prop('disabled', true);\n\t\t\t\t$backgroundRepeat.prop('disabled', true);\n\t\t\t\t$backgroundInputSize.prop('disabled', true);\n\t\t\t\t$backgroundWidthHeight.find('input').prop('disabled', true);\n\t\t\t}\n\t\t},\n\t\tbackgroundInputSizeChanged: function() {\n\t\t\tvar $inputField = $('input[name=\"background_size\"]:checked');\n\t\t\tif ($inputField.val() === 'width/height') {\n\t\t\t\t$backgroundWidthHeight.show();\n\t\t\t} else {\n\t\t\t\t$backgroundWidthHeight.hide();\n\t\t\t}\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tthis.fileIdInputChanged();\n\t\t\tthis.backgroundInputSizeChanged();\n\t\t\tthis.$super && this.$super();\n\t\t}\n\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/leafletplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\n\tdjango.cascade.LeafletPlugin = ring.create(eval(django.cascade.ring_plugin_bases.LeafletPlugin), {\n\t\tconstructor: function() {\n\t\t\tthis.$super();\n\t\t\tthis.mapPosition = JSON.parse($('#id_map_position').val());\n\t\t\tthis.startPosition = $.extend({}, this.mapPosition);\n\t\t\tthis.editMap = L.map('leaflet_edit_map', {scrollWheelZoom: false});\n\t\t\tL.tileLayer(\n\t\t\t\tdjango.cascade.leaflet_settings.tilesURL,\n\t\t\t\tdjango.cascade.leaflet_settings\n\t\t\t).addTo(this.editMap);\n\t\t\tif (django.cascade.leaflet_settings.defaultMarkerIcon) {\n\t\t\t\tL.Marker.prototype.options.icon = L.icon(django.cascade.leaflet_settings.defaultMarkerIcon);\n\t\t\t}\n\t\t\tL.easyButton('<span class=\"map-button\" title=\"Center\">&target;</span>', this.resetCenter, this).addTo(this.editMap);\n\t\t\tthis.resetCenter();\n\t\t\tthis.setMarkers();\n\t\t\t$('#inline_elements-group .add-row a').on('click', this, this.addInlineElement);\n\t\t\t$('#inline_elements-group .field-use_icon input').on('change', this, this.changeUseIcon);\n\t\t\t$.each($('#inline_elements-group .inline-related'), this.changeUseIcon);\n\t\t\t$('#inline_elements-group .field-address_lookup input').on('blur keypress', this, this.addressLookup);\n\t\t\tthis.editMap.on('drag zoom', this.onMapDrag, this);\n\t\t},\n\t\tresetCenter: function(event) {\n\t\t\tvar self = event ? event.options : this;\n\t\t\tself.editMap.setView([self.startPosition.lat, self.startPosition.lng], self.startPosition.zoom);\n\t\t},\n\t\tonMapDrag: function(evt) {\n\t\t\t$.extend(\n\t\t\t\tthis.mapPosition,\n\t\t\t\tthis.editMap.getCenter(),\n\t\t\t\t{zoom: this.editMap.getZoom()}\n\t\t\t);\n\t\t\t$('#id_map_position').val(JSON.stringify(this.mapPosition));\n\t\t},\n\t\tsetMarkers: function() {\n\t\t\tvar self = this;\n\t\t\t$.each($('#inline_elements-group .inline-related.has_original'), function(index) {\n\t\t\t\tvar title = $(this).find('.field-title input').val();\n\t\t\t\tvar inputField = $(this).find('.field-position input');\n\t\t\t\tvar marker = L.marker(JSON.parse(inputField.val()), {draggable: true, markerId: index});\n\t\t\t\tmarker.addTo(self.editMap);\n\t\t\t\tmarker.bindTooltip(title);\n\t\t\t\tmarker.on('dragend', self.dragMarker, inputField);\n\t\t\t});\n\t\t},\n\t\taddMarker: function(latlng, markerId) {\n\t\t\tvar element = $('#inline_elements-group .inline-related.last-related.dynamic-inline_elements:last');\n\t\t\tvar title = element.find('.field-title input').val();\n\t\t\tvar inputField = element.find('.field-position input');\n\t\t\tif (typeof markerId === 'undefined') {\n\t\t\t\tmarkerId = element.index('.inline-related');\n\t\t\t}\n\t\t\tvar marker = L.marker(latlng, {draggable: true, markerId: markerId});\n\t\t\tinputField.val(JSON.stringify(marker.getLatLng()));\n\t\t\tmarker.addTo(this.editMap);\n\t\t\tmarker.bindTooltip(title);\n\t\t\tmarker.on('dragend', this.dragMarker, inputField);\n\n\t\t\t$('#leaflet_edit_map').removeClass('leaflet-crosshair');\n\t\t\t$('#inline_elements-group .add-row a').removeClass('disable-click');\n\t\t\tthis.editMap.off('click');\n\t\t},\n\t\tdragMarker: function(event) {\n\t\t\tvar marker = event.target;\n\t\t\tthis.val(JSON.stringify(marker.getLatLng()));\n\t\t},\n\t\taddInlineElement: function(event) {\n\t\t\tvar $this = $(this);\n\t\t\t$this.addClass('disable-click');\n\t\t\t$('#inline_elements-group .inline-deletelink').one('click', function() {\n\t\t\t\t$this.removeClass('disable-click');\n\t\t\t});\n\t\t\t$('#leaflet_edit_map').addClass('leaflet-crosshair');\n\t\t\tevent.data.editMap.on('click', evt => event.data.addMarker(evt.latlng), event.data);\n\t\t},\n\t\tchangeUseIcon: function(event) {\n\t\t\tvar checkbox, markerImage;\n\t\t\tif (event.target) {\n\t\t\t\tcheckbox = $(event.target);\n\t\t\t\tmarkerImage = checkbox.parents('.inline-related').find('.field-marker_image')\n\t\t\t\t\t.add(checkbox.parents('.inline-related').find('.field-marker_width'))\n\t\t\t\t\t.add(checkbox.parents('.inline-related').find('.field-marker_anchor'));\n\t\t\t} else {\n\t\t\t\tcheckbox = $(this).find('.field-use_icon input');\n\t\t\t\tmarkerImage = $(this).find('.field-marker_image')\n\t\t\t\t\t.add($(this).find('.field-marker_width'))\n\t\t\t\t\t.add($(this).find('.field-marker_anchor'));\n\t\t\t}\n\t\t\tif (checkbox.is(':checked')) {\n\t\t\t\tmarkerImage.show();\n\t\t\t} else {\n\t\t\t\tmarkerImage.hide();\n\t\t\t}\n\t\t},\n\t\taddressLookup: function(event) {\n\t\t\tconst divWrapper = event.target.closest('div');\n\t\t\tif (!divWrapper)\n\t\t\t\tthrow new Error(\"<div> wrapper missing\");\n\t\t\tconst ulElement = divWrapper.querySelector('ul');\n\t\t\tif (ulElement) {\n\t\t\t\tdivWrapper.removeChild(ulElement);\n\t\t\t}\n\t\t\tif (event.target.value.length < 3 || event.type === 'keypress' && event.which !== 13)\n\t\t\t\treturn;\n\t\t\tfetch(django.cascade.leaflet_settings.addressLookupURL + '?format=json&q=' + event.target.value)\n\t\t\t\t.then(response => response.json())\n\t\t\t\t.then(body => event.data.renderLookupResults(body, divWrapper));\n\t\t\tevent.preventDefault();\n\t\t},\n\t\trenderLookupResults: function(data, divWrapper) {\n\t\t\tconst ulElement = document.createElement('ul');\n\t\t\tdivWrapper.appendChild(ulElement);\n\t\t\tdata.forEach(address => {\n\t\t\t\tconst liElement = document.createElement('li');\n\t\t\t\tliElement.setAttribute('data-lng', address.lon);\n\t\t\t\tliElement.setAttribute('data-lat', address.lat);\n\t\t\t\tliElement.innerText = address.display_name;\n\t\t\t\tliElement.addEventListener('click', evt => this.moveMarkerIcon(evt.target, divWrapper.closest('.inline-related')));\n\t\t\t\tulElement.appendChild(liElement);\n\t\t\t});\n\t\t\tif (data.length === 0) {\n\t\t\t\tconst liElement = document.createElement('li');\n\t\t\t\tliElement.innerText = django.cascade.no_results;\n\t\t\t\tulElement.appendChild(liElement);\n\t\t\t}\n\t\t},\n\t\tmoveMarkerIcon: function(resultElement, inlineElement) {\n\t\t\tconst latlng = [resultElement.getAttribute('data-lat'), resultElement.getAttribute('data-lng')];\n\t\t\tconst index = $(inlineElement).index('.inline-related');\n\t\t\tconst markers = [];\n\t\t\t$.each(this.editMap._layers, function() {\n\t\t\t\tif (this.options && this.options.markerId === index) {\n\t\t\t\t\tmarkers.push(this);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (markers.length === 0) {\n\t\t\t\tthis.addMarker(latlng, index);\n\t\t\t} else {\n\t\t\t\tmarkers[0].setLatLng(latlng);\n\t\t\t\t$(inlineElement).find('.field-position input').val(JSON.stringify(markers[0].getLatLng()));\n\t\t\t}\n\t\t\tdocument.getElementById('leaflet_edit_map').scrollIntoView({behavior: 'smooth'});\n\t\t}\n\t});\n\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/linkplugin.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\tvar $document = $(document);\n\tvar $link_type = $(\"#id_link_type\"), $cmspage_select = $(\"#id_cms_page\");\n\tvar $link_ext_url = $('#id_ext_url');\n\tvar $link_type_row = $('.form-row.field-link_type');\n\tvar $link_target = $(\".form-row.field-link_target\");\n\tvar $link_title = $(\".form-row.field-link_title\");\n\n\tdjango.cascade.LinkPluginBase = ring.create({\n\t\tLinkType: ring.create({\n\t\t\tconstructor: function(selector, addTarget) {\n\t\t\t\tthis.$element = $(selector);\n\t\t\t\tthis.addTarget = Boolean(addTarget);\n\t\t\t},\n\t\t\tshow: function() {\n\t\t\t\tthis.$element.show();\n\t\t\t\tif (this.addTarget) {\n\t\t\t\t\t$link_target.show();\n\t\t\t\t}\n\t\t\t\t$link_title.show();\n\t\t\t\tthis.$element.find('input').focus();\n\t\t\t},\n\t\t\thide: function() {\n\t\t\t\tthis.$element.hide();\n\t\t\t}\n\t\t}),\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.linkTypes = {};\n\t\t\tthis.$super();\n\t\t\tthis.initializeLinkTypes();\n\n\t\t\t$document.on('select2:open', function() {\n\t\t\t\t$('.select2-search__field').get(0).focus();\n\t\t\t});\n\n\t\t\t// register event handlers on changing link_type and cms_page select boxes\n\t\t\t$link_type.change(function(evt) {\n\t\t\t\tself.toggleLinkTypes(evt.target.value);\n\t\t\t});\n\t\t\t$cmspage_select.change(function(evt) {\n\t\t\t\tself.toggleCMSPage(evt.target.value);\n\t\t\t});\n\t\t\t$link_ext_url.on('blur', function(evt) {\n\t\t\t\tself.validateExtUrl(evt.target.value);\n\t\t\t});\n\t\t\tthis.refreshChangeForm();\n\t\t},\n\t\tinitializeLinkTypes: function() {\n\t\t\tthis.linkTypes['cmspage'] = new this.LinkType('.form-row.field-cms_page, .form-row.field-section', true);\n\t\t\tthis.linkTypes['download'] = new this.LinkType('.form-row.field-download_file');\n\t\t\tthis.linkTypes['exturl'] = new this.LinkType('.form-row.field-ext_url', true);\n\t\t\tthis.linkTypes['email'] = new this.LinkType('.form-row.field-mail_to');\n\t\t\tthis.linkTypes['phonenumber'] = new this.LinkType('.form-row.field-phone_number');\n\t\t},\n\t\ttoggleLinkTypes: function(linkTypeName) {\n\t\t\tif (linkTypeName) {\n\t\t\t\t$link_type_row.removeClass('no-link');\n\t\t\t} else {\n\t\t\t\t$link_type_row.addClass('no-link');\n\t\t\t}\n\t\t\t$.each(this.linkTypes, function(name, linkType) {\n\t\t\t\tif (name === linkTypeName) {\n\t\t\t\t\tlinkType.show();\n\t\t\t\t} else {\n\t\t\t\t\tlinkType.hide();\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (!this.linkTypes[linkTypeName] || !this.linkTypes[linkTypeName].addTarget) {\n\t\t\t\t$link_target.hide();\n\t\t\t}\n\t\t\tif (!this.linkTypes[linkTypeName]) {\n\t\t\t\t$link_title.hide();\n\t\t\t}\n\t\t},\n\t\ttoggleCMSPage: function(page_id) {\n\t\t\tconst url = django.cascade.page_sections_url + page_id;\n\t\t\tconst $selSection = $('#id_section');\n\t\t\tconst lookupUrl = (function() {\n\t\t\t\ttry {\n\t\t\t\t\treturn new URL($('.select2-search__field').val());\n\t\t\t\t} catch (e) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t})();\n\n\t\t\t$.get(url, function(response) {\n\t\t\t\tlet preselected = null;\n\t\t\t\t$selSection.children('option:gt(0)').remove();\n\t\t\t\tfor (let k = 0; k < response.element_ids.length; k++) {\n\t\t\t\t\tconst val = response.element_ids[k];\n\t\t\t\t\tconst option = $(\"<option></option>\").attr(\"value\", val[0]).text(val[1]);\n\t\t\t\t\tif (lookupUrl && '#' + val[1] === lookupUrl.hash) {\n\t\t\t\t\t\tpreselected = val[0];\n\t\t\t\t\t}\n\t\t\t\t\t$selSection.append(option);\n\t\t\t\t}\n\t\t\t\t$selSection.val(preselected);\n\t\t\t});\n\t\t},\n\t\tvalidateExtUrl: function(exturl) {\n\t\t\tif (!exturl) {\n\t\t\t\t$link_ext_url.removeClass('valid').removeClass('invalid');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t$.get(django.cascade.validate_exturl_url, {\n\t\t\t\texturl: exturl,\n\t\t\t}, function(response) {\n\t\t\t\tif (!response.status_code) {\n\t\t\t\t\t$link_ext_url.removeClass('valid').removeClass('invalid');\n\t\t\t\t} else if (response.status_code === 200) {\n\t\t\t\t\t$link_ext_url.addClass('valid').removeClass('invalid');\n\t\t\t\t} else {\n\t\t\t\t\t$link_ext_url.addClass('invalid').removeClass('valid');\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\trefreshChangeForm: function() {\n\t\t\tthis.toggleLinkTypes($link_type.val());\n\t\t\tthis.validateExtUrl($link_ext_url.val());\n\t\t\tthis.$super && this.$super();\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/pictureplugin.js",
    "content": "\ndjango.jQuery(function($) {\n\t'use strict';\n\n\t// create class handling the client-side part of PicturePlugin\n\tdjango.cascade.PicturePlugin = ring.create(eval(django.cascade.ring_plugin_bases.PicturePlugin), {\n\t\tconstructor: function() {\n\t\t\tthis.$super();\n\n\t\t\t// be more intuitive, reorganize layout by moving 'Link Target'\n\t\t\t$('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/segmentation.js",
    "content": "\ndjango.jQuery(function($) {\n\t'use strict';\n\n\tfunction reloadBrowser() {\n\t\tvar parent = (window.parent) ? window.parent : window;\n\t\tparent.setTimeout(function () {\n\t\t\tparent.location.reload()\n\t\t}, 0);\n\t}\n\n\t$('#result_list a.emulate-user').each(function(idx, element) {\n\t\tvar data = {'href': $(element).attr('href')};\n\t\t$(element).on('click', data, function(event) {\n\t\t\t$.get(data.href).then(reloadBrowser);\n\t\t\treturn false;\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/segmentplugin.js",
    "content": "\ndjango.jQuery(function($) {\n\t'use strict';\n\tvar $id_open_tag = $(\"#id_open_tag\");\n\tvar $condition_element = $(\".field-condition\");\n\n\t// create class handling the client-side part of SegmentPlugin\n\tdjango.cascade.SegmentPlugin = ring.create(eval(django.cascade.ring_plugin_bases.SegmentPlugin), {\n\t\tconstructor: function() {\n\t\t\tvar self = this;\n\t\t\tthis.$super();\n\n\t\t\t// register event handler on changing `open_tag` select box\n\t\t\t$id_open_tag.change(function(evt) {\n\t\t\t\tself.selectOpenTag(evt.target.value);\n\t\t\t});\n\t\t\tthis.selectOpenTag($id_open_tag.val());\n\t\t},\n\t\tselectOpenTag: function(val) {\n\t\t\tif (val == 'if' || val == 'elif') {\n\t\t\t\t$condition_element.show();\n\t\t\t} else {\n\t\t\t\t// `else` does not require a condition \n\t\t\t\t$condition_element.hide();\n\t\t\t}\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/sharableglossary.js",
    "content": "\ndjango.cascade = django.cascade || {};\n\ndjango.jQuery(function($) {\n\t'use strict';\n\n\tvar url = new URL(window.location.href);\n\tvar $sel_shared_glossary = $('#id_shared_glossary');\n\tif ($sel_shared_glossary.length === 0)\n\t\treturn;\n\n\t// create class handling the SharableGlossaryMixin\n\tdjango.cascade.SharableGlossaryMixin = ring.create({\n\t\tconstructor: function() {\n\t\t\tthis.$super();\n\n\t\t\tif ($sel_shared_glossary.children('option').length > 1) {\n\t\t\t\t// set the value for 'Shared Settings' to that one provided by the URL\n\t\t\t\tif (url.searchParams.has('glossary')) {\n\t\t\t\t\t$sel_shared_glossary.val(url.searchParams.get('glossary'));\n\t\t\t\t}\n\t\t\t\t// add event handler to select box for 'Shared Settings'\n\t\t\t\t$sel_shared_glossary.change(this.toggleSharedGlossary);\n\t\t\t} else {\n\t\t\t\t// remove the select box to choose from 'Shared Settings', since it doesn't contain any options\n\t\t\t\t$('.field-shared_glossary').remove();\n\t\t\t}\n\t\t},\n\t\ttoggleSharedGlossary: function(event) {\n\t\t\tvar glossary = event.target.value;\n\n\t\t\tif (glossary) {\n\t\t\t\turl.searchParams.set('glossary', glossary);\n\t\t\t\twindow.location.href = url.href;\n\t\t\t} else {\n\t\t\t\turl.searchParams.set('glossary', '');\n\t\t\t\twindow.location.href = url.href;\n\t\t\t}\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/sharedsettingsfield.js",
    "content": "django.jQuery(function($) {\n\t'use strict';\n\tvar $checkboxElem = $('.form-row.field-save_settings_as input[type=\"checkbox\"]');\n\n\tfunction saveAsChanged(checkboxInput) {\n\t\tvar $identifierInput = $(checkboxInput).siblings('input[type=\"text\"]');\n\t\t$identifierInput.prop('disabled', !checkboxInput.checked);\n\t}\n\n\t$checkboxElem.on('change', function(evt) {\n\t\tsaveAsChanged(evt.target);\n\t});\n\t$.each($checkboxElem, function() {\n\t\tsaveAsChanged(this);\n\t});\n\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/textimageplugin.js",
    "content": "django.jQuery(function($) {\n\t// create class handling the client-side part of a TextImagePlugin\n\tdjango.cascade.TextImagePlugin = ring.create(eval(django.cascade.ring_plugin_bases.TextImagePlugin), {\n\t\tconstructor: function() {\n\t\t\tthis.$super();\n\n\t\t\t// be more intuitive, reorganize layout by moving 'Link Target'\n\t\t\t$('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));\n\t\t}\n\t});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/admin/textlinkplugin.js",
    "content": "django.jQuery(function($) {\n\t// create class handling the client-side part of a TextLinkPlugin\n\tdjango.cascade.TextLinkPlugin = ring.create(eval(django.cascade.ring_plugin_bases.TextLinkPlugin), {});\n});\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/picturefill.js",
    "content": "/*! Picturefill - v2.1.0-beta - 2014-06-03\n* http://scottjehl.github.io/picturefill\n* Copyright (c) 2014 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */\n/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */\n\nwindow.matchMedia || (window.matchMedia = function() {\n\t\"use strict\";\n\n\t// For browsers that support matchMedium api such as IE 9 and webkit\n\tvar styleMedia = (window.styleMedia || window.media);\n\n\t// For those that don't support matchMedium\n\tif (!styleMedia) {\n\t\tvar style       = document.createElement('style'),\n\t\t\tscript      = document.getElementsByTagName('script')[0],\n\t\t\tinfo        = null;\n\n\t\tstyle.type  = 'text/css';\n\t\tstyle.id    = 'matchmediajs-test';\n\n\t\tscript.parentNode.insertBefore(style, script);\n\n\t\t// 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers\n\t\tinfo = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle;\n\n\t\tstyleMedia = {\n\t\t\tmatchMedium: function(media) {\n\t\t\t\tvar text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';\n\n\t\t\t\t// 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers\n\t\t\t\tif (style.styleSheet) {\n\t\t\t\t\tstyle.styleSheet.cssText = text;\n\t\t\t\t} else {\n\t\t\t\t\tstyle.textContent = text;\n\t\t\t\t}\n\n\t\t\t\t// Test if media query is true or false\n\t\t\t\treturn info.width === '1px';\n\t\t\t}\n\t\t};\n\t}\n\n\treturn function(media) {\n\t\treturn {\n\t\t\tmatches: styleMedia.matchMedium(media || 'all'),\n\t\t\tmedia: media || 'all'\n\t\t};\n\t};\n}());\n/*! Picturefill - Responsive Images that work today.\n*  Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar )\n*  License: MIT/GPLv2\n*  Spec: http://picture.responsiveimages.org/\n*/\n(function( w, doc ) {\n\t// Enable strict mode\n\t\"use strict\";\n\n\t// If picture is supported, well, that's awesome. Let's get outta here...\n\tif ( w.HTMLPictureElement ) {\n\t\treturn;\n\t}\n\n\t// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround)\n\tdoc.createElement( \"picture\" );\n\n\t// local object for method references and testing exposure\n\tvar pf = {};\n\n\t// namespace\n\tpf.ns = \"picturefill\";\n\n\t// srcset support test\n\tpf.srcsetSupported = new w.Image().srcset !== undefined;\n\n\t// just a string trim workaround\n\tpf.trim = function( str ) {\n\t\treturn str.trim ? str.trim() : str.replace( /^\\s+|\\s+$/g, \"\" );\n\t};\n\n\t// just a string endsWith workaround\n\tpf.endsWith = function( str, suffix ) {\n\t\treturn str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1;\n\t};\n\n\t/**\n\t * Shortcut method for matchMedia ( for easy overriding in tests )\n\t */\n\tpf.matchesMedia = function( media ) {\n\t\treturn w.matchMedia && w.matchMedia( media ).matches;\n\t};\n\n\t/**\n\t * Shortcut method for `devicePixelRatio` ( for easy overriding in tests )\n\t */\n\tpf.getDpr = function() {\n\t\treturn ( w.devicePixelRatio || 1 );\n\t};\n\n\t/**\n\t * Get width in css pixel value from a \"length\" value\n\t * http://dev.w3.org/csswg/css-values-3/#length-value\n\t */\n\tpf.getWidthFromLength = function( length ) {\n\t\t// If no length was specified, or it is 0, default to `100vw` (per the spec).\n\t\tlength = length && parseFloat( length ) > 0 ? length : \"100vw\";\n\n\t\t/**\n\t\t* If length is specified in  `vw` units, use `%` instead since the div we’re measuring\n\t\t* is injected at the top of the document.\n\t\t*\n\t\t* TODO: maybe we should put this behind a feature test for `vw`?\n\t\t*/\n\t\tlength = length.replace( \"vw\", \"%\" );\n\n\t\t// Create a cached element for getting length value widths\n\t\tif ( !pf.lengthEl ) {\n\t\t\tpf.lengthEl = doc.createElement( \"div\" );\n\t\t\tdoc.documentElement.insertBefore( pf.lengthEl, doc.documentElement.firstChild );\n\t\t}\n\n\t\t// Positioning styles help prevent padding/margin/width on `html` from throwing calculations off.\n\t\tpf.lengthEl.style.cssText = \"position: absolute; left: 0; width: \" + length + \";\";\n\t\t// Using offsetWidth to get width from CSS\n\t\treturn pf.lengthEl.offsetWidth;\n\t};\n\n\t// container of supported mime types that one might need to qualify before using\n\tpf.types =  {};\n\n\t// Add support for standard mime types.\n\tpf.types[\"image/jpeg\"] = true;\n\tpf.types[\"image/gif\"] = true;\n\tpf.types[\"image/png\"] = true;\n\n\t// test svg support\n\tpf.types[ \"image/svg+xml\" ] = doc.implementation.hasFeature(\"http://www.w3.org/TR/SVG11/feature#Image\", \"1.1\");\n\n\t// test webp support, only when the markup calls for it\n\tpf.types[ \"image/webp\" ] = function() {\n\t\t// based on Modernizr's lossless img-webp test\n\t\t// note: asynchronous\n\t\tvar img = new w.Image(),\n\t\t\ttype = \"image/webp\";\n\n\t\timg.onerror = function() {\n\t\t\tpf.types[ type ] = false;\n\t\t\tpicturefill();\n\t\t};\n\t\timg.onload = function() {\n\t\t\tpf.types[ type ] = img.width === 1;\n\t\t\tpicturefill();\n\t\t};\n\t\timg.src = \"data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=\";\n\t};\n\n\t/**\n\t * Takes a source element and checks if its type attribute is present and if so, supported\n\t * Note: for type tests that require a async logic,\n\t * you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete.\n\t * see the async webp test above for example\n\t */\n\tpf.verifyTypeSupport = function( source ) {\n\t\tvar type = source.getAttribute( \"type\" );\n\t\t// if type attribute exists, return test result, otherwise return true\n\t\tif ( type === null || type === \"\" ) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\t// if the type test is a function, run it and return \"pending\" status. The function will rerun picturefill on pending elements once finished.\n\t\t\tif ( typeof( pf.types[ type ] ) === \"function\" ) {\n\t\t\t\tpf.types[ type ]();\n\t\t\t\treturn \"pending\";\n\t\t\t} else {\n\t\t\t\treturn pf.types[ type ];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t* Parses an individual `size` and returns the length, and optional media query\n\t*/\n\tpf.parseSize = function( sourceSizeStr ) {\n\t\tvar match = /(\\([^)]+\\))?\\s*(.+)/g.exec( sourceSizeStr );\n\t\treturn {\n\t\t\tmedia: match && match[1],\n\t\t\tlength: match && match[2]\n\t\t};\n\t};\n\n\t/**\n\t * Takes a string of sizes and returns the width in pixels as a number\n\t */\n\tpf.findWidthFromSourceSize = function( sourceSizeListStr ) {\n\t\t// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33%\n\t\t//                            or (min-width:30em) calc(30% - 15px)\n\t\tvar sourceSizeList = pf.trim( sourceSizeListStr ).split( /\\s*,\\s*/ ),\n\t\t\twinningLength;\n\n\t\tfor ( var i = 0, len = sourceSizeList.length; i < len; i++ ) {\n\t\t\t// Match <media-condition>? length, ie ( min-width: 50em ) 100%\n\t\t\tvar sourceSize = sourceSizeList[ i ],\n\t\t\t\t// Split \"( min-width: 50em ) 100%\" into separate strings\n\t\t\t\tparsedSize = pf.parseSize( sourceSize ),\n\t\t\t\tlength = parsedSize.length,\n\t\t\t\tmedia = parsedSize.media;\n\n\t\t\tif ( !length ) {\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( !media || pf.matchesMedia( media ) ) {\n\t\t\t\t// if there is no media query or it matches, choose this as our winning length\n\t\t\t\t// and end algorithm\n\t\t\t\twinningLength = length;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// pass the length to a method that can properly determine length\n\t\t// in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value\n\t\treturn pf.getWidthFromLength( winningLength );\n\t};\n\n\tpf.parseSrcset = function( srcset ) {\n\t\t/**\n\t\t* A lot of this was pulled from Boris Smus’ parser for the now-defunct WHATWG `srcset`\n\t\t* https://github.com/borismus/srcset-polyfill/blob/master/js/srcset-info.js\n\t\t*\n\t\t* 1. Let input (`srcset`) be the value passed to this algorithm.\n\t\t* 2. Let position be a pointer into input, initially pointing at the start of the string.\n\t\t* 3. Let raw candidates be an initially empty ordered list of URLs with associated \n\t\t*    unparsed descriptors. The order of entries in the list is the order in which entries \n\t\t*    are added to the list.\n\t\t*/\n\t\tvar candidates = [];\n\n\t\twhile ( srcset !== \"\" ) {\n\t\t\tsrcset = srcset.replace(/^\\s+/g,\"\");\n\n\t\t\t// 5. Collect a sequence of characters that are not space characters, and let that be url.\n\t\t\tvar pos = srcset.search(/\\s/g),\n\t\t\t\turl, descriptor = null;\n\n\t\t\tif ( pos !== -1 ) {\n\t\t\t\turl = srcset.slice( 0, pos );\n\n\t\t\t\tvar last = url[ url.length - 1 ];\n\n\t\t\t\t// 6. If url ends with a U+002C COMMA character (,), remove that character from url\n\t\t\t\t// and let descriptors be the empty string. Otherwise, follow these substeps\n\t\t\t\t// 6.1. If url is empty, then jump to the step labeled descriptor parser.\n\n\t\t\t\tif ( last === \",\" || url === \"\" ) {\n\t\t\t\t\turl = url.replace(/,+$/, \"\");\n\t\t\t\t\tdescriptor = \"\";\n\t\t\t\t}\n\t\t\t\tsrcset = srcset.slice( pos + 1 );\n\n\t\t\t\t// 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and \n\t\t\t\t// let that be descriptors.\n\t\t\t\tif ( descriptor === null ) {\n\t\t\t\t\tvar descpos = srcset.indexOf(\",\");\n\t\t\t\t\tif ( descpos !== -1 ) {\n\t\t\t\t\t\tdescriptor = srcset.slice( 0, descpos );\n\t\t\t\t\t\tsrcset = srcset.slice( descpos + 1 );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdescriptor = srcset;\n\t\t\t\t\t\tsrcset = \"\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\turl = srcset;\n\t\t\t\tsrcset = \"\";\n\t\t\t}\n\n\t\t\t// 7. Add url to raw candidates, associated with descriptors.\n\t\t\tif ( url || descriptor ) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\turl: url,\n\t\t\t\t\tdescriptor: descriptor\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn candidates;\n\t};\n\n\tpf.parseDescriptor = function( descriptor, sizes ) {\n\t\t// 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list \n\t\t// is the order in which entries are added to the list.\n\t\tvar sizeDescriptor = descriptor && descriptor.replace(/(^\\s+|\\s+$)/g, \"\"),\n\t\t\twidthInCssPixels = sizes ? pf.findWidthFromSourceSize( sizes ) : \"100%\",\n\t\t\tresCandidate;\n\n\t\t\tif ( sizeDescriptor ) {\n\t\t\t\tvar splitDescriptor = sizeDescriptor.split(\" \");\n\n\t\t\t\tfor (var i = splitDescriptor.length + 1; i >= 0; i--) {\n\n\t\t\t\t\tvar curr = splitDescriptor[ i ],\n\t\t\t\t\t\tlastchar = curr && curr.slice( curr.length - 1 );\n\n\t\t\t\t\tif ( lastchar === \"w\" || lastchar === \"x\" ) {\n\t\t\t\t\t\tresCandidate = curr;\n\t\t\t\t\t}\n\t\t\t\t\tif ( sizes && resCandidate ) {\n\t\t\t\t\t\t// get the dpr by taking the length / width in css pixels\n\t\t\t\t\t\tresCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// get the dpr by grabbing the value of Nx\n\t\t\t\t\t\tvar res = curr && parseFloat( curr, 10 );\n\n\t\t\t\t\t\tresCandidate = res && !isNaN( res ) && lastchar === \"x\" || lastchar === \"w\" ? res : 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresCandidate = 1;\n\t\t\t}\n\t\treturn resCandidate;\n\t};\n\n\t/**\n\t * Takes a srcset in the form of url/\n\t * ex. \"images/pic-medium.png 1x, images/pic-medium-2x.png 2x\" or\n\t *     \"images/pic-medium.png 400w, images/pic-medium-2x.png 800w\" or\n\t *     \"images/pic-small.png\"\n\t * Get an array of image candidates in the form of\n\t *      {url: \"/foo/bar.png\", resolution: 1}\n\t * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value\n\t * If sizes is specified, resolution is calculated\n\t */\n\tpf.getCandidatesFromSourceSet = function( srcset, sizes ) {\n\t\tvar candidates = pf.parseSrcset( srcset ),\n\t\t\tformattedCandidates = [];\n\n\t\tfor ( var i = 0, len = candidates.length; i < len; i++ ) {\n\t\t\tvar candidate = candidates[ i ];\n\n\t\t\tformattedCandidates.push({\n\t\t\t\turl: candidate.url,\n\t\t\t\tresolution: pf.parseDescriptor( candidate.descriptor, sizes )\n\t\t\t});\n\t\t}\n\t\treturn formattedCandidates;\n\t};\n\n\t/*\n\t * if it's an img element and it has a srcset property,\n\t * we need to remove the attribute so we can manipulate src\n\t * (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate)\n\t * this moves srcset's value to memory for later use and removes the attr\n\t */\n\tpf.dodgeSrcset = function( img ) {\n\t\tif ( img.srcset ) {\n\t\t\timg[ pf.ns ].srcset = img.srcset;\n\t\t\timg.removeAttribute( \"srcset\" );\n\t\t}\n\t};\n\n\t/*\n\t * Accept a source or img element and process its srcset and sizes attrs\n\t */\n\tpf.processSourceSet = function( el ) {\n\t\tvar srcset = el.getAttribute( \"srcset\" ),\n\t\t\tsizes = el.getAttribute( \"sizes\" ),\n\t\t\tcandidates = [];\n\n\t\t// if it's an img element, use the cached srcset property (defined or not)\n\t\tif ( el.nodeName.toUpperCase() === \"IMG\" && el[ pf.ns ] && el[ pf.ns ].srcset ) {\n\t\t\tsrcset = el[ pf.ns ].srcset;\n\t\t}\n\n\t\tif ( srcset ) {\n\t\t\tcandidates = pf.getCandidatesFromSourceSet( srcset, sizes );\n\t\t}\n\t\treturn candidates;\n\t};\n\n\tpf.applyBestCandidate = function( candidates, picImg ) {\n\t\tvar candidate,\n\t\t\tlength,\n\t\t\tbestCandidate;\n\n\t\tcandidates.sort( pf.ascendingSort );\n\n\t\tlength = candidates.length;\n\t\tbestCandidate = candidates[ length - 1 ];\n\n\t\tfor ( var i = 0; i < length; i++ ) {\n\t\t\tcandidate = candidates[ i ];\n\t\t\tif ( candidate.resolution >= pf.getDpr() ) {\n\t\t\t\tbestCandidate = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif ( !pf.endsWith( picImg.src, bestCandidate.url ) ) {\n\t\t\tpicImg.src = bestCandidate.url;\n\t\t\t// currentSrc attribute and property to match\n\t\t\t// http://picture.responsiveimages.org/#the-img-element\n\t\t\tpicImg.currentSrc = picImg.src;\n\t\t}\n\t};\n\n\tpf.ascendingSort = function( a, b ) {\n\t\treturn a.resolution - b.resolution;\n\t};\n\n\t/*\n\t * In IE9, <source> elements get removed if they aren't children of\n\t * video elements. Thus, we conditionally wrap source elements\n\t * using <!--[if IE 9]><video style=\"display: none;\"><![endif]-->\n\t * and must account for that here by moving those source elements\n\t * back into the picture element.\n\t */\n\tpf.removeVideoShim = function( picture ) {\n\t\tvar videos = picture.getElementsByTagName( \"video\" );\n\t\tif ( videos.length ) {\n\t\t\tvar video = videos[ 0 ],\n\t\t\t\tvsources = video.getElementsByTagName( \"source\" );\n\t\t\twhile ( vsources.length ) {\n\t\t\t\tpicture.insertBefore( vsources[ 0 ], video );\n\t\t\t}\n\t\t\t// Remove the video element once we're finished removing its children\n\t\t\tvideo.parentNode.removeChild( video );\n\t\t}\n\t};\n\n\t/*\n\t * Find all picture elements and,\n\t * in browsers that don't natively support srcset, find all img elements\n\t * with srcset attrs that don't have picture parents\n\t */\n\tpf.getAllElements = function() {\n\t\tvar pictures = doc.getElementsByTagName( \"picture\" ),\n\t\t\telems = [],\n\t\t\timgs = doc.getElementsByTagName( \"img\" );\n\n\t\tfor ( var h = 0, len = pictures.length + imgs.length; h < len; h++ ) {\n\t\t\tif ( h < pictures.length ) {\n\t\t\t\telems[ h ] = pictures[ h ];\n\t\t\t} else {\n\t\t\t\tvar currImg = imgs[ h - pictures.length ];\n\n\t\t\t\tif ( currImg.parentNode.nodeName.toUpperCase() !== \"PICTURE\" &&\n\t\t\t\t\t( ( pf.srcsetSupported && currImg.getAttribute( \"sizes\" ) ) ||\n\t\t\t\t\tcurrImg.getAttribute( \"srcset\" ) !== null ) ) {\n\t\t\t\t\t\telems.push( currImg );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn elems;\n\t};\n\n\tpf.getMatch = function( picture ) {\n\t\tvar sources = picture.childNodes,\n\t\t\tmatch;\n\n\t\t// Go through each child, and if they have media queries, evaluate them\n\t\tfor ( var j = 0, slen = sources.length; j < slen; j++ ) {\n\t\t\tvar source = sources[ j ];\n\n\t\t\t// ignore non-element nodes\n\t\t\tif ( source.nodeType !== 1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Hitting an `img` element stops the search for `sources`.\n\t\t\t// If no previous `source` matches, the `img` itself is evaluated later.\n\t\t\tif ( source.nodeName.toUpperCase() === \"IMG\" ) {\n\t\t\t\treturn match;\n\t\t\t}\n\n\t\t\t// ignore non-`source` nodes\n\t\t\tif ( source.nodeName.toUpperCase() !== \"SOURCE\" ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar media = source.getAttribute( \"media\" );\n\n\t\t\t// if source does not have a srcset attribute, skip\n\t\t\tif ( !source.getAttribute( \"srcset\" ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// if there's no media specified, OR w.matchMedia is supported\n\t\t\tif ( ( !media || pf.matchesMedia( media ) ) ) {\n\t\t\t\tvar typeSupported = pf.verifyTypeSupport( source );\n\n\t\t\t\tif ( typeSupported === true ) {\n\t\t\t\t\tmatch = source;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if ( typeSupported === \"pending\" ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn match;\n\t};\n\n\tfunction picturefill( options ) {\n\t\tvar elements,\n\t\t\telement,\n\t\t\telemType,\n\t\t\tfirstMatch,\n\t\t\tcandidates,\n\t\t\tpicImg;\n\n\t\toptions = options || {};\n\t\telements = options.elements || pf.getAllElements();\n\n\t\t// Loop through all elements\n\t\tfor ( var i = 0, plen = elements.length; i < plen; i++ ) {\n\t\t\telement = elements[ i ];\n\t\t\telemType = element.nodeName.toUpperCase();\n\t\t\tfirstMatch = undefined;\n\t\t\tcandidates = undefined;\n\t\t\tpicImg = undefined;\n\n\t\t\t// expando for caching data on the img\n\t\t\tif ( !element[ pf.ns ] ) {\n\t\t\t\telement[ pf.ns ] = {};\n\t\t\t}\n\n\t\t\t// if the element has already been evaluated, skip it\n\t\t\t// unless `options.force` is set to true ( this, for example,\n\t\t\t// is set to true when running `picturefill` on `resize` ).\n\t\t\tif ( !options.reevaluate && element[ pf.ns ].evaluated ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// if element is a picture element\n\t\t\tif ( elemType === \"PICTURE\" ) {\n\n\t\t\t\t// IE9 video workaround\n\t\t\t\tpf.removeVideoShim( element );\n\n\t\t\t\t// return the first match which might undefined\n\t\t\t\t// returns false if there is a pending source\n\t\t\t\t// TODO the return type here is brutal, cleanup\n\t\t\t\tfirstMatch = pf.getMatch( element );\n\n\t\t\t\t// if any sources are pending in this picture due to async type test(s)\n\t\t\t\t// remove the evaluated attr and skip for now ( the pending test will\n\t\t\t\t// rerun picturefill on this element when complete)\n\t\t\t\tif ( firstMatch === false ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Find any existing img element in the picture element\n\t\t\t\tpicImg = element.getElementsByTagName( \"img\" )[ 0 ];\n\t\t\t} else {\n\t\t\t\t// if it's an img element\n\t\t\t\tfirstMatch = undefined;\n\t\t\t\tpicImg = element;\n\t\t\t}\n\n\t\t\tif ( picImg ) {\n\n\t\t\t\t// expando for caching data on the img\n\t\t\t\tif ( !picImg[ pf.ns ] ) {\n\t\t\t\t\tpicImg[ pf.ns ] = {};\n\t\t\t\t}\n\n\t\t\t\t// Cache and remove `srcset` if present and we’re going to be doing `sizes`/`picture` polyfilling to it.\n\t\t\t\tif ( picImg.srcset && ( elemType === \"PICTURE\" || picImg.getAttribute( \"sizes\" ) ) ) {\n\t\t\t\t\tpf.dodgeSrcset( picImg );\n\t\t\t\t}\n\n\t\t\t\tif ( firstMatch ) {\n\t\t\t\t\tcandidates = pf.processSourceSet( firstMatch );\n\t\t\t\t\tpf.applyBestCandidate( candidates, picImg );\n\t\t\t\t} else {\n\t\t\t\t\t// No sources matched, so we’re down to processing the inner `img` as a source.\n\t\t\t\t\tcandidates = pf.processSourceSet( picImg );\n\n\t\t\t\t\tif ( picImg.srcset === undefined || picImg[ pf.ns ].srcset ) {\n\t\t\t\t\t\t// Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality.\n\t\t\t\t\t\tpf.applyBestCandidate( candidates, picImg );\n\t\t\t\t\t} // Else, resolution-only `srcset` is supported natively.\n\t\t\t\t}\n\n\t\t\t\t// set evaluated to true to avoid unnecessary reparsing\n\t\t\t\telement[ pf.ns ].evaluated = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Sets up picture polyfill by polling the document and running\n\t * the polyfill every 250ms until the document is ready.\n\t * Also attaches picturefill on resize\n\t */\n\tfunction runPicturefill() {\n\t\tpicturefill();\n\t\tvar intervalId = setInterval( function() {\n\t\t\t// When the document has finished loading, stop checking for new images\n\t\t\t// https://github.com/ded/domready/blob/master/ready.js#L15\n\t\t\tpicturefill();\n\t\t\tif ( /^loaded|^i|^c/.test( doc.readyState ) ) {\n\t\t\t\tclearInterval( intervalId );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}, 250 );\n\t\tif ( w.addEventListener ) {\n\t\t\tvar resizeThrottle;\n\t\t\tw.addEventListener( \"resize\", function() {\n\t\t\t\tif (!w._picturefillWorking) {\n\t\t\t\t\tw._picturefillWorking = true;\n\t\t\t\t\tw.clearTimeout( resizeThrottle );\n\t\t\t\t\tresizeThrottle = w.setTimeout( function() {\n\t\t\t\t\t\tpicturefill({ reevaluate: true });\n\t\t\t\t\t\tw._picturefillWorking = false;\n\t\t\t\t\t}, 60 );\n\t\t\t\t}\n\t\t\t}, false );\n\t\t}\n\t}\n\n\trunPicturefill();\n\n\t/* expose methods for testing */\n\tpicturefill._ = pf;\n\n\t/* expose picturefill */\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t\t// CommonJS, just export\n\t\tmodule.exports = picturefill;\n\t} else if ( typeof define === \"function\" && define.amd ){\n\t\t// AMD support\n\t\tdefine( function() { return picturefill; } );\n\t} else if ( typeof w === \"object\" ) {\n\t\t// If no AMD and we are in the browser, attach to window\n\t\tw.picturefill = picturefill;\n\t}\n\n} )( this, this.document );\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/ring.js",
    "content": "/*\nRing.js\n\nCopyright (c) 2013, Nicolas Vanhoren\n\nReleased under the MIT license\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell copies of the\nSoftware, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\nAN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n(function() {\n/* jshint es3: true, proto: true */\n\"use strict\";\n\nif (typeof(exports) !== \"undefined\") { // nodejs\n    var underscore = require(\"underscore\");\n    underscore.extend(exports, declare(underscore));\n} else if (typeof(define) !== \"undefined\") { // amd\n    define([\"underscore\"], declare);\n} else { // define global variable\n    window.ring = declare(_);\n}\n\n\nfunction declare(_) {\n    var ring = {};\n\n    function RingObject() {}\n    /**\n        ring.Object\n\n        The base class of all other classes. It doesn't have much uses except\n        testing testing if an object uses the Ring.js class system using\n        ring.instance(x, ring.Object)\n    */\n    ring.Object = RingObject;\n    _.extend(ring.Object, {\n        __mro__: [ring.Object],\n        __properties__: {__ringConstructor__: function() {}},\n        __classId__: 1,\n        __parents__: [],\n        __classIndex__: {\"1\": ring.Object},\n        $extend: function(props) {\n            return ring.create([this], props);\n        }\n    });\n    _.extend(ring.Object.prototype, {\n        __ringConstructor__: ring.Object.__properties__.__ringConstructor__\n    });\n\n    // utility function to have Object.create on all browsers\n    var objectCreate = function(o) {\n        function CreatedObject(){}\n        CreatedObject.prototype = o;\n        var tmp = new CreatedObject();\n        tmp.__proto__ = o;\n        return tmp;\n    };\n    ring.__objectCreate = objectCreate;\n\n    var classCounter = 3;\n    var fnTest = /xyz/.test(function(){xyz();}) ? /\\$super\\b/ : /.*/;\n\n    /**\n        ring.create([parents,] properties)\n\n        Creates a new class and returns it.\n\n        properties is a dictionary of the methods and attributes that should\n        be added to the new class' prototype.\n\n        parents is a list of the classes this new class should extend. If not\n        specified or an empty list is specified this class will inherit from one\n        class: ring.Object.\n    */\n    ring.create = function() {\n        // arguments parsing\n        var args = _.toArray(arguments);\n        args.reverse();\n        var props = args[0];\n        var parents = args.length >= 2 ? args[1] : [];\n        if (! (parents instanceof Array))\n            parents = [parents];\n        _.each(parents, function(el) {\n            toRingClass(el);\n        });\n        if (parents.length === 0)\n            parents = [ring.Object];\n        // constructor handling\n        var cons = props.constructor !== Object ? props.constructor : undefined;\n        props = _.clone(props);\n        delete props.constructor;\n        if (cons)\n            props.__ringConstructor__ = cons;\n        else { //retro compatibility\n            cons = props.init;\n            delete props.init;\n            if (cons)\n                props.__ringConstructor__ = cons;\n        }\n        // put the classInit aside to use later\n        var classInit = props.classInit;\n        delete props.classInit;\n        // create real class\n        var claz = function Instance() {\n            this.$super = null;\n            this.__ringConstructor__.apply(this, arguments);\n        };\n        claz.__properties__ = props;\n        // mro creation\n        var toMerge = _.pluck(parents, \"__mro__\");\n        toMerge = toMerge.concat([parents]);\n        var __mro__ = [claz].concat(mergeMro(toMerge));\n        // generate prototype\n        var prototype = Object.prototype;\n        _.each(_.clone(__mro__).reverse(), function(claz) {\n            var current = objectCreate(prototype);\n            _.extend(current, claz.__properties__);\n            _.each(_.keys(current), function(key) {\n                var p = current[key];\n                if (typeof p !== \"function\" || ! fnTest.test(p) || p.__classId__ ||\n                    (key !== \"__ringConstructor__\" && claz.__ringConvertedObject__))\n                    return;\n                current[key] = (function(name, fct, supProto) {\n                    return function() {\n                        var tmp = this.$super;\n                        this.$super = supProto[name];\n                        try {\n                            return fct.apply(this, arguments);\n                        } finally {\n                            this.$super = tmp;\n                        }\n                    };\n                })(key, p, prototype);\n            });\n            current.constructor = claz;\n            prototype = current;\n        });\n        // remaining operations\n        var id = classCounter++;\n        claz.__mro__ = __mro__;\n        claz.__parents__ = parents;\n        claz.prototype = prototype;\n        claz.__classId__ = id;\n        // construct classes index\n        claz.__classIndex__ = {};\n        _.each(claz.__mro__, function(c) {\n            claz.__classIndex__[c.__classId__] = c;\n        });\n        // class init\n        claz.__classInit__ = classInit;\n        _.each(_.chain(claz.__mro__).clone().reverse().value(), function(c) {\n            if (c.__classInit__) {\n                var ret = c.__classInit__(claz.prototype);\n                if (ret !== undefined)\n                    claz.prototype = ret;\n            }\n        });\n        // $extend\n        claz.$extend = ring.Object.$extend;\n\n        return claz;\n    };\n\n    var mergeMro = function(toMerge) {\n        /* jshint loopfunc:true */\n        // C3 merge() implementation\n        var __mro__ = [];\n        var current = _.clone(toMerge);\n        while (true) {\n            var found = false;\n            for (var i=0; i < current.length; i++) {\n                if (current[i].length === 0)\n                    continue;\n                var currentClass = current[i][0];\n                var isInTail = _.find(current, function(lst) {\n                    return _.contains(_.rest(lst), currentClass);\n                });\n                if (! isInTail) {\n                    found = true;\n                    __mro__.push(currentClass);\n                    current = _.map(current, function(lst) {\n                        if (_.head(lst) === currentClass)\n                            return _.rest(lst);\n                        else\n                            return lst;\n                    });\n                    break;\n                }\n            }\n            if (found)\n                continue;\n            if (_.all(current, function(i) { return i.length === 0; }))\n                return __mro__;\n            throw new ring.ValueError(\"Cannot create a consistent method resolution order (MRO)\");\n        }\n    };\n\n    /**\n        Convert an existing class to be used with the ring.js class system.\n    */\n    var toRingClass = function(claz) {\n        if (claz.__classId__)\n            return;\n        var proto = ! Object.getOwnPropertyNames ? claz.prototype : (function() {\n            var keys = {};\n            (function crawl(p) {\n                if (p === Object.prototype)\n                    return;\n                _.extend(keys, _.chain(Object.getOwnPropertyNames(p))\n                    .map(function(el) {return [el, true];})\n                    .object().value());\n                crawl(Object.getPrototypeOf(p));\n            })(claz.prototype);\n            return _.object(_.map(_.keys(keys), function(k) {return [k, claz.prototype[k]];}));\n        })();\n        proto = _.chain(proto).map(function(v, k) { return [k, v]; })\n            .filter(function(el) {return el[0] !== \"constructor\" && el[0] !== \"__proto__\";})\n            .object().value();\n        var id = classCounter++;\n        _.extend(claz, {\n            __mro__: [claz, ring.Object],\n            __properties__: _.extend({}, proto, {\n                __ringConstructor__: function() {\n                    this.$super.apply(this, arguments);\n                    var tmp = this.$super;\n                    this.$super = null;\n                    try {\n                        claz.apply(this, arguments);\n                    } finally {\n                        this.$super = tmp;\n                    }\n                }\n            }),\n            __classId__: id,\n            __parents__: [ring.Object],\n            __classIndex__: {\"1\": ring.Object},\n            __ringConvertedObject__: true\n        });\n        claz.__classIndex__[id] = claz;\n    };\n\n    /**\n        ring.instance(obj, type)\n\n        Returns true if obj is an instance of type or an instance of a sub-class of type.\n\n        It is necessary to use this method instead of instanceof when using the Ring.js class\n        system because instanceof will not be able to detect sub-classes.\n\n        If used with obj or type that do not use the Ring.js class system this method will\n        use instanceof instead. So it should be safe to replace all usages of instanceof\n        by ring.instance() in any program, whether or not it uses Ring.js.\n\n        Additionaly this method allows to test the type of simple JavaScript types like strings.\n        To do so, pass a string instead of a type as second argument. Examples:\n\n            ring.instance(\"\", \"string\") // returns true\n            ring.instance(function() {}, \"function\") // returns true\n            ring.instance({}, \"object\") // returns true\n            ring.instance(1, \"number\") // returns true\n    */\n    ring.instance = function(obj, type) {\n        if (obj !== null && typeof(obj) === \"object\" && obj.constructor && obj.constructor.__classIndex__ &&\n            typeof(type) === \"function\" && typeof(type.__classId__) === \"number\") {\n            return obj.constructor.__classIndex__[type.__classId__] !== undefined;\n        }\n        if (typeof(type) === \"string\")\n            return typeof(obj) === type;\n        return obj instanceof type;\n    };\n\n    /**\n        A class to easily create new classes representing exceptions. This class is special\n        because it is a sub-class of the standard Error class of JavaScript. Examples:\n\n        ring.instance(e, Error)\n\n        e instanceof Error\n\n        This two expressions will always be true if e is an instance of ring.Error or any\n        sub-class of ring.Error.\n\n    */\n    ring.Error = ring.create({\n        /**\n            The name attribute is used in the default implementation of the toString() method\n            of the standard JavaScript Error class. According to the standard, all sub-classes\n            of Error should define a new name.\n        */\n        name: \"ring.Error\",\n        /**\n            A default message to use in instances of this class if there is no arguments given\n            to the constructor.\n        */\n        defaultMessage: \"\",\n        /**\n            Constructor arguments:\n\n            message: The message to put in the instance. If there is no message specified, the\n            message will be this.defaultMessage.\n        */\n        constructor: function(message) {\n            this.message = message || this.defaultMessage;\n        },\n        classInit: function(prototype) {\n            // some black magic to reconstitute a complete prototype chain\n            // with Error at the end\n            var protos = [];\n            var gather = function(proto) {\n                if (! proto)\n                    return;\n                protos.push(proto);\n                gather(proto.__proto__);\n            };\n            gather(prototype);\n            var current = new Error();\n            _.each(_.clone(protos).reverse(), function(proto) {\n                var tmp = objectCreate(current);\n                // using _.each to avoid traversing prototypes\n                _.each(proto, function(v, k) {\n                    if (k !== \"__proto__\")\n                        tmp[k] = v;\n                });\n                current = tmp;\n            });\n            return current;\n        }\n    });\n\n    /**\n        A type of exception to inform that a method received an argument with an incorrect value.\n    */\n    ring.ValueError = ring.create([ring.Error], {\n        name: \"ring.ValueError\"\n    });\n\n    /**\n        This method allows to find the super of a method when that method has been re-defined\n        in a child class.\n\n        Contrary to this.$super(), this function allows to find a super method in another method\n        than the re-defining one. Example:\n\n        var A = ring.create({\n            fctA: function() {...};\n        });\n\n        var B = ring.create([A], {\n            fctA: function() {...};\n            fctB: function() {\n                ring.getSuper(B, this, \"fctA\")(); // here we call the original fctA() method\n                // as it was defined in the A class\n            };\n        });\n\n        This method is much slower than this.$super(), so this.$super() should always be\n        preferred when it is possible to use it.\n\n        Arguments:\n\n        * currentClass: The current class. It is necessary to specify it for this function\n          to work properly.\n        * obj: The current object (this in most cases).\n        * attributeName: The name of the desired attribute as it appeared in the base class.\n\n        Returns the attribute as it was defined in the base class. If that attribute is a function,\n        it will be binded to obj.\n    */\n    ring.getSuper = function(currentClass, obj, attributeName) {\n        var pos;\n        var __mro__ = obj.constructor.__mro__;\n        for (var i = 0; i < __mro__.length; i++) {\n            if (__mro__[i] === currentClass) {\n                pos = i;\n                break;\n            }\n        }\n        if (pos === undefined)\n            throw new ring.ValueError(\"Class not found in instance's method resolution order.\");\n        var find = function(proto, counter) {\n            if (counter === 0)\n                return proto;\n            return find(proto.__proto__, counter - 1);\n        };\n        var proto = find(obj.constructor.prototype, pos + 1);\n        var att;\n        if (attributeName !== \"constructor\" && attributeName !== \"init\") // retro compatibility\n            att = proto[attributeName];\n        else\n            att = proto.__ringConstructor__;\n        if (ring.instance(att, \"function\"))\n            return _.bind(att, obj);\n        else\n            return att;\n    };\n\n    return ring;\n}\n})();\n"
  },
  {
    "path": "cmsplugin_cascade/static/cascade/js/underscore.js",
    "content": "//     Underscore.js 1.5.2\n//     http://underscorejs.org\n//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n//     Underscore may be freely distributed under the MIT license.\n\n(function() {\n\n  // Baseline setup\n  // --------------\n\n  // Establish the root object, `window` in the browser, or `exports` on the server.\n  var root = this;\n\n  // Save the previous value of the `_` variable.\n  var previousUnderscore = root._;\n\n  // Establish the object that gets returned to break out of a loop iteration.\n  var breaker = {};\n\n  // Save bytes in the minified (but not gzipped) version:\n  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;\n\n  // Create quick reference variables for speed access to core prototypes.\n  var\n    push             = ArrayProto.push,\n    slice            = ArrayProto.slice,\n    concat           = ArrayProto.concat,\n    toString         = ObjProto.toString,\n    hasOwnProperty   = ObjProto.hasOwnProperty;\n\n  // All **ECMAScript 5** native function implementations that we hope to use\n  // are declared here.\n  var\n    nativeForEach      = ArrayProto.forEach,\n    nativeMap          = ArrayProto.map,\n    nativeReduce       = ArrayProto.reduce,\n    nativeReduceRight  = ArrayProto.reduceRight,\n    nativeFilter       = ArrayProto.filter,\n    nativeEvery        = ArrayProto.every,\n    nativeSome         = ArrayProto.some,\n    nativeIndexOf      = ArrayProto.indexOf,\n    nativeLastIndexOf  = ArrayProto.lastIndexOf,\n    nativeIsArray      = Array.isArray,\n    nativeKeys         = Object.keys,\n    nativeBind         = FuncProto.bind;\n\n  // Create a safe reference to the Underscore object for use below.\n  var _ = function(obj) {\n    if (obj instanceof _) return obj;\n    if (!(this instanceof _)) return new _(obj);\n    this._wrapped = obj;\n  };\n\n  // Export the Underscore object for **Node.js**, with\n  // backwards-compatibility for the old `require()` API. If we're in\n  // the browser, add `_` as a global object via a string identifier,\n  // for Closure Compiler \"advanced\" mode.\n  if (typeof exports !== 'undefined') {\n    if (typeof module !== 'undefined' && module.exports) {\n      exports = module.exports = _;\n    }\n    exports._ = _;\n  } else {\n    root._ = _;\n  }\n\n  // Current version.\n  _.VERSION = '1.5.2';\n\n  // Collection Functions\n  // --------------------\n\n  // The cornerstone, an `each` implementation, aka `forEach`.\n  // Handles objects with the built-in `forEach`, arrays, and raw objects.\n  // Delegates to **ECMAScript 5**'s native `forEach` if available.\n  var each = _.each = _.forEach = function(obj, iterator, context) {\n    if (obj == null) return;\n    if (nativeForEach && obj.forEach === nativeForEach) {\n      obj.forEach(iterator, context);\n    } else if (obj.length === +obj.length) {\n      for (var i = 0, length = obj.length; i < length; i++) {\n        if (iterator.call(context, obj[i], i, obj) === breaker) return;\n      }\n    } else {\n      var keys = _.keys(obj);\n      for (var i = 0, length = keys.length; i < length; i++) {\n        if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;\n      }\n    }\n  };\n\n  // Return the results of applying the iterator to each element.\n  // Delegates to **ECMAScript 5**'s native `map` if available.\n  _.map = _.collect = function(obj, iterator, context) {\n    var results = [];\n    if (obj == null) return results;\n    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);\n    each(obj, function(value, index, list) {\n      results.push(iterator.call(context, value, index, list));\n    });\n    return results;\n  };\n\n  var reduceError = 'Reduce of empty array with no initial value';\n\n  // **Reduce** builds up a single result from a list of values, aka `inject`,\n  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.\n  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {\n    var initial = arguments.length > 2;\n    if (obj == null) obj = [];\n    if (nativeReduce && obj.reduce === nativeReduce) {\n      if (context) iterator = _.bind(iterator, context);\n      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);\n    }\n    each(obj, function(value, index, list) {\n      if (!initial) {\n        memo = value;\n        initial = true;\n      } else {\n        memo = iterator.call(context, memo, value, index, list);\n      }\n    });\n    if (!initial) throw new TypeError(reduceError);\n    return memo;\n  };\n\n  // The right-associative version of reduce, also known as `foldr`.\n  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.\n  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {\n    var initial = arguments.length > 2;\n    if (obj == null) obj = [];\n    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {\n      if (context) iterator = _.bind(iterator, context);\n      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);\n    }\n    var length = obj.length;\n    if (length !== +length) {\n      var keys = _.keys(obj);\n      length = keys.length;\n    }\n    each(obj, function(value, index, list) {\n      index = keys ? keys[--length] : --length;\n      if (!initial) {\n        memo = obj[index];\n        initial = true;\n      } else {\n        memo = iterator.call(context, memo, obj[index], index, list);\n      }\n    });\n    if (!initial) throw new TypeError(reduceError);\n    return memo;\n  };\n\n  // Return the first value which passes a truth test. Aliased as `detect`.\n  _.find = _.detect = function(obj, iterator, context) {\n    var result;\n    any(obj, function(value, index, list) {\n      if (iterator.call(context, value, index, list)) {\n        result = value;\n        return true;\n      }\n    });\n    return result;\n  };\n\n  // Return all the elements that pass a truth test.\n  // Delegates to **ECMAScript 5**'s native `filter` if available.\n  // Aliased as `select`.\n  _.filter = _.select = function(obj, iterator, context) {\n    var results = [];\n    if (obj == null) return results;\n    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);\n    each(obj, function(value, index, list) {\n      if (iterator.call(context, value, index, list)) results.push(value);\n    });\n    return results;\n  };\n\n  // Return all the elements for which a truth test fails.\n  _.reject = function(obj, iterator, context) {\n    return _.filter(obj, function(value, index, list) {\n      return !iterator.call(context, value, index, list);\n    }, context);\n  };\n\n  // Determine whether all of the elements match a truth test.\n  // Delegates to **ECMAScript 5**'s native `every` if available.\n  // Aliased as `all`.\n  _.every = _.all = function(obj, iterator, context) {\n    iterator || (iterator = _.identity);\n    var result = true;\n    if (obj == null) return result;\n    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);\n    each(obj, function(value, index, list) {\n      if (!(result = result && iterator.call(context, value, index, list))) return breaker;\n    });\n    return !!result;\n  };\n\n  // Determine if at least one element in the object matches a truth test.\n  // Delegates to **ECMAScript 5**'s native `some` if available.\n  // Aliased as `any`.\n  var any = _.some = _.any = function(obj, iterator, context) {\n    iterator || (iterator = _.identity);\n    var result = false;\n    if (obj == null) return result;\n    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);\n    each(obj, function(value, index, list) {\n      if (result || (result = iterator.call(context, value, index, list))) return breaker;\n    });\n    return !!result;\n  };\n\n  // Determine if the array or object contains a given value (using `===`).\n  // Aliased as `include`.\n  _.contains = _.include = function(obj, target) {\n    if (obj == null) return false;\n    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;\n    return any(obj, function(value) {\n      return value === target;\n    });\n  };\n\n  // Invoke a method (with arguments) on every item in a collection.\n  _.invoke = function(obj, method) {\n    var args = slice.call(arguments, 2);\n    var isFunc = _.isFunction(method);\n    return _.map(obj, function(value) {\n      return (isFunc ? method : value[method]).apply(value, args);\n    });\n  };\n\n  // Convenience version of a common use case of `map`: fetching a property.\n  _.pluck = function(obj, key) {\n    return _.map(obj, function(value){ return value[key]; });\n  };\n\n  // Convenience version of a common use case of `filter`: selecting only objects\n  // containing specific `key:value` pairs.\n  _.where = function(obj, attrs, first) {\n    if (_.isEmpty(attrs)) return first ? void 0 : [];\n    return _[first ? 'find' : 'filter'](obj, function(value) {\n      for (var key in attrs) {\n        if (attrs[key] !== value[key]) return false;\n      }\n      return true;\n    });\n  };\n\n  // Convenience version of a common use case of `find`: getting the first object\n  // containing specific `key:value` pairs.\n  _.findWhere = function(obj, attrs) {\n    return _.where(obj, attrs, true);\n  };\n\n  // Return the maximum element or (element-based computation).\n  // Can't optimize arrays of integers longer than 65,535 elements.\n  // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)\n  _.max = function(obj, iterator, context) {\n    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {\n      return Math.max.apply(Math, obj);\n    }\n    if (!iterator && _.isEmpty(obj)) return -Infinity;\n    var result = {computed : -Infinity, value: -Infinity};\n    each(obj, function(value, index, list) {\n      var computed = iterator ? iterator.call(context, value, index, list) : value;\n      computed > result.computed && (result = {value : value, computed : computed});\n    });\n    return result.value;\n  };\n\n  // Return the minimum element (or element-based computation).\n  _.min = function(obj, iterator, context) {\n    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {\n      return Math.min.apply(Math, obj);\n    }\n    if (!iterator && _.isEmpty(obj)) return Infinity;\n    var result = {computed : Infinity, value: Infinity};\n    each(obj, function(value, index, list) {\n      var computed = iterator ? iterator.call(context, value, index, list) : value;\n      computed < result.computed && (result = {value : value, computed : computed});\n    });\n    return result.value;\n  };\n\n  // Shuffle an array, using the modern version of the \n  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).\n  _.shuffle = function(obj) {\n    var rand;\n    var index = 0;\n    var shuffled = [];\n    each(obj, function(value) {\n      rand = _.random(index++);\n      shuffled[index - 1] = shuffled[rand];\n      shuffled[rand] = value;\n    });\n    return shuffled;\n  };\n\n  // Sample **n** random values from an array.\n  // If **n** is not specified, returns a single random element from the array.\n  // The internal `guard` argument allows it to work with `map`.\n  _.sample = function(obj, n, guard) {\n    if (arguments.length < 2 || guard) {\n      return obj[_.random(obj.length - 1)];\n    }\n    return _.shuffle(obj).slice(0, Math.max(0, n));\n  };\n\n  // An internal function to generate lookup iterators.\n  var lookupIterator = function(value) {\n    return _.isFunction(value) ? value : function(obj){ return obj[value]; };\n  };\n\n  // Sort the object's values by a criterion produced by an iterator.\n  _.sortBy = function(obj, value, context) {\n    var iterator = lookupIterator(value);\n    return _.pluck(_.map(obj, function(value, index, list) {\n      return {\n        value: value,\n        index: index,\n        criteria: iterator.call(context, value, index, list)\n      };\n    }).sort(function(left, right) {\n      var a = left.criteria;\n      var b = right.criteria;\n      if (a !== b) {\n        if (a > b || a === void 0) return 1;\n        if (a < b || b === void 0) return -1;\n      }\n      return left.index - right.index;\n    }), 'value');\n  };\n\n  // An internal function used for aggregate \"group by\" operations.\n  var group = function(behavior) {\n    return function(obj, value, context) {\n      var result = {};\n      var iterator = value == null ? _.identity : lookupIterator(value);\n      each(obj, function(value, index) {\n        var key = iterator.call(context, value, index, obj);\n        behavior(result, key, value);\n      });\n      return result;\n    };\n  };\n\n  // Groups the object's values by a criterion. Pass either a string attribute\n  // to group by, or a function that returns the criterion.\n  _.groupBy = group(function(result, key, value) {\n    (_.has(result, key) ? result[key] : (result[key] = [])).push(value);\n  });\n\n  // Indexes the object's values by a criterion, similar to `groupBy`, but for\n  // when you know that your index values will be unique.\n  _.indexBy = group(function(result, key, value) {\n    result[key] = value;\n  });\n\n  // Counts instances of an object that group by a certain criterion. Pass\n  // either a string attribute to count by, or a function that returns the\n  // criterion.\n  _.countBy = group(function(result, key) {\n    _.has(result, key) ? result[key]++ : result[key] = 1;\n  });\n\n  // Use a comparator function to figure out the smallest index at which\n  // an object should be inserted so as to maintain order. Uses binary search.\n  _.sortedIndex = function(array, obj, iterator, context) {\n    iterator = iterator == null ? _.identity : lookupIterator(iterator);\n    var value = iterator.call(context, obj);\n    var low = 0, high = array.length;\n    while (low < high) {\n      var mid = (low + high) >>> 1;\n      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;\n    }\n    return low;\n  };\n\n  // Safely create a real, live array from anything iterable.\n  _.toArray = function(obj) {\n    if (!obj) return [];\n    if (_.isArray(obj)) return slice.call(obj);\n    if (obj.length === +obj.length) return _.map(obj, _.identity);\n    return _.values(obj);\n  };\n\n  // Return the number of elements in an object.\n  _.size = function(obj) {\n    if (obj == null) return 0;\n    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;\n  };\n\n  // Array Functions\n  // ---------------\n\n  // Get the first element of an array. Passing **n** will return the first N\n  // values in the array. Aliased as `head` and `take`. The **guard** check\n  // allows it to work with `_.map`.\n  _.first = _.head = _.take = function(array, n, guard) {\n    if (array == null) return void 0;\n    return (n == null) || guard ? array[0] : slice.call(array, 0, n);\n  };\n\n  // Returns everything but the last entry of the array. Especially useful on\n  // the arguments object. Passing **n** will return all the values in\n  // the array, excluding the last N. The **guard** check allows it to work with\n  // `_.map`.\n  _.initial = function(array, n, guard) {\n    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));\n  };\n\n  // Get the last element of an array. Passing **n** will return the last N\n  // values in the array. The **guard** check allows it to work with `_.map`.\n  _.last = function(array, n, guard) {\n    if (array == null) return void 0;\n    if ((n == null) || guard) {\n      return array[array.length - 1];\n    } else {\n      return slice.call(array, Math.max(array.length - n, 0));\n    }\n  };\n\n  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.\n  // Especially useful on the arguments object. Passing an **n** will return\n  // the rest N values in the array. The **guard**\n  // check allows it to work with `_.map`.\n  _.rest = _.tail = _.drop = function(array, n, guard) {\n    return slice.call(array, (n == null) || guard ? 1 : n);\n  };\n\n  // Trim out all falsy values from an array.\n  _.compact = function(array) {\n    return _.filter(array, _.identity);\n  };\n\n  // Internal implementation of a recursive `flatten` function.\n  var flatten = function(input, shallow, output) {\n    if (shallow && _.every(input, _.isArray)) {\n      return concat.apply(output, input);\n    }\n    each(input, function(value) {\n      if (_.isArray(value) || _.isArguments(value)) {\n        shallow ? push.apply(output, value) : flatten(value, shallow, output);\n      } else {\n        output.push(value);\n      }\n    });\n    return output;\n  };\n\n  // Flatten out an array, either recursively (by default), or just one level.\n  _.flatten = function(array, shallow) {\n    return flatten(array, shallow, []);\n  };\n\n  // Return a version of the array that does not contain the specified value(s).\n  _.without = function(array) {\n    return _.difference(array, slice.call(arguments, 1));\n  };\n\n  // Produce a duplicate-free version of the array. If the array has already\n  // been sorted, you have the option of using a faster algorithm.\n  // Aliased as `unique`.\n  _.uniq = _.unique = function(array, isSorted, iterator, context) {\n    if (_.isFunction(isSorted)) {\n      context = iterator;\n      iterator = isSorted;\n      isSorted = false;\n    }\n    var initial = iterator ? _.map(array, iterator, context) : array;\n    var results = [];\n    var seen = [];\n    each(initial, function(value, index) {\n      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {\n        seen.push(value);\n        results.push(array[index]);\n      }\n    });\n    return results;\n  };\n\n  // Produce an array that contains the union: each distinct element from all of\n  // the passed-in arrays.\n  _.union = function() {\n    return _.uniq(_.flatten(arguments, true));\n  };\n\n  // Produce an array that contains every item shared between all the\n  // passed-in arrays.\n  _.intersection = function(array) {\n    var rest = slice.call(arguments, 1);\n    return _.filter(_.uniq(array), function(item) {\n      return _.every(rest, function(other) {\n        return _.indexOf(other, item) >= 0;\n      });\n    });\n  };\n\n  // Take the difference between one array and a number of other arrays.\n  // Only the elements present in just the first array will remain.\n  _.difference = function(array) {\n    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));\n    return _.filter(array, function(value){ return !_.contains(rest, value); });\n  };\n\n  // Zip together multiple lists into a single array -- elements that share\n  // an index go together.\n  _.zip = function() {\n    var length = _.max(_.pluck(arguments, \"length\").concat(0));\n    var results = new Array(length);\n    for (var i = 0; i < length; i++) {\n      results[i] = _.pluck(arguments, '' + i);\n    }\n    return results;\n  };\n\n  // Converts lists into objects. Pass either a single array of `[key, value]`\n  // pairs, or two parallel arrays of the same length -- one of keys, and one of\n  // the corresponding values.\n  _.object = function(list, values) {\n    if (list == null) return {};\n    var result = {};\n    for (var i = 0, length = list.length; i < length; i++) {\n      if (values) {\n        result[list[i]] = values[i];\n      } else {\n        result[list[i][0]] = list[i][1];\n      }\n    }\n    return result;\n  };\n\n  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),\n  // we need this function. Return the position of the first occurrence of an\n  // item in an array, or -1 if the item is not included in the array.\n  // Delegates to **ECMAScript 5**'s native `indexOf` if available.\n  // If the array is large and already in sort order, pass `true`\n  // for **isSorted** to use binary search.\n  _.indexOf = function(array, item, isSorted) {\n    if (array == null) return -1;\n    var i = 0, length = array.length;\n    if (isSorted) {\n      if (typeof isSorted == 'number') {\n        i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);\n      } else {\n        i = _.sortedIndex(array, item);\n        return array[i] === item ? i : -1;\n      }\n    }\n    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);\n    for (; i < length; i++) if (array[i] === item) return i;\n    return -1;\n  };\n\n  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.\n  _.lastIndexOf = function(array, item, from) {\n    if (array == null) return -1;\n    var hasIndex = from != null;\n    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {\n      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);\n    }\n    var i = (hasIndex ? from : array.length);\n    while (i--) if (array[i] === item) return i;\n    return -1;\n  };\n\n  // Generate an integer Array containing an arithmetic progression. A port of\n  // the native Python `range()` function. See\n  // [the Python documentation](http://docs.python.org/library/functions.html#range).\n  _.range = function(start, stop, step) {\n    if (arguments.length <= 1) {\n      stop = start || 0;\n      start = 0;\n    }\n    step = arguments[2] || 1;\n\n    var length = Math.max(Math.ceil((stop - start) / step), 0);\n    var idx = 0;\n    var range = new Array(length);\n\n    while(idx < length) {\n      range[idx++] = start;\n      start += step;\n    }\n\n    return range;\n  };\n\n  // Function (ahem) Functions\n  // ------------------\n\n  // Reusable constructor function for prototype setting.\n  var ctor = function(){};\n\n  // Create a function bound to a given object (assigning `this`, and arguments,\n  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if\n  // available.\n  _.bind = function(func, context) {\n    var args, bound;\n    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));\n    if (!_.isFunction(func)) throw new TypeError;\n    args = slice.call(arguments, 2);\n    return bound = function() {\n      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));\n      ctor.prototype = func.prototype;\n      var self = new ctor;\n      ctor.prototype = null;\n      var result = func.apply(self, args.concat(slice.call(arguments)));\n      if (Object(result) === result) return result;\n      return self;\n    };\n  };\n\n  // Partially apply a function by creating a version that has had some of its\n  // arguments pre-filled, without changing its dynamic `this` context.\n  _.partial = function(func) {\n    var args = slice.call(arguments, 1);\n    return function() {\n      return func.apply(this, args.concat(slice.call(arguments)));\n    };\n  };\n\n  // Bind all of an object's methods to that object. Useful for ensuring that\n  // all callbacks defined on an object belong to it.\n  _.bindAll = function(obj) {\n    var funcs = slice.call(arguments, 1);\n    if (funcs.length === 0) throw new Error(\"bindAll must be passed function names\");\n    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });\n    return obj;\n  };\n\n  // Memoize an expensive function by storing its results.\n  _.memoize = function(func, hasher) {\n    var memo = {};\n    hasher || (hasher = _.identity);\n    return function() {\n      var key = hasher.apply(this, arguments);\n      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));\n    };\n  };\n\n  // Delays a function for the given number of milliseconds, and then calls\n  // it with the arguments supplied.\n  _.delay = function(func, wait) {\n    var args = slice.call(arguments, 2);\n    return setTimeout(function(){ return func.apply(null, args); }, wait);\n  };\n\n  // Defers a function, scheduling it to run after the current call stack has\n  // cleared.\n  _.defer = function(func) {\n    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));\n  };\n\n  // Returns a function, that, when invoked, will only be triggered at most once\n  // during a given window of time. Normally, the throttled function will run\n  // as much as it can, without ever going more than once per `wait` duration;\n  // but if you'd like to disable the execution on the leading edge, pass\n  // `{leading: false}`. To disable execution on the trailing edge, ditto.\n  _.throttle = function(func, wait, options) {\n    var context, args, result;\n    var timeout = null;\n    var previous = 0;\n    options || (options = {});\n    var later = function() {\n      previous = options.leading === false ? 0 : new Date;\n      timeout = null;\n      result = func.apply(context, args);\n    };\n    return function() {\n      var now = new Date;\n      if (!previous && options.leading === false) previous = now;\n      var remaining = wait - (now - previous);\n      context = this;\n      args = arguments;\n      if (remaining <= 0) {\n        clearTimeout(timeout);\n        timeout = null;\n        previous = now;\n        result = func.apply(context, args);\n      } else if (!timeout && options.trailing !== false) {\n        timeout = setTimeout(later, remaining);\n      }\n      return result;\n    };\n  };\n\n  // Returns a function, that, as long as it continues to be invoked, will not\n  // be triggered. The function will be called after it stops being called for\n  // N milliseconds. If `immediate` is passed, trigger the function on the\n  // leading edge, instead of the trailing.\n  _.debounce = function(func, wait, immediate) {\n    var timeout, args, context, timestamp, result;\n    return function() {\n      context = this;\n      args = arguments;\n      timestamp = new Date();\n      var later = function() {\n        var last = (new Date()) - timestamp;\n        if (last < wait) {\n          timeout = setTimeout(later, wait - last);\n        } else {\n          timeout = null;\n          if (!immediate) result = func.apply(context, args);\n        }\n      };\n      var callNow = immediate && !timeout;\n      if (!timeout) {\n        timeout = setTimeout(later, wait);\n      }\n      if (callNow) result = func.apply(context, args);\n      return result;\n    };\n  };\n\n  // Returns a function that will be executed at most one time, no matter how\n  // often you call it. Useful for lazy initialization.\n  _.once = function(func) {\n    var ran = false, memo;\n    return function() {\n      if (ran) return memo;\n      ran = true;\n      memo = func.apply(this, arguments);\n      func = null;\n      return memo;\n    };\n  };\n\n  // Returns the first function passed as an argument to the second,\n  // allowing you to adjust arguments, run code before and after, and\n  // conditionally execute the original function.\n  _.wrap = function(func, wrapper) {\n    return function() {\n      var args = [func];\n      push.apply(args, arguments);\n      return wrapper.apply(this, args);\n    };\n  };\n\n  // Returns a function that is the composition of a list of functions, each\n  // consuming the return value of the function that follows.\n  _.compose = function() {\n    var funcs = arguments;\n    return function() {\n      var args = arguments;\n      for (var i = funcs.length - 1; i >= 0; i--) {\n        args = [funcs[i].apply(this, args)];\n      }\n      return args[0];\n    };\n  };\n\n  // Returns a function that will only be executed after being called N times.\n  _.after = function(times, func) {\n    return function() {\n      if (--times < 1) {\n        return func.apply(this, arguments);\n      }\n    };\n  };\n\n  // Object Functions\n  // ----------------\n\n  // Retrieve the names of an object's properties.\n  // Delegates to **ECMAScript 5**'s native `Object.keys`\n  _.keys = nativeKeys || function(obj) {\n    if (obj !== Object(obj)) throw new TypeError('Invalid object');\n    var keys = [];\n    for (var key in obj) if (_.has(obj, key)) keys.push(key);\n    return keys;\n  };\n\n  // Retrieve the values of an object's properties.\n  _.values = function(obj) {\n    var keys = _.keys(obj);\n    var length = keys.length;\n    var values = new Array(length);\n    for (var i = 0; i < length; i++) {\n      values[i] = obj[keys[i]];\n    }\n    return values;\n  };\n\n  // Convert an object into a list of `[key, value]` pairs.\n  _.pairs = function(obj) {\n    var keys = _.keys(obj);\n    var length = keys.length;\n    var pairs = new Array(length);\n    for (var i = 0; i < length; i++) {\n      pairs[i] = [keys[i], obj[keys[i]]];\n    }\n    return pairs;\n  };\n\n  // Invert the keys and values of an object. The values must be serializable.\n  _.invert = function(obj) {\n    var result = {};\n    var keys = _.keys(obj);\n    for (var i = 0, length = keys.length; i < length; i++) {\n      result[obj[keys[i]]] = keys[i];\n    }\n    return result;\n  };\n\n  // Return a sorted list of the function names available on the object.\n  // Aliased as `methods`\n  _.functions = _.methods = function(obj) {\n    var names = [];\n    for (var key in obj) {\n      if (_.isFunction(obj[key])) names.push(key);\n    }\n    return names.sort();\n  };\n\n  // Extend a given object with all the properties in passed-in object(s).\n  _.extend = function(obj) {\n    each(slice.call(arguments, 1), function(source) {\n      if (source) {\n        for (var prop in source) {\n          obj[prop] = source[prop];\n        }\n      }\n    });\n    return obj;\n  };\n\n  // Return a copy of the object only containing the whitelisted properties.\n  _.pick = function(obj) {\n    var copy = {};\n    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));\n    each(keys, function(key) {\n      if (key in obj) copy[key] = obj[key];\n    });\n    return copy;\n  };\n\n   // Return a copy of the object without the blacklisted properties.\n  _.omit = function(obj) {\n    var copy = {};\n    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));\n    for (var key in obj) {\n      if (!_.contains(keys, key)) copy[key] = obj[key];\n    }\n    return copy;\n  };\n\n  // Fill in a given object with default properties.\n  _.defaults = function(obj) {\n    each(slice.call(arguments, 1), function(source) {\n      if (source) {\n        for (var prop in source) {\n          if (obj[prop] === void 0) obj[prop] = source[prop];\n        }\n      }\n    });\n    return obj;\n  };\n\n  // Create a (shallow-cloned) duplicate of an object.\n  _.clone = function(obj) {\n    if (!_.isObject(obj)) return obj;\n    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);\n  };\n\n  // Invokes interceptor with the obj, and then returns obj.\n  // The primary purpose of this method is to \"tap into\" a method chain, in\n  // order to perform operations on intermediate results within the chain.\n  _.tap = function(obj, interceptor) {\n    interceptor(obj);\n    return obj;\n  };\n\n  // Internal recursive comparison function for `isEqual`.\n  var eq = function(a, b, aStack, bStack) {\n    // Identical objects are equal. `0 === -0`, but they aren't identical.\n    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).\n    if (a === b) return a !== 0 || 1 / a == 1 / b;\n    // A strict comparison is necessary because `null == undefined`.\n    if (a == null || b == null) return a === b;\n    // Unwrap any wrapped objects.\n    if (a instanceof _) a = a._wrapped;\n    if (b instanceof _) b = b._wrapped;\n    // Compare `[[Class]]` names.\n    var className = toString.call(a);\n    if (className != toString.call(b)) return false;\n    switch (className) {\n      // Strings, numbers, dates, and booleans are compared by value.\n      case '[object String]':\n        // Primitives and their corresponding object wrappers are equivalent; thus, `\"5\"` is\n        // equivalent to `new String(\"5\")`.\n        return a == String(b);\n      case '[object Number]':\n        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for\n        // other numeric values.\n        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);\n      case '[object Date]':\n      case '[object Boolean]':\n        // Coerce dates and booleans to numeric primitive values. Dates are compared by their\n        // millisecond representations. Note that invalid dates with millisecond representations\n        // of `NaN` are not equivalent.\n        return +a == +b;\n      // RegExps are compared by their source patterns and flags.\n      case '[object RegExp]':\n        return a.source == b.source &&\n               a.global == b.global &&\n               a.multiline == b.multiline &&\n               a.ignoreCase == b.ignoreCase;\n    }\n    if (typeof a != 'object' || typeof b != 'object') return false;\n    // Assume equality for cyclic structures. The algorithm for detecting cyclic\n    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.\n    var length = aStack.length;\n    while (length--) {\n      // Linear search. Performance is inversely proportional to the number of\n      // unique nested structures.\n      if (aStack[length] == a) return bStack[length] == b;\n    }\n    // Objects with different constructors are not equivalent, but `Object`s\n    // from different frames are.\n    var aCtor = a.constructor, bCtor = b.constructor;\n    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&\n                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {\n      return false;\n    }\n    // Add the first object to the stack of traversed objects.\n    aStack.push(a);\n    bStack.push(b);\n    var size = 0, result = true;\n    // Recursively compare objects and arrays.\n    if (className == '[object Array]') {\n      // Compare array lengths to determine if a deep comparison is necessary.\n      size = a.length;\n      result = size == b.length;\n      if (result) {\n        // Deep compare the contents, ignoring non-numeric properties.\n        while (size--) {\n          if (!(result = eq(a[size], b[size], aStack, bStack))) break;\n        }\n      }\n    } else {\n      // Deep compare objects.\n      for (var key in a) {\n        if (_.has(a, key)) {\n          // Count the expected number of properties.\n          size++;\n          // Deep compare each member.\n          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;\n        }\n      }\n      // Ensure that both objects contain the same number of properties.\n      if (result) {\n        for (key in b) {\n          if (_.has(b, key) && !(size--)) break;\n        }\n        result = !size;\n      }\n    }\n    // Remove the first object from the stack of traversed objects.\n    aStack.pop();\n    bStack.pop();\n    return result;\n  };\n\n  // Perform a deep comparison to check if two objects are equal.\n  _.isEqual = function(a, b) {\n    return eq(a, b, [], []);\n  };\n\n  // Is a given array, string, or object empty?\n  // An \"empty\" object has no enumerable own-properties.\n  _.isEmpty = function(obj) {\n    if (obj == null) return true;\n    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;\n    for (var key in obj) if (_.has(obj, key)) return false;\n    return true;\n  };\n\n  // Is a given value a DOM element?\n  _.isElement = function(obj) {\n    return !!(obj && obj.nodeType === 1);\n  };\n\n  // Is a given value an array?\n  // Delegates to ECMA5's native Array.isArray\n  _.isArray = nativeIsArray || function(obj) {\n    return toString.call(obj) == '[object Array]';\n  };\n\n  // Is a given variable an object?\n  _.isObject = function(obj) {\n    return obj === Object(obj);\n  };\n\n  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.\n  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {\n    _['is' + name] = function(obj) {\n      return toString.call(obj) == '[object ' + name + ']';\n    };\n  });\n\n  // Define a fallback version of the method in browsers (ahem, IE), where\n  // there isn't any inspectable \"Arguments\" type.\n  if (!_.isArguments(arguments)) {\n    _.isArguments = function(obj) {\n      return !!(obj && _.has(obj, 'callee'));\n    };\n  }\n\n  // Optimize `isFunction` if appropriate.\n  if (typeof (/./) !== 'function') {\n    _.isFunction = function(obj) {\n      return typeof obj === 'function';\n    };\n  }\n\n  // Is a given object a finite number?\n  _.isFinite = function(obj) {\n    return isFinite(obj) && !isNaN(parseFloat(obj));\n  };\n\n  // Is the given value `NaN`? (NaN is the only number which does not equal itself).\n  _.isNaN = function(obj) {\n    return _.isNumber(obj) && obj != +obj;\n  };\n\n  // Is a given value a boolean?\n  _.isBoolean = function(obj) {\n    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';\n  };\n\n  // Is a given value equal to null?\n  _.isNull = function(obj) {\n    return obj === null;\n  };\n\n  // Is a given variable undefined?\n  _.isUndefined = function(obj) {\n    return obj === void 0;\n  };\n\n  // Shortcut function for checking if an object has a given property directly\n  // on itself (in other words, not on a prototype).\n  _.has = function(obj, key) {\n    return hasOwnProperty.call(obj, key);\n  };\n\n  // Utility Functions\n  // -----------------\n\n  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its\n  // previous owner. Returns a reference to the Underscore object.\n  _.noConflict = function() {\n    root._ = previousUnderscore;\n    return this;\n  };\n\n  // Keep the identity function around for default iterators.\n  _.identity = function(value) {\n    return value;\n  };\n\n  // Run a function **n** times.\n  _.times = function(n, iterator, context) {\n    var accum = Array(Math.max(0, n));\n    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);\n    return accum;\n  };\n\n  // Return a random integer between min and max (inclusive).\n  _.random = function(min, max) {\n    if (max == null) {\n      max = min;\n      min = 0;\n    }\n    return min + Math.floor(Math.random() * (max - min + 1));\n  };\n\n  // List of HTML entities for escaping.\n  var entityMap = {\n    escape: {\n      '&': '&amp;',\n      '<': '&lt;',\n      '>': '&gt;',\n      '\"': '&quot;',\n      \"'\": '&#x27;'\n    }\n  };\n  entityMap.unescape = _.invert(entityMap.escape);\n\n  // Regexes containing the keys and values listed immediately above.\n  var entityRegexes = {\n    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),\n    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')\n  };\n\n  // Functions for escaping and unescaping strings to/from HTML interpolation.\n  _.each(['escape', 'unescape'], function(method) {\n    _[method] = function(string) {\n      if (string == null) return '';\n      return ('' + string).replace(entityRegexes[method], function(match) {\n        return entityMap[method][match];\n      });\n    };\n  });\n\n  // If the value of the named `property` is a function then invoke it with the\n  // `object` as context; otherwise, return it.\n  _.result = function(object, property) {\n    if (object == null) return void 0;\n    var value = object[property];\n    return _.isFunction(value) ? value.call(object) : value;\n  };\n\n  // Add your own custom functions to the Underscore object.\n  _.mixin = function(obj) {\n    each(_.functions(obj), function(name) {\n      var func = _[name] = obj[name];\n      _.prototype[name] = function() {\n        var args = [this._wrapped];\n        push.apply(args, arguments);\n        return result.call(this, func.apply(_, args));\n      };\n    });\n  };\n\n  // Generate a unique integer id (unique within the entire client session).\n  // Useful for temporary DOM ids.\n  var idCounter = 0;\n  _.uniqueId = function(prefix) {\n    var id = ++idCounter + '';\n    return prefix ? prefix + id : id;\n  };\n\n  // By default, Underscore uses ERB-style template delimiters, change the\n  // following template settings to use alternative delimiters.\n  _.templateSettings = {\n    evaluate    : /<%([\\s\\S]+?)%>/g,\n    interpolate : /<%=([\\s\\S]+?)%>/g,\n    escape      : /<%-([\\s\\S]+?)%>/g\n  };\n\n  // When customizing `templateSettings`, if you don't want to define an\n  // interpolation, evaluation or escaping regex, we need one that is\n  // guaranteed not to match.\n  var noMatch = /(.)^/;\n\n  // Certain characters need to be escaped so that they can be put into a\n  // string literal.\n  var escapes = {\n    \"'\":      \"'\",\n    '\\\\':     '\\\\',\n    '\\r':     'r',\n    '\\n':     'n',\n    '\\t':     't',\n    '\\u2028': 'u2028',\n    '\\u2029': 'u2029'\n  };\n\n  var escaper = /\\\\|'|\\r|\\n|\\t|\\u2028|\\u2029/g;\n\n  // JavaScript micro-templating, similar to John Resig's implementation.\n  // Underscore templating handles arbitrary delimiters, preserves whitespace,\n  // and correctly escapes quotes within interpolated code.\n  _.template = function(text, data, settings) {\n    var render;\n    settings = _.defaults({}, settings, _.templateSettings);\n\n    // Combine delimiters into one regular expression via alternation.\n    var matcher = new RegExp([\n      (settings.escape || noMatch).source,\n      (settings.interpolate || noMatch).source,\n      (settings.evaluate || noMatch).source\n    ].join('|') + '|$', 'g');\n\n    // Compile the template source, escaping string literals appropriately.\n    var index = 0;\n    var source = \"__p+='\";\n    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {\n      source += text.slice(index, offset)\n        .replace(escaper, function(match) { return '\\\\' + escapes[match]; });\n\n      if (escape) {\n        source += \"'+\\n((__t=(\" + escape + \"))==null?'':_.escape(__t))+\\n'\";\n      }\n      if (interpolate) {\n        source += \"'+\\n((__t=(\" + interpolate + \"))==null?'':__t)+\\n'\";\n      }\n      if (evaluate) {\n        source += \"';\\n\" + evaluate + \"\\n__p+='\";\n      }\n      index = offset + match.length;\n      return match;\n    });\n    source += \"';\\n\";\n\n    // If a variable is not specified, place data values in local scope.\n    if (!settings.variable) source = 'with(obj||{}){\\n' + source + '}\\n';\n\n    source = \"var __t,__p='',__j=Array.prototype.join,\" +\n      \"print=function(){__p+=__j.call(arguments,'');};\\n\" +\n      source + \"return __p;\\n\";\n\n    try {\n      render = new Function(settings.variable || 'obj', '_', source);\n    } catch (e) {\n      e.source = source;\n      throw e;\n    }\n\n    if (data) return render(data, _);\n    var template = function(data) {\n      return render.call(this, data, _);\n    };\n\n    // Provide the compiled function source as a convenience for precompilation.\n    template.source = 'function(' + (settings.variable || 'obj') + '){\\n' + source + '}';\n\n    return template;\n  };\n\n  // Add a \"chain\" function, which will delegate to the wrapper.\n  _.chain = function(obj) {\n    return _(obj).chain();\n  };\n\n  // OOP\n  // ---------------\n  // If Underscore is called as a function, it returns a wrapped object that\n  // can be used OO-style. This wrapper holds altered versions of all the\n  // underscore functions. Wrapped objects may be chained.\n\n  // Helper function to continue chaining intermediate results.\n  var result = function(obj) {\n    return this._chain ? _(obj).chain() : obj;\n  };\n\n  // Add all of the Underscore functions to the wrapper object.\n  _.mixin(_);\n\n  // Add all mutator Array functions to the wrapper.\n  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {\n    var method = ArrayProto[name];\n    _.prototype[name] = function() {\n      var obj = this._wrapped;\n      method.apply(obj, arguments);\n      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];\n      return result.call(this, obj);\n    };\n  });\n\n  // Add all accessor Array functions to the wrapper.\n  each(['concat', 'join', 'slice'], function(name) {\n    var method = ArrayProto[name];\n    _.prototype[name] = function() {\n      return result.call(this, method.apply(this._wrapped, arguments));\n    };\n  });\n\n  _.extend(_.prototype, {\n\n    // Start chaining a wrapped Underscore object.\n    chain: function() {\n      this._chain = true;\n      return this;\n    },\n\n    // Extracts the result from a wrapped and chained object.\n    value: function() {\n      return this._wrapped;\n    }\n\n  });\n\n}).call(this);\n"
  },
  {
    "path": "cmsplugin_cascade/strides.py",
    "content": "from django.core.cache import caches\nfrom django.template.context import make_context\nfrom django.template.exceptions import TemplateDoesNotExist\nfrom django.template.loader import get_template\nfrom django.utils.html import format_html_join\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import get_language_from_request\n\nfrom classytags.utils import flatten_context\nfrom djangocms_text_ckeditor.utils import OBJ_ADMIN_RE\n\nfrom cmsplugin_cascade import app_settings\nfrom cmsplugin_cascade.mixins import CascadePluginMixin\n\n__all__ = ['register_stride', 'StrideContentRenderer']\n\n\nclass EmulateQuerySet:\n    def __init__(self, elements):\n        self.elements = elements\n\n    def all(self):\n        for id, glossary in enumerate(self.elements, 1):\n            yield type(str('StrideInlineElement'), (object,), {'id': id, 'glossary': glossary})()\n\n\nclass StrideElementBase:\n    \"\"\"\n    Emulate a CascadeElement to be used by the CascadeContentRenderer instead of the CMSContentRenderer.\n    \"\"\"\n    def __init__(self, plugin, data, children_data, parent=None):\n        self.plugin = plugin\n        self.id = data.get('pk')\n        self.glossary = data.get('glossary', {})\n        self.sortinline_elements = self.inline_elements = EmulateQuerySet(data.get('inlines', []))\n        self.children_data = children_data\n        self.parent = parent\n\n    @property\n    def pk(self):\n        return self.id\n\n    @property\n    def plugin_class(self):\n        return self.plugin.__class__\n\n    def child_plugin_instances(self):\n        for plugin_type, data, children_data in self.children_data:\n            plugin_class = strides_plugin_map.get(plugin_type)\n            element_class = strides_element_map.get(plugin_type)\n            if element_class:\n                yield element_class(plugin_class(), data, children_data, parent=self)\n\n    def get_num_children(self):\n        return len(self.children_data)\n\n    def get_complete_glossary(self):\n        if not hasattr(self, '_complete_glossary_cache'):\n            self._complete_glossary_cache = self.get_parent_glossary().copy()\n            self._complete_glossary_cache.update(self.glossary or {})\n        return self._complete_glossary_cache\n\n    def get_parent_glossary(self):\n        if self.parent:\n            return self.parent.get_complete_glossary()\n        return {}\n        # TODO: use self.placeholder.glossary as the starting dictionary\n        # template = self.placeholder.page.template if self.placeholder.page else None\n        # return get_placeholder_conf('glossary', self.placeholder.slot, template=template, default={})\n\n    @property\n    def tag_type(self):\n        return self.plugin_class.get_tag_type(self)\n\n    @property\n    def css_classes(self):\n        css_classes = self.plugin_class.get_css_classes(self)\n        return mark_safe(' '.join(c for c in css_classes if c))\n\n    @property\n    def inline_styles(self):\n        inline_styles = self.plugin_class.get_inline_styles(self)\n        return format_html_join(' ', '{0}: {1};', (s for s in inline_styles.items() if s[1]))\n\n    @property\n    def html_tag_attributes(self):\n        attributes = self.plugin_class.get_html_tag_attributes(self)\n        joined = format_html_join(' ', '{0}=\"{1}\"', ((attr, val) for attr, val in attributes.items() if val))\n        if joined:\n            return mark_safe(' ' + joined)\n        return ''\n\n\nclass TextStrideElement:\n    def __init__(self, plugin, data, children_data, parent=None):\n        self.plugin = plugin\n        self.pk = data.get('pk')\n        self.body = data.get('body')\n        self.children_data = children_data\n        self.parent = parent\n\n    def tags_to_user_html(self, context, placeholder):\n        content_renderer = context['cms_content_renderer']\n        children_instances = {}\n        for plugin_type, data, children_data in self.children_data:\n            plugin_class = strides_plugin_map.get(plugin_type)\n            element_class = strides_element_map.get(plugin_type)\n            if element_class and 'pk' in data:\n                sub_plugin = plugin_class()\n                children_instances[data['pk']] = element_class(sub_plugin, data, children_data, parent=self)\n\n        def _render_tag(m):\n            plugin_id = int(m.groupdict()['pk'])\n            instance = children_instances[plugin_id]\n            with context.push():\n                sub_context = instance.plugin.render(context, instance, placeholder)\n                return content_renderer.render_plugin(instance, sub_context)\n\n        return OBJ_ADMIN_RE.sub(_render_tag, self.body)\n\n\nclass StridePluginBase(CascadePluginMixin):\n    \"\"\"\n    Whenever djangocms-cascade is used in readonly mode, all Cascade plugins are instantiated a second time\n    where class CascadePluginBase is replaced against this class in order to remove its dependency to django-CMS.\n    \"\"\"\n\n    def __init__(self, model=None, admin_site=None, glossary_fields=None):\n        if isinstance(glossary_fields, (list, tuple)):\n            self.glossary_fields = list(glossary_fields)\n        elif not hasattr(self, 'glossary_fields'):\n            self.glossary_fields = []\n\n    @classmethod\n    def super(cls, klass, instance):\n        return super(strides_plugin_map[klass.__name__], instance)\n\n    def render(self, context, instance, placeholder):\n        context.update({\n            'instance': instance,\n        })\n        return context\n\n    def _get_render_template(self, context, instance, placeholder):\n        if hasattr(self, 'get_render_template'):\n            template = self.get_render_template(context, instance, placeholder)\n        elif getattr(self, 'render_template', False):\n            template = getattr(self, 'render_template', False)\n        else:\n            template = None\n\n        if not template:\n            raise TemplateDoesNotExist(\"plugin {} has no render_template\".format(self.__class__))\n        return template\n\n    def in_edit_mode(self, request, placeholder):\n        return False\n\n    def get_previous_instance(self, obj):\n        if obj and obj.parent:\n            for pos, sibling in enumerate(obj.parent.children_data):\n                if sibling[1].get('pk') == obj.pk and pos > 0:\n                    prev_pt, prev_data, prev_cd = obj.parent.children_data[pos - 1]\n                    return strides_plugin_map[prev_pt]()\n\n    def get_next_instance(self, obj):\n        if obj and obj.parent:\n            for pos, sibling in enumerate(obj.parent.children_data):\n                if sibling[1].get('pk') == obj.pk and pos < len(obj.parent.children_data):\n                    next_pt, next_data, next_cd = obj.parent.children_data[pos + 1]\n                    return strides_plugin_map[next_pt]()\n\n\nclass TextStridePlugin(StridePluginBase):\n    render_template = 'cms/plugins/text.html'\n\n    def render(self, context, instance, placeholder):\n        context.update({\n            'body': instance.tags_to_user_html(context, placeholder),\n        })\n        return context\n\n\nclass StrideContentRenderer:\n    def __init__(self, request):\n        self.request = request\n        self.language = get_language_from_request(request)\n        self._cached_templates = {}\n\n    def render_cascade(self, context, tree_data):\n        contents = []\n        # create temporary copy of context to prevent pollution for other CMS placeholders\n        context = make_context(flatten_context(context))\n        for plugin_type, data, children_data in tree_data.get('plugins', []):\n            plugin_class = strides_plugin_map.get(plugin_type)\n            element_class = strides_element_map.get(plugin_type)\n            plugin_instance = element_class(plugin_class(), data, children_data)\n            # create a temporary object to store the plugins cache status\n            cms_cachable_plugins = type(str('CachablePlugins'), (object,), {'value': True})\n            context.push(cms_cachable_plugins=cms_cachable_plugins)\n            contents.append(self.render_plugin(plugin_instance, context))\n        return mark_safe(''.join(contents))\n\n    def render_plugin(self, instance, context, placeholder=None, editable=False):\n        from sekizai.helpers import get_varname as get_sekizai_context_key\n\n        sekizai_context_key = get_sekizai_context_key()\n        if app_settings.CMSPLUGIN_CASCADE['cache_strides'] and getattr(instance.plugin, 'cache', not editable):\n            cache = caches['default']\n            key = 'cascade_element-{}'.format(instance.pk)\n            content = cache.get(key)\n            if content:\n                context[sekizai_context_key]['css'].extend(cache.get(key + ':css_list', []))\n                context[sekizai_context_key]['js'].extend(cache.get(key + ':js_list', []))\n                return content\n        else:\n            context['cms_cachable_plugins'].value = False\n        context = instance.plugin.render(context, instance, placeholder)\n        context = flatten_context(context)\n\n        template = instance.plugin._get_render_template(context, instance, placeholder)\n        template = self.get_cached_template(template)\n        content = template.render(context)\n        if context['cms_cachable_plugins'].value:\n            cache.set(key, content)\n            cache.set(key + ':css_list', context[sekizai_context_key]['css'].data)\n            cache.set(key + ':js_list', context[sekizai_context_key]['js'].data)\n        return content\n\n    def user_is_on_edit_mode(self):\n        return False\n\n    def get_cached_template(self, template):\n        if hasattr(template, 'render'):\n            return template\n\n        if not template in self._cached_templates:\n            self._cached_templates[template] = get_template(template)\n        return self._cached_templates[template]\n\n\ndef register_stride(name, bases, attrs, model_mixins):\n    # create a fake plugin class\n    plugin_bases = tuple(strides_plugin_map.get(b.__name__, b) for b in bases)\n    if name == 'CascadePluginBase':\n        plugin_bases += (StridePluginBase,)\n        strides_plugin_map[name] = type(str('StridePluginBase'), plugin_bases, {})\n    else:\n        strides_plugin_map[name] = type(name, plugin_bases, attrs)\n\n        # create a corresponding stride element class\n        element_bases = model_mixins + (StrideElementBase,)\n        strides_element_map[name] = type(str(name + 'Element'), element_bases, {})\n\n\nstrides_plugin_map = {\n    'TextPlugin': TextStridePlugin,\n}\nstrides_element_map = {\n    'TextPlugin': TextStrideElement,\n}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/change_form.html",
    "content": "{% extends \"admin/cms/usersettings/change_form.html\" %}\n{% load admin_urls i18n static %}\n\n{% block field_sets %}\n\t{{ plugin_intro }}\n\t{% if empty_form %}\n<div class=\"module\">\n\t<h2>{% trans \"There are no further settings for this plugin\" %}</h2>\n\t<p>{% trans \"Please hit OK to save.\" %}</p>\n</div>\n\t{% else %}\n\t{{ block.super }}\n\t{% endif %}\n\t{{ plugin_footnote }}\n{% endblock %}\n\n{% block after_field_sets %}\n\t{{ block.super }}\n\t<script type=\"text/javascript\">\n\tdjango.cascade = django.cascade || {};\n\tdjango.cascade.page_sections_url = \"{% url 'admin:get_page_sections' %}\";\n\tdjango.cascade.validate_exturl_url = \"{% url 'admin:validate_exturl' %}\";\n\tdjango.cascade.ring_plugin_bases = {{% for key, bases in ring_plugin_bases.items %}\n\t\t{{ key }}: \"[{{ bases|join:\", \" }}]\"{% if not forloop.last %},{% endif %}{% endfor %}\n\t};\n\t{% if ring_plugin %}\n\tdjango.jQuery(function($) {\n\t\tnew django.cascade.{{ ring_plugin }}();\n\t});\n\t{% endif %}{% if icon_fonts %}\n\tdjango.cascade.iconfont_stylesheet_urls = {{% for icof in icon_fonts %}\n\t\t'{{ icof.id }}': '{{ icof.get_stylesheet_url }}'{% if not forloop.last %},{% endif %}{% endfor %}\n\t};\n\tdjango.cascade.fetch_fonticons_url = \"{% url 'admin:fetch_fonticons' %}\";\n\t{% endif %}\n\t</script>\n{% endblock %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/ckeditor.wysiwyg.txt",
    "content": "(function() {\n\tCKEDITOR.dtd.$removeEmpty.i = 0;\n\tCKEDITOR.stylesSet.add('default', [{% for text_editor_config in text_editor_configs %}\n\t\t{{ text_editor_config.get_config|safe }}{% if not forloop.last %},{% endif %}{% spaceless %}\n{% endspaceless %}{% endfor %}\n\t]);{% for icon_font in icon_fonts %}\n\tCMS.CKEditor.editor.addContentsCss('{{ icon_font.get_stylesheet_url }}');{% spaceless %}\n{% endspaceless %}{% endfor %}\n})();\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/clipboard_close_frame.html",
    "content": "<div>\n\t<div class=\"messagelist\">\n\t\t<div class=\"success\"></div>\n\t</div>\n</div>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/clipboard_paste_plugins.html",
    "content": "<script>\n\twindow.top.CMS.API.StructureBoard.invalidateState('PASTE', {{ structure_data|safe }});\n</script>\n<div>\n\t<div class=\"messagelist\">\n\t\t<div class=\"success\"></div>\n\t</div>\n</div>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/clipboard_reload_page.html",
    "content": "<script>\n\twindow.top.CMS.API.Helpers.reloadBrowser();\n</script>\n<div>\n\t<div class=\"messagelist\">\n\t\t<div class=\"error\"></div>\n\t</div>\n</div>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/leaflet_plugin_change_form.html",
    "content": "{% extends \"cascade/admin/change_form.html\" %}\n{% load i18n %}\n\n{% block after_field_sets %}\n\t{{ block.super }}\n\t<div id=\"leaflet_edit_map\"></div>\n\t<script type=\"text/javascript\">\n\tdjango.cascade.leaflet_settings = {{ settings }};\n\tdjango.cascade.no_results = \"{% translate 'no results' %}\";\n\t</script>\n{% endblock %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/legacy_widgets/button_sizes.html",
    "content": "{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"field-box\">\n\t\t<div class=\"button-samples\">\n\t\t\t<span class=\"btn btn-primary {{ widget.value }}\">{{ widget.label }}</span>\n\t\t</div>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/legacy_widgets/button_types.html",
    "content": "{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"field-box\">\n\t\t<span class=\"btn {{ widget.value }}\">{{ widget.label }}</span>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/legacy_widgets/container_breakpoints.html",
    "content": "{% load static %}{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"field-box\">\n\t\t<div class=\"container-thumbnail\">\n\t\t\t<img src=\"{% static 'cascade/admin/bootstrap4' %}{{ widget.attrs.version }}/{{ widget.value }}.svg\" style=\"height: 55px;\" />\n\t\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t\t</div>\n\t</div>{% endwith %}{% endfor %}\n</div>{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/legacy_widgets/panel_types.html",
    "content": "{% load i18n %}{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"field-box\">\n\t\t<div class=\"panel {{ widget.value }}\">\n\t\t\t<div class=\"panel-heading\">{{ widget.label }}</div>\n\t\t\t<div class=\"panel-body\">{% trans \"Content\" %}</div>\n\t\t</div>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/segmentation_list.html",
    "content": "{% extends \"admin/base_site.html\" %}\n<<<<<<< HEAD\n{% load i18n admin_urls admin_list static %}\n=======\n{% load i18n admin_urls static admin_list %}\n>>>>>>> d2c35e77fa7244d982632ea643aeb976eab3b072\n\n{% block extrastyle %}\n  {{ block.super }}\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"{% static \"admin/css/changelists.css\" %}\" />\n  {% if cl.formset %}\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{% static \"admin/css/forms.css\" %}\" />\n  {% endif %}\n  {% if cl.formset or action_form %}\n    <script type=\"text/javascript\" src=\"{% url 'admin:jsi18n' %}\"></script>\n  {% endif %}\n  {{ media.css }}\n  {% if not actions_on_top and not actions_on_bottom %}\n    <style>\n      #changelist table thead th:first-child {width: inherit}\n    </style>\n  {% endif %}\n{% endblock %}\n\n{% block extrahead %}\n{{ block.super }}\n{{ media.js }}\n{% if action_form %}{% if actions_on_top or actions_on_bottom %}\n<script type=\"text/javascript\">\n(function($) {\n    $(document).ready(function($) {\n        $(\"tr input.action-select\").actions();\n    });\n})(django.jQuery);\n</script>\n{% endif %}{% endif %}\n{% endblock %}\n\n{% block bodyclass %}change-list{% endblock %}\n\n{% block breadcrumbs %}\n<div class=\"breadcrumbs\">\n<a href=\"{% url 'admin:index' %}\">{% trans 'Home' %}</a>\n&rsaquo; <a href=\"{% url 'admin:app_list' app_label=opts.app_label %}\">{{ app_label|capfirst|escape }}</a>\n&rsaquo; {% if has_change_permission %}<a href=\"{% url opts|admin_urlname:'changelist' %}\">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}\n&rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:\"18\" }}{% endif %}\n</div>\n{% endblock %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/sharedglossary_change_form.html",
    "content": "{% extends \"admin/change_form.html\" %}\n{% load cms_tags %}\n\n{% block after_field_sets %}\n\t{{ block.super }}\n\t<script type=\"text/javascript\">\n\tdjango.cascade = django.cascade || {};\n\tdjango.cascade.page_sections_url = \"{% url 'admin:get_page_sections' %}\";\n\tdjango.cascade.ring_plugin_bases = {{% for key, bases in ring_plugin_bases.items %}\n\t\t{{ key }}: \"[{{ bases|join:\", \" }}]\"{% if not forloop.last %},{% endif %}{% endfor %}\n\t};\n\tdjango.cascade.iconfont_stylesheet_urls = {{% for icof in icon_fonts %}\n\t\t'{{ icof.id }}': '{{ icof.get_stylesheet_url }}'{% if not forloop.last %},{% endif %}{% endfor %}\n\t};\n{% if ring_plugin %}\n\tdjango.jQuery(function($) {\n\t\tnew django.cascade.{{ ring_plugin }}();\n\t});\n{% endif %}\n\t</script>\n{% endblock %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/borderchoice.html",
    "content": "{% spaceless %}\n<div class=\"cascade-colorpicker cascade-border-choice\">\n\t{% with widget=widget.subwidgets.0 %}{% include widget.template_name %}{% endwith %}<em></em>\n\t{% with widget=widget.subwidgets.1 %}{% include widget.template_name %}{% endwith %}<em></em>\n\t{% with widget=widget.subwidgets.2 %}{% include widget.template_name %}{% endwith %}\n\t{% with widget=widget.subwidgets.3 %}{% include widget.template_name %}{% endwith %}\n</div>\n{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/button_sizes.html",
    "content": "{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"fieldBox\">\n\t\t<span class=\"btn btn-primary {{ widget.value }}\">{{ widget.label }}</span>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/button_types.html",
    "content": "{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"fieldBox\">\n\t\t<span class=\"btn {{ widget.value }}\">{{ widget.label }}</span>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/colorpicker.html",
    "content": "{% load i18n %}\n{% spaceless %}\n<div class=\"cascade-colorpicker cascade-color-picker\">\n\t{% with widget=widget.subwidgets.0 %}{% include widget.template_name %}{% endwith %}\n\t{% with widget=widget.subwidgets.1 %}{% include widget.template_name %}{% endwith %}\n\t<em>{% trans \"Inherit\" %}</em>\n\t{% with widget=widget.subwidgets.2 %}{% include widget.template_name %}{% endwith %}\n</div>\n{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/container_breakpoints.html",
    "content": "{% load static %}{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"fieldBox\">\n\t\t<div class=\"container-thumbnail\">\n\t\t\t<img src=\"{% static 'cascade/admin/bootstrap4/' %}{{ widget.value }}.svg\" style=\"height: 55px;\" />\n\t\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t\t</div>\n\t</div>{% endwith %}{% endfor %}\n</div>{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/inherit_color.html",
    "content": "{% load i18n %}\n<em style=\"margin-left: 1em; margin-right: 1em;\">{% trans \"Inherit\" %}</em> {% include \"django/forms/widgets/input.html\" %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/admin/widgets/panel_types.html",
    "content": "{% load i18n %}{% spaceless %}\n<div class=\"form-row\">\n{% for group, options, index in widget.optgroups %}{% with widget=options.0 %}\n\t<div class=\"fieldBox\">\n\t\t<div class=\"panel {{ widget.value }}\">\n\t\t\t<div class=\"panel-heading\">{{ widget.label }}</div>\n\t\t\t<div class=\"panel-body\">{% trans \"Content\" %}</div>\n\t\t</div>\n\t\t<div class=\"label\">{% include widget.template_name %}</div>\n\t</div>\n{% endwith %}{% endfor %}</div>\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/accordion.html",
    "content": "{% load l10n cascade_tags %}\n\n{% localize off %}{% spaceless %}{% with inline_styles=instance.inline_styles plugin_id=instance.id %}\n<div id=\"cmsplugin_{{ plugin_id }}\" class=\"{{ instance.css_classes }}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n\t{% for card in instance.child_plugin_instances %}\n\t<div class=\"card\">\n\t\t<div class=\"card-header\" id=\"heading_{{ card.id }}\">\n\t\t\t<h5 class=\"mb-0\">\n\t\t\t\t<button class=\"btn btn-link\" type=\"button\" data-toggle=\"collapse\" data-target=\"#collapse_{{ card.id }}\" aria-expanded=\"true\" aria-controls=\"collapse_{{ card.id }}\">\n\t\t\t\t{{ card.glossary.heading }}\n\t\t\t\t</button>\n\t\t\t</h5>\n\t\t</div>\n\t\t<div id=\"collapse_{{ card.id }}\" class=\"collapse\" aria-labelledby=\"heading_{{ card.id }}\" data-parent=\"#cmsplugin_{{ plugin_id }}\">\n\t\t\t<div class=\"card-body\">\n\t\t{% for child in card.child_plugin_instances %}\n\t\t\t{% render_plugin child %}\n\t\t{% endfor %}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\t{% endfor %}\n</div>\n{% endwith %}{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/angular-ui/accordion.html",
    "content": "{% load cascade_tags %}{% spaceless %}\n{% with inline_styles=instance.inline_styles %}\n\n<uib-accordion close-others=\"{% if instance.glossary.close_others %}true{% else %}false{% endif %}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n{% for card in instance.child_plugin_instances %}{% with css_classes=card.css_classes %}\n\t<div uib-accordion-group heading=\"{{ card.glossary.heading }}\"{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if forloop.first and instance.glossary.first_is_open %} is-open=\"true\"{% endif %}{% if card.no_body_padding %} template-url=\"cascade/accordion-group-no-padding.html\"{% endif %}>\n\t{% render_plugin card %}\n\t</div>\n{% endwith %}{% endfor %}\n</uib-accordion>\n{% endwith %}\n\n{% verbatim %}\n<script id=\"cascade/accordion-group-no-padding.html\" type=\"text/ng-template\">\n<div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"card-header\" ng-keypress=\"toggleOpen($event)\">\n\t<h5 class=\"mb-0\">\n\t\t<a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::cardId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\" ng-disabled=\"isDisabled\" uib-tabindex-toggle><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n\t</h5>\n</div>\n<div id=\"{{::cardId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabcard\" class=\"card-collapse collapse\" uib-collapse=\"!isOpen\">\n\t<div class=\"card-body p-0\" ng-transclude></div>\n</div>\n</script>\n{% endverbatim %}\n\n{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/angular-ui/carousel.html",
    "content": "{% load l10n cascade_tags %}{% spaceless %}\n\n{% with css_classes=instance.css_classes %}\n<div uib-carousel{% if css_classes %} class=\"{{ css_classes }}\"{% endif %} interval=\"{{ instance.options.interval|default:5000|unlocalize }}\" active=\"0\" {# style=\"{{ instance.inline_styles }}\" #}>\n\t{% for slide in instance.child_plugin_instances %}\n\t<div uib-slide index=\"{{ forloop.counter0 }}\">{% render_plugin slide %}</div>\n\t{% endfor %}\n</div>\n{% endwith %}\n\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/angular-ui/tabset.html",
    "content": "{% load static cascade_tags %}\n{% spaceless %}{% with justified=instance.glossary.justified %}\n\n<uib-tabset{% if justified %} justified=\"true\"{% endif %}>\n{% for pane in instance.child_plugin_instances %}\n\t<uib-tab heading=\"{{ pane.glossary.tab_title|safe }}\">{% render_plugin pane %}</uib-tab>\n{% endfor %}\n</uib-tabset>\n\n{% endwith %}{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/button.html",
    "content": "{% extends \"cascade/link/link-base.html\" %}\n{% load sekizai_tags %}\n\n{% block link_link %}{% with instance_link=instance.link|default:\"#\" %}\n{% if stylesheet_url %}{% addtoblock \"css\" %}<link href=\"{{ stylesheet_url }}\" rel=\"stylesheet\" type=\"text/css\" />{% endaddtoblock %}{% endif %}\n{{ block.super }}\n{% endwith %}{% endblock link_link %}\n\n{% block link_content %}{{ icon_left }}{{ block.super }}{{ icon_right }}{% endblock %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/card.html",
    "content": "{% load cascade_tags %}{% spaceless %}\n{% with inline_styles=instance.inline_styles %}\n<div class=\"{{ instance.css_classes }}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n{% for child in instance.child_plugin_instances %}\n\t{% render_plugin child %}\n{% endfor %}\n</div>\n{% endwith %}{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/carousel-slide.html",
    "content": "{% include \"cascade/bootstrap4/picture.html\" %}\n{% load cascade_tags %}\n{% if instance.num_children %}\n<div class=\"carousel-caption\">\n{% for child in instance.child_plugin_instances %}{% render_plugin child %}{% endfor %}\n</div>\n{% endif %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/carousel.html",
    "content": "{% load l10n cascade_tags %}\n{% localize off %}{% spaceless %}{% with bootstrap_element=\"carousel\" inline_styles=instance.inline_styles %}\n<div id=\"cmsplugin_{{ instance.id }}\" class=\"{{ instance.css_classes }}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}{{ instance.html_tag_attributes }}>\n<ol class=\"carousel-indicators\">\n\t{% for slide in instance.child_plugin_instances %}\n\t<li data-target=\"#cmsplugin_{{ instance.id }}\" data-slide-to=\"{{ forloop.counter0 }}\"{% if forloop.first %} class=\"active\"{% endif %}></li>\n\t{% endfor %}\n</ol>\n<div class=\"carousel-inner\" role=\"listbox\">\n\t{% for slide in instance.child_plugin_instances %}\n\t<div class=\"carousel-item {% if forloop.first %} active{% endif %}\">\n\t\t{% render_plugin slide %}\n\t</div>\n\t{% endfor %}\n\t</div>\n\t<a class=\"carousel-control-prev\" href=\"#cmsplugin_{{ instance.id }}\" role=\"button\" data-slide=\"prev\">\n\t\t<span class=\"carousel-control-prev-icon\" aria-hidden=\"true\"></span>\n\t</a>\n\t<a class=\"carousel-control-next\" href=\"#cmsplugin_{{ instance.id }}\" role=\"button\" data-slide=\"next\">\n\t\t<span class=\"carousel-control-next-icon\" aria-hidden=\"true\"></span>\n\t</a>\n</div>\n{% endwith %}{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/framedicon.html",
    "content": "{% load sekizai_tags %}\n{% if stylesheet_url %}{% addtoblock \"css\" %}<link href=\"{{ stylesheet_url }}\" rel=\"stylesheet\" type=\"text/css\" />{% endaddtoblock %}{% endif %}\n{% spaceless %}\n{% with instance_link=instance.link tag_type=instance.tag_type css_classes=instance.css_classes inline_styles=instance.inline_styles %}\n{% if instance_link %}<a href=\"{{ instance_link }}\" {{ link_html_tag_attributes }}>{% endif %}\n{% if tag_type %}<{{ tag_type }}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>{% endif %}\n\t<span {{ icon_font_attrs }}></span>\n{% if tag_type %}</{{ tag_type }}>{% endif %}\n{% if instance_link %}</a>{% endif %}\n{% endwith %}\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/image.html",
    "content": "{% load l10n static cascade_tags thumbnail %}\n{% localize off %}{% spaceless %}{% with css_classes=instance.css_classes inline_styles=instance.inline_styles %}\n<img {{ instance.html_tag_attributes }}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}\n{% if instance.image|is_valid_image %}\n\t{% if sizes %} sizes=\"{{ sizes|join:', ' }}\"{% endif %}\n\tsrcset=\"{% for key, srcset in srcsets.items %}\n\t\t{% thumbnail instance.image srcset.size crop=srcset.crop upscale=srcset.upscale subject_location=srcset.subject_location as thumb %}{{ thumb.url }} {{ key }}{% if not forloop.last %},{% endif %}\n\t{% endfor %}\"\n\t{% thumbnail instance.image src.size crop=src.crop upscale=src.upscale subject_location=src.subject_location as thumb %}\n\tsrc=\"{{ thumb.url }}\"{% if not sizes %} width=\"{{ thumb.width }}\" height=\"{{ thumb.height }}\"{% endif %}\n{% else %}\n\tsrc=\"{% static 'cascade/fallback.svg' %}\"\n{% endif %}\n/>\n{% endwith %}{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html",
    "content": "{% load l10n cascade_tags thumbnail sekizai_tags %}\n\n{% addtoblock \"css\" %}{% localize off %}\n<style>\n{% for elem in instance.element_heights %}\n@media{{ elem.media }} {\n\t#cascadeelement_id-{{ instance.pk }} {\n\t\theight: {{ elem.height }};\n\t}\n}\n{% endfor %}\n#cascadeelement_id-{{ instance.pk }} {\n\t{{ instance.background_color }}{% if instance.image %}\n\t{{ instance.background_attachment }}\n\t{{ instance.background_position }}\n\t{{ instance.background_repeat }}\n\t{{ instance.background_size }}{% endif %}\n\t{{ instance.inline_styles }}\n}\n{% if instance.image %}\n\t{% for elem in elements %}\n\t\t{% thumbnail instance.image elem.size crop=elem.crop upscale=elem.upscale subject_location=elem.subject_location as thumb %}\n\t\t\t{% if elem.size2 %}{% thumbnail instance.image elem.size2 zoom=elem.zoom crop=elem.crop upscale=elem.upscale subject_location=elem.subject_location as thumb2 %}{% endif %}\n@media{{ elem.media }} {\n\t#cascadeelement_id-{{ instance.pk }} {\n\t\tbackground-image: url({{ thumb.url }});{% if thumb2 %}{% for imgage_set in CSS_PREFIXES.image_set %}\n\t\tbackground-image: {{ imgage_set }}(url({{ thumb.url }}) 1x, url({{ thumb2.url }}) 2x);{% endfor %}{% endif %}\n\t}\n}\n\t{% endfor %}\n{% endif %}\n</style>\n{% endlocalize %}{% endaddtoblock %}\n\n{% localize off %}{% spaceless %}\n<div id=\"cascadeelement_id-{{ instance.pk }}\" class=\"{{ instance.css_classes }}\">\n\t{% for plugin in instance.child_plugin_instances %}\n\t\t{% render_plugin plugin %}\n\t{% endfor %}\n</div>  \n{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/linked-image.html",
    "content": "{% extends \"cascade/link/link-base-nostyle.html\" %}\n{% spaceless %}\n\n{% block link_content %}\n\t{% include \"cascade/bootstrap4/image.html\" %}\n{% endblock link_content %}\n\n{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/linked-picture.html",
    "content": "{% extends \"cascade/link/link-base.html\" %}\n{% spaceless %}\n\n{% block link_content %}\n\t{% include \"cascade/bootstrap4/picture.html\" %}\n{% endblock link_content %}\n\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/picture.html",
    "content": "{% load l10n static cascade_tags thumbnail %}\n{% localize off %}{% spaceless %}\n<picture{{ instance.html_tag_attributes }}>\n{% if instance.image|is_valid_image %}\n\t{% for elem in elements %}\n\t\t{% thumbnail instance.image elem.size zoom=elem.zoom crop=elem.crop upscale=elem.upscale subject_location=elem.subject_location as thumb %}\n\t\t{% if elem.size2 %}\n\t\t\t{% thumbnail instance.image elem.size2 zoom=elem.zoom crop=elem.crop upscale=elem.upscale subject_location=elem.subject_location as thumb2 %}\n\t\t{% endif %}\n\t\t{% if elem.tag == \"source\" %}\n\t<source srcset=\"{{ thumb.url }}{% if thumb2 %} 1x, {{ thumb2.url }} 2x{% endif %}\" media=\"{{ elem.media }}\" />\n\t\t{% elif elem.tag == \"img\" %}\n\t<img src=\"{{ thumb.url }}\" class=\"{{ instance.css_classes }}\"{% if not is_fluid %} width=\"{{ thumb.width }}\" height=\"{{ thumb.height }}\"{% endif %} />\n\t\t{% endif %}\n\t{% endfor %}\n{% else %}\n\t<img src=\"{% static 'cascade/fallback.svg' %}\" style=\"background-color:lightgray;\" class=\"{{ instance.css_classes }}\" />\n{% endif %}\n</picture>\n{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/secmenu-list-group.html",
    "content": "{% load bootstrap_tags %}\n<div class=\"list-group\">\n\t{% main_menu_below_id page_id \"cascade/bootstrap4/secmenu-list-item.html\" offset limit %}\n</div>"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/secmenu-list-item.html",
    "content": "{% for child in children %}\n\t<a href=\"{{ child.get_absolute_url }}\" class=\"list-group-item{% if child.selected %} active{% endif %}\">{{ child.get_menu_title }}</a>\n{% endfor %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/secmenu-unstyled-list-item.html",
    "content": "{% for child in children %}\n\t<li><a href=\"{{ child.get_absolute_url }}\">{{ child.get_menu_title }}</a></li>\n{% endfor %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/secmenu-unstyled-list.html",
    "content": "{% load bootstrap_tags %}\n<ul class=\"list-unstyled\">\n\t{% main_menu_below_id page_id \"cascade/bootstrap4/secmenu-unstyled-list-item.html\" offset limit %}\n</ul>"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/tabset.html",
    "content": "{% load l10n cascade_tags %}\n{% localize off %}{% spaceless %}{% with inline_styles=instance.inline_styles css_classes=instance.css_classes %}\n<ul class=\"nav{% if css_classes %} {{ css_classes }}{% endif %}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %} role=\"tablist\">\n\t{% for pane in instance.child_plugin_instances %}\n\t<li class=\"nav-item\">\n\t\t<a class=\"nav-link{% if forloop.first %} active{% endif %}\" href=\"#cmsplugin_{{ pane.id }}\" role=\"tab\" data-toggle=\"tab\">\n\t\t{{ pane.glossary.tab_title|safe }}\n\t\t</a>\n\t</li>\n\t{% endfor %}\n</ul>\n\n<div class=\"tab-content\">\n\t{% for pane in instance.child_plugin_instances %}\n\t<div role=\"tabpanel\" class=\"tab-pane{% if forloop.first %} active{% endif %}\" id=\"cmsplugin_{{ pane.id }}\">{% render_plugin pane %}</div>\n\t{% endfor %}\n</div>\n{% endwith %}{% endspaceless %}{% endlocalize %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/bootstrap4/youtube.html",
    "content": "{% with inline_styles=instance.inline_styles %}\n<div class=\"{{ instance.css_classes }}\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n\t<iframe class=\"embed-responsive-item\" src=\"{{ youtube_url }}\" {{ allowfullscreen }}></iframe>\n</div>\n{% endwith %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/generic/does_not_exist.html",
    "content": "{# instead of throwing an exception, just render a warning message #}\n<div class=\"alert alert-danger\" role=\"alert\">The chosen template '{{ instance.glossary.render_template }}' could not be found.</div>"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/generic/heading.html",
    "content": "{% load l10n %}\n{% with tag_type=instance.tag_type css_classes=instance.css_classes inline_styles=instance.inline_styles element_id=instance.element_id %}\n<{{ tag_type }}{% if element_id %} id=\"{{ element_id|unlocalize }}\"{% endif %}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>{{ content }}</{{ tag_type }}>\n{% endwith %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/generic/naked.html",
    "content": "{% load cascade_tags %}\n{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %}\n{% for plugin in instance.child_plugin_instances %}\n\t{% render_plugin plugin %}\n{% endfor %}\n{% endwith %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/generic/single.html",
    "content": "{% with css_classes=instance.css_classes inline_styles=instance.inline_styles %}\n<{{ instance.tag_type }}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %} />\n{% endwith %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/generic/wrapper.html",
    "content": "{% load l10n cascade_tags %}\n{% with tag_type=instance.tag_type css_classes=instance.css_classes inline_styles=instance.inline_styles element_id=instance.element_id %}\n<{{ tag_type }}{{ instance.html_tag_attributes }}{% if element_id %} id=\"{{ element_id|unlocalize }}\"{% endif %}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n{% for plugin in instance.child_plugin_instances %}\n\t{% render_plugin plugin %}\n{% endfor %}\n</{{ tag_type }}>\n{% endwith %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/link/.editorconfig",
    "content": "[*.html]\ninsert_final_newline = false\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/link/link-base-nostyle.html",
    "content": "{% spaceless %}{% with instance_link=instance.link download=instance.download_name %}\n{% block link_link %}\n\t{% if instance_link %}<a href=\"{{ instance_link }}\"{{ instance.html_tag_attributes }}{% if download %} download=\"{{ download }}\" target=\"_blank\"{% endif %}>{% endif %}{% block link_content %}{{ instance.content }}{% endblock %}{% if instance_link %}</a>{% endif %}\n{% endblock %}\n{% endwith %}{% endspaceless %}{# NEVER ADD A FINAL NEWLINE #}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/link/link-base.html",
    "content": "{% spaceless %}{% with instance_link=instance.link instance_css_classes=instance.css_classes download=instance.download_name instance_inline_styles=instance.inline_styles %}\n{% block link_link %}\n\t{% if instance_link %}<a href=\"{{ instance_link }}\"{{ instance.html_tag_attributes }}{% if instance_css_classes %} class=\"{{ instance_css_classes }}\"{% endif %}{% if download %} download=\"{{ download }}\" target=\"_blank\"{% endif %}{% if instance_inline_styles %} style=\"{{ instance_inline_styles }}\"{% endif %}>{% endif %}{% block link_content %}{{ instance.content }}{% endblock %}{% if instance_link %}</a>{% endif %}\n{% endblock %}\n{% endwith %}{% endspaceless %}{# NEVER ADD A FINAL NEWLINE #}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/link/text-link-linebreak.html",
    "content": "{% include \"cascade/link/text-link.html\" %}<br/>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/link/text-link.html",
    "content": "{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %}<a href=\"{{ instance.link }}\"{{ instance.html_tag_attributes }}{% if instance_css_classes %} class=\"{{ instance_css_classes }}\"{% endif %}{% if instance_inline_styles %} style=\"{{ instance_inline_styles }}\"{% endif %}>{% block link_content %}{{ instance.content }}{% endblock %}</a>{% endwith %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/googlemap.html",
    "content": "{% load l10n static sekizai_tags thumbnail %}\n\n{% addtoblock \"css\" %}{% localize off %}\n<style>\n#cascadeelement_id-{{ instance.pk }} {\n\twidth: {{ instance.glossary.map_width }};\n\theight: {{ instance.glossary.map_height }};\n}\n</style>\n{% endlocalize %}{% endaddtoblock %}\n\n{% addtoblock \"js\" %}<script async=\"async\" defer=\"defer\" src=\"https://maps.googleapis.com/maps/api/js?key={{ config.apiKey }}&callback=initMap\"></script>{% endaddtoblock %}\n\n{% addtoblock \"js\" %}{% localize off %}\n<script type=\"text/javascript\">\nfunction initMap() {\n\tvar mapPosition = {{ instance.map_position }};\n\tvar canvas = document.getElementById('cascadeelement_id-' + {{ instance.pk }});\n\tvar viewMap = new google.maps.Map(canvas, {\n\t\tzoom: mapPosition.zoom,\n\t\tcenter: mapPosition\n\t});\n\t{% for marker in markers %}\n\t(function(data) {\n\t\tvar iconUrl, infowindow;\n\t\t{% if marker.image %}\n\t\t\t{% thumbnail marker.image marker.size as thumb %}\n\t\ticonUrl = \"{{ thumb.url }}\";\n\t\t{% endif %}\n\t\tvar marker = new google.maps.Marker({\n\t\t\tposition: data.position,\n\t\t\ttitle: data.title,\n\t\t\ticon: iconUrl\n\t\t});\n\t\tif (data.popup_text) {\n\t\t\tinfowindow = new google.maps.InfoWindow({content: data.popup_text});\n\t\t\tmarker.addListener('click', function() {\n\t\t\t\tinfowindow.open(viewMap, marker);\n\t\t\t});\n\t\t}\n\t\tmarker.setMap(viewMap);\n\t})({{ marker.data }});\n\t{% endfor %}\n}\n</script>\n{% endlocalize %}{% endaddtoblock %}\n\n<div id=\"cascadeelement_id-{{ instance.pk|unlocalize }}\"></div>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/leaflet.html",
    "content": "{% load l10n static sekizai_tags thumbnail %}\n\n{% addtoblock \"css\" %}<link href=\"{% static 'node_modules/leaflet/dist/leaflet.css' %}\" rel=\"stylesheet\" type=\"text/css\" />{% endaddtoblock %}\n{% addtoblock \"js\" %}<script src=\"{% static 'node_modules/leaflet/dist/leaflet.js' %}\" type=\"text/javascript\"></script>{% endaddtoblock %}\n\n{% addtoblock \"css\" %}{% localize off %}\n<style>\n#cascadeelement_id-{{ instance.pk }} {\n\twidth: {{ instance.glossary.map_width }};\n\theight: {{ instance.glossary.map_height }};{% with map_min_height=instance.glossary.map_min_height %}{% if map_min_height %}\n\tmin-height: {{ map_min_height }};{% endif %}{% endwith %}\n}\n</style> \n{% endlocalize %}{% endaddtoblock %}\n\n{% addtoblock \"js\" %}{% localize off %}\n<script type=\"text/javascript\">\n(function() {\n\tvar mapPosition = {{ instance.map_position }};\n\tvar viewMap = L.map('cascadeelement_id-{{ instance.pk }}', {scrollWheelZoom: {{ instance.glossary.scroll_wheel_zoom|yesno:'true,false'}}}).setView([mapPosition.lat, mapPosition.lng], mapPosition.zoom);\n\tvar settings = {{ settings }};\n\tL.tileLayer(\n\t\tsettings.tilesURL,\n\t\tsettings\n\t).addTo(viewMap);\n\n\t{% for marker in markers %}\n\t(function(data) {\n\t\tvar marker;\n\t\t{% if marker.image %}\n\t\t\t{% thumbnail marker.image marker.size as thumb %}\n\t\t\t{% thumbnail marker.image marker.size2x as thumb2x %}\n\t\tvar icon =  L.icon({\n\t\t\ticonUrl: \"{{ thumb.url }}\",\n\t\t\ticonRetinaUrl: \"{{ thumb2x.url }}\",\n\t\t\t{% if marker.anchor %}iconAnchor: {{ marker.anchor }},{% endif %}\n\t\t\ticonSize: {{ marker.size }}\n\t\t});\n\t\tmarker = L.marker(data.position, {icon: icon});\n\t\t{% else %}\n\t\tmarker = L.marker(data.position);\n\t\t{% endif %}\n\t\tmarker.bindTooltip(data.title);\n\t\tif (data.popup_text) {\n\t\t\tmarker.bindPopup(data.popup_text);\n\t\t}\n\t\tmarker.addTo(viewMap);\n\t})({{ marker.data }});\n\t{% endfor %}\n})();\n</script>\n{% endlocalize %}{% endaddtoblock %}\n\n<div id=\"cascadeelement_id-{{ instance.pk|unlocalize }}\"></div>\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/link.html",
    "content": "{% extends \"cascade/link/link-base.html\" %}\n<!-- deprecated, please use \"cascade/link/text-link.html\" or extend from \"cascade/link/link-base.html\" -->"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/simpleicon.html",
    "content": "{% load sekizai_tags %}\n{% if stylesheet_url %}{% addtoblock \"css\" %}<link href=\"{{ stylesheet_url }}\" rel=\"stylesheet\" type=\"text/css\" />{% endaddtoblock %}{% endif %}\n{% spaceless %}\n{% with instance_link=instance.link tag_type=instance.tag_type css_classes=instance.css_classes inline_styles=instance.inline_styles %}\n{% if instance_link %}\n<a href=\"{{ instance_link }}\" {{ link_html_tag_attributes }}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n{% elif css_classes or inline_styles %}\n<span {% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>\n{% endif %}\n\t<i class=\"{{ icon_font_class }}\"></i>\n{% if instance_link %}</a>{% elif css_classes or inline_styles %}</span>{% endif %}\n{% endwith %}\n{% endspaceless %}"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/texticon.html",
    "content": "{% load static sekizai_tags %}{% spaceless %}\n{% if stylesheet_url %}{% addtoblock \"css\" %}<link href=\"{{ stylesheet_url }}\" rel=\"stylesheet\" type=\"text/css\" />{% endaddtoblock %}{% endif %}\n{% with instance_link=instance.link %}\n{% if instance_link %}<a href=\"{{ instance_link }}\"{{ instance.html_tag_attributes }}>{% endif %}<i class=\"{{ icon_font_class }}\">{% if not icon_font_class %}{# render a placeholder for icon selection #} &square; {% endif %}</i>{% if instance_link %}</a>{% endif %}\n{% endwith %}\n{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templates/cascade/plugins/textimage.html",
    "content": "{% load static cascade_tags thumbnail %}\n{% spaceless %}\n{% with css_classes=instance.css_classes inline_styles=instance.inline_styles instance_link=instance.link %}\n{% if instance_link %}<a href=\"{{ instance_link }}\" {{ link_html_tag_attributes }}>{% endif %}\n<img{{ instance.html_tag_attributes }}{% if css_classes %} class=\"{{ css_classes }}\"{% endif %}{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}\n{% if instance.image|is_valid_image %}\n\t{% thumbnail instance.image src.size crop=src.crop upscale=src.upscale subject_location=src.subject_location as thumb %}\n\tsrc=\"{{ thumb.url }}\" width=\"{{ thumb.width }}\" height=\"{{ thumb.height }}\"\n\t{% if src.high_resolution %}{% thumbnail instance.image src.size2x crop=src.crop upscale=src.upscale subject_location=src.subject_location as thumb2 %}srcset=\"{{ thumb2.url }} 2x\"{% endif %}\n{% else %}\n\tsrc=\"{% static 'cascade/fallback.svg' %}\"\n{% endif %}\n/>\n{% if instance_link %}</a>{% endif %}\n{% endwith %}{% endspaceless %}\n"
  },
  {
    "path": "cmsplugin_cascade/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "cmsplugin_cascade/templatetags/cascade_tags.py",
    "content": "import io\nimport json\nimport os\n\nfrom cms.toolbar.utils import get_toolbar_from_request\nfrom cms.plugin_rendering import StructureRenderer\n\nfrom django import template\nfrom django.conf import settings\nfrom django.core.cache import caches\nfrom django.template.exceptions import TemplateDoesNotExist\nfrom django.contrib.staticfiles import finders\nfrom django.utils.safestring import mark_safe\nfrom classytags.arguments import Argument\nfrom classytags.core import Options, Tag\nfrom cmsplugin_cascade.strides import StrideContentRenderer\n\nregister = template.Library()\n\n\nclass StrideRenderer(Tag):\n    \"\"\"\n    Render the serialized content of a placeholder field using the full cascade of plugins.\n    {% render_cascade \"cascade-data.json\" %}\n\n    Keyword arguments:\n    datafile -- Filename containing the cascade tree. Must be file locatable by Django's\n    static file finders.\n    \"\"\"\n    name = 'render_cascade'\n    options = Options(\n        Argument('datafile'),\n    )\n\n    def render_tag(self, context, datafile):\n        from sekizai.helpers import get_varname as get_sekizai_context_key\n        from cmsplugin_cascade.strides import StrideContentRenderer\n\n        cache = caches['default']\n        tree_data_key = 'cascade-strides:' + datafile\n        tree_data = cache.get(tree_data_key) if cache else None\n        if tree_data is None:\n            jsonfile = finders.find(datafile)\n            if not jsonfile:\n                raise IOError(\"Unable to find file: {}\".format(datafile))\n\n            with io.open(jsonfile) as fp:\n                tree_data = json.load(fp)\n\n        content_renderer = StrideContentRenderer(context['request'])\n        with context.push(cms_content_renderer=content_renderer):\n            content = content_renderer.render_cascade(context, tree_data)\n\n        # some templates use Sekizai's templatetag `addtoblock` or `add_data`, which have to be re-added to the context\n        cache = caches['default']\n        if cache:\n            sekizai_context_key = get_sekizai_context_key()\n            SEKIZAI_CONTENT_HOLDER = cache.get_or_set(sekizai_context_key, context.get(sekizai_context_key))\n            if SEKIZAI_CONTENT_HOLDER:\n                for name in SEKIZAI_CONTENT_HOLDER:\n                    context[sekizai_context_key][name] = SEKIZAI_CONTENT_HOLDER[name]\n        return content\n\nregister.tag('render_cascade', StrideRenderer)\n\n\nclass RenderPlugin(Tag):\n    \"\"\"\n    Alternative implementation of django-CMS's templatetag ``render_plugin``.\n    It can either handle normal CMS plugins or Strides.\n    \"\"\"\n    name = 'render_plugin'\n    options = Options(\n        Argument('plugin')\n    )\n\n    def render_tag(self, context, plugin):\n        if not plugin:\n            return ''\n\n        request = context['request']\n        toolbar = get_toolbar_from_request(request)\n        if 'cms_content_renderer' in context and isinstance(context['cms_content_renderer'], StrideContentRenderer):\n            content_renderer = context['cms_content_renderer']\n        elif 'cms_renderer' in context:\n            content_renderer = context['cms_renderer']\n        elif 'cms_content_renderer' in context:\n            content_renderer = context['cms_content_renderer']\n        else:\n            content_renderer = toolbar.content_renderer\n        if isinstance(content_renderer, StructureRenderer):\n            return content_renderer.render_plugin(plugin)\n        else:\n            return content_renderer.render_plugin(\n                instance=plugin,\n                context=context,\n                editable=toolbar.edit_mode_active,\n            )\n\nregister.tag('render_plugin', RenderPlugin)\n\n\n@register.filter\ndef is_valid_image(image):\n    try:\n        return image.file.file\n    except:\n        return False\n\n\n@register.simple_tag\ndef sphinx_docs_include(path):\n    filename = os.path.join(settings.SPHINX_DOCS_ROOT, path)\n    if not os.path.exists(filename):\n        raise TemplateDoesNotExist(\"'{path}' does not exist\".format(path=path))\n    with io.open(filename) as fh:\n        return mark_safe(fh.read())\n"
  },
  {
    "path": "cmsplugin_cascade/utils.py",
    "content": "from django.core.exceptions import ValidationError\nfrom django.forms.fields import MultipleChoiceField\nfrom django.forms.widgets import CheckboxSelectMultiple, MediaDefiningClass\nfrom django.utils.translation import gettext_lazy as _, gettext_noop\n\nfrom entangled.forms import EntangledModelFormMixin\n\n\ndef remove_duplicates(lst):\n    \"\"\"\n    Emulate what a Python ``set()`` does, but keeping the element's order.\n    \"\"\"\n    dset = set()\n    return [l for l in lst if l not in dset and not dset.add(l)]\n\n\ndef rectify_partial_form_field(base_field, partial_form_fields):\n    \"\"\"\n    In base_field reset the attributes label and help_text, since they are overriden by the\n    partial field. Additionally, from the list, or list of lists of partial_form_fields\n    append the bound validator methods to the given base field.\n    \"\"\"\n    base_field.label = ''\n    base_field.help_text = ''\n    for fieldset in partial_form_fields:\n        if not isinstance(fieldset, (list, tuple)):\n            fieldset = [fieldset]\n        for field in fieldset:\n            base_field.validators.append(field.run_validators)\n\ndef validate_link(link_data):\n    \"\"\"\n    Check if the given model exists, otherwise raise a Validation error\n    \"\"\"\n    from django.apps import apps\n\n    try:\n        Model = apps.get_model(link_data['model'])\n        Model.objects.get(pk=link_data['pk'])\n    except Model.DoesNotExist:\n        raise ValidationError(_(\"Unable to link onto '{0}'.\").format(Model.__name__))\n\n\ndef compute_aspect_ratio(image):\n    if image.exif.get('Orientation', 1) > 4:\n        # image is rotated by 90 degrees, while keeping width and height\n        return float(image.width) / float(image.height)\n    else:\n        return float(image.height) / float(image.width)\n\n\ndef compute_aspect_ratio_with_glossary(glossary):\n    if glossary['image']['exif_orientation'] > 4:\n        # image is rotated by 90 degrees, while keeping width and height\n        return float(glossary['image']['width']) / float(glossary['image']['height'])\n    else:\n        return float(glossary['image']['height']) / float(glossary['image']['width'])\n\n\ndef get_image_size(width, image_height, aspect_ratio):\n    if image_height[0]:\n        # height was given in px\n        return (width, image_height[0])\n    elif image_height[1]:\n        # height was given in %\n        return (width, int(round(width * image_height[1])))\n    else:\n        # as fallback, adopt height to current width\n        return (width, int(round(width * aspect_ratio)))\n\n\ndef parse_responsive_length(responsive_length):\n    \"\"\"\n    Takes a string containing a length definition in pixels or percent and parses it to obtain\n    a computational length. It returns a tuple where the first element is the length in pixels and\n    the second element is its length in percent divided by 100.\n    Note that one of both returned elements is None.\n    \"\"\"\n    responsive_length = responsive_length.strip()\n    if responsive_length.endswith('px'):\n        return (int(responsive_length.rstrip('px')), None)\n    elif responsive_length.endswith('%'):\n        return (None, float(responsive_length.rstrip('%')) / 100)\n    return (None, None)\n\n\nclass CascadeUtilitiesMixin(metaclass=MediaDefiningClass):\n    \"\"\"\n    If a Cascade plugin is listed in ``settings.CMSPLUGIN_CASCADE['plugins_with_extra_mixins']``,\n    then this ``CascadeUtilitiesMixin`` class is added automatically to its plugin class in order to\n    enrich it with utility classes, such as :class:`cmsplugin_cascade.bootstrap4.mixins.BootstrapUtilities`.\n    \"\"\"\n    def __str__(self):\n        return self.plugin_class.get_identifier(self)\n\n    def get_form(self, request, obj=None, **kwargs):\n        form = kwargs.get('form', self.form)\n        assert issubclass(form, EntangledModelFormMixin), \"Form must inherit from EntangledModelFormMixin\"\n        kwargs['form'] = type(form.__name__, (self.utility_form_mixin, form), {})\n        return super().get_form(request, obj, **kwargs)\n\n    @classmethod\n    def get_css_classes(cls, obj):\n        \"\"\"Enrich list of CSS classes with customized ones\"\"\"\n        css_classes = super().get_css_classes(obj)\n        for utility_field_name in cls.utility_form_mixin.base_fields.keys():\n            css_class = obj.glossary.get(utility_field_name)\n            if css_class:\n                if isinstance(css_class, str):\n                    css_classes.append(css_class)\n                elif isinstance(css_class, list):\n                    css_classes.extend(css_class)\n        return css_classes\n\n\nclass NamedCSSClasses:\n    \"\"\"\n    Factory for building a class ``NamedCSSClassesMixin``. This class then is used as a mixin to\n    all sorts of Cascade plugins. It shall be used to optionally activate preconfigured CSS classes\n    for each configured plugin.\n    To configure, pass in a list of 2-tuples, consisting of the CSS class plus a string describing\n    this feature. For example:\n    ```\n    CMSPLUGIN_CASCADE['plugins_with_extra_mixins'] = {\n        'LinkPlugin': NamedCSSClasses([\n            ('stretched-link', \"Use stretched Link\"),\n            ('full-width', \"Full width\"),\n            …\n        ]),\n        …\n    }\n    ```\n\n    The above configuration will add a checkbox for each entry in that list to the plugin editors.\n    The administrator then can choose whether to add those classes to the HTML element or not.\n    \"\"\"\n    def __new__(cls, choices):\n        named_css_classes = MultipleChoiceField(\n            label=_(\"Extra Styles\"),\n            choices=choices,\n            widget=CheckboxSelectMultiple,\n            required=False,\n        )\n\n        class Meta:\n            entangled_fields = {'glossary': ['named_css_classes']}\n\n        attrs = {\n            'named_css_classes': named_css_classes,\n            'Meta': Meta,\n        }\n        utility_form_mixin = type('UtilitiesFormMixin', (EntangledModelFormMixin,), attrs)\n        return type('NamedCSSClassesMixin', (CascadeUtilitiesMixin,), {'utility_form_mixin': utility_form_mixin})\n\n\ngettext_noop(\"Margins\")\ngettext_noop(\"Paddings\")\ngettext_noop(\"Widths\")\ngettext_noop(\"Heights\")\ngettext_noop(\"Text Alignement\")\ngettext_noop(\"Font Size\")\ngettext_noop(\"Line Height\")\ngettext_noop(\"Colors\")\ngettext_noop(\"Border\")\ngettext_noop(\"Border Radius\")\ngettext_noop(\"Overflow\")\n"
  },
  {
    "path": "cmsplugin_cascade/widgets.py",
    "content": "import re\nfrom html.parser import HTMLParser\nimport json\nimport warnings\n\nfrom django.core.exceptions import ValidationError\nfrom django.contrib.staticfiles.finders import find\nfrom django.forms import Media, widgets\nfrom django.utils.html import escape, format_html, format_html_join\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass JSONMultiWidget(widgets.MultiWidget):\n    \"\"\"\n    Deprecated.\n    Base class for MultiWidgets using a JSON field in database.\n    \"\"\"\n    html_parser = HTMLParser()\n\n    def __init__(self, glossary_fields):\n        from cmsplugin_cascade.fields import GlossaryField\n        warnings.warn(\"JSONMultiWidget is deprecated\")\n\n        self.glossary_fields = list(glossary_fields)\n        self.normalized_fields = []\n        for field in self.glossary_fields:\n            if isinstance(field, GlossaryField):\n                self.normalized_fields.append(field)\n            elif isinstance(field, (list, tuple)):\n                self.normalized_fields.extend([f for f in field if isinstance(f, GlossaryField)])\n            else:\n                raise ValueError(\"Given fields must be of type GlossaryField or list of thereof\")\n        unique_keys = set([field.name for field in self.normalized_fields])\n        if len(self.normalized_fields) > len(unique_keys):\n            raise ValueError('List of glossary_fields may contain only unique keys')\n        super().__init__((field.widget for field in self.normalized_fields))\n\n    def decompress(self, values):\n        if not isinstance(values, dict):\n            values = json.loads(values or '{}')\n        for field in self.normalized_fields:\n            initial_value = field.initial() if callable(field.initial) else field.initial\n            if isinstance(field.widget, widgets.MultiWidget):\n                values[field.name] = field.widget.decompress(values.get(field.name, initial_value))\n            else:\n                values.setdefault(field.name, initial_value)\n        return values\n\n    def value_from_datadict(self, data, files, name):\n        result = {}\n        for field in self.normalized_fields:\n            if isinstance(field.widget, widgets.MultiWidget):\n                result[field.name] = field.widget.value_from_datadict(data, files, field.name)\n            elif getattr(field.widget, 'allow_multiple_selected', False):\n                result[field.name] = list(map(escape, data.getlist(field.name)))\n            else:\n                result[field.name] = escape(data.get(field.name, ''))\n        return result\n\n    def value_omitted_from_data(self, data, files, name):\n        # required since Django-1.10 during invocation of `construct_instance`\n        return self.normalized_fields and all(\n            field.widget.value_omitted_from_data(data, files, field.name)\n            for field in self.normalized_fields\n        )\n\n    def render(self, name, value, attrs=None, renderer=None):\n        values = self.decompress(value)\n        render_fieldsets = []\n        for fieldset in self.glossary_fields:\n            render_fields = []\n            if not isinstance(fieldset, (list, tuple)):\n                fieldset = [fieldset]\n            for field in fieldset:\n                field_attrs = dict(**attrs)\n                field_attrs.update(id='{id}_{0}'.format(field.name, **attrs))\n                field_value = values.get(field.name)\n                if isinstance(field_value, str):\n                    field_value = self.html_parser.unescape(field_value)\n                render_fields.append((\n                    field.name,\n                    str(field.label),\n                    field.widget.render(field.name, field_value, field_attrs),\n                    str(field.help_text),\n                ))\n            html = format_html_join('',\n                 '<div class=\"glossary-field glossary_{0}\"><h1>{1}</h1><div class=\"glossary-box\">{2}</div><small>{3}</small></div>',\n                 render_fields)\n            render_fieldsets.append((html,))\n        return format_html_join('\\n', '<div class=\"glossary-widget\">{0}</div>', render_fieldsets)\n\n\nclass NumberInputWidget(widgets.NumberInput):\n    validation_pattern = re.compile('^-?\\d+(\\.\\d{1,2})?$')\n    required_message = _(\"In '%(label)s': This field is required.\")\n    invalid_message = _(\"In '%(label)s': Value '%(value)s' shall contain a valid decimal number.\")\n\n    def validate(self, value):\n        if value and not self.validation_pattern.match(value):\n            raise ValidationError(self.invalid_message, code='invalid', params={'value': value})\n\n\nclass AColorPickerMixin:\n    acolorpicker_js = 'node_modules/a-color-picker/dist/acolorpicker.js'\n\n    def __init__(self, with_alpha, *args, **kwargs):\n        self.with_acolorpicker = bool(find(self.acolorpicker_js))\n        if with_alpha and not self.with_acolorpicker:\n            msg = \"Node package 'a-color-picker' not found in 'node_modules'.\\n\" \\\n                  \"Please install it from npm into your project directory and add \" \\\n                  \"('node_modules', 'project_directory/node_modules') to your STATICFILES_DIRS.\"\n            raise FileNotFoundError(msg)\n        super().__init__(*args, **kwargs)\n\n    @property\n    def media(self):\n        if self.with_acolorpicker:\n            js = [self.acolorpicker_js, 'admin/js/jquery.init.js', 'cascade/js/admin/colorpicker.js']\n        else:\n            js = ['admin/js/jquery.init.js', 'cascade/js/admin/colorpicker.js']\n        return Media(js=js)\n\n    @classmethod\n    def rgb2hex(cls, val):\n        match = re.search('rgb[a]?\\((\\d+),\\s*(\\d+),\\s*(\\d+)[^)]*\\)', val)\n        if match:\n            val = \"#{:02x}{:02x}{:02x}\".format(*[int(m) for m in match.groups()])\n        return val\n\n\nclass ColorPickerWidget(AColorPickerMixin, widgets.MultiWidget):\n    \"\"\"\n    Use this field to enter a color value. Clicking onto this widget will pop up a color picker.\n    The value passed to the consumer is a tuple of a Boolean and a string guaranteed to be in #rgb format.\n    \"\"\"\n    template_name = 'cascade/admin/widgets/colorpicker.html'\n\n    def __init__(self, with_alpha):\n        widget_list = [\n            widgets.TextInput(attrs={'data-with_alpha': str(with_alpha).lower(), 'class': 'cascade-rgba'}),\n            widgets.TextInput(attrs={'type': 'color'}),\n            widgets.CheckboxInput(),\n        ]\n        super().__init__(with_alpha, widget_list)\n\n    class Media:\n        css = {'all': ['cascade/css/admin/colorpicker.css']}\n\n    def decompress(self, values):\n        assert isinstance(values, (list, tuple)), \"Values to decompress are kept as lists in JSON\"\n        values = list(values)\n        return values\n\n    def value_from_datadict(self, data, files, name):\n        if self.with_acolorpicker:\n            color = data.get('{}_0'.format(name))\n        else:\n            color = data.get('{}_1'.format(name))\n        values = [\n            escape(color),\n            bool(data.get('{}_2'.format(name))),\n        ]\n        return values\n\n    def get_context(self, name, value, attrs):\n        if not isinstance(value, list):\n            values = self.decompress(value)\n        else:\n            values = list(value)\n        assert len(values) == 2\n        values.insert(0, values[0])\n        values[1] = self.rgb2hex(values[0])\n        context = super().get_context(name, values, attrs)\n        return context\n\n\nclass MultipleTextInputWidget(widgets.MultiWidget):\n    \"\"\"\n    A widgets accepting multiple input values.\n    \"\"\"\n    required = False\n\n    def __init__(self, labels, attrs=None):\n        text_widgets = [widgets.TextInput()] * len(labels)\n        super().__init__(text_widgets, attrs)\n        self.labels = labels[:]\n\n    def decompress(self, values):\n        assert isinstance(values, dict), \"Values to decompress are kept as dict in JSON\"\n        return list(values.values())\n\n    def value_from_datadict(self, data, files, name):\n        values = [escape(data.get('{0}-{1}'.format(name, label), '')) for label in self.labels]\n        return values\n\n    def render(self, name, value, attrs=None, renderer=None):\n        widgets = []\n        values = value[:] if isinstance(value, (list, tuple)) else []\n        values.extend([''] * max(len(self.labels) - len(values), 0))\n        elem_id = attrs['id']\n        for index, key in enumerate(self.labels):\n            label = '{0}-{1}'.format(name, key)\n            attrs['id'] = '{0}_{1}'.format(elem_id, key)\n            widgets.append((self.widgets[index].render(label, values[index], attrs, renderer), key, label))\n        return format_html('<div>{0}</div>',\n                           format_html_join('\\n',\n                                            '<div class=\"sibling-field\"><label for=\"{2}\">{1}</label>{0}</div>',\n                                            widgets))\n\n\nclass BorderChoiceWidget(AColorPickerMixin, widgets.MultiWidget):\n    \"\"\"\n    Use this field to enter the three values of a border: width style color.\n    \"\"\"\n    template_name = 'cascade/admin/widgets/borderchoice.html'\n\n    def __init__(self, choices, with_alpha):\n        widget_list = [\n            widgets.TextInput(attrs={'size': 5}),\n            widgets.Select(choices=choices),\n            widgets.TextInput(attrs={'data-with_alpha': str(with_alpha).lower(), 'class': 'cascade-rgba'}),\n            widgets.TextInput(attrs={'type': 'color'}),\n        ]\n        super().__init__(with_alpha, widget_list)\n\n    @property\n    def media(self):\n        return super().media + Media(css={'all': ['cascade/css/admin/colorpicker.css',\n                                                  'cascade/css/admin/borderchoice.css']})\n\n    def decompress(self, values):\n        assert isinstance(values, (list, tuple)), \"Values to decompress are kept as lists in JSON\"\n        return list(values)\n\n    def value_from_datadict(self, data, files, name):\n        try:\n            if self.with_acolorpicker:\n                color = data.get('{}_2'.format(name))\n            else:\n                color = data.get('{}_3'.format(name))\n            values = [\n                escape(data['{}_0'.format(name)]),\n                escape(data['{}_1'.format(name)]),\n                escape(color),\n            ]\n        except KeyError:\n            values = ['0px', 'none', 'none']\n        return values\n\n    def get_context(self, name, value, attrs):\n        if not isinstance(value, list):\n            values = self.decompress(value)\n        else:\n            values = list(value)\n        assert len(values) == 3\n        values.insert(2, values[2])\n        values[3] = self.rgb2hex(values[2])\n        context = super().get_context(name, values, attrs)\n        return context\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/djangocms-bootstrap.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/djangocms-bootstrap.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/djangocms-bootstrap\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/djangocms-bootstrap\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source\nset I18NSPHINXOPTS=%SPHINXOPTS% source\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\djangocms-bootstrap.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\djangocms-bootstrap.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/source/_static/shop_link_plugin.py",
    "content": "# -*- coding: utf-8 -*-\nfrom django.utils.translation import ugettext_lazy as _\nfrom django.apps import apps\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django_select2.fields import AutoModelSelect2Field\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.link.forms import TextLinkForm\nfrom cmsplugin_cascade.link.models import SimpleLinkElement\nfrom cmsplugin_cascade.link.plugin_base import TextLinkPluginBase\nfrom shop.models.product import Product\n\n\nclass ProductSearchField(AutoModelSelect2Field):\n    empty_value = []\n    search_fields = ['product_code__startswith', 'translations__name__startswith']\n    queryset = Product.objects.all()\n\n    def security_check(self, request, *args, **kwargs):\n        user = request.user\n        if user and not user.is_anonymous() and user.is_staff:\n            return True\n        return False\n\n    def prepare_value(self, value):\n        if not value:\n            return None\n        return super(ProductSearchField, self).prepare_value(value)\n\n\nclass LinkForm(TextLinkForm):\n    LINK_TYPE_CHOICES = (('cmspage', _(\"CMS Page\")), ('product', _(\"Product\")), ('exturl', _(\"External URL\")), ('email', _(\"Mail To\")),)\n    product = ProductSearchField(required=False, label='',\n        help_text=_(\"An internal link onto a product from the shop\"))\n\n    def clean_product(self):\n        if self.cleaned_data.get('link_type') == 'product':\n            self.cleaned_data['link_data'] = {\n                'type': 'product',\n                'model': 'shop.Product',\n                'pk': self.cleaned_data['product'] and self.cleaned_data['product'].pk or None,\n            }\n\n    def set_initial_product(self, initial):\n        try:\n            Model = apps.get_model(*initial['link']['model'].split('.'))\n            initial['product'] = Model.objects.get(pk=initial['link']['pk'])\n        except (KeyError, ObjectDoesNotExist):\n            pass\n\n\nclass LinkPlugin(TextLinkPluginBase):\n    model = SimpleLinkElement\n    fields = ('link_content', ('link_type', 'cms_page', 'product', 'ext_url', 'mail_to'), 'glossary',)\n    form = LinkForm\n\n    class Media:\n        js = ['shop/js/admin/linkplugin.js']\n\nplugin_pool.register_plugin(LinkPlugin)\n"
  },
  {
    "path": "docs/source/bootstrap3/gallery.rst",
    "content": "=======\nGallery\n=======\n\nA gallery is a collection of images displayed as a group. Since it normally consists of many similar\nimages, **djangocms-cascade** does not require to use child plugins for each image. Instead they\ncan be added directly to the **Bootstrap Gallery Plugin**. Here, **djangocms-cascade** uses a\nspecial model, named :class:`cmsplugin_cascade.models.InlineCascadeElement` which also uses a JSON\nfield to store it's payload. It thus can be configured to accept any kind of data, just as it's\ncounterpart :class:`cmsplugin_cascade.models.CascadeElement` does.\n\nSince plugin editors are based on Django's admin backend, the Gallery Plugin uses the Stacked Inline\nformset to manage it's children. If **django-admin-sortable2** is installed, the entries in the\nplugin can even be sorted using drag and drop.\n"
  },
  {
    "path": "docs/source/bootstrap3/grid.rst",
    "content": ".. _bootstrap3/grid:\n\n=======================\nBootstrap 3 Grid system\n=======================\nIn order to take full advantage of **djangocms-cascade**, you should be familiar with the\nconcepts of the `Bootstrap Grid System`_, since all other Bootstrap components depend upon.\n\n.. _Bootstrap Grid System: http://getbootstrap.com/css/#grid\n\nBootstrap Container\n===================\n\nA **Container** is the outermost component the Bootstrap framework knows of. Here the designer can\nspecify the breakpoints of a web page. By default, Bootstrap offers 4 breakpoints: “large”,\n“medium”, “small” and “tiny”. These determine for which kind of screen widths, the grid system may\nswitch the layout.\n\nThe editor window for a Container element offers the possibility to deactivate certain breakpoints.\nWhile this might make sense under certain conditions, it is safe to always keep all four breakpoints\nactive, since this gives the designer of the web page the maximum flexibility.\n\n|edit-container|\n\n.. |edit-container| image:: /_static/bootstrap3/edit-container.png\n\n\nSmall devices exclusively\n-------------------------\n\nIf the web page shall be optimized just for small but not for large devices, then disable the\nbreakpoints for **Large** and/or **Medium**. In the project's style-sheets, the maximum width\nof the container element then must be reduced to that chosen breakpoint:\n\n.. code-block:: css\n\n\t@media(min-width: 1200px) {\n\t  .container {\n\t    max-width: 970px;\n\t  }\n\t}\n\nor, if you prefers the SASS syntax:\n\n.. code-block:: guess\n\n\t@media(min-width: $screen-lg) {\n\t  .container {\n\t    max-width: $container-desktop;\n\t  }\n\t}\n\n\nLarge devices exclusively\n-------------------------\n\nIf the web page shall be optimized just for large but not for small devices, then disable the\nbreakpoints for **Tiny** and/or **Small**.\n\nChanging the style-sheets then is not required for this configuration setting.\n\n\nFluid Container\n---------------\n\nA variant of the normal Bootstrap Container is the Fluid Container. It can be enabled by a checkbox\nin the editors window. Fluid Containers have no hards breakpoints, they adopt their width to\nwhatever the browser pretends and are slightly larger than their non-fluid counterpart.\n\nA fluid container makes it impossible to determine the maximum width of responsive images for the\n*large media breakpoint*, because it is applied whenever the browser width extends 1200 pixels,\nbut there is no upper limit. For responsive images in the smaller breakpoints (“tiny”, “small”\nand “medium”) we use the width of the next larger breakpoint, but for images in the “large” media\nbreakpoints we somehow must specify an arbitrary maximum width. The default width is set to 1980\npixels, but can be changed, to say 2500 pixels, using the following configuration in your\n``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'bootstrap3': (\n\t        ('xs', (768, 'mobile', _(\"mobile phones\"), 750, 768)),\n\t        ('sm', (768, 'tablet', _(\"tablets\"), 750, 992)),\n\t        ('md', (992, 'laptop', _(\"laptops\"), 970, 1200)),\n\t        ('lg', (1200, 'desktop', _(\"large desktops\"), 1170, 2500)),\n\t    ),\n\t}\n\n.. note:: Fluid container are specially useful for Hero images, full-width Carousels and the\n\tJumbotron plugin. When required, add a free standing fluid container to the placeholder and as\n\tit's only child, use the picture or carousel plugin. Its content then is stretched to the\n\tbrowser's full width.\n\nBootstrap Row\n=============\n\nEach Bootstrap Container may contain one or more Bootstrap Rows. A row does not accept any\nconfiguration setting. However, while editing, one can specify the number of columns. When adding or\nchanging a row, then this number of columns are added if its value exceeds the current number of\ncolumns. Reducing the number of columns does not delete any of them; they must explicitly be chosen\nfrom the context menu in structure view.\n\n|edit-row|\n\n.. |edit-row| image:: /_static/bootstrap3/edit-row.png\n\n\nHorizontal Rule\n===============\n\nA horizontal rule is used to separate rows optically from each other.\n\n|rule-editor|\n\n.. |rule-editor| image:: /_static/rule-editor.png\n\n\nColumn\n======\n\nIn the column editor, one can specify the width, the offset and the visibility of each column.\nThese values can be set for each of the four breakpoints (*tiny*, *small*, *medium* and *large*),\nas specified by the Container plugin.\n\nAt the beginning this may feel rather complicate, but consider that **Bootstrap 3 is mobile first**,\ntherefore all column settings, *first* are applied to the narrow breakpoints, which *later* can be\noverridden for larger breakpoints at a later stage. This is the reason why this editor starts with\nthe *column widths* and *column offsets* for tiny rather than for large displays.\n\n|edit-column|\n\n.. |edit-column| image:: /_static/bootstrap3/edit-column.png\n\n.. note:: If the current column is member of a container which disables some of its breakpoints\n          (*large*, *medium*, *small* or *tiny*), then that column editor shows up only with the\n          input fields for the enabled breakpoints.\n\n\nComplete DOM Structure\n======================\n\nAfter having added a container with different rows and columns, you may add the leaf plugins. These\nhold the actual content, such as text and images.\n\n|structure-container|\n\n.. |structure-container| image:: /_static/structure-container.png\n\nBy pressing the button **Publish changes**, the single blocks are regrouped and displayed using\nthe Bootstrap's grid system.\n\n\nAdding Plugins into a hard coded grid\n=====================================\n\nSometimes the given Django template already defines a Bootstrap Container, or Row inside a Container\nelement. Example:\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t    {% placeholder \"Row Content\" %}\n\t</div>\n\nor\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t    <div class=\"row\">\n\t        {% placeholder \"Column Content\" %}\n\t    </div>\n\t</div>\n\nHere the Django templatetag ``{% placeholder \"Row Content\" %}`` requires a Row- rather than a\nContainer-plugin; and the templatetag ``{% placeholder \"Column Content\" %}`` requires a\nColumn-plugin. Hence we must tell **djangocms-cascade** which breakpoints shall be allowed and what\nthe containers extensions shall be. This must be hard-coded inside your ``setting.py``:\n\n.. code-block:: python\n\n\tCMS_PLACEHOLDER_CONF = {\n\t    # for a row-like placeholder configuration ...\n\t    'Row Content': {\n\t        'plugins': ['BootstrapRowPlugin'],\n\t        'parent_classes': {'BootstrapRowPlugin': []},\n\t        'require_parent': False,\n\t        'glossary': {\n\t            'breakpoints': ['xs', 'sm', 'md', 'lg'],\n\t            'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},\n\t            'fluid': False,\n\t            'media_queries': {\n\t                'xs': ['(max-width: 768px)'],\n\t                'sm': ['(min-width: 768px)', '(max-width: 992px)'],\n\t                'md': ['(min-width: 992px)', '(max-width: 1200px)'],\n\t                'lg': ['(min-width: 1200px)'],\n\t            },\n\t        }\n\t    },\n\t    # or, for a column-like placeholder configuration ...\n\t    'Colummn Content': {\n\t        'plugins': ['BootstrapColumnPlugin'],\n\t        'parent_classes': {'BootstrapColumnPlugin': []},\n\t        'require_parent': False,\n\t        'glossary': {\n\t            'breakpoints': ['xs', 'sm', 'md', 'lg'],\n\t            'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},\n\t            'fluid': False,\n\t            'media_queries': {\n\t                'xs': ['(max-width: 768px)'],\n\t                'sm': ['(min-width: 768px)', '(max-width: 992px)'],\n\t                'md': ['(min-width: 992px)', '(max-width: 1200px)'],\n\t                'lg': ['(min-width: 1200px)'],\n\t            },\n\t        }\n\t    },\n\t}\n\nPlease refer to the `DjangoCMS documentation`_ for details about these settings with the exception\nof the dictionary ``glossary``. This latter setting is special to **djangocms-cascade**: It gives\nthe placeholder the ability to behave like a plugin for the Cascade app. Remember, each\n**djangocms-cascade** plugin stores all of its settings inside a Python dictionary which is\nserialized into a single database field. By having a placeholder behaving like a plugin, here this\nso named *glossary* is emulated using an additional entry inside the setting\n``CMS_PLACEHOLDER_CONF``, and it should:\n\n- include all the settings a child plugin would expect from a real container plugin\n- reflect how hard coded container was defined (e.g. whether it is fluid or not)\n\n.. _DjangoCMS documentation: https://django-cms.readthedocs.org/en/latest/basic_reference/configuration.html#std:setting-CMS_PLACEHOLDER_CONF\n\n\nNested Columns and Rows\n=======================\n\nOne of the great features of Bootstrap is the ability to nest Rows inside Columns. These nested Rows\nthen can contain Columns of 2nd level order. A quick example:\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t  <div class=\"row\">\n\t    <div class=\"col-md-3\">\n\t      Left column\n\t    </div>\n\t    <div class=\"col-md-9\">\n\t      <div class=\"row\">\n\t        <div class=\"col-md-6\">\n\t          Left nested column\n\t        </div>\n\t        <div class=\"col-md-6\">\n\t          Right nested column\n\t        </div>\n\t      </div>\n\t    </div>\n\t  </div>\n\t</div>\n\nrendered, it would look like:\n\n|nested-rows|\n\n.. |nested-rows| image:: /_static/nested-rows.png\n\nIf a responsive image shall be placed inside a column, we must estimate the width of this image, so\nthat when rendered, it fits exactly into that column. We want easy-thumbnails_ to resize our images\nto the columns width and not having the browser to up- or down-scale them.\n\n.. _easy-thumbnails: https://github.com/SmileyChris/easy-thumbnails\n\nTherefore **djangocms-cascade** keeps track of all the breakpoints and the chosen column widths.\nFor simplicity, this example only uses the breakpoint “medium”. The default Boostrap settings for\nthis width is 992 pixels. Doing simple math, the outer left column widths gives\n3 / 12 * 992 = 248 pixels. Hence, adding a responsive image to that column means, that\n**easy-thumnails** automatically resizes it to a width of 248 pixels.\n\nTo calculate the width of the nested columns, first evaluate the width of the outer right column,\nwhich is 9 / 12 * 992 = 744 pixels. Then this width is subdivided again, using the width of the\nnested columns, which is 6 / 12 * 744 = 372 pixels.\n\nThese calculations are always performed recursively for all nested column and for all available\nbreakpoints.\n\n.. warning:: As the name implies, a container marked as *fluid*, does not specify a fixed width.\n\tHence instead of the inner width, the container's outer width is used as its maximum. For the\n\tlarge media query (with a browser width of 1200 pixels or more), the maximum width is limited\n\tto 1980 pixels.\n"
  },
  {
    "path": "docs/source/bootstrap3/image-picture.rst",
    "content": "==========================================\nHTML5 <picture> and the new <img> elements\n==========================================\n\nBootstrap's responsive grid system, helps developers to adapt their site layout to a wide range of\ndevices, from smart-phones to large displays. This works fine as long as the content can adopt to\nthe different widths. Adding the CSS class ``img-responsive`` to an ``<img ... />`` tag, resizes\nthat image to fit into the surrounding column. However, since images are delivered by the server\nin one specific size, they either are too small and must be upscaled, resulting in an grainy image,\nor are too big, resulting in a waste of bandwidth and slowing down the user experience, when surfing\nover slow networks.\n\nAdaptive resizing the images\n============================\n\nAn obvious idea would be to let the server decide, which image resolution fits best to the browsing\ndevice. This however is bad practice. Images typically are served upon a GET-request pointing onto\na specific URL. GET-requests shall be idempotent and thus are predestined to be cached by proxies\non the way to the client. Therefore it is a very bad idea to let the client transmit its screen\nwidth via a cookie, and deliver different images depending on this value.\n\nSince the sever side approach doesn't work, it is the browsers responsibility to select the\nappropriate image size. An ideal adaptive image strategy should do the following:\n\n * Images should fit the screen, regardless of their size. An adaptive strategy needs to resize the\n   image, so that it can resize into the current column width.\n * Downloading images shall minimize the required bandwidth. Large images are enjoying greater\n   popularity with the advent of Retina displays, but those devices normally are connected to the\n   Internet using DSL rather than mobiles, which run on 3G.\n * Not all images look good when squeezed onto a small display, particularly images with a lot of\n   detail. When displaying an image on a mobile device, you might want to crop only the interesting\n   part of it.\n\nAs these criteria can't be fulfilled using the well known ``<img src=\"...\" />`` element,\n**djangocms-cascade** offers two responsive variants recently added to the HTML5 standard:\n\nOne is the ``<img>`` tag, but with the additional attributes ``sizes`` and ``srcset``. This element\ncan be used as a direct replacement for ``<img src=\"...\">``.\n\nThe other is a new element named ``<picture>``. Use this element, if the image's shape or details\nshall adopt their shape and/or details to the displaying media device. The correct terminology for\nthis kind of behavior is `art direction`_.\n\n|art-direction|\n\n.. |art-direction| image:: /_static/art_direction.jpg\n.. _art direction: http://usecases.responsiveimages.org/#art-direction\n\nBut in the majority of use cases, the **Bootstrap Image Plugin** will work for you. Use the\n**Bootstrap Picture Plugin** only in those few cases, where in addition to the image width,\nyou also want to change the aspect ratio and/or zoom factor, depending on the display's sizes.\n\nUsing these new elements, the browser always fetches the image which best fits the current layout.\nAdditionally, if the browser runs on a high resolution (Retina) display, an image with double\nresolution is downloaded. This results in much sharper images.\n\nBrowser support\n---------------\nSince Chrome 38, the ``<img ... />`` element fully supports `srcset and sizes`_. It also supports\nthe ``<picture>`` element right out of the box. Here is a list of native browser support for the\npicture_ and the image element with attribute srcset_.\n\n.. _srcset and sizes: http://ericportis.com/posts/2014/srcset-sizes/\n.. _picture: http://caniuse.com/#feat=picture\n.. _srcset: http://caniuse.com/#feat=srcset\n\nFor legacy browsers, there is a JavaScript library named picturefill.js_, which emulates the built\nin behavior of these new features. But even without that library, **djangocms-cascade** renders\nthese HTML elements in a way to fall back on a sensible default image.\n\n.. _picturefill.js: http://scottjehl.github.io/picturefill/\n\n\nImage Plugin Reference\n========================\n\nIn edit mode, double clicking on an image, opens the **Image Plugin** editor. This editor offers the\nfollowing fields in order to adapt an image to the current layout.\n\n|edit-image|\n\n.. |edit-image| image:: /_static/bootstrap3/edit-image.png\n\nImage\n-----\nClicking on the magnifying glass opens a pop-up window from django-filer_ where you can choose the\nappropriate image.\n\n.. _django-filer: https://github.com/stefanfoulis/django-filer\n\nImage Title\n-----------\nThis optional field shall be used to set the ``<img title=\"some text\" .../>`` tag inside this HTML\nelement.\n\nAlternative Description\n-----------------------\nThis field shall be used to set the ``alt`` tag inside the ``<picture>`` or ``<img>``\nelement. While the editor does require this field to be filled, it is strongly recommended to add\nsome basic information about that picture.\n\nLink type\n---------\nUsing this select box, one can choose to add an internal, or external link to the image. Please\ncheck the appropriate section for details.\n\nImage Shapes\n------------\nThese checkboxes control the four CSS classes from the Bootstrap3 framework:  ``img-responsive``,\n``img-rounded``, ``img-circle`` and ``img-thumbnail``. While rendering HTML, they will be added to\nthe ``<img ... />`` element.\n\nHere the option *Responsive* has a special meaning. The problem with responsive images is, that\ntheir size depends on the media width of the device displaying the image. Therefore we can not use\nthe well known ``<img ... />`` element with a fixed ``width=\"..\"`` and ``height=\"..\"``. Instead,\nwhen rendering responsive images, the additional attributes ``srcset`` and ``sizes`` are added to\nthe element. The attribute ``srcset`` contains the URLs, of up to four differently scaled images.\nThe width of these images is determined by the maximum width of the wrapping container ``<div>``,\nnormally a Bootstrap column.\n\nResponsive Image Width\n----------------------\nThis field is only available for *responsive* images. If set to 100% (the default), the image will\nspawn the whole column width. By setting this to a smaller value, one may group more than one image\nside by side into one column.\n\nFixed Image Width\n-----------------\nThis field is only available for *non-responsive* images. Here an image size must be specified in\npixels. The image then will be rendered with a fixed width, independently of the current screen\nwidth. Images rendered with a fixed width do not neither contain the attributes ``srcset`` nor\n``sizes``.\n\nAdapt Image Height\n------------------\nLeaving this empty (the default), keeps the natural aspect ratio of an image. By setting this to a\npercentage value, the image's height is resized to its current used width, hence setting this to\n``100%`` reshapes the image into a square. Note that this normally requires to *crop* the image,\nsee *Resize Options* below. Setting this value in pixels, set the image to a fixed height.\n\nResize Options\n--------------\n* **Upscale image**: If the original image is smaller than the desired drawing area, then the image\n  is upscaled. This in general leads to blurry images and should be avoided.\n\n* **Crop image**: If the aspect ratio of the image and the desired drawing area do not correlate,\n  than the image is cropped to fit, rather than leaving white space arround it.\n\n* **With subject location**: When cropping, use the red circle to locate the most important part of\n  the image. This is a feature of Django's Filer.\n\n* **Optimized for Retina**: Currently only available for images marked as *responsive*, this option\n  adds an images variant suitable for Retina displays.\n\nPicture Plugin Reference\n========================\n\nA picture is another wording for image. It offers some rarely required options when working with\nimages using `art direction`_. By double-clicking onto a picture, its editor pops up.\n\n|edit-picture|\n\n.. |edit-picture| image:: /_static/bootstrap3/edit-picture.png\n\nThe field **Image**, **Image Title**, **Alternative Description**, **Link type** and **Resize\nOptions** behave exactly the same as for the **Image Plugin**.\n\nBeware that *Pictures* always are considered as responsive, and they always spawn to the whole width\nof the wrapping element, using the CSS style ``width: 100%``. They make the most sense for large\nimages extending over a large area. Therefore it is not possible to specify a width for a picture.\n\nAdapt Picture Heights\n---------------------\nDepending on the current screen's width, one may set different heights for an image. This is useful\nin order to adopt the aspect ratio of an image, when switching from desktops to mobile devices.\nNormally, one should use a fixed height in pixels here, but when specifying the heights in percent,\nthese heights are considered relative to the current image height.\n\nAdapt Picture Zoom\n------------------\nDepending on the current screen's width, one may set different zoom levels for an image. This is\nuseful for keeping the level of detail constant, at the cost of cropping more of the image's\nmargins.\n"
  },
  {
    "path": "docs/source/bootstrap3/index.rst",
    "content": "=======================\nPlugins for Bootstrap-3\n=======================\n\nThis is a collection of plugins to be used with the Bootstrap-3 CSS framework:\n\n.. toctree::\n    :maxdepth: 1\n\n    gallery\n    grid\n    image-picture\n    navbar\n    other-components\n"
  },
  {
    "path": "docs/source/bootstrap3/navbar.rst",
    "content": "======================================\nTemplate tag for the Bootstrap3 Navbar\n======================================\n\n\n.. warning:: This template tag is now deprecated. It's functionality has been\n             split off into a new project that can be found here: \n             `Django CMS Bootstrap 3`_.\n\n.. _Django CMS Bootstrap 3: https://github.com/jrief/djangocms-bootstrap3\n\nAlthough it's not derived from the ``CascadeElement`` class, this Django app is shipped with a\ntemplate tag to render the main menu inside a `Bootstrap Navbar`_. This tag is named ``main_menu``\nand shall be used instead of ``show_menu``, as shipped with the DjangoCMS menu app.\n\n.. _Bootstrap Navbar: http://getbootstrap.com/components/#navbar\n\nRender a Navbar according to the Bootstrap3 guide:\n\n.. code-block:: html\n\n\t{% load bootstrap3_tags %}\n\t...\n\t<div class=\"navbar navbar-default navbar-fixed-top\" role=\"navigation\">\n\t  <div class=\"container\">\n\t    <div class=\"navbar-header\">\n\t      <button type=\"button\" class=\"navbar-toggle\" data-toggle=\"collapse\" data-target=\".navbar-collapse\">\n\t        <span class=\"sr-only\">Toggle navigation</span>\n\t        <span class=\"icon-bar\"></span>\n\t        <span class=\"icon-bar\"></span>\n\t        <span class=\"icon-bar\"></span>\n\t      </button>\n\t      <a class=\"navbar-brand\" href=\"/\">Project name</a>\n\t    </div>\n\t    <div class=\"collapse navbar-collapse\">\n\t      <ul class=\"nav navbar-nav\">{% main_menu %}</ul>\n\t    </div>\n\t  </div>\n\t</div>\n\nAssume, the page hierarchy in DjangoCMS is set up like this:\n\n|page-hierarchy|\n\n.. |page-hierarchy| image:: /_static/page-hierarchy.png\n\nthen in the front-end, the navigation bar will be rendered as\n\n|navbar|\n\n.. |navbar| image:: /_static/navbar.png\n\non computer displays, and as\n\n|navbar-mobile|\n\n.. |navbar-mobile| image:: /_static/navbar-mobile.png\n\non mobile devices.\n\n.. note:: Bootstrap3 does not support \"hover\", since this event can't be handled by touch screens.\n          Therefore the client has to click on the menu item, rather than moving the mouse cursor\n          over it. In order to make CMS pages with children selectable, those menu items are\n          duplicated. For instance, clicking on **Dropdown** in the Navbar, just opens the pull-down\n          menu. Here the menu item for the page named \"Dropdown\" is rendered again. Clicking on this\n          item, finally loads that page from the CMS.\n\n.. note:: Bootstrap3 does not support nested menus, because they wouldn't be usable on mobile\n          devices. Therefore the template tag ``main_menu`` renders only one level of children, no\n          matter how deep the page hierarchy is in DjangoCMS.\n"
  },
  {
    "path": "docs/source/bootstrap3/other-components.rst",
    "content": "=============\nPanel element\n=============\n\nBootstrap is shipped with CSS helpers to facilitate the creation of Panels_. In **djangocms-cascade**\nthese panels can be added to any placholder. In the context menu of a placeholder, select **Panel**\nbelow the section **Bootstrap** and chose the style. The panel heading and footer are optional.\nAs body, the panel element accepts other plugins, normally this is a Text plugin.\n\n.. _Panels: http://getbootstrap.com/components/#panels\n\n.. bootstrap3/jumbotron\n\n=========\nJumbotron\n=========\n\nBootstrap is shipped with CSS helpers to facilitate the creation of a Jumbotron_, sometimes also\nnamed \"Hero\" element. In **djangocms-cascade**, such a Jumbotron plugin can be added anywhere,\neven as the root element of a placeholder, in other words, even outside of a Bootstrap Container\nplugin. The latter configuration is specially useful for images, which shall extend over the full\nwidth of the web page.\n\nIf used outside a Bootstrap Container, we first must configure the allowed breakpoints. This is\nthe same behaviour as for the Container plugin. Then we optionally can chose an image or a\nbackground color, it's size, attachment, position and repetitions. For more details read\n`this article`_ on how to configure background images using pure CSS.\n\nA Jumbotron without any content has a default height of 96 pixels, which is 48 pixels for the\ntop- and bottom paddings, each. These values are given by the Bootstrap 3 framework.\n\nTo increase the height of a Jumbotron you have two choices. The simpler one is to add some\ncontent to the Jumbotron plugin which then increases it's height. Another, is to explicitly\nto set other padding of the Jumbotron plugin.\n\n.. _Jumbotron: http://getbootstrap.com/components/#jumbotron\n.. _this article: https://css-tricks.com/almanac/properties/b/background-image/\n\n\n========\nTab Sets\n========\n\nBootstrap is shipped with CSS helpers to facilitate the creation of Tabs_. In **djangocms-cascade**,\nsuch a Tab plugin can be added anywhere inside columns or rows.\n\nIn the context menu of a placeholder, select **Tab Set**. Depending on the chosen number of\nchildren, it will add as many **Tab Pane**s. Each **Tab Pane** has a Title field, its content is\ndisplayed in the tab. Below a **Tab Pane** you are free to add whatever you want.\n\n\n.. _Tabs: http://getbootstrap.com/javascript/#tabs\n\n==============\nSecondary menu\n==============\n\n.. warning:: This plugin is experimental. It may disappear or be replaced. Use it at your own risk!\n\nOften there is a need to add secondary menus at arbitrary locations. The **Secondary menu** plugin\ncan be used in any placeholder to display links onto child pages of a CMS page. Currently only\npages marked as **Soft Root** with a defined **Page Id** are allowed as parent of such a secondary\nmenu.\n\n.. note:: This plugins reqires the template tag ``main_menu_below_id`` which is shipped with\n          djangocms-bootstrap3_\n\n.. _djangocms-bootstrap3: https://github.com/jrief/djangocms-bootstrap3\n"
  },
  {
    "path": "docs/source/bootstrap4/grid.rst",
    "content": ".. _bootstrap4/grid:\n\n==================\nBootstrap 4 Layout\n==================\n\nIn order to take full advantage of **djangocms-cascade**, you should be familiar with the\nconcepts of the `Bootstrap Layout`_, since all other Bootstrap components depend upon.\n\n.. _Bootstrap Layout: https://getbootstrap.com/docs/4.1/layout/overview/\n\nBootstrap Container\n===================\n\nA **Container** is the outermost component the Bootstrap framework knows of. Here the designer can\nspecify the breakpoints of a web page. By default, Bootstrap offers 5 breakpoints: \"extra small\",\n\"small\", \"medium\", \"large\" and \"extra large\". These determine for which kind of screen widths, the\ngrid system may switch the layout.\n\nThe editor window for a Container element offers the possibility to deactivate certain breakpoints.\nWhile this might make sense under certain conditions, it is safe to always keep all four breakpoints\nactive, since this gives the designer of the web page the maximum flexibility.\n\n|edit-container|\n\n.. |edit-container| image:: /_static/bootstrap4/edit-container.png\n\n\nSmall devices exclusively\n-------------------------\n\nIf the web page shall be optimized just for small but not for large devices, then you may\noptionally disable the larger breakpoints. This doesn't have any effect on the layout, it just adds\nless options to the other editors of the Cascade plugins eco-system.\n\n\nor, if you prefers the SASS syntax:\n\n.. code-block:: css\n\n\t@media(min-width: $screen-lg) {\n\t  .container {\n\t    max-width: $container-desktop;\n\t  }\n\t}\n\n\nLarge devices exclusively\n-------------------------\n\nIf the web page shall be optimized just for large but not for small devices, then disable the\nbreakpoints for **Tiny** and/or **Small**.\n\nChanging the style-sheets then is not required for this configuration setting.\n\n\nFluid Container\n---------------\n\nA variant of the normal Bootstrap Container is the Fluid Container. It can be enabled by a checkbox\nin the editors window. Fluid Containers have no hards breakpoints, they adopt their width to\nwhatever the browser pretends and are slightly larger than their non-fluid counterpart.\n\nA fluid container makes it impossible to determine the maximum width of responsive images for the\n*large media breakpoint*, because it is applied whenever the browser width extends 1200 pixels,\nbut there is no upper limit. For responsive images in the smaller breakpoints (“tiny”, “small”\nand “medium”) we use the width of the next larger breakpoint, but for images in the “large” media\nbreakpoints we somehow must specify an arbitrary maximum width. The default width is set to 1980\npixels, but can be changed, to say 2500 pixels, using the following configuration in your\n``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'bootstrap3': (\n\t        ('xs', (768, 'mobile', _(\"mobile phones\"), 750, 768)),\n\t        ('sm', (768, 'tablet', _(\"tablets\"), 750, 992)),\n\t        ('md', (992, 'laptop', _(\"laptops\"), 970, 1200)),\n\t        ('lg', (1200, 'desktop', _(\"large desktops\"), 1170, 2500)),\n\t    ),\n\t}\n\n.. note:: Fluid container are specially useful for Hero images, full-width Carousels and the\n\tJumbotron plugin. When required, add a free standing fluid container to the placeholder and as\n\tit's only child, use the picture or carousel plugin. Its content then is stretched to the\n\tbrowser's full width.\n\nBootstrap Row\n=============\n\nEach Bootstrap Container may contain one or more Bootstrap Rows. A row does not accept any\nconfiguration setting. However, while editing, one can specify the number of columns. When adding or\nchanging a row, then this number of columns are added if its value exceeds the current number of\ncolumns. Reducing the number of columns does not delete any of them; they must explicitly be chosen\nfrom the context menu in structure view.\n\n|edit-row|\n\n.. |edit-row| image:: /_static/edit-row.png\n\n\nHorizontal Rule\n===============\n\nA horizontal rule is used to separate rows optically from each other.\n\n|rule-editor|\n\n.. |rule-editor| image:: /_static/rule-editor.png\n\n\nColumn\n======\n\nIn the column editor, one can specify the width, the offset and the visibility of each column.\nThese values can be set for each of the four breakpoints (*tiny*, *small*, *medium* and *large*),\nas specified by the Container plugin.\n\nAt the beginning this may feel rather complicate, but consider that **Bootstrap 3 is mobile first**,\ntherefore all column settings, *first* are applied to the narrow breakpoints, which *later* can be\noverridden for larger breakpoints at a later stage. This is the reason why this editor starts with\nthe *column widths* and *column offsets* for tiny rather than for large displays.\n\n|edit-column|\n\n.. |edit-column| image:: /_static/edit-column.png\n\n.. note:: If the current column is member of a container which disables some of its breakpoints\n          (*large*, *medium*, *small* or *tiny*), then that column editor shows up only with the\n          input fields for the enabled breakpoints.\n\n\nComplete DOM Structure\n======================\n\nAfter having added a container with different rows and columns, you may add the leaf plugins. These\nhold the actual content, such as text and images.\n\n|structure-container|\n\n.. |structure-container| image:: /_static/structure-container.png\n\nBy pressing the button **Publish changes**, the single blocks are regrouped and displayed using\nthe Bootstrap's grid system.\n\n\nAdding Plugins into a hard coded grid\n=====================================\n\nSometimes the given Django template already defines a Bootstrap Container, or Row inside a Container\nelement. Example:\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t    {% placeholder \"Row Content\" %}\n\t</div>\n\nor\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t    <div class=\"row\">\n\t        {% placeholder \"Column Content\" %}\n\t    </div>\n\t</div>\n\nHere the Django templatetag ``{% placeholder \"Row Content\" %}`` requires a Row- rather than a\nContainer-plugin; and the templatetag ``{% placeholder \"Column Content\" %}`` requires a\nColumn-plugin. Hence we must tell **djangocms-cascade** which breakpoints shall be allowed and what\nthe containers extensions shall be. This must be hard-coded inside your ``setting.py``:\n\n.. code-block:: python\n\n\tCMS_PLACEHOLDER_CONF = {\n\t    # for a row-like placeholder configuration ...\n\t    'Row Content': {\n\t        'plugins': ['BootstrapRowPlugin'],\n\t        'parent_classes': {'BootstrapRowPlugin': []},\n\t        'require_parent': False,\n\t        'glossary': {\n\t            'breakpoints': ['xs', 'sm', 'md', 'lg'],\n\t            'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},\n\t            'fluid': False,\n\t            'media_queries': {\n\t                'xs': ['(max-width: 768px)'],\n\t                'sm': ['(min-width: 768px)', '(max-width: 992px)'],\n\t                'md': ['(min-width: 992px)', '(max-width: 1200px)'],\n\t                'lg': ['(min-width: 1200px)'],\n\t            },\n\t        }\n\t    },\n\t    # or, for a column-like placeholder configuration ...\n\t    'Colummn Content': {\n\t        'plugins': ['BootstrapColumnPlugin'],\n\t        'parent_classes': {'BootstrapColumnPlugin': []},\n\t        'require_parent': False,\n\t        'glossary': {\n\t            'breakpoints': ['xs', 'sm', 'md', 'lg'],\n\t            'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},\n\t            'fluid': False,\n\t            'media_queries': {\n\t                'xs': ['(max-width: 768px)'],\n\t                'sm': ['(min-width: 768px)', '(max-width: 992px)'],\n\t                'md': ['(min-width: 992px)', '(max-width: 1200px)'],\n\t                'lg': ['(min-width: 1200px)'],\n\t            },\n\t        }\n\t    },\n\t}\n\nPlease refer to the `DjangoCMS documentation`_ for details about these settings with the exception\nof the dictionary ``glossary``. This latter setting is special to **djangocms-cascade**: It gives\nthe placeholder the ability to behave like a plugin for the Cascade app. Remember, each\n**djangocms-cascade** plugin stores all of its settings inside a Python dictionary which is\nserialized into a single database field. By having a placeholder behaving like a plugin, here this\nso named *glossary* is emulated using an additional entry inside the setting\n``CMS_PLACEHOLDER_CONF``, and it should:\n\n- include all the settings a child plugin would expect from a real container plugin\n- reflect how hard coded container was defined (e.g. whether it is fluid or not)\n\n.. _DjangoCMS documentation: https://django-cms.readthedocs.org/en/latest/basic_reference/configuration.html#std:setting-CMS_PLACEHOLDER_CONF\n\n\nNested Columns and Rows\n=======================\n\nOne of the great features of Bootstrap is the ability to nest Rows inside Columns. These nested Rows\nthen can contain Columns of 2nd level order. A quick example:\n\n.. code-block:: html\n\n\t<div class=\"container\">\n\t  <div class=\"row\">\n\t    <div class=\"col-md-3\">\n\t      Left column\n\t    </div>\n\t    <div class=\"col-md-9\">\n\t      <div class=\"row\">\n\t        <div class=\"col-md-6\">\n\t          Left nested column\n\t        </div>\n\t        <div class=\"col-md-6\">\n\t          Right nested column\n\t        </div>\n\t      </div>\n\t    </div>\n\t  </div>\n\t</div>\n\nrendered, it would look like:\n\n|nested-rows|\n\n.. |nested-rows| image:: /_static/nested-rows.png\n\nIf a responsive image shall be placed inside a column, we must estimate the width of this image, so\nthat when rendered, it fits exactly into that column. We want easy-thumbnails_ to resize our images\nto the columns width and not having the browser to up- or down-scale them.\n\n.. _easy-thumbnails: https://github.com/SmileyChris/easy-thumbnails\n\nTherefore **djangocms-cascade** keeps track of all the breakpoints and the chosen column widths.\nFor simplicity, this example only uses the breakpoint “medium”. The default Boostrap settings for\nthis width is 992 pixels. Doing simple math, the outer left column widths gives\n3 / 12 * 992 = 248 pixels. Hence, adding a responsive image to that column means, that\n**easy-thumnails** automatically resizes it to a width of 248 pixels.\n\nTo calculate the width of the nested columns, first evaluate the width of the outer right column,\nwhich is 9 / 12 * 992 = 744 pixels. Then this width is subdivided again, using the width of the\nnested columns, which is 6 / 12 * 744 = 372 pixels.\n\nThese calculations are always performed recursively for all nested column and for all available\nbreakpoints.\n\n.. warning:: As the name implies, a container marked as *fluid*, does not specify a fixed width.\n\tHence instead of the inner width, the container's outer width is used as its maximum. For the\n\tlarge media query (with a browser width of 1200 pixels or more), the maximum width is limited\n\tto 1980 pixels.\n"
  },
  {
    "path": "docs/source/bootstrap4/index.rst",
    "content": "=======================\nPlugins for Bootstrap-4\n=======================\n\nThis is a collection of plugins to be used with the Bootstrap-4 CSS framework:\n\n.. toctree::\n\t:maxdepth: 1\n\n\tgrid\n\tutilities\n"
  },
  {
    "path": "docs/source/bootstrap4/utilities.rst",
    "content": ".. _bootstrap4/utilities:\n\n=====================\nBootstrap 4 Utilities\n=====================\n\nBootstrap-4 pursues the approach of offering various utilities, which can be optionally applied to\nall Bootstrap elements. **djangocms-cascade** follows this approach and offers mixin classes which\ncan be optionally added to all Bootstrap-4 plugins. They extend their respective's plugin editor by\none or more extra fields, which the user can use to configure the appearance of the plugin.\n\n\nMotivation\n==========\n\nSay, you want to configure the Bootstrap Button Plugin to use the `responsive float utilities`_.\nThe clumsy approach would be to add a field using a select box, for adding the float classes\n``float-left``, ``float-right`` and ``float-none`` to the plugin's editor. This would be fine,\nas long as these classes can only be used inside the context of a button element. The idea of\nBootstrap however is, to offer reusable components and CSS classes. Since **djangocms-cascade**\nwants to follow this mindset, the editor for controlling those utilities is part of the mixin class\n:class:`cmsplugin_cascade.bootstrap4.mixins.BootstrapUtilitiesMixin`.\n\n.. _responsive float utilities: https://getbootstrap.com/docs/4.3/utilities/float/\n\n\nUsage\n=====\n\nA Bootstrap-4 plugin, wanting to offer additional editable fields to the plugin's editor, should\nsimple add this configuration to the project's ``settings.py``:\n\n.. code-block:: python\n\n    CMSPLUGIN_CASCADE['plugins_with_extra_mixins'] = {\n        'BootstrapButtonPlugin': BootstrapUtilities(\n            BootstrapUtilities.floats,\n        ),\n    }\n\n\nImplemented Utilitiy Properties\n===============================\n\nCurrently the following utilities are implemented:\n\nBackground and Color\n--------------------\n\nThis adds a combination of one CSS class for the `background and one for the foreground color`_.\n\n.. _background and one for the foreground color: https://getbootstrap.com/docs/4.3/utilities/colors/\n\n\nMargins and Paddings\n--------------------\n\nThis adds all the CSS classes for `margins and paddings`_. They follow the *mobile first*\nprinciple, which means that all selected values can be overridden by a larger media breakpoint.\n\n.. _margins and paddings: https://getbootstrap.com/docs/4.3/utilities/spacing/\n\n\nFloats\n------\n\nThis adds all the CSS classes for adding `responsive float utilities`_.\n"
  },
  {
    "path": "docs/source/changelog.rst",
    "content": "===============\nRelease History\n===============\n\n2.3.14\n======\n* Fix regression introduced in 2.3.13.\n\n2.3.13\n======\n* When linking onto a CMS page using a URL with a hash for lookup, now the select box for section\n  is be preset with that hash value.\n\n2.3.12\n======\n* Fix some German translations.\n\n2.3.11\n======\n* Set minimum width on select2 widget to 150px.\n\n2.3.10\n======\n* In LinkPlugin editor, set focus to input field after changing link type.\n\n2.3.9\n=====\n* Fix option \"justified\" in **BootstrapTabSetPlugin**.\n\n2.3.8\n=====\n* Fix some German translations.\n* Allow to override the default marker icon in the Leaflet **MapPlugin**.\n\n2.3.7\n=====\n* Add German translations for labels on extra inline styles.\n\n2.3.6\n=====\n* Slightly larger Select2 Element for chosing ``Page`` object from CMS.\n\n2.3.5\n=====\n* Don't use this version!\n\n2.3.4\n=====\n* Add HTML elements ``<ol>`` and ``<ul>`` to the choices in ``TextEditorConfigFields``.\n\n2.3.3\n=====\n* In ``LeafletPlugin``: Render empty search result list, if service Nominatim does not find any\n  address.\n\n\n2.3.2\n=====\n* Add helptext to options in YouTube-plugin.\n\n\n2.3.1\n=====\n* Allow Django version 4.0 as requirement.\n\n2.3\n===\n* In ``LeafletPlugin`` inlined markers, add an address lookup field. Entering an address creates a\n  list with possible coordinates.\n\n2.2.2\n=====\n* Ignore empty field when validating URL and add a timeout value of 5 seconds when fetching remote\n  URLs.\n\n2.2.1\n=====\n* Fix: Always add ``admin:validate_exturl`` to the JS context in the plugin's\n  ``change_form`` template.\n* Increase width of URL input field to 100 characters.\n* Fix: Method ``cms.plugin_rendering.StructureRenderer.render_plugin`` has a signature incompatible\n  to other renderers. There is a check now.\n\n2.2\n===\n* In HeadingPlugin, reduce width of content input field.\n* Refactor legacy code to be compatible with Django-4.\n* In Ajax request ``get_published_pagelist``:\n  * Make querying link by path language independent.\n  * Strip whitespace from query term.\n* Change ``__repr__`` method for better readability.\n* Fix major bug: Plugins allowing anchor-IDs, can now share the same ID on the same page for each\n  language.\n* Drop support for Python-3.7.\n* Drop support for Django-3.1.\n* Add support for Python-3.10.\n* Add support for django-CMS-3.10.\n\n2.1.7\n=====\n* Second fix (see 2.1.4): Editor for LinkPlugin offered anchors to empty ``id``-attributes.\n\n\n2.1.6\n=====\n* Fix: In the Clipboard admin, pasting of JSON copied from another field sometimes was impossible.\n\n2.1.5\n=====\n* Links of type \"CMS-page\" and \"Download\" pointing onto a remove page or file, ie. \"broken links\"\n  are now dysfunctional. This prevents linking onto non-existing pages.\n\n2.1.4\n=====\n* Editor for LinkPlugin offered anchors to empty ``id``-attributes. This has been fixed.\n\n\n2.1.3\n=====\n* Update some misstranslations in German.\n\n\n2.1.2\n=====\n* For staff users, the menu item \"Segmentation\" only appears in the CMS toolbar, if the proper\n  permissions are set. Please refer to the documentation for details.\n\n\n2.1.1\n=====\n* Bugfix: ``LinkSearchField`` sometimes raised a ``MultipleObjectsReturned`` exception, because\n  of duplicates in queryset.\n\n\n2.1\n===\n* ``MultiSizeField`` accepts ``sublabels`` for all of its sub-fields.\n\n\n2.0.8\n=====\n* Adopted translation string for German.\n\n\n2.0.7\n=====\n* Configuration setting `CMSPLUGIN_CASCADE['plugins_with_extra_mixins']` now also accepts\n  a tuple of extra mixin classes, rather than only one.\n* Updated German translation strings.\n* Fix for windows path signs in icon fonts folder path causing rendering problem during icon\n  plugins addition.\n\n2.0.6\n=====\n* Leaflet doesn't quote the content in its `url()` statements in its CSS. This\n  causes some trouble with **django-compressor** which appends a hash value.\n  This fix imports those CSS files locally until Leaflet comes up with a patch.\n\n2.0.5\n=====\n* Fix Icon Plugin: If icon is optional, nullify initial value for Icon Font.\n\n2.0.4\n=====\n* Fix: Adding a link onto a freshly created page did not work, which was caused by an already\n  evaluated queryset.\n\n\n2.0.3\n=====\n* Fix ``BootstrapTabSetPlugin``: Add CSS class ``nav-tab`` to outer wrapper.\n* Fix many translation strings in German.\n\n\n2.0.2\n=====\n* Fix alignment of widget ``CheckboxInput`` and Link-Type.\n\n\n2.0.1\n=====\n* Fix #397: Bug with bad tags of ``TextImagePlugin``.\n* Remove useless aria-controls from link in ``TabSetPlugin``.\n\n\n2.0\n===\n* Replace all external occurences of ``JSONField`` against Django's new internal ``JSONField``.\n  The migration towards this version shall run smoothly, but you will be unable to migrate back.\n\n1.3.7\n=====\n* Perform validation of external URL during editing, instead of form validation. Do not reject\n  invalid external URLs, just warn about them.\n\n\n1.3.6\n=====\n* Fix regression on link validation introduced in 1.3.5.\n\n\n1.3.5\n=====\n* In plugins inheriting from ``LinkPluginBase``, refactor validation from method ``clean()`` to\n  ``_post_clean()``, in order to avoid unjustified validation errors.\n* Fix rare error when pasting from clipboard into structure view.\n* Remove empty final line in templates used to render links; they added unwanted whitespace to\n  output.\n* In some JS files: Replace deprecated jQuery `.bind()` call with `.on()`.\n* Removed some legacy code dating back to Python2.\n\n\n1.3.4\n=====\n* Fix: Internal Server error raised when deleting content of link to CMS page's form field.\n* When testing external links, use request with `User-Agent: Django-CMS-Cascade` instead of the\n  default.\n\n\n1.3.3\n=====\n* Fix: Deletion of markers in map plugin failed.\n\n\n1.3.2\n=====\n* Fix migration ``0027_version_1.py`` to migrate links from version<1 upwards.\n\n\n1.3.1\n=====\n* Fix external requiremnts.\n* Replace deprecated ugettext against gettext.\n\n\n1.3\n===\n* Drop support for Python 2.\n* Drop support for Django-1.11 and Django-2.0.\n* Add support for Django-3.0 and Python-3.8.\n* Paste structure of placeholder directly to, and from Persisting Clipboard Content.\n* CascadeClipboardPlugin is not a system plugin anymore.\n* Add link target for phone numbers.\n* Add mixin to accept preconfigured CSS classes.\n* Fix: Style of center button in Leaflet Plugin.\n* Fix: In Leaflet Plugin accepts pasting from clipboard.\n* Move configuration setting for HorizontalRule into submodule ``generic``.\n* Add fields ``created_by``, ``created_at`` and ``last_accessed_at`` to Clipboard model.\n* Make loading of icon plugins configurable.\n\n\n1.2.3\n=====\n* Make page editor for extra fields configurable.\n\n\n1.2.2\n=====\n* Refactor ``TextLinkFormMixin`` from ``cmsplugin_cascade.link.cms_plugin`` into\n  ``cmsplugin_cascade.link.forms``. Adopt your import accordingly.\n\n1.2.1\n=====\n* In **TextEditorConfig** add HTML element ``<span>`` to available choices.\n* Fix: Adding a link onto a page with different translations, can cause a too many pages exception.\n\n1.2\n===\n* Refactor submodule ``generic`` into separate modules. This enables the client project\n  to use them individually.\n\n1.1.9\n=====\n* Fix: Handle float values in size fields expecting ``em``-s and ``rem``-s.\n\n1.1.8\n=====\n* Fix: Handle plugins with defined but empty forms.\n\n1.1.7\n=====\n* Support to use use a swappable Image model in django-filer.\n\n1.1.6\n=====\n* Fix regression introduced in 1.1.5: In ``BootstrapButtonPlugin``, Strides did not work anymore.\n* Fallback to empty form, if a Cascade plugin had not a form enheriting from ``EntangledModelFormMixin``.\n\n1.1.5\n=====\n* Fix regression introduced in 1.1.4: In ``LinkSearchField`` reduce the initial number of choices for\n  the ``ModelChoice`` field to max. 15 entities.\n\n1.1.4\n=====\n* Add special unit ``auto`` to the existing sizing units. Allow it as unit for element heights in Jumbotron.\n* Adopt JavaScript code for some plugins by enforcing the loading order, so that the file ``query.init.js``\n  always is loaded before its plugin-JS.\n* In Carousel plugin, only allow pixels as unit.\n* Fix problem of possible non-existing folder, when deleting an icon font.\n* On plugins with more than one rendering template, allow to deactivate that choice actively.\n* Actively check, that ``django_select2`` is installed.\n* Fix: Pasting an invalid URL into the LinkPluigin's external link field, could cause a KeyError.\n* In In LinkPlugin, render search results as safe html, without htmlentities.\n* Fix: When rendering the editor of a LinkPlugin on sites with thousands of CMS pages, it took considerably\n  too long.\n* Allows the user to paste an existing URL into the CMS link box, pointing onto the correct CMS page.\n\n\n1.1.3\n=====\n* Fix problem in rendering the plugin's identifier, if Bootstrap Row is created with flexible widths columns.\n\n\n1.1.2\n=====\n* Prepend ``admin/js/jquery.init.js`` in front of JS file paths using ``django.jQuery``. This is required by a\n  change in Django-2.2 (https://docs.djangoproject.com/en/2.2/releases/2.2/#merging-of-form-media-assets).\n\n\n1.1.1\n=====\n* Support for django-CMS version 3.7 and Django version 2.2.\n* In the CMS-Toolbar: Segmentation > Clear emulations is enabled only, if emulations are active.\n\n\n1.1\n===\n* In **BootstrapJumbotrom**: Add multiple fields to set height in all five breakpoints.\n* Allow Jumbotron to be child of a Bootstrap Column.\n* Fix regression in JavaScript part of Jumbotron: Some HTML selectors did not work in version 1.0.\n* Fix regression in ColorField of Jumbotron. Background color is rendered correctly.\n* ``MultiSizeField`` accepts ``initial`` as single value or list in addition to dictionary.\n* Add reusable field ``CascadeImageField`` to reference images in **django-filer**.\n\n\n1.0 (Warning: API changes!)\n===========================\n* Add support for Django-2.0/2.1.\n* Drop support for Python-2.7.\n* Introduce a much simpler way for writing customized CMS plugins. Instead of using a special multi-widget,\n  thanks to django-entangled_, now all plugins use Django forms to create the editors for their plugin models.\n  As with previous versions of **djangocms-cascade**, all data is kept in a JSON field, but in a slightly\n  different format. Therefore you must invoke ``./manage.py migrate cmsplugin_cascade`` after upgrading.\n* If used in your project's settings, change ``CMSPLUGIN_CASCADE['link_plugin_classes']`` to a 2-tuple\n  providing a ``LinkPluginBase``- and a ``LinkForm``-class.\n\n.. _django-entangled: https://github.com/jrief/django-entangled\n\n0.19\n====\n* For each **django-CMS** page, **djangocms-cascade** optionally adds a one-to-one relation onto\n  a page extension named ``CascadePage``. This model has been extended to optionally point onto an\n  icon font and a font symbol. In **djangocms-bootstrap** version 1.1 this symbol now can be\n  rendered in front of the page title.\n\n  Remember to invoke ``./manage.py migrate cmsplugin_cascade``.\n\n\n0.18.2\n======\n* Fixed: CSS files, such as those extracted from an icon-font, served from the ``/media`` folder\n  can not be compressed by django-compressor. To prevent this, an alternative compressor for\n  Sekizai's templatetag ``{% render_block \"css/js\" ... %}`` has been added. Adopt your templates!\n\n0.18.1\n======\n* Fix problem with PicturePlugin: subject_location not honored.\n* Use predefined margins for **HorizontalRulePlugin** in Bootstrap-4.\n* In Django admin: ``jquery.init.js`` must be the first dependency in admin media.\n\n0.18\n====\n* In Plugins using Icons, such as **TextIconPlugin**, **BootstrapButtonPlugin** and\n  **FramedIconPlugin**, it now is possible to select the Icon Font. This was the behaviour <0.17 and\n  has been dropped, because back then, icons using two or more different fonts on he same page, lead\n  to confusion. By enforcing a specific CSS icon prefix, it now is possible to use as many different\n  icon fonts, as you want, on the same page.\n* In your ``settings.py`` replace ``CKEDITOR_SETTINGS['stylesSet']`` by\n  ``format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config'))``.\n* Add **SimpleIconPlugin**, which renders an icon without any frame.\n* Add a special link type to download arbitrary files. All plugins which can link, can now set a\n  link onto a downloadable file, managed by ``django.Filer``.\n* Add **BootstrapYoutubePlugin** which can be used to embed video available on YouTube.\n* When managing Icon Fonts, one entry can be set as the default font.\n* Fix: Hide link title, when no linking is desired.\n* In plugins with links, refactor the usage of ``get_form`` by using a new ``VoluntaryLinkForm``.\n* Plugin **BootstrapSecondaryMenuPlugin** can be used outside of columns.\n* Add property ``floats`` to mixin ``BootstrapUtilities`` in order to handle Bootstrap's float\n  utilities.\n  Replace field ``quick_float`` in **BootstrapButton** against this mixin property.\n* Card plugin offers three distinct children for Header, Body and Footer. Themselves, they can be\n  extended individually.\n\n0.17.10\n=======\n* Fix problem of missing referer, required to determine the current page when accessing an\n  **IconFontPlugin** from inside the CKEditor.\n\n0.17.9\n======\n* Catch IconFont exceptions, if CKEditor is used outside of CMS pages.\n* Fix: Supress AttributeError in `BootstrapCardPlugin.get_identifier`.\n\n0.17.8\n======\n* For better reusability of ``IconFont``-s: Refactor method ``unzip_archive`` into external\n  utility function.\n\n0.17.7\n======\n* Fix: Restore-to-Clipboard with data from differently configured instances of Cascade\n  may cause an Internal Server Error. Now such an import shows an error message importing\n  whatever is parsable.\n* Add ``role=\"button\"`` to the **ButtonPlugin**.\n* Optionally add CSS class ``stretched-link`` introduced in Bootstrap-4.3 to ``<a href=\"...\">``.\n* Fix: We can not see the SVG file, if the image file existed and was not found, specifically\n  when copying a Persisted clipboard.\n* Fix: If jsonfield is serialized as string, convert and reload as JSON.\n* Fix: **ImagePlugin** / **PicturePlugin** can't be copied by clipboard CMS.\n* Fix: Strides Plugin Element object has no attribute ``placeholder``.\n\n\n0.17.6\n======\n* Fix: Tabset support for Bootstrap-4 using jQuery.\n\n\n0.17.5\n======\n* Fix: Limit the number of results to 16 while searchung for a link.\n\n\n0.17.4\n======\n* Fix: TextIconPlugin does not raise an exception if no IconFont was selected for the current page.\n\n\n0.17.3\n======\n* Use ``HeavySelect2Widget`` to choose the CMS page if **django-select2** is available.\n* For **ButtonPlugin**, make IconFont optional.\n* Fix: Limit number of decimal places in breakpoint selection to one.\n* Increase width of select2 widget to 400px.\n* Add feature: if a plugin use ImageFormMixin and that the source of the media is missing,\n  instead it uses a svg with old witdh and heigth who use srcset.\n\n0.17.2\n======\n* Fix  problem with lazy evaluation during initialization by lazy formating translated strings\n  in ``BootstrapUtilities``.\n* Fix font-size icon don't work without text_align.\n* In ``HeadingPlugin`` set width of content field to 100%.\n* Add nicer ColorPicker widget for fields containing a color.\n\n0.17.1\n======\n* User margin classes for HeadingPlugin as provided by Bootstrap-4.\n* In SectionMixin, fix problem if no cascadepage is associated with CMS page.\n* Fix: Can add BootstrapColumn with interface +.\n* Fix: Add missing file carsousel template file.\n* Render a nicer warning box if plugin template not found.\n* Fix problem with missing placeholderreference.\n* Adopted examples to support Bootstrap-3 as well as Bootstrap-4.\n* Add filter function to find font-icon by name.\n\n0.17\n====\n* Add support for django-CMS 3.5.\n* Add support for Bootstrap-4.\n* Drop support for Django-1.9.\n* Remove deprectated function ``cmsplugin_cascade.utils.resolve_dependencies``.\n* Replace function ``cmsplugin_cascade.utils.format_lazy`` by Django's internal function.\n* Font Icons now must be chosen per page, rather than for each Icon plugin. This prevents the\n  problem of rendering unwanted symbols in case more than one Icon Font was selected on a given CMS\n  page. Therefore, after migrating to this version of **djangocms-cascade**, check on all CMS\n  pages if the selected icon font is the desired one. Use the django-CMS toolbar, and click onto\n  ``Page > Choose Icon Font…``.\n* Fix: Prevent double registration of proxy models.\n* Append fields to plugins, which are missing in list ``glossary_fields_order``.\n\n\n0.16.3\n======\n* Fix **CarouselPlugin**, if used with newer versions of the **angular-ui-bootstrap** NPM library.\n* Fix corner-case of dysfunctional **elif** evaluation in **SegmentPlugin**.\n\n\n0.16.2\n======\n* Fix Markdown while uploading to PyPI.\n\n\n0.16.1\n======\n* Fix: Compute link of ``Page`` object holding documentation menu.\n* Fix regression in sharable glossary caused by upgrade to Django-1.11.\n* Adopt child plugin editing for django-CMS 3.5 to behave as earlier versions.\n* Icons in the Text field may have a foreground color.\n\n\n0.16\n====\n* Drop support for **Django-CMS CKEditor** version 3.4 in favor of version 3.5 and later. In ``CKEDITOR_SETTINGS``\n  change the ``skin`` setting to ``moono-lisa``.\n* Remove monkey patch required for django-CMS 3.4. This has been fixed by applying\n  this pull request: https://github.com/divio/django-cms/pull/5809\n* Icons inside the ``TextPlugin`` can have an optional link.\n* Simplify JavaScript plugins to extend alternative link types.\n* Added ``TextImagePlugin`` allowing to add simple images inside the CKEditor.\n* Move common image functionality into utility class ``cmsplugin_cascade/image.py``.\n* Optional checkbox to hide plugin is moved at the end of the editor window.\n\n\n0.15.5\n======\n* Fix: ImagePlugin should not have duplicate css_classes and inline_styles if there is a link.\n* Fix regression: Cascade Clipboard did not work anymore with Django<=1.10.\n* Fix: If ``USE_THOUSAND_SEPARATOR`` was set to ```True``, some templates where not unlocalized\n  properly.\n\n\n0.15.4\n======\n* Fix: In ``FramedIconPlugin``, use ``ColorWidget`` for glossary attribute ``color`` instead of\n  using a text field. This allows to inherit the foreground color from the given CSS settings.\n  This fix required to run migration ``0018_iconfont_color``.\n* Fix: A Bootstrap Row now can be the child of a Jumbotron Plugin.\n* Added a CMSAppHook named \"Sphinx Documentation\" which routes a documentaion tree directly onto\n  the ``SphinxDocsView``. Therefore the documentation tree can be handled directly by the CMS and\n  doesn't require any special routes in the project's URL config.\n\n0.15.3\n======\n* Bugfix: If more than one CheckboxInput in plugin only the first seem work correctly.\n* Bugfix: Cascade works properly if ``'cmsplugin_cascade.icon'`` is missing in ``INSTALLED_APPS``.\n\n0.15.1 and 0.15.2\n=================\n* Fixed one failing occurrence of ``settings.SPHINX_DOCS_ROOT``.\n\n0.15\n====\n* Posibility to integrate documentation pages generated by Sphinx, manged by the CMS menu tree.\n\n0.14.4\n======\n* Adopted button- and container selection widget rendering to work with Django-1.11.\n* Fixed clipboard issued regarding Django-1.11.\n\n0.14.3\n======\n* Fix: If plugin is missing, now templatetag ``render_plugin`` renders empty string, instead\n  of raising a TemplateSyntaxError.\n* Fix: Method ``RenderTemplateMixin.get_render_template()`` now properly expands templates with\n  placeholders.\n\n0.14.2\n======\n* In Leaflet Map Plugin:\n  * For unset markers, place the position into the center of the current map.\n  * Fix positioning of the markers anchor.\n* When using templatetag ``render_cascade``, the HTML content is cached to improve performance.\n\n0.14.1\n======\n* Restored deleted font files.\n* Fix template for rendering a Google map.\n* Add fields ``offset`` and ``limit`` to **SecondaryMenuPlugin**, to segment the menus.\n* Fix bug in HeadingPlugin: Can not be used in static_placeholder tag.\n* Fix bug in HeadingPlugin: HTML entities, such as ampersand can be used as content.\n* Fix in Panel Plugin: Show identifier in Placeholder tree.\n* Fix in Section Plugin: Can now be used in ``static_placeholder``.\n\n0.14\n====\n* Added static rendering of a serialized representation of plugins copied from a ``placeholder``\n  to the clipboard. For details, please read on how to :ref:`strides`.\n\n0.13.1\n======\n* Prepare for Django-1.11 compatibility: Replace renderer classes by specialized widgets\n  overriding its ``render()`` method.\n\n0.13\n====\n* Added Leaflet Plugin which allows to integrate interactive maps from Google, Mapbox and\n  OpenStreetMap. The editor can add any number of markers using arbitrary logos with an optional\n  popup box.\n* Refactored the app's settings modules to use an ``AppSettings`` class, rather than merging\n  application specific settings on the fly.\n\n0.12.5\n======\n* Fixed: Wrapper for transparent plugins did not find all children which declared\n  these kind of plugins as their parents.\n\n0.12.4\n======\n* Fixed: Initial Image is reseted after reopening Image plugin editor.\n* Changed order of fields in Accordion plugin editor.\n* Moved directory ``workdir`` for demo project from root folder into examples.\n\n0.12.3\n======\n* Fixed: When using an Element ID while adding a Heading Plugin, under certain circumstances\n  the validation ran into an infinite loop.\n\n0.12.2\n======\n* Fixed: Allow transparent instances as root objects.\n\n0.12.1\n======\n* Fixed: Do not invoke ``{% addtoblock \"css\" %}...`` for empty values of ``stylesheet_url``.\n* Renamed buttons in clipboard admin to \"Insert Data\" (instead of \"Save\") and \"Restore Data\"\n  (instead of \"restore\").\n\n0.12.0\n======\n* Added compatibility for Django version 1.10.\n* Added compatibility for django-CMS version 3.4.\n* Added monkey patch to resolve issues handled by PR https://github.com/divio/django-cms/pull/5809\n* Added compatibility for djangocms-text-ckeditor-3.4.\n* **Important for AngularJS users**: Please upgrade to angular-ui-bootstrap version 0.14.3. All\n  versions later than 0.13 use the prefix ``uib-`` on all AngularJS directives, hence this upgrade\n  is required.\n* In the ``CarouselSlide`` plugin, caption is added as a child ``TextPlugin`` instead of using the\n  glossary. Currently the migration of ``TextLinkPlugins`` inside this caption field does not work\n  properly. Please create an issue, if you really need it.\n* Added method ``value_omitted_from_data`` to ``JSONMultiWidget`` to override the Django method\n  implemented in ``django.forms.widgets.MultiWidget``.\n* In ``cmsplugin_cascade.models.CascadeElement`` the foreign key ``shared_glossary`` now is marked\n  as editable. Instead to plugins without sharable glossary, the attribute\n  ``exclude = ['shared_glossary']`` is added.\n* Instead of handling ring.js plugin inheritance through ``get_ring_bases()``, Cascade plugins\n  just have to add ``ring_plugin = '...'`` to their class declaration.\n* Function ``cmsplugin_cascade.utils.resolve_dependencies`` is deprecated, since Javascript\n  dependencies now are handled via their natural inheritance relation.\n* The configuration option ``settings.CMSPLUGIN_CASCADE['dependencies']`` has been removed.\n* Added method ``save()`` to model ``SharedGlossary``, which filters the glossary to be stored to\n  only those fields marked as sharable.\n* Accessing the CMS page via ``plugin_instance.page`` is deprecated and has been replaced by\n  invocations to ``plugin_instance.placeholder.page``.\n* Removed directory ``static/cascade/css/fonts/glyphicons-halflings``, since they are available\n  through the Bootstrap npm packages.\n* All Javascript files accessing a property ``disabled``, now use the proper jQuery function\n  intended for it.\n* Added interface to upload fonts and use them as framed icons, text icons or button decorators.\n* The permission system now is fine grained. Administrators can give their staff users\n  add/change/delete permissions to each of the many Cascade plugins. When adding new plugins, this\n  does not even require a database migration.\n* Fixed: On saving a **CarouselPlugin**, the glossary of it's children, ie. **CarouselSlidePlugin**,\n  is sanitized.\n* Handle the high resolution of the **PicturePlugin** through ``srcset`` rather than a ``@media``\n  query.\n* Handle the high resolution background of the **JumbotronPlugin** through ``image-set`` rather than\n  a ``@media`` query.\n* Use default configurations from provides Cascade settings rathern than from the Django project.\n\n0.11.1\n======\n* Added preconfigured ``FilePathField`` to prevent the creation of useless migration files.\n* SegmentPlugin.get_form OrderedDict value lookups now compatible with python3.\n* Fixed database migration failing on multiple database setup.\n\n0.11.0\n======\n* Instead of adding a list of ``PartialFormField``s named ``glossary_fields``, we now can add these\n  fields to the plugin class, as we would in a Django ``forms.Form`` or ``models.Model``, for\n  instance: ``fieldname = GlossaryField(widget, label=\"A Label\", initial=some_value)`` instead of\n  ``glossary_fields = <list-or-tuple-of PartialFormField s>``. This is only important for third\n  party apps inheriting from ``CascadePluginBase``.\n\n  **Remember**: In some field names, the ``-`` (dash) has been replaced against an ``_``\n  (underscore). Therefore please run ``./manage.py migrate cmsplugin_cascade`` which modifies the\n  plugin's payloads.\n\n0.10.2\n======\n* Fix #188: Using shared settings does not remember it's value.\n\n0.10.1\n======\n* Fix #185: Undefined variables in case of uncaught exception.\n\n0.10.0\n======\n* Added **BootstrapJumbotronPlugin**. This for instance can be used to place background images\n  extending over the full width of a page using a parallax effect.\n* *Experimental*: Utility to manage font icons, so that symbol icons can be used anywhere in any\n  size.\n* ``CMSPLUGIN_CASCADE['plugins_with_extra_fields']`` is a dict instead of a tuple. This allows\n  the site administrator to enable extra styles globally and without adding them using the\n  administration backend.\n* Tuples in ``CMSPLUGIN_CASCADE['bootstrap3']['breakpoints']`` now accepts five parameters instead\n  of four. The 5th parameter specifies the image width for fluid containers and the Jumbotron\n  plugin.\n* The plugin's change form now can add an introduction and a footnote HTML. This is useful to add\n  some explanation text.\n\n0.9.4\n=====\n* Added function ``.utils.validate_link`` to check if submitted link information is valid.\n\n0.9.3\n=====\n* Fixed: enabled subject_location did not work properly for **ImagePlugin** and **PicturePlugin**.\n* Fixed indention in admin interface for extra fields model.\n* Moved template 'testing.html' -> 'cascade/testing.html'.\n* Added German translations.\n\n0.9.2\n=====\n* Restore global jQuery object (required by the Select2 widget) in explicit file instead of doing\n  it implicitly in ``linkpluginbase.js``\n\n0.9.1\n=====\n* Prepared for django-1.10\n* Upgrade ring.js to version 2.1.0\n* In LinkPlugin, forgive if sub-dict ``link`` was missing in ``glossary``\n* Fixed HTML escaping problem in Bootstrap Carousel\n* Increase height of Select2 fields\n\n0.9.0\n=====\n* Compatible with django-cms version 3.3.0\n* Converted ``SharableCascadeElement`` into a proxy model, sharing the same data as model\n  ``CascadeElement``. This allows adding plugins to ``CMSPLUGIN_CASCADE['plugins_with_sharables']``\n  without requiring a data-migration. (**Note:** A migration merges the former two models, so\n  please backup your database before upgrading!)\n* Add support for Section Bookmarks.\n* Fixed: Do not set width/height on <img>-element inside a <picture>, if wrapping container is fluid.\n* Replaced configuration settings ``CMSPLUGIN_CASCADE_LINKPLUGIN_CLASSES`` against\n  ``CMSPLUGIN_CASCADE['link_plugin_classes']`` for better consistency.\n\n**Note:** If you want to continue using django-CMS 3.2 please use djangocms-cascade 0.8.5.\n\n0.8.5\n=====\n* Dropped support for Python-2.6.\n\n0.8.4\n=====\n* Fixed a regression in \"Restore from clipboard\".\n* Fixed TextLinkPlugin to work again as child of TextPlugin.\n* ContainerPlugin can only be added below a placeholder.\n* Prepared demo to work with Django-1.10.\n* Plugins marked as \"transparent\" are only allowed as parents,\n  if they allow children.\n\n0.8.3\n=====\n* Added ``CustomSnippetPlugin``. It allows to add arbitrary custom templates to the project.\n* Fixed #160: Error copying Carousel plugin\n* Plugins marked as \"transparent\" can be parents of everybody.\n* BootstrapPanelPlugin now accepts inline CSS styles.\n\n0.8.2\n=====\n* Cascade does not create migrations for proxy models anymore. This created major problems if\n  Cascade components have been switched on and off. All existing migrations of proxy models have\n  been removed from the migration files.\n* Fixed: Response of more than one entry on non unique clipboards.\n* Added :class:`cmsplugin_cascade.models.SortableInlineCascadeElement` which can be used for\n  keeping sorted inline elements.\n* :class:`cmsplugin_cascade.bootstrap3.gallery.BootstrapGalleryPlugin` can sort its images.\n\n0.8.1\n=====\n* Hotfix: removed invalid dependency in migration 0007.\n\n0.8.0\n=====\n* Compatible with Django-1.9\n* Fixed #133: BootstrapPanelPlugin now supports custom CSS classes.\n* Fixed #132: Carousel Slide plugin with different form.\n* Fixed migration problems for proxy models outside Cascade.\n* Replaced SelectMultiple against CheckboxSelectMultiple in admin for extra fields.\n* Removed SegmentationAdmin from admin backend.\n* Disallow whitespace in CSS attributes.\n* Require django-reversion 1.10.1 or newer.\n* Require django-polymorphic 0.9.1 or newer.\n* Require django-filer 1.1.1 or newer.\n* Require django-treebeard 4.0 or newer.\n* Require django-sekizai 0.9.0 or newer.\n\n\n0.7.3\n=====\n* Use the outer width for fluid containers. This allows us to add images and carousels which extend\n  the browser's edges.\n* Fixed #132: Carousel Slide plugin different form.\n* Fixed #133: BootstrapPanelPlugin does not support custom CSS classes.\n* Fixed #134: More plugins can be children of the ``SimpleWrapperPlugin``. This allows us to be more\n  flexible when building the DOM tree.\n* ``BootstrapContainerPlugin`` now by default accepts extra inline styles and CSS classes.\n\n0.7.2\n=====\n* Add a possibility to prefix Cascade plugins with a symbol of your choice, to avoid confusion\n  if the same name has been used by another plugin.\n* All Bootstrap plugins can override their templates globally though a configuration settings\n  variable. Usefule to switch between jQuery and AngularJS versions of a widget.\n* Added TabSet and TabPanel plugins.\n* It is possible to persist the content of the clipboard in the database, retrieve and export\n  it as JSON to be reimported on an unrelated site.\n\n0.7.1\n=====\n* Added a **HeadingPlugin** to add single text headings independently of the HTML TextEditorPlugin.\n\n0.7.0\n=====\nCleanup release, removing a lot of legacy code. This adds some incompatibilities to previous\nversions:\n\n* Instead of half o dozen of configuration directives, now one Python dict is used. Therefore\n  check your ``settings.py`` for configurations starting with ``CMSPLUGIN_CASCADE_...``.\n* Tested with **Django-1.8**. Support for version 1.7 and lower has been dropped.\n* Tested with **djangoCMS** version 3.2. Support for version 3.0 and lower has been dropped.\n* Tested with **django-select2** version 5.2. Support for version 4 has been dropped.\n* The demo project now uses SASS instead of plain CSS, but SASS is not a requirement during normal\n  development.\n\n0.6.2\n=====\n* In Segment: A condition raising a TemplateSyntaxError now renders that error inside a HTML\n  comment. This is useful for debugging non working conditions.\n* In Segment: An alternative AdminModel to UserAdmin, using a callable instead of a model field,\n  now works.\n* In Segment: It is possible to use ``segmentation_list_display = (list-of-fields)`` in an\n  alternative AdminModel, to override the list view, when emulating a user.\n\n0.6.1\n=====\n* Added a panel plugin to support the Bootstrap Panel.\n* Added experimental support for secondary menus.\n* Renamed ``AccordionPlugin`` to ``BootstrapAccordionPlugin`` for consistency and to avoid future\n  naming conflicts.\n\n0.6.0\n=====\n* Fixed #79: The column width is not reduced in width, if a smaller column precedes a column for a\n  smaller displays.\n* Fixed: Added extra space before left prefix in buttons.\n* Enhanced: Access the link content through the glossary's ``link_content``.\n* New: Plugins now can be rendered using an alternative template, choosable through the plugin\n  editor.\n* Fixed in SegmentationPlugin: When overriding the context, this updated context was only used for\n  the immediate child of segment. Now the overridden context is applied to all children and\n  grandchildren.\n* Changed in SegmentationPlugin: When searching for siblings, use a list index instead of\n  ``get_children().get(position=...)``.\n* Added unit tests for SegmentationPlugin.\n* Added support for **django-reversion**.\n* By using the setting ``CMSPLUGIN_CASCADE_LINKPLUGIN_CLASSES``, one can replace the class\n  ``LinkPluginBase`` by an alternative implementation.\n* When using *Extra Styles* distances now can have negative values.\n* In caption field of ``CarouselSlidePlugin`` it now is possible to set links onto arbitrary pages.\n\n**Possible backwards incompatibility**:\n\n* For consistency with naming conventions on other plugins, renamed ``cascade/plugins/link.html``\n  -> ``cascade/link/link-base.html``. **Check your templates**!\n* The setting ``CMSPLUGIN_CASCADE_SEGMENTATION_MIXINS`` now is a list of two-tuples, where the first\n  declares the plugin's model mixin, while the second declares the model admin mixin.\n* Removed from setting: ``CMSPLUGIN_CASCADE_BOOTSTRAP3_TEMPLATE_DIR``. The rendering template now\n  can be specified during runtime.\n* Refactored and moved ``SimpleWrapperPlugin`` and ``HorizontalRulePlugin`` from\n  ``cmsplugin_cascade/bootstrap3/`` into ``cmsplugin_cascade/generic/``. The glossary field\n  ``element_tag`` has been renamed to ``tag_type``.\n* Refactored ``LinkPluginBase`` so that external implementations can create their own version,\n  which then is used as base for TextLinkPlugin, ImagePlugin and PicturePlugin.\n* Renamed: ``PanelGroupPlugin`` -> ``Accordion``, ``PanelPlugin`` -> ``AccordionPanelPlugin``,\n  because the Bootstrap project renamed them back to their well known names.\n\n0.5.0\n=====\n* Added SegmentationPlugin. This allows to conditionally render parts of the DOM, depending on\n  the status of various ``request`` object members, such as ``user``.\n* Setting ``CASCADE_LEAF_PLUGINS`` has been replaced by ``CMSPLUGIN_CASCADE_ALIEN_PLUGINS``. This simplifies\n  the programming of third party plugins, since the author of a plugin now only must set the member\n  ``alien_child_classes = True``.\n\n0.4.5\n=====\n* Fixed: If no breakpoints are set, don't delete widths and offsets from the glossary, as otherwise\n  this information is lost.\n* Fixed broken import for ``PageSelectFormField`` when not using **django_select2**.\n* Admin form for ``PluginExtraFields`` now is created on the fly. This fixes a rare circular\n  dependency issue, when accessing ``plugin_pool.get_all_plugins()``.\n\n0.4.4\n=====\n* Removed hard coded input fields for styling margins from **BootstrapButtonPlugin**, since\n  it is possible to add them through the **Extra Fields** dialog box.\n* [Column ordering](http://getbootstrap.com/css/#grid-column-ordering) using ``col-xx-push-n``\n  and ``col-xx-pull-n`` has been added.\n* Fixed: Media file ``linkplugin.js`` was missing for **BootstrapButtonPlugin**.\n* Hard coded configuration option ``EXTRA_INLINE_STYLES`` can now be overridden by the projects\n  settings\n\n\n0.4.3\n=====\n* The templatetag ``bootstrap3_tags`` and the templates to build Boostrap3 styled menus,\n  breadcrumbs and paginator, have been moved into their own repository\n  at https://github.com/jrief/djangocms-bootstrap3.\n* `Column ordering`_ using ``col-xx-push-n`` and ``col-xx-pull-n`` has been added.\n\n.. _Column ordering: http://getbootstrap.com/css/#grid-column-ordering\n\n0.4.2\n=====\n* Fixed: Allow empty setting for CMSPLUGIN_CASCADE_PLUGINS\n* Fixed: Use str(..) instead of b'' in combination with from __future__ import unicode_literals\n\n0.4.1\n=====\n* Fixed: Exception when saving a ContainerPlugin with only one breakpoint.\n* The ``required`` flag on a field for an inherited LinkPlugin is set to False for shared settings.\n* Fixed: Client side code for disabling shared settings did not work.\n\n0.4.0\n=====\n* Renamed ``context`` from model ``CascadeElement`` to ``glossary`. The identifier ``context`` lead\n  to too much confusion, since it is used all way long in other CMS plugins, where it has a\n  complete different meaning.\n* Renamed ``partial_fields`` in all plugins to ``glossary_fields``, since that's the model field\n  where they keep their information.\n* Huge refactoring of the code base, allowing a lot of more features.\n\n0.3.2\n=====\n* Fixed: Missing unicode conversion for method ``get_identifier()``\n* Fixed: Exception handler for form validation used ``getattr`` incorrectly.\n\n0.3.1\n=====\n* Added compatibility layer for Python-3.3.\n\n0.3.0\n=====\n* Complete rewrite. Now offers elements for Bootstrap 3 and other CSS frameworks.\n\n0.2.0\n=====\n* Added carousel.\n\n0.1.2\n=====\n* Fixed: Added missign migration.\n\n0.1.1\n=====\n* Added unit tests.\n\n0.1.0\n=====\n* First published revision.\n\nThanks\n======\n\nThis DjangoCMS plugin originally was derived from https://github.com/divio/djangocms-style, so the\nhonor for the idea of this software goes to Divio and specially to Patrick Lauber, aka digi604.\n\nHowever, since my use case is different, I removed all the existing code and replaced it against\nsomething more generic suitable to add a collection of highly configurable plugins.\n"
  },
  {
    "path": "docs/source/client-side.rst",
    "content": "========================\nHandling the client side\n========================\n\n**DjangoCMS-Cascade** is shipped with a lot of plugins, all having their own inheritance hierarchy.\nDue to the flexibility of Cascade, this inheritance hierarchy can be extended though some\nconfiguration settings, while bootstrapping the runtime environment. Some plugins for instance, can\nbe configured to store some settings in a common data store. This in the admin backend requires a\nspecial Javascript plugin, from which the client side must inherit as well.\n\nHence on the client side, we would like to describe the same inheritance hierarchy using Javascript.\nTherefore Cascade is equipped with a small, but very powerful library named ring.js_. It makes\nJavascript behave almost like Python. If a Cascade plugin provides a Javascript counterpart,\nthen other Cascade plugins inheriting from the former one, map their inheritance hierarchy in\nJavascript exactly as provided by the plugins written in Python.\n\n\nImplementing the client\n=======================\n\nSay, we want to add some client side code to a Cascade plugin. We first must import that Javascript\nfile through Django's `static asset definitions`_ using the ``Media`` class, or if you prefer in a\ndynamic property method ``media()``.\n\nAt some point during the initialization, Cascade must call the constructor of the Javascript\nplugin we just added. Therefore Cascade plugins provide an extra attribute named ``ring_plugin``,\nwhich is required to name the Javascript's counterpart of our Python class. You can use any name\nyou want, but it is good practice to use the same name as the plugin.\n\nThe Python class of our custom Cascade plugin then might look like:\n\n.. code-block:: python\n\n\tfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\tclass MyCustomPlugin(CascadePluginBase):\n\t    name = \"Custom Plugin\"\n\t    ... other class attributes\n\t    ring_plugin = 'MyCustomPlugin'\n\n\t    class Media:\n\t        js = ['mycustomproject/js/admin/mycustomplugin.js']\n\nwhereas it's Javascript counterpart might look like:\n\n.. code-block:: javascript\n\t:caption: mycustomproject/js/admin/mycustomplugin.js\n\n\tdjango.jQuery(function($) {\n\t    'use strict';\n\n\t    django.cascade.MyCustomPlugin = ring.create({\n\t        constructor: function() {\n\t            // initialization code\n\t        },\n\t        custom_func: function() {\n\t            // custom functionality\n\t        }\n\t    });\n\t});\n\n\nAfter yours, and all other Cascade plugins have been initialized in the browser, the Cascade\nframework invokes ``new django.cascade.MyCustomPlugin();`` to call the constructor function.\n\n\nPlugin Inheritance\n==================\n\nIf for instance, our ``MyCustomPlugin`` requires functionality to set a link, then instead of\nreplication the code required to handle the link input fields, we can rewrite our plugin as:\n\n.. code-block:: python\n\n\tfrom cmsplugin_cascade.link.config import LinkPluginBase\n\n\tclass MyCustomPlugin(LinkPluginBase):\n\t    ... class attributes as in the previous example\n\nSince ``LinkPluginBase`` provides it's own ``ring_plugin`` attribute, the corresponding Javascript\ncode *also must inherit* from that base class. Cascade handles this for you automatically, if the\nJavascript code of the plugin is structured as:\n\n.. code-block:: javascript\n\t:caption: mycustomproject/js/admin/mycustomplugin.js\n\n\tdjango.jQuery(function($) {\n\t    'use strict';\n\n\t    var plugin_bases = eval(django.cascade.ring_plugin_bases.MyCustomPlugin);\n\n\t    django.cascade.MyCustomPlugin = ring.create(plugin_bases, {\n\t        constructor: function() {\n\t            this.$super();\n\t            // initialization code\n\t        },\n\t        ...\n\t    });\n\t});\n\nThe important parts here is the call to ``eval(django.cascade.ring_plugin_bases.MyCustomPlugin)``,\nwhich resolves the Javascript functions our custom plugin inherits from.\n\n\n.. note:: In case you forgot to add a missing JavaScript requirement, then ring.js complains with\n\tthe error message ``Uncaught TypeError: Cannot read property '__classId__' of undefined``. \n\tIf you run into this problem, recheck that all Javascript files have been loaded and\n\tinitialized in the correct order.\n\n.. _ring.js: http://ringjs.neoname.eu/\n.. _static asset definitions: https://docs.djangoproject.com/en/stable/topics/forms/media/\n"
  },
  {
    "path": "docs/source/clipboard.rst",
    "content": ".. _clipboard:\n\n=================\nThe CMS Clipboard\n=================\n\n**DjangoCMS** offers a Clipboard where one can copy or cut and add a subtree of plugins to the DOM.\nThis Clipboard is very handy when copying plugins from one placeholder to another one, or to another\nCMS page. In version 0.7.2 **djangocms-cascade** extended the functionality of this clipboard, so\nthat the content of the CMS clipboard can be persited to – and restored from the database. This\nallows the site-administrator to prepare a toolset of plugin-trees, which can be inserted anywhere\nat any time.\n\n\nPersisting the Clipboard\n========================\n\nIn the context menu of a CMS plugin, use **Cut** or **Copy** to move a plugin together with its\nchildren to the CMS clipboard. In **Edit Mode** this clipboard is available from the primary menu\nitem within the CMS toolbar. From this clipboard, the copy plugins can be dragged and dropped to\nany CMS placeholder which is allowed to accept the root node.\n\nSince the content of the clipboard is overridden by every operation which cuts or copies a tree of\nplugins, **djangocms-cascade** offers some functionality to persist the clipboard's content. To do\nthis, locate **Persited Clipboard Content** in Django's administration backend.\n\n|persist-clipboard|\n\n.. |persist-clipboard| image:: _static/persist-clipboard.png\n\nThe **Identifier** field is used to give a unique name to the persited clipboard entity.\n\nThe **Save** button fetches the content from the CMS clipboard and persists it.\n\nThe **Restore** button replaces the content of the CMS clipboard with the current persisted entity.\nThis is the opposite operation of **Save**.\n\nSince the clipboard content is serialized using JSON, the site administrator can grab and paste it\ninto another site using **djangocms-cascade**, if persisting clipboards are enabled.\n\n\nConfiguration\n-------------\n\nPersisting the clipboards content must be configured in the projects ``settings.py``:\n\n.. code-block:: python\n\n\tINSTALLED_APPS = (\n\t    ...\n\t    'cmsplugin_cascade',\n\t    'cmsplugin_cascade.clipboard',\n\t    ...\n\t)\n\n\nCaveats\n-------\n\nOnly CMS plugins from the Cascade eco-system are eligible to be used for persisting. This is because\nthey already use a JSON representation of their content. The only exception is the **TextPlugin**,\nsince **djangocms-cascade** added some serialization code.\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# djangocms-cascade documentation build configuration file, created by\n# sphinx-quickstart on Wed Oct 16 10:55:21 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport sys\nimport datetime\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(os.path.join('..', '..')))\nfrom cmsplugin_cascade import __version__\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'djangocms-cascade'\ncopyright = datetime.date.today().strftime('Copyright %Y, Jacob Rief')\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = __version__\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'alabaster'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'djangocms-cascadedoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'djangocms-cascade.tex', 'djangocms-cascade Documentation',\n   'Jacob Rief', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'djangocms-cascade', 'djangocms-cascade Documentation',\n     ['Jacob Rief'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'djangocms-cascade', 'djangocms-cascade Documentation',\n   'Jacob Rief', 'djangocms-cascade', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n"
  },
  {
    "path": "docs/source/customize-styles.rst",
    "content": "=======================================\nCustomize CSS classes and inline styles\n=======================================\n\nPlugins shipped with **djangocms-cascade** offer a basic set of CSS classes as declared by the\nchosen CSS framework. These offered classes normally do not fulfill the requirements for real world\nsites. This is specially true, if you extend your site with one of the many available\n`Bootstrap themes`_.\n\nWhile **djangocms-cascade** is easily expendable, it would be overkill to re-implement the available\nplugins, just to add an extra field for a customized CSS class or an extra inline style. For that\npurpose, one can add a set of potential CSS classes and potential CSS inline styles for Cascade\nplugins, enabled for this feature. Moreover, this feature can be adopted individually on a per-site\nbase.\n\n.. _Bootstrap themes: https://themes.getbootstrap.com/\n\n\n.. named-css-classes\n\nConfigure a Cascade plugins to accept preconfigured CSS classes\n===============================================================\n\nIf you want to allow, that the site editor can add additional CSS classes to Cascade plugins, then\nthis can be done by a simple reconfiguration. Say we want to add the possibility to mark a link as\n`stretched link`_. This can be done by adding the CSS class ``stretched-link`` to the HTML-element\n``<a href=\"...\" class=\"...\">...</a>``. Since we don't neccessarily want to reimplement the\n**LinkPlugin** editor, just to add a simple checkbox allowing to add that CSS class, we can do that\nthrough a configuration settings:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE['plugins_with_extra_mixins'] = {\n\t    'LinkPlugin': NamedCSSClasses([\n\t        ('stretched-link', \"Use stretched Link\"),\n\t    ]),\n\t}\n\nThis will add a checkbox to the **LinkPlugin** editor, allowing the administrator to optionally\nadd that CSS class to the HTML-element.\n\n\n.. _stretched link: https://getbootstrap.com/docs/4.4/utilities/stretched-link/\n\n.. extra-fields:\n\nConfigure a Cascade plugins to accept extra fields\n==================================================\n\nIf you want even more flexibility, is also possible to make those optional CSS classes configurable.\nThen in addition, each plugin may optionally accept an ID tag, one ore more CSS classes or some\ninline styles.\n\nTo override these defaults, first assure that ``'cmsplugin_cascade.extra_fields'`` is part of\nyour ``INSTALLED_APPS``. Then add a dictionary of Cascade plugins, which shall be extendible\nto the project's ``settings.py``, for instance:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_extra_fields': {\n\t        'BootstrapButtonPlugin': PluginExtraFieldsConfig(),\n\t        'BootstrapRowPlugin': PluginExtraFieldsConfig(),\n\t        'BootstrapJumbotronPlugin': PluginExtraFieldsConfig(inline_styles={\n\t            'extra_fields:Paddings': ['padding-top', 'padding-bottom'],\n\t            'extra_units:Paddings': 'px,em'}),\n\t        'SimpleWrapperPlugin': PluginExtraFieldsConfig(),\n\t        'HeadingPlugin': PluginExtraFieldsConfig(inline_styles={\n\t            'extra_fields:Paddings': ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],\n\t            'extra_units:Paddings': 'px,em'}, allow_override=False),\n\t        'HorizontalRulePlugin': PluginExtraFieldsConfig(inline_styles={\n\t            'extra_fields:Paddings': ['margin-top', 'margin-bottom'],\n\t            'extra_units:Paddings': 'px,em'}, allow_override=False),\n\t    },\n\t    ...\n\t}\n\nHere the class ``PluginExtraFieldsConfig`` is used to fine-tune which extra fields can be\nset while editing the plugin. Assigning that class without arguments to a plugin, allows us to\nspecify the extra fields using the Django administration backend at:\n\n*Home › django CMS Cascade › Custom CSS classes and styles*\n\nHere the site administrator can specify for each concrete plugin, which extra CSS classes, ID tags\nand extra inline styles shall be used.\n\nIf we use ``PluginExtraFieldsConfig(allow_override=False)``, then the site administrator *can not*\noverride the configuration using the backend. Then every extra field must be specified by the\nconfiguration directive:\n\n.. autoclass:: cmsplugin_cascade.extra_fields.config.PluginExtraFieldsConfig\n   :members:\n\n\nEnable extra fields through the administration backend\n======================================================\n\nTo enable this feature, in the administration backend navigate to\n\n*Home › django CMS Cascade › Custom CSS classes and styles*  and click onto the button named\n**Add Custom CSS classes styles**.\n\nFrom the field named \"Plugin Name\", select one of the available plugins, for example\n**Bootstrap Simple Wrapper**. Then, from the field named \"Site\", select the site where those extra\nstyles shall be applied.\n\n|customize-styles|\n\n.. |customize-styles| image:: /_static/customize-styles.png\n\n\nAllow ID\n--------\n\nWith \"Allow id tag\" enabled, an extra field will appear on the named plugin editor. There a user\ncan add any arbitrary name which will be rendered as ``id=\"any_name\"`` for the corresponding plugin\ninstance.\n\nAllowing ID's is specially useful for the **Heading Plugin**, so the Links can point directly to\nthat ID (bookmark linking).\n\n\nCSS classes\n-----------\n\nIn the field named \"CSS class names\", the administrator may specify arbitrary CSS classes separated\nby commas. One of these CSS classes then can be added to the corresponding Cascade plugin. If\nmore than one CSS class shall be addable concurrently, activate the checkbox named \"Allow multiple\".\n\n\nCSS inline styles\n-----------------\n\nThe administrator may activate all kinds of CSS inline styles by clicking on the named checkbox. For\nsettings describing distances, additionally specify the allowed units to be used.\n\nIf a user opens the corresponding plugin inside the **Structure View**, he will see an extra select\nfield to choose the CSS class and some input fields to enter say, extra margins, heights or\nwhatever has been activated.\n\n\nUse it rarely, use it wise\n..........................\n\nAdding too many styling fields to a plugin can mess up any web project. Therefore be advised to use\nthis feature rarely and wise. If many people have write access to plugins, set extra permissions on\nthis table, in order to not mess things up. For instance, it rarely makes sense to activate\n``min-width``, ``width`` and ``max-width``.\n\n\nDynamically add styles to the Text-Editor\n=========================================\n\nIn Cascade it is also possible to dynamically add styles to the CKTextEditor.\nEnsure that in your ``settings.py`` the following is active:\n\n.. python\n\n\tCKEDITOR_SETTINGS = {\n\t    ...\n\t    'toolbar_CMS': [\n\t        ...\n\t        ['Styles'],\n\t        ...\n\t    ],\n\t    'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),\n\t}\n\nThen in the Django-Admin, by adding entries in *Start › django CMS Cascade › Text Editor Configs*\nit is possible to use these special styles inside the text editor.\n"
  },
  {
    "path": "docs/source/customized-plugins.rst",
    "content": "=================\nExtending Cascade\n=================\n\nAll Cascade plugins are derived from the same base class\n:class:`cmsplugin_cascade.plugin_base.CascadeModelBase`, which stores all its model fields inside a\ndictionary, serialized as JSON string in the database. This makes it much easier to extend the\nCascade eco-system, since no database migration [#migration]_ is required when adding a new, or\nextending plugins from this project.\n\nThe database model ``CascadeModelBase`` stores all the plugin settings in a single JSON field named\n``glossary``. This in practice behaves like a Django context, but in order to avoid confusion with\nthe latter, it has been named \"glossary\".\n\n.. note:: Custom Cascade plugins should set the ``app_label`` attribute (see\n\tbelow). This is important so migrations for the proxy models generated by\n\tCascade are created in the correct app.\n\n\tIf this attribute is not set, Cascade will default to the left-most\n\tpart of the plugin's module path. So if your plugin lives in\n\t``myapp.cascadeplugins``, Cascade will use ``myapp`` as the app label.\n\tWe recommend that you always set ``app_label`` explicitly.\n\n\nSimple Example\n==============\n\nThis plugin is very simple and just renders static content which has been declared in the template.\n\n.. code-block:: python\n\n\tfrom cms.plugin_pool import plugin_pool\n\tfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\t\n\tclass StylishPlugin(CascadePluginBase):\n\t    name = 'Stylish Element'\n\t    render_template = 'myapp/cascade/stylish-element.html'\n\t\n\tplugin_pool.register_plugin(StylishPlugin)\n\nIf the editor form pops up for this plugin, a dumb message appears: \"There are no further settings\nfor this plugin\". This is because no editable fields have been added to that plugin yet.\n\n\nCustomize the Plugin Editor\n===========================\n\nIn order to make the plugin remember its settings and other optional data, we must specify a Django\nform to be used by the plugin. Since its payload data is stored in a JSON field, we use\ndjango-entangled_, to map the form fields.\n\nEach of those form fields handles a special field value, or in some cases, a list of field values.\nThey all require one or more Django form fields, which are rendered by the plugins popup editor.\n\nLet's add a simple selector to choose between a red and a green color. Do this by adding ``form``\nto the plugin class.\n\n.. code-block:: python\n\n\tfrom django.forms import ChoiceField\n\tfrom entangled.forms import EntangledModelFormMixin\n\tfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\t\n\tclass StylishFormMixin(EntangledModelFormMixin):\n\t    color = ChoiceField(\n\t        choices=[('red', 'Red'), ('green', 'Green')],\n\t        label=\"Element's Color\",\n\t        initial='red',\n\t        help_text=\"Specify the color of the DOM element.\"\n\t    )\n\n\t    class Meta:\n\t        entangled_fields = {'glossary': ['color']}\n\n\tclass StylishPlugin(CascadePluginBase):\n\t    …\n\t    form = StylishFormMixin\n\nIn the plugin's editor, the form now pops up with a single select box, where the user can choose\nbetween a *red* and a *green* element.\n\nThe form ``StylishFormMixin`` inherits from ``EntangledModelFormMixin`` available through the\nseparate Django app django-entangled_. This app allows to edit JSON-Model fields using a standard\nDjango form. Since **djangocms-cascade** may extend this form with additional fields, here we use\na special mixin class, rather than a Django ``ModelForm``. Remember to add class ``Meta`` to this\nform, in order to specify the mapping of form fields inside the JSON field named ``glossary``.\n\n.. _django-entangled: https://pypi.org/project/django-entangled/\n\n\nSpecial Form Field for Plugin Editors\n=====================================\n\nFor single text fields or select boxes, Django's built-in fields, such as ``CharField`` or\n``ChoiceField`` can be used. Sometimes these simple fields are not enough, therefore\n**djangocms-cascade** additionally provides special form fields, which makes it easier to\ncreate editors specialized for styling CSS. These special fields are all part of the module\n``cmsplugin_cascade.fields``.\n\n:SizeField:\n\tWhen entering measurements, such as margins, paddings, widths, heights, etc, one may choose\n\tbetween different units, such as ``px``, ``em``, ``rem`` or ``%``. This fields validates its\n\tinput by checking if a unit is specified.\n\n:MultiSizeField:\n\tUse this field to group a list of size input fields together. This for instance is used, to\n\tencapsulate all margins into one list inside the JSON object.\n\n:ColorField:\n\tUse this field when the user shall enter a color value. Since the color picker widget built\n\tinto the browser often is inconvenient, the special picker a-color-picker_ can be used instead.\n\tThis external library even supports alpha channels. Simply install it into the directory of\n\tthe Django project and add ``node_modules`` to the list of ``STATICFILES_DIRS``. Since we can\n\tnot leave the color field empty, this field adds a checkbox to inform the plugin editor, if no\n\tcolor is desired. The latter means, that the color is inherited by an upper DOM element.\n\n:BorderChoiceField:\n\tUse this field to style borders. It adds three input fields, one to set the border width, one for\n\tthe border style and one for the border color. The latter uses the special picker\n\ta-color-picker_, if installed. Otherwise it falls back to the built-in color widget.\n\n.. _a-color-picker: https://www.npmjs.com/package/a-color-picker\n\n\nOverriding the Model\n====================\n\nSince all **djangocms-cascade** plugins store their data in a JSON-serializable field, there rarely\nis a need to add another database field to the common models ``CascadeElement`` and/or\n``SharableCascadeElement`` and thus no need for database migrations.\n\nHowever, quite often there is a need to add or override the methods for these models. Therefore each\nCascade plugin creates its own `proxy model`_ on the fly. These models inherit from\n``CascadeElement`` and/or ``SharableCascadeElement`` and named like the plugin class, with the\nsuffix ``Model``. By default, their behavior is the same as for their parent model classes.\n\nTo extend this behavior, the author of a plugin may declare a tuple of mixin classes, which are\ninjected during the creation of the proxy model. Example:\n\n.. code-block:: python\n\n\tclass MySpecialPropertyMixin(object):\n\t    def processed_value(self):\n\t        value = self.glossary.get('field_name')\n\t        # process value\n\t        return value\n\t\n\tclass MySpecialPlugin(LinkPluginBase):\n\t    module = 'My Module'\n\t    name = 'My special Plugin'\n\t    model_mixins = (MySpecialPropertyMixin,)\n\t    render_template = 'my_module/my_special_plugin.html'\n\t    ...\n\nThe proxy model created for this plugin class, now contains the extra method ``processed_value()``,\nwhich for instance may be accessed during template rendering.\n\n``templates/my_module/my_special_plugin.html``:\n\n.. code-block:: html\n\n\t<div>{{ instance.processed_value }}</div>\n\nNeedless to say, that you can't add any extra database fields to the class named\n``MySpecialPropertyMixin``, since the corresponding model class is marked as proxy.\n\n\nJavaScript\n----------\n\nIn case your customized plugin requires some Javascript code to improve the editor's experience,\nplease refer to the section :ref:`client-side`.\n\n\nAdding extra fields to the model\n--------------------------------\n\nIn rare situations, we might want to add extra fields to the model, which inherit from\n:class:`django.db.models.fields.Field` rather than using django-entangled_ to emulate this\nbehavior, by mapping Django form fields to a JSON model field (``glossary``).\nIn other words: We want a *real* database field.\n\nThis can be achieved by creating a Django model inheriting from\n:class:`cmsplugin_cascade.models_base.CascadeModelBase` and referring to it, such as:\n\n.. code-block:: python\n\n\tclass MyPluginModel(CascadeModelBase):\n\t    class Meta:\n\t        db_table = 'shop_cart_cascadeelement'\n\t        verbose_name = _(\"Cart Element\")\n\n\t    byte_val = models.PositiveSmallIntegerField(\"Byte Value\")\n\n\tclass MySpecialPlugin(LinkPluginBase):\n\t    module = 'My Module'\n\t    name = 'My special Plugin'\n\t    model = MyModel\n\n\n*Transparent* Plugins\n=====================\n\nSome of the plugins in Cascade's ecosystem are considered as *transparent*. This means that they\nlogically don't fit into the given grid-system, but should rather be considered as wrappers of\nother HTML elements.\n\nFor example, the `Bootstrap Panel`_ can be added as child of a Column. However, it may contain\nexactly the same plugins, as the Column does. Now, instead of adding the ``PanelPlugin`` as\na possible parent to all of our existing Bootstrap plugins, we simply declare the Panel as\n\"transparent\". It then behaves as it's own parent, allowing all plugins as children, which\nthemselves are permitted to be added to that column.\n\nTransparent plugins can be stacked. For example, the `Bootstrap Accordion`_ consists of one or more\nAccordion Panels. Both of them are considered as *transparent*, which means that we can add all\nplugins to an Accordion Panels, which we also could add to a Column.\n\n\n\n.. _Bootstrap Panel: http://getbootstrap.com/components/#panels\n.. _Bootstrap Accordion: http://getbootstrap.com/javascript/#collapse\n\n\nPlugin Attribute Reference\n==========================\n\n``CascadePluginBase`` is derived from ``CMSPluginBase``, so all `CMSPluginBase attributes`_ can\nalso be overridden by plugins derived from ``CascadePluginBase``. Please refer to their\ndocumentation for details.\n\nAdditionally ``BootstrapPluginBase`` allows the following attributes:\n\n:name:\n\tThis name is shown in the pull down menu in structure view. There is not default value.\n\n:app_label:\n    The app_label to use on generated proxy models. This should usually be the\n    same as the app_label of the app that defines the plugin.\n\n:tag_type:\n\tA HTML element into which this plugin is wrapped. Generic templates can render their\n\tcontent into any ``tag_type``. Specialized rendering templates usually have a hard coded tag\n\ttype, then this attribute can be omitted.\n\n:require_parent:\n\tDefault: ``True``. This differs from ``CMSPluginBase``.\n\n\tIs it required that this plugin is a child of another plugin? Otherwise the plugin can be added\n\tto any placeholder.\n\n:parent_classes:\n\tDefault: None.\n\n\tA list of Plugin Class Names. If this is set, the plugin may only be added to plugins listed\n\there.\n\n:allow_children:\n\tDefault: ``True``. This differs from ``CMSPluginBase``.\n\n\tCan this plugin have child plugins? Or can other plugins be placed inside this plugin?\n\n:child_classes:\n\tDefault: A list of plugins, which are allowed as children of this plugin. This differs from\n\t``CMSPluginBase``, where this attribute is None.\n\n\tDo not override this attribute. **DjangoCMS-Cascade** automatically generates a list of allowed\n\tchildren plugins, by evaluating the list ``parent_classes`` from the other plugins in the pool.\n\n\tPlugins, which are part of the plugin pool, but which do not specify their parents using the\n\tlist ``parent_classes``, may be added as children to the current plugin by adding them to the\n\tattribute ``generic_child_classes``.\n\n:generic_child_classes:\n\tDefault: None.\n\n\tA list of plugins which shall be added as children to a plugin, but which themselves do not\n\tdeclare this plugin in their ``parent_classes``.\n\n:default_css_class:\n\tDefault: None.\n\n\tA CSS class which is always added to the wrapping DOM element.\n\n:default_inline_styles:\n\tDefault: None.\n\n\tA dictionary of inline styles, which is always added to the wrapping DOM element.\n\n:get_identifier:\n\tThis is a classmethod, which can be added to a plugin to give it a meaningful name.\n\n\tIts signature is::\n\n\t    @classmethod\n\t    def get_identifier(cls, obj):\n\t        return 'A plugin name'\n\n\tThis method shall be used to name the plugin in structured view.\n\n:form:\n\tOverride the form used by the plugin editor. This must be a class inheriting from\n\t``entangled.forms.EntangledModelFormMixin``. Remember to list all form fields in\n\t``entangled_fields`` inside the ``Meta`` class.\n\n:model_mixins:\n\tTuple of mixin classes, with additional methods to be added the auto-generated proxy model\n\tfor the given plugin class.\n\n\tCheck section \"Overriding the Model\" for a detailed explanation.\n\n.. _CMSPluginBase attributes: https://django-cms.readthedocs.org/en/develop/extending_cms/custom_plugins.html#plugin-attribute-reference\n.. _proxy model: https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models\n\n\nPlugin Permissions\n==================\n\nTo register (or unregister) a plugin, simply invoke ``./manage.py migrate cmsplugin_cascade``. This\nwill add (or remove) the content type and the model permissions. We therefore can control in a very\nfine grained manner, which user or group is allowed to edit which types of plugins.\n\n.. rubric:: Footnotes\n\n.. [#migration] After having created a customized plugin, it must be registered in Django's\n\t\tpermission system, otherwise only administrators, but no staff users, are allowed to add,\n\t\tchange or delete them.\n"
  },
  {
    "path": "docs/source/embeds.rst",
    "content": "================\nEmbedding Videos\n================\n\nStaring in 0.18, it is possible to embedd a video inside a Bootstrap column. Each video provider\noffers a distinct set of parameters to configure how their videos are broadcasted. Therefore,\n**djangocms-cascade** offers a plugin for each of them.\n\n\nYouTube\n=======\n\nWhen adding a plugin to a column, select the **YouTube**. The YouTube URL is found by clicking on\nthe **SHARE** button, then copying and pasting it into the URL field in the editor. Select the\nright aspect ratio, otherwise you end up with right and left edged cut away.\n\nFor all other options, the YouTube plugin editor offers, consult the `IFrame Player API`_.\n\n.. _IFrame Player API: https://developers.google.com/youtube/player_parameters\n\n\nVimeo\n=====\n\nCurrently no plugin for Vimeo has been written yet. It would however be quite easy to do so.\n"
  },
  {
    "path": "docs/source/generic-plugins.rst",
    "content": "===============\nGeneric Plugins\n===============\n\n\n**DjangoCMS-Cascade** is shipped with a few plugins, which can be used independently of the\nunderlying CSS framework. To avoid duplication, they are bundled into the section **Generic** and\nare available by default in the placeholders context menu.\n\nAll these plugins qualify as plugins with `extra fields`_, which means that they can be configured\nby the site administrator to accept additional CSS styles and classes.\n\n\nConfiguration\n=============\n\nIn order to use these generic plugins, you must activate them in the project's ``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS = [\n\t    ...\n\t    'cmsplugin_cascade.generic',\n\t    ...\n\t]\n\nIf only a few of those plugins shall we activated, name them explicitly, for instance:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS = [\n\t    ...\n\t    'cmsplugin_cascade.generic.heading',\n\t    'cmsplugin_cascade.generic.horizontal_rule',\n\t    ...\n\t]\n\n\n.. _extra fields: extra-fields\n\nSimpleWrapperPlugin\n===================\n\nUse this plugin to add a wrapping element around a group of other plugins. Currently these HTML\nelements can be used as wrapper: ``<div>``, ``<span>``, ``<section>``, ``<article>``. There is one\nspecial wrapper named ``naked``. It embeds its children only logically, without actually embedding\nthem into any HTML element.\n\n\nHorizontalRulePlugin\n====================\n\nThis plugins adds a horizontal rule ``<hr>`` to the DOM. It is suggested to enable the\n``margin-top`` and ``margin-bottom`` CSS styles, so that the ruler can be positioned\nappropriately.\n\n\nHeadingPlugin\n=============\n\nThis plugins adds a text heading ``<h1>``...``<h6>`` to the DOM. Although simple headings can be\nachieved with the **TextPlugin**, there they can't be styled using special CSS classes or styles.\nHere the **HeadingPlugin** can be used, since any allowed CSS class or style can be added.\n\n\nCustomSnippetPlugin\n===================\n\nNot every collection of DOM elements can be composed using the Cascade plugin system. Sometimes one\nmight want to add a simple HTML snippet. Altough it is quite simple to create a customized plugin\nyourself, an easier approach to just render an arbitrary HTML snippet, is to use the\n**CustomSnippetPlugin**. This can be achieved by adding the customized template to the project's\n``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    # other settings\n\t    'plugins_with_extra_render_templates': {\n\t        'CustomSnippetPlugin': [\n\t            ('myproject/snippets/custom-template.html', \"Custom Template Identifier\"),\n\t            # other tuples\n\t        ],\n\t    },\n\t}\n\nNow, when editing the page, a plugin named **Custom Snippet** appears in the *Generic* section in\nthe plugin's dropdown menu. This plugin then offers a select element, where the site editor then can\nchose between the configured templates.\n\n\nAdding children to a CustomSnippetPlugin\n----------------------------------------\n\nIt is even possible to add children to the **CustomSnippetPlugin**. Simple add these templatetag_s\nto the customized template, and all plugins which are children of the **CustomSnippetPlugin** will\nbe rendered as well.\n\n.. code-block:: django\n\n\t{% load cms_tags %}\n\t<wrapping-element>\n\t{% for plugin in instance.child_plugin_instances %}\n\t    {% render_plugin plugin %}\n\t{% endfor %}\n\t</wrapping-element>\n\n.. _templatetag: https://docs.djangoproject.com/en/stable/ref/templates/language/#tags\n"
  },
  {
    "path": "docs/source/hide-plugins.rst",
    "content": "==============================\nConditionally hide some plugin\n==============================\n\nSometimes a placholder contains some plugins, which temporarily should not show up while rendering.\nIf this feature is enabled, then instead of deleting them, it is possible to hide them.\n\n\nEnable the meachanism\n=====================\n\nIn the projects ``settings.py``, add:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'allow_plugin_hiding': True,\n\t    ...\n\t}\n\nBy default, this feature is disabled. If enabled, **djangocms-cascade** adds a checkbox to every\nplugin editor. This checkbox is labeled *Hide plugin*. If checked, the plugin and all of it's\nchildren are not rendered in the current tree. To easily distinguish hidden plugins in structure\nmode, they are rendered using a shaded background.\n"
  },
  {
    "path": "docs/source/icon-fonts.rst",
    "content": "======================\nUsing Fonts with Icons\n======================\n\nIntroduction\n============\n\nSometimes we want to enrich our web pages with vectorized symbols. A lot of them can be found in\nvarious font libraries, such as `Font Awesome`_, `Material Icons`_, `Streamline Icons`_ and many\nmore. A typical approach would be to upload the chosen SVG symbol, and use it as image. This\nprocess however is time consuming and error-prone to organize. Therefore, **djangocms-cascade**\noffers an optional submodule, so that we can work with externally packed icon fonts.\n\nIn order to use such a font, currently we must use Fontello_, an external service for icon font\npackaging. In the future, this service  might be integrated into **djangocms-cascade** itself.\n\nThis submodule, if enabled adds three additional plugins: **Icon with frame**, **Simple icon** and\n**Icon in text**. Additionally it allows to decorate buttons with an icon on the left or right side\nof its main content.\n\n\nConfiguration\n-------------\n\nTo enable this service in **djangocms-cascade**, in ``settings.py`` add:\n\n.. code-block:: python\n\n\tINSTALLED_APPS = [\n\t    …\n\t    'cmsplugin_cascade',\n\t    'cmsplugin_cascade.icon',\n\t    …\n\t]\n\n\tCMSPLUGIN_CASCADE_PLUGINS = [\n\t    …\n\t    'cmsplugin_cascade.icon',\n\t    …\n\t]\n\nThis submodule, can of course be combined with all other submodules available for the Cascade\necosystem.\n\nIf ``CMS_PLACEHOLDER_CONF`` is used to configure available plugins for each placeholder, assure\nthat the ``TextIconPlugin`` is added to the list of ``text_only_plugins``.\n\nSince the CKEditor widget must load the font stylesheets for it's own WYSIWIG mode, we have to add\nthis special setting to our configuration:\n\n.. code-block:: python\n\n\tfrom django.core.urlresolvers import reverse_lazy\n\tfrom django.utils.text import format_lazy\n\n\tCKEDITOR_SETTINGS = {\n\t    …\n\t    'stylesSet': format_lazy(reverse_lazy('admin:cascade_texteditor_config')),\n\t}\n\n\nUploading the Font\n==================\n\nIn order to start with an external font icon, choose one or more icons and/or whole font families\nfrom the Fontello_ website and download the generated webfont zip-file to a local folder on your\ncomputer.\n\nIn Django's admin backend, change into ``Start › django CMS Cascade › Uploaded Icon Fonts`` and\nadd an Icon Font object. Choose an appropriate name and upload the just downloaded webfont file,\nwithout unzipping it. After the upload completed, all the imported icons appear grouped by their\nfont family name. They now are ready for being used by the Icon plugin.\n\n.. attention::\n\tThe icon fonts generated by Fontello_, offer a generated ``….css`` file containing a mapping of\n\tprivate UTF-8 characters onto their font symbol. This means that the genarated font files may\n\thave an overlapping encoding. Therefore each uploaded font requires a unique CSS prefix,\n\totherwise it wouldn't be possible to use more than one icon font per page. This prefix must be\n\tset under Fontello's settings, located left of the **Download webfont** button.\n\nAttempting to upload an icon fonts with a CSS prefix, which is already used, will be rejected.\n\n.. warning::\n\tDepending on your settings, Safari auto-unzips that file and hence makes it unusable for\n\tre-upload. Either change your settings in Safari (Preferences > General > Open \"safe\" files),\n\tor use another browser.\n\n.. note::\n\tDuring the 0.17-series of **djangocms-cascade**, an icon font had to be selected per page,\n\trather than per element. This feature turned out to be impractical and has been reverted to\n\tthe pre-0.17 behaviour.\n\n\nUsing the Icon Plugin\n=====================\n\nIn **djangocms-cascade**, currently four plugins make use of the icon font sublibrary. These\nare the **Simple Icon**, the **Icon with frame**, the **Button** and the **Icon in Text** plugin.\nThe latter is available only as subplugin of the **Text Editor** plugin.\n\nIn their respective editors, the user may select one of the uploaded icon fonts. Each time one\nof them is selected, the table of symbols is rerendered. Use the search field on the top of the\ntable to restrict the list of icons, in case there are too many.\n\nChoose the desired symbol, its size and color. Optionally choose a background color, a border with\nwidth, color and style, and the relative position in respect of its wrapping element. After saving\nthe form, that element should appear inside the chosen container.\n\n\nShared Settings\n---------------\n\nBy default, the **IconPlugin** is configured to allow to share the following styling attributes:\n\n* Icon size\n* Icon color\n* Background color, or without background\n* Text alignment\n* Border width, color and style\n* Border radius\n\nBy storing these attributes under a common name, one can reuse them across various icons, without\nhaving to set them for each one, separately. Additionally, each of the shared styling attributes\ncan be changed globally in Django's admin backend at\n``Start › django CMS Cascade › Shared between Plugins``. For details please refer to the section\nabout :ref:`sharable-fields`.\n\n\nUsing the Icon Plugin in plain text\n===================================\n\nIf **django-CMS** is configured to use the `CKEditor for django-CMS`_, then you may use the\n**Icon Plugin** inside plain text. Place the cursor at the desired location in text and select\n**Icon** from the pull down menu **CMS Plugins**. This opens a popup where you may select the\nfont family and the symbol. All other attributes described above, are not available with this\ntype of plugin.\n\n.. _Font Awesome: http://fontawesome.io/\n.. _Material Icons: https://design.google.com/icons/\n.. _Streamline Icons: https://streamlineicons.com/\n.. _Fontello: http://fontello.com/\n.. _CKEditor for django-CMS: https://pypi.org/project/djangocms-text-ckeditor/\n"
  },
  {
    "path": "docs/source/impatient.rst",
    "content": "=================\nFor the Impatient\n=================\n\nThis HowTo gives you a quick instruction on how to get a demo of **djangocms-cascade** up and\nrunning. It also is a good starting point to ask questions or report bugs, since its backend is\nused as a fully functional reference implementation, used by the unit tests of project.\n\n\nCreate a Python Virtual Environment\n===================================\n\nTo keep environments separate, create a virtual environment and install external dependencies.\nMissing packages with JavaScript files and Style Sheets, which are not available via pip must be\ninstalled via npm:\nDependency packaging to made easy with Pipenv or Poetry.\n\n.. code-block:: bash\n\n\t$ git clone --depth=1 https://github.com/jrief/djangocms-cascade.git\n\t$ cd djangocms-cascade/examples/bs4demo\n\t$ python -m venv .venv \n\t$ poetry shell\n\t$ poetry update\n\nInitialize the database, create a superuser and start the development server:\n\n.. code-block:: bash\n\n\t$ cd djangocms-cascade/examples/bs4demo\n\t$ npm install\n\t$ ./manage.py migrate\n\t$ ./manage.py createsuperuser\n\t$ ./manage.py runserver\n\nPoint a browser to http://localhost:8000/?edit and log in as the super user you just\ncreated. Hit \"next\" and fill out the form to create your first page. Afterwards, click **Structure**\non the top of the page.  A heading named **Main Content** appears, it symbolizes our main\n**django-CMS** Placeholder.\n\nLocate the plus sign right to the heading and click on it. From its context menu select\n**Container** located in the section **Bootstrap**:\n\n|add-container|\n\n.. |add-container| image:: _static/bootstrap3/add-container.png\n\nThis brings you into the editor mode for a Bootstrap container. To this container you may add one or\nmore Bootstrap **Rows**. Inside these rows you may organize the layout using some Bootstrap\n**Columns**.\n\nPlease proceed with the detailed explanation on how to use the\n:ref:`Bootstrap's grid <bootstrap3/grid>` system within **djangocms-cascade**.\n"
  },
  {
    "path": "docs/source/index.rst",
    "content": "============================================\nWelcome to DjangoCMS-Cascade's documentation\n============================================\n\nProject's home\n==============\n\nCheck for the latest release of this project on GitHub_.\n\nPlease report bugs or ask questions using the `Issue Tracker`_.\n\n\nProject's goals\n===============\n\n#. Create a modular system, which allows programmers to add simple widget code, without having to\n   implement an extra djangoCMS_ plugins for each of them.\n\n#. Make available a meaningful subset of widgets as available for the most common CSS frameworks,\n   such as `Twitter Bootstrap`_. With these special plugins, in many configurations, **djangoCMS**\n   can be operated using one single template, containing one generic placeholder.\n\n#. Extend this **djangoCMS** plugin, to be used with other CSS frameworks such as `Foundation 5`_,\n   Unsemantic_ and others.\n\n#. Use the base functionality of **djangoCMS-Cascade** to easily add special plugins. For instance,\n   djangoSHOP_ implements all its cart and checkout specific forms this way.\n\n\nContents:\n=========\n\n.. toctree::\n    :maxdepth: 2\n    :numbered:\n\n    impatient\n    introduction\n    installation\n    link-plugin\n    bootstrap3/index\n    icon-fonts\n    leaflet\n    client-side\n    section\n    segmentation\n    sharable-fields\n    customize-styles\n    render-template\n    hide-plugins\n    clipboard\n    strides\n    sphinx\n    customized-plugins\n    generic-plugins\n    changelog\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n.. _Github: https://github.com/jrief/djangocms-cascade\n.. _Issue Tracker: https://github.com/jrief/djangocms-cascade/issues\n.. _djangoCMS: https://www.django-cms.org/\n.. _djangoSHOP: https://www.django-shop.org/\n.. _Twitter Bootstrap: http://getbootstrap.com/\n.. _Foundation 5: http://foundation.zurb.com/\n.. _Grid System 960: http://960.gs/\n.. _Unsemantic: http://unsemantic.com/\n"
  },
  {
    "path": "docs/source/installation.rst",
    "content": "============\nInstallation\n============\n\nInstall the latest stable release\n\n.. code-block:: bash\n\n\t$ pip install djangocms-cascade\n\nor the current development release from github\n\n.. code-block:: bash\n\n\t$ pip install -e git+https://github.com/jrief/djangocms-cascade.git#egg=djangocms-cascade\n\n\nPython Package Dependencies\n===========================\n\nDue to some incompatibilities in the API of Django, django-CMS and djangocms-text-ckeditor, please\nonly use these combinations of Python package dependencies:\n\ndjangocms-cascade-0.11.x\n------------------------\n\n* Django_ >=1.8, <=1.9\n* Django-CMS_ >=3.2, <=3.3\n* djangocms-text-ckeditor_ == 3.0\n\ndjangocms-cascade-0.12.x\n------------------------\n\n* Django_ >=1.9, <1.11\n* Django-CMS_ >=3.4.3\n* djangocms-text-ckeditor_ >= 3.3\n\ndjangocms-cascade-0.13.x\n------------------------\n\n* Django_ >=1.9, <1.11\n* Django-CMS_ >=3.4.3\n* djangocms-text-ckeditor_ >= 3.4\n\ndjangocms-cascade-0.14.x\n------------------------\n\n* Django_ >=1.9, <1.11\n* Django-CMS_ >=3.4.4\n* djangocms-text-ckeditor_ >= 3.4\n* django-filer_ >= 1.2.8\n\ndjangocms-cascade-0.17.x - 0.19.x\n---------------------------------\n\n* Django_ >=1.10, <2.0\n* Django-CMS_ >=3.4.4, <=3.6\n* djangocms-text-ckeditor_ >= 3.4\n\ndjangocms-cascade-1.0.x\n-----------------------\n\n* Django_ >=1.11, <=2.1\n* Django-CMS_ >=3.5.3, <=3.6.x\n* djangocms-text-ckeditor_ >= 3.7\n\nother combinations might work, but have not been tested.\n\n\nOptional packages\n-----------------\n\nIf you intend to use Image, Picture, Jumbotron, or FontIcons you will have to install django-filer\nin addition:\n\n.. code-block:: bash\n\n\t$ pip install django-filer\n\nFor a full list of working requirements see the `requirements folder`_ in the sources.\n\n.. _requirements folder: https://github.com/jrief/djangocms-cascade/tree/master/requirements\n\n\nCreate a database schema\n========================\n\n.. code-block:: bash\n\n\t./manage.py migrate cmsplugin_cascade\n\n\nInstall Dependencies not handled by PIP\n=======================================\n\nSince the Bootstrap CSS and other JavaScript files are part of their own repositories, they are\nnot shipped within this package. Furthermore, as they are not part of the PyPI network, they have\nto be installed through the `Node Package Manager`_, ``npm``.\n\nIn your Django projects it is good practice to keep a reference onto external node modules using\nthe file ``packages.json`` added to its own version control repository, rather than adding the\ncomplete node package.\n\n.. code-block:: bash\n\n\tcd my-project-dir\n\tnpm init\n\tnpm install bootstrap@3 bootstrap-sass@3 jquery@3 leaflet@1 leaflet-easybutton@2.2 picturefill select2@4 --save\n\nIf the Django project contains already a file named ``package.json``, then skip the ``npm init``\nin the above command.\n\nThe node packages ``leaflet`` and ``leaflet-easybutton`` are only required if the Leaflet plugin\nis activated.\n\nThe node packages ``picturefill`` is a shim to support the ``srcset`` and ``sizes`` attributes on\n``<img ... />`` elements. Please check `browser support`_ if that feature is required in your\nproject.\n\nThe node packages ``select2`` is required for autofilling the select box in Link plugins. It is\noptional, but strongly suggested.\n\nRemember to commit the changes in ``package.json`` into the projects version control repository.\n\nSince these Javascript and Stylesheet files are located outside of the project's ``static`` folder,\nwe must add them explicitly to our lookup path, using ``STATICFILES_DIRS`` in ``settings.py``:\n\n.. code-block:: python\n\n\tSTATICFILES_DIRS = [\n\t    ...\n\t    ('node_modules', os.path.join(MY_PROJECT_DIR, 'node_modules')),\n\t]\n\n\nUsing AngularJS instead of jQuery\n---------------------------------\n\nIf you prefer AngularJS over jQuery, then replace the above install command with:\n\n.. code-block:: bash\n\n\tnpm 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\n\nRemember to point to the prepared AngularJS templates using this setting:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'bootstrap3': {\n\t        'template_basedir': 'angular-ui',\n\t    },\n\t    ...\n\t}\n\n\nConfiguration\n=============\n\nAdd ``'cmsplugin_cascade'`` to the list of ``INSTALLED_APPS`` in the project’s ``settings.py``\nfile. Optionally add 'cmsplugin_cascade.extra_fields' and/or 'cmsplugin_cascade.sharable' to\nthe list of ``INSTALLED_APPS``. Make sure that these entries are located before the entry ``cms``.\n\n\nConfigure the CMS plugin\n------------------------\n\n.. code-block:: python\n\n\tINSTALLED_APPS = (\n\t    ...\n\t    'cmsplugin_cascade',\n\t    'cmsplugin_cascade.clipboard',  # optional\n\t    'cmsplugin_cascade.extra_fields',  # optional\n\t    'cmsplugin_cascade.sharable',  # optional\n\t    'cmsplugin_cascade.segmentation',  # optional\n\t    'cms',\n\t    ...\n\t)\n\n\nActivate the plugins\n--------------------\n\nBy default, no **djangocms-cascade** plugins is activated. Activate them in the project’s\n``settings.py`` with the directive ``CMSPLUGIN_CASCADE_PLUGINS``.\n\nTo activate all available Bootstrap plugins, use:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS = ['cmsplugin_cascade.bootstrap3']\n\nIf for some reason, only a subset of the available Bootstrap plugins shall be activated, name each\nof them. If for example, only the grid system shall be used but no other Bootstrap plugins, then\nconfigure:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS = ['cmsplugin_cascade.bootstrap3.container']\n\nA very useful plugin is the **LinkPlugin**. It superseds the djangocms-link_-plugin, normally used\ntogether with the CMS.\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.link')\n\nIf this plugin is enabled ensure, that the node package ``select2`` has been installed and findable\nby the static files finder using these directives in ``settings.py``:\n\n.. code-block:: python\n\n    SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'\n    SELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'\n\n:ref:`generic-plugins` which are not opinionated towards a specific CSS framework, are kept in a\nseparate folder. It is strongly suggested to always activate them:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.generic')\n\nSometimes it is useful to do a :ref:`segmentation`. Activate this by adding its plugin:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.segmentation')\n\n\nWhen :ref:`icon-fonts`: on your site, add ``'cmsplugin_cascade.icon'`` to ``INSTALLED_APPS``\nand add it to the configured Cascade plugins:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS.append('cmsplugin_cascade.icon')\n\n\nSpecial settings when using the TextPlugin\n------------------------------------------\n\nSince it is possible to add plugins from the Cascade ecosystem as children to the\n`djangocms-text-ckeditor`_, we must add a special configuration:\n\n.. code-block:: python\n\n\tfrom django.core.urlresolvers import reverse_lazy\n\tfrom django.utils.text import format_lazy\n\n\tCKEDITOR_SETTINGS = {\n\t    'language': '{{ language }}',\n\t    'skin': 'moono-lisa',\n\t    'toolbar': 'CMS',\n\t    'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),\n\t}\n\nThe last line in this configuration invokes a special function, which adds special configuration settings to the\nCKTextEditor plugin.\n\n.. note:: The skin ``moono-lisa`` has been introduced in Django CKEditor version 3.5, so if you upgrade from an earlier\n\tversion, please adopt this in your settings.\n\n\nRestrict plugins to a particular placeholder\n--------------------------------------------\n\n.. warning:: You **must** set ``parent_classes`` for your placeholder, else you\n    won't be able to add a container to your placeholder. This means that as an\n    absolute minimum, you must add this to your settings:\n\n.. code-block:: python\n\n\tCMS_PLACEHOLDER_CONF = {\n\t    ...\n\t    'content': {\n\t        'parent_classes': {'BootstrapContainerPlugin': None,},\n\t    },\n\t    ...\n\t}\n\nUnfortunately **django-CMS** does not allow to declare dynamically which plugins are eligible to be\nadded as children of other plugins. This is determined while bootstrapping the Django project and\nthus remains static. We therefore must somehow trick the CMS to behave as we want.\n\nSay, our Placeholder named \"Main Content\" shall accept the **BootstrapContainerPlugin** as its only\nchild, we then must use this CMS settings directive:\n\n.. code-block:: python\n\n\tCMS_PLACEHOLDER_CONF = {\n\t    ...\n\t    'Main Content Placeholder': {\n\t        'plugins': ['BootstrapContainerPlugin'],\n\t        'text_only_plugins': ['TextLinkPlugin'],\n\t        'parent_classes': {'BootstrapContainerPlugin': None},\n\t        'glossary': {\n\t            'breakpoints': ['xs', 'sm', 'md', 'lg'],\n\t            'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},\n\t            'fluid': False,\n\t            'media_queries': {\n\t                'xs': ['(max-width: 768px)'],\n\t                'sm': ['(min-width: 768px)', '(max-width: 992px)'],\n\t                'md': ['(min-width: 992px)', '(max-width: 1200px)'],\n\t                'lg': ['(min-width: 1200px)'],\n\t            },\n\t        },\n\t    },\n\t    ...\n\t}\n\nHere we add the **BootstrapContainerPlugin** to ``plugins`` and ``parent_classes``. This is because\nthe Container plugin normally is the root plugin in a placeholder. If this plugin would not restrict\nits parent plugin classes, we would be allowed to use it as a child of any plugin. This could\ndestroy the page's grid.\n\nFurthermore, in the above example we must add the **TextLinkPlugin** to ``text_only_plugins``.\nThis is because the **TextPlugin** is not part of the Cascade ecosystem and hence does not know\nwhich plugins are allowed as its children.\n\nThe dictionary named ``glossary`` sets the initial parameters of the :ref:`bootstrap3/grid`.\n\n\nDefine the leaf plugins\n-----------------------\n\nLeaf plugins are those, which contain real data, say text or images. Hence the default setting\nis to allow the **TextPlugin** and the **FilerImagePlugin** as leafs. This can be overridden using\nthe configuration directive\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'alien_plugins': ['TextPlugin', 'FilerImagePlugin', 'OtherLeafPlugin'],\n\t    ...\n\t}\n\n\nBootstrap 3 with AngularJS\n--------------------------\n\nSome Bootstrap3 plugins can be rendered using templates which are suitable for the very popular\n`Angular UI Bootstrap`_ framework. This can be done during runtime; when editing the plugin a\nselect box appears which allows to chose an alternative template for rendering.\n\n\nTemplate Customization\n======================\n\nMake sure that the style sheets are referenced correctly by the used templates. **Django-CMS**\nrequires django-sekizai_ to organize these includes, so a strong recommendation is to use that\nDjango app.\n\nThe templates used for a **django-CMS** project shall include a header, footer, the menu bar and\noptionally a breadcrumb, but should leave out an empty working area. When using HTML5, wrap this\narea into an ``<article>`` or ``<section>`` element or just use it unwrapped.\n\nThis placeholder then shall be named using a generic identifier, for instance \"Main Content\" or\nsimilar:\n\n.. code-block:: html\n\n\t{% load cms_tags sekizai_tags %}\n\t<head>\n\t    ...\n\t    {% render_block \"css\" postprocessor \"cmsplugin_cascade.sekizai_processors.compress\" %}\n\t</head>\n\n\t<body>\n\t    ...\n\t    <!-- wrapping element (optional) -->\n\t        {% placeholder \"Main Content\" %}\n\t    <!-- /wrapping element -->\n\t    {% render_block \"js\" postprocessor \"cmsplugin_cascade.sekizai_processors.compress\" %}\n\t</body>\n\nFrom now on, the page layout can be adopted inside this placeholder, without having to fiddle with\ntemplate coding anymore.\n\nNote the two templatetags ``render_block``. The upper one collects all the CSS files referenced by\n``{% addtoblock \"css\" ... %}``. The lower one collects all the JS files referenced by\n``{% addtoblock \"js\" ... %}``. They then are rendered alltogether instead of beeing distributed all\nacross the page. If django-compressor_ is installed and enabled, then add the special compressor\n``\"cmsplugin_cascade.sekizai_processors.compress\"`` to the templatetag. It can handle files outside\nthe ``STATIC_ROOT``directory.\n\n.. _Django: http://djangoproject.com/\n.. _Django-CMS: https://www.django-cms.org/\n.. _Angular UI Bootstrap: http://angular-ui.github.io/bootstrap/\n.. _pip: http://pypi.python.org/pypi/pip\n.. _django-sekizai: http://django-sekizai.readthedocs.org/en/latest/\n.. _django-compressor: http://django-compressor.readthedocs.org/en/latest/\n.. _djangocms-link: https://github.com/divio/djangocms-link\n.. _djangocms-text-ckeditor: https://github.com/divio/djangocms-text-ckeditor\n.. _django-filer: https://github.com/divio/django-filer\n.. _Node Package Manager: https://nodejs.org/en/download/\n.. _browser support: https://caniuse.com/#search=srcset\n"
  },
  {
    "path": "docs/source/introduction.rst",
    "content": "============\nIntroduction\n============\n\n**DjangoCMS-Cascade** is a collection of plugins for Django-CMS_ >=3.3 to add various HTML elements\nfrom CSS frameworks, such as `Twitter Bootstrap`_ to the Django templatetag_ placeholder_. This\nDjango App makes it very easy to add other CSS frameworks, or to extend an existing collection with\nadditional elements.\n\n**DjangoCMS-Cascade** allows web editors to layout their pages, without having to create different\n`Django templates`_ for each layout modification. In most cases, one template with one single\nplaceholder is enough. The editor then can subdivide that placeholder into rows and columns, and\nadd additional DOM_ elements such as buttons, rulers, or even the Bootstrap Carousel. Some basic\nunderstanding on how the DOM works is required though.\n\n**Twitter Bootstrap** is a well documented CSS framework which gives web designers lots of\npossibilities to add a consistent structure to their pages. This collection of `Django-CMS plugins`_\noffers a subset of these predefined elements to web designers.\n\n\nExtensibility\n=============\n\nThis module requires one database table with one column to store all data in a JSON object. All\n**DjangoCMS-Cascade** plugins share this same model, therefore they can be easily extended, because\nnew data structures are added to that JSON object without requiring a database migration.\n\nAnother three database tables are required for additional optional features.\n\n\nNaming Conflicts\n----------------\n\nSome **djangoCMS** plugins may use the same name as plugins from **djangocms-cascade**. To prevent\nconfusion, since version 0.7.2, all Cascade plugins as prefixed with a Ϟ (koppa) symbol. This can\nbe deactivated or changed by setting ``CMSPLUGIN_CASCADE['plugin_prefix']`` to ``False`` or any\nother symbol.\n\n\n.. _Django-CMS: https://github.com/divio/django-cms/\n.. _Twitter Bootstrap: http://getbootstrap.com/\n.. _Django templates: https://docs.djangoproject.com/en/dev/topics/templates/\n.. _templatetag: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/\n.. _placeholder: https://django-cms.readthedocs.org/en/latest/advanced/templatetags.html#placeholder\n.. _DOM: http://www.w3.org/DOM/\n.. _Django-CMS plugins: https://django-cms.readthedocs.org/en/latest/getting_started/plugin_reference.html\n"
  },
  {
    "path": "docs/source/leaflet.rst",
    "content": "=====================================\nMap Plugin using the Leaflet frontend\n=====================================\n\nIf you want to add a interactive maps to a **Django-CMS** placeholder, the **Cascade Leaflet Map\nPlugin** may be your best choice. It is not activated by default, because it requires a special\nJavaScript library, an active Internet connection (in order to load the map tiles), and a license\nkey (this depends on the chosen tiles layer). By default the **Cascade Leaflet Map Plugin** uses\nthe `Open Street Map`_ tile layer, but this can be changed to Mapbox_, `Google Maps`_ or another\nprovider.\n\nThis plugin uses third party packages, based on the `Leaflet JavaScript`_ library for mobile-friendly\ninteractive maps.\n\n.. _Open Street Map: http://www.openstreetmap.org/\n.. _Mapbox: https://www.mapbox.com/\n.. _Google Maps: https://developers.google.com/maps/\n.. _Leaflet JavaScript: http://leafletjs.com/\n\n\nInstallation\n============\n\nThe required JavaScript dependencies are not shipped with **djangocms-cascade**. They must be\ninstalled separately from the `Node JS repository`_.\n\n.. code-block:: shell\n\n\tnpm install leaflet\n\tnpm install leaflet-easybutton\n\n.. note:: Leaflet Easybutton is only required for the administration backend.\n\n.. _Node JS repository: https://www.npmjs.com/\n\n\nConfiguration\n=============\n\nThe default Cascade settings must be active in order to use the **Leaflet Map Plugin**. Additionally\nadd to the project's settings:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE_PLUGINS = [\n\t    ...\n\t    'cmsplugin_cascade.leaflet',\n\t    ...\n\t]\n\nBy modifying the dictionary ``CMSPLUGIN_CASCADE['leaflet']`` you may override Leaflet specific\nsettings. Change ``CMSPLUGIN_CASCADE['leaflet']['tilesURL']`` to the `titles layer`_ of your choice.\n\nAll other attributes of that dictionary are passed as options to the Leaflet ``tileLayer``\nconstructor. For details, please refer to the Leaflet specific documentation.\n\n.. _titles layer: http://leafletjs.com/reference-1.0.3.html#tilelayer\n\n\nUsage\n=====\n\nAdd a **Map Plugin** to any **django-CMS** placeholder. Here you may adjust the width and height of\nthe map.\n\nThe map can be repositioned at any time. Use the *Center* button on the top left corner to reset the\nposition to the coordinates and zoom level, it was saved the last time.\n\n\nAdding a marker to the map\n--------------------------\n\nFirst click on *Add another Marker* and enter a title of your choice. Afterwards go to the map and\nplace the marker. After saving the map, this new marker will be persisted.\n\nAdditionally, one may choose a customized marker icon: Click on *Use customized marker icon* and\nchoose an image from your media files. It is recommended to use PNG images with a transparent layer\nas marker icons.\n\nAdjust the icon's size by setting the marker width. The height is computed in order to keep the same\naspect ratio.\n\n.. note:: Customized marker icons are only displayed in the frontend. The backend always uses the\n\tdefault pin symbol.\n\nBy settings the marker's anchor, the icon can be positioned exactly.\n\nMarkers can be repositioned at any time and the new coordinates are saved together with the map.\n\n\nAlternative Tiles\n=================\n\nBy default, **djangocms-cascade** is shipped using tiles from the `Open Street Map`_ project.\nThis is mainly because these tiles can be used without requiring a license key. However, they load\nslowly and their appearance might not be what your customers expect.\n\n\nMapbox\n------\n\nA good alternative are tiles from Mapbox_. Please refer to their terms and conditions for details.\nThere you can also apply for an access token, they offer free plans for low traffic sites.\n\nThen add to the project's ``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'leaflet': {\n\t        'tilesURL': 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',\n\t        'accessToken': YOUR-MAPBOX-ACCESS-TOKEN,\n\t        ...\n\t    }\n\t    ...\n\t}\n\n\nGoogle Maps\n-----------\n\nThe problem with Google is that its Terms of Use forbid any means of tile access other than through\nthe Google Maps API. Therefore in the frontend, Google Maps are rendered using a different template,\nwhich is not based on the LeafletJS library. This means that you must edit your maps using Mapbox or\nOpenStreetMap titles, whereas Google Maps is only rendered in the frontend.\n\nTo start with, apply for a `Google Maps API key`_ and add it to the project's ``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'leaflet': {\n\t        ...\n\t        'apiKey': YOUR-GOOGLE-MAPS-API-KEY,\n\t        ...\n\t    }\n\t    ...\n\t}\n\nWhen editing a **Map** plugin, choose *Google Map* from the select field named *Render template*.\n\nIf want to render Google Maps exclusively in the frontend, change this in your project's\n``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_extra_render_templates': {\n\t        'LeafletPlugin': [\n\t            ('cascade/plugins/googlemap.html', \"Google Map\"),\n\t        ],\n\t    }\n\t    ...\n\t}\n\n.. _Google Maps API key: https://developers.google.com/maps/documentation/javascript/get-api-key\n\n\nDefault Starting Position\n=========================\n\nDepending of the region you normally create maps, you can specify the default starting position. If for instance\nyour main area of interest is Germany, than these coordinates are a good setting:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'leaflet': {\n\t        ...\n\t        'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6},\n\t    }\n\t    ...\n\t}\n\n\nDefault Marker Icon\n===================\n\nIn case you don't like the default marker icon, you can replace it with your own one.\nSimply add to the configuration\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'leaflet': {\n\t        ...\n\t        'defaultMarkerIcon': {\n\t            'iconUrl': STATIC_URL + 'my_project/icons/marker.svg',\n\t            'iconSize': (25, 41),\n\t            'iconAnchor': (13, 41),\n\t            ...\n\t        },\n\t    }\n\t    ...\n\t}\n\nFor details about all the possible options for a marker icon, refer to the `Leaflet documentation`_.\n\n.. _Leaflet documentation: https://leafletjs.com/reference.html#icon\n\n\nAddress Lookup\n==============\n\nSince version 2.3 it is possible to search for a location, using the `OSM Nominatim`_ lookup\nservice.\n\n.. _OSM Nominatim: https://nominatim.org/\n\nWhen adding or editing a marker, there is a field named **Address lookup**. Entering an address\ninto that field and pressing the \"Enter\" key, generates a list of possible locations. Choose one\nfrom that list and the marker will be placed at the specified coordinates.\n\nIn case you want to override the address lookup service with one compatible the Nominatim's API,\nchange the URL in the Django settings:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'leaflet': {\n\t        ...\n\t        'addressLookupURL': 'https://my-nominatim.example.org/search',\n\t    }\n\t    ...\n\t}\n"
  },
  {
    "path": "docs/source/link-plugin.rst",
    "content": ".. _link-plugin:\n\n===========\nLink Plugin\n===========\n\n**djangocms-cascade** ships with its own link plugin. This is because other plugins from the\nCascade eco-system, such as the **BootstrapButtonPlugin**, the **BootstrapImagePlugin** or the\n**BootstrapPicturePlugin** also require a functionality in order to set links to internal- and\nexternal URLs. Since we do not want to duplicate the linking functionality for each of those\nplugins, it has been moved into its own mixin-classes. Therefore we will use the terminology\n**TextLinkPlugin** when referring to text-based links.\n\nThe de-facto plugin for links, djangocms-link_ can't be used as a base class for these plugins,\nhence an alternative implementation has been created within the Cascade framework. The link related\ndata is stored in a various fields in our main JSON field (named ``glossary``).\n\n\nPrerequisites\n=============\n\nBefore using this plugin, assure that ``'cmsplugin_cascade.link'`` is member of the list or\ntuple ``CMSPLUGIN_CASCADE_PLUGINS`` in the project's ``settings.py``.\n\n|simple-link-element|\n\n.. |simple-link-element| image:: _static/simple-link-element.png\n\nThe behavior of this Plugin is what you expect from a Link editor. The field **Link Content** is the\ntext displayed between the opening and closing ``<a>`` tag. If used in combination with\ndjangocms-text-ckeditor_ the field automatically is filled out.\n\nBy changing the **Link type**, the user can choose between different types of Links:\n\n * Internal Links pointing to another page inside the CMS.\n * External Links pointing to a valid Internet URL.\n * Files from **django-filer** to download.\n * Links pointing to a valid e-mail address.\n * Optionally any other linkable object, if another Django application extends the Link-Plugin (see\n   below for details).\n\nThe optional field **Title** can be used to add a ``title=\"some value\"`` attribute to the\n``<a ...>`` element.\n\nWith **Link Target**, the user can specify, whether the linked content shall open in the current\nwindow or if the browser shall open a new window.\n\n\nLink Plugin with Sharable Fields\n================================\n\nIf your web-site contains many links pointing onto a few external URLs, you might want to refer to\nthem by a symbolic name, rather than having to reenter the URL repeatedly. With\n**djangocms-cascade** this can be achieved easily by declaring some of the plugin's fields as\n*sharable*.\n\nAssure that ``INSTALLED_APPS`` contains ``'cmsplugin_cascade.sharable'``, then redefine the\n**TextLinkPlugin** to have sharable fields in ``settings.py``:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_sharables':\n\t        …\n\t        'TextLinkPlugin': ['link_type', 'ext_url'],\n\t        …\n\t    },\n\t    ...\n\t}\n\nThis will change the Link Plugin's editor slightly. Note the extra field added to the bottom of the\nform.\n\n|sharable-link-element|\n\n.. |sharable-link-element| image:: _static/sharable-link-element.png\n\nThe URL for this link entity now is stored in a central entity. This feature is useful, if for\ninstance the URL of an external web page may change in the future. Then the administrator can change\nthat link in the administration area once, rather than having to go through all the pages and check\nif that link was used.\n\nTo retain the Link settings, click onto the checkbox *Remember these settings as: ...* and give it\na name of your choice. The next time your create a Shared Link element, you may select a previously\nnamed settings from the select field *Shared Settings*. Since these settings can be shared among\nother plugins, these input fields are disabled and can't be changed anymore.\n\n\nChanging shared settings\n------------------------\n\nThe settings of a shared plugin can be changed globally, for all plugins using them. To edit such a\nshared setting, in the Django Admin, go into the list view for\n**Home › django CMS Cascade › Shared between Plugins** and choose the named shared settings.\n\nPlease note, that each plugin type can specify which fields shall be sharable between plugins of\nthe same type. In this example, only the Link itself is shared, but one could configure\n**djangocms-cascade** to also share the link's ``title``, the ``target``, and other tags.\n\nThen only these fields are editable in the detail view **Shared between Plugins**. The interface\nfor other shared plugin may vary substantially, depending of their type definition.\n\n\nExtending the Link Plugin\n=========================\n\nWhile programming third party modules for Django, one might have to access a model instance through\na URL and thus add the method get_absolute_url_ to that Django model. Since such a URL is neither a\nCMS page, nor a URL to an external web page, it would be convenient to access that model using a\nspecial Link type.\n\nFor example, in django-shop_ we can allow to link directly to a product, sold by the shop.\nThis is achieved by reconfiguring the Link Plugin inside Cascade with:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    …\n\t    'link_plugin_classes': (\n\t        'shop.cascade.plugin_base.CatalogLinkPluginBase',\n\t        'shop.cascade.plugin_base.CatalogLinkForm',\n\t    ),\n\t    …\n\t}\n\nThe tuple specified through ``link_plugin_classes`` replaces the base class for the **LinkPlugin**\nclass and the form class used by its editor.\n\nHere we replace the two built-in classes :class:`cmsplugin_cascade.link.plugin_base.DefaultLinkPluginBase`\nand :class:`cmsplugin_cascade.link.forms.LinkForm` by alternative implementations.\n\n.. code-block:: python\n\t:caption: shop/cascade/plugin_base.py\n\n\tfrom entangled.forms import get_related_object\n\tfrom cmsplugin_cascade.link.plugin_base import LinkPluginBase\n\n\tclass CatalogLinkPluginBase(LinkPluginBase):\n\t    @classmethod\n\t    def get_link(cls, obj):\n\t        link_type = obj.glossary.get('link_type')\n\t        if link_type == 'product':\n\t            relobj = get_related_object(obj.glossary, 'product')\n\t            if relobj:\n\t                return relobj.get_absolute_url()\n\t        else:\n\t            return super().get_link(obj) or link_type\n\nThis class handles links of type \"Product\" and creates a URL pointing onto a Django model implementing\nthe method ``get_absolute_url``.\n\nAdditionally, we have to override the form class used by the Link plugin editor:\n\n.. code-block:: python\n\t:caption: shop/cascade/plugin_base.py\n\n\tfrom cms.plugin_pool import plugin_pool\n\tfrom django.forms import models\n\tfrom shop.models.product import ProductModel\n\n\tclass CatalogLinkForm(LinkForm):\n\t    LINK_TYPE_CHOICES = [\n\t        ('cmspage', _(\"CMS Page\")),\n\t        ('product', _(\"Product\")),\n\t        ('download', _(\"Download File\")),\n\t        ('exturl', _(\"External URL\")),\n\t        ('email', _(\"Mail To\")),\n\t    ]\n\n\t    product = models.ModelChoiceField(\n\t        label=_(\"Product\"),\n\t        queryset=ProductModel.objects.all(),\n\t        required=False,\n\t        help_text=_(\"An internal link onto a product from the catalog\"),\n\t    )\n\n\t    class Meta:\n\t        entangled_fields = {'glossary': ['product']}\n\nNow the select box for **Link type** will offer one additional option named \"Product\". When this is\nselected, the page administrator can select one product in the shop and the link will point onto\nits proper detail page.\n\n\nUsing Links in your own Plugins\n===============================\n\nMany HTML components allow to link onto other resources, for instance images, the button element,\nicons, etc. Since we don't want the reimplement the linking functionality for each of them,\n**djangocms-cascade** offers a few base classes, which can be used by those plugin. As an example,\nlet's implement a simple button plugin.\n\n.. code-block:: python\n\t:caption: myproject/cascade/button.py\n\n\tfrom django.forms import models\n\tfrom cms.plugin_pool import plugin_pool\n\tfrom cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin\n\tfrom cmsplugin_cascade.link.plugin_base import LinkElementMixin\n\n\tclass ButtonForm(LinkFormMixin):\n\t    require_link = False\n\n\t    button_content = models.CharField(\n\t        label=_(\"Button Content\"),\n\t    )\n\n\t    class Meta:\n\t        entangled_fields = {'glossary': ['link_content']}\n\n\tclass ButtonPlugin(LinkPluginBase):\n\t    name = _(\"Button\")\n\t    model_mixins = (LinkElementMixin,)\n\t    form = ButtonForm\n\t    render_template = 'myproject/button.html'\n\t    allow_children = False\n\n\tplugin_pool.register_plugin(ButtonPlugin)\n\nWhat we see here is, that our ``ButtonForm``, which is used by our ``ButtonPlugin`` inherits from\na base form offering all the fields required to link somewhere. Sine the button may just display\nsome content, but without linking anywhere, we make that optional by setting ``require_link`` to\n``False``. The box for selecting the \"Link Type\" then adds \"No Link\" to its set of options.\n\nWe don't even have to bother, whether our custom button can point onto links types specified by yet\nanother third party app, and not handled by **djangocms-cascade** – All these additional link types\nare handled automatically by the configuration setting ``CMSPLUGIN_CASCADE['link_plugin_classes']``\nas explained in the previous section.\n\n\n.. _djangocms-link: https://github.com/divio/djangocms-link\n.. _djangocms-text-ckeditor: https://github.com/divio/djangocms-text-ckeditor\n.. _get_absolute_url: https://docs.djangoproject.com/en/stable/ref/models/instances/#get-absolute-url\n.. _django-shop: https://github.com/awesto/django-shop\n"
  },
  {
    "path": "docs/source/release-notes-1.rst",
    "content": "# Release Notes for version 1.0\n\nApart from dropping support for Python-2.7, **djangocms-cascade** version 1.0 internally changes a lot.\nUntil version 0.19 it used a special widget :class:`cmsplugin_cascade.widgets.JSONMultiWidget` which\ntook care of converting the so named \"glossary fields\" into an editor, used to change the properties\nof all Cascade plugins.\n\nWhile this editor was able to handle all kinds of primitive data types, such as strings, numeric input,\nsimple- and multiple choices, it failed to handle references onto foreign keys and other data inputs,\nrequiring input validation and rectification. Therefore many Cascade plugins turned into kind of hybrids,\nusing a mixture of classic Django form fields plus one special \"glossary\" field, using the ``JSONMultiWidget``\nmentioned before.\n\nThis approach turned out to be impracticable, because input widgets rendered by the form fields could not\nbe mixed with fields rendered by the ``JSONMultiWidget``. It also was complicated from a point of understanding\nand other programmers had difficulties to implement their own plugins.\n\nTherefore in version 1.0, the list of \"glossary fields\" will be replaced against a slightly modified Django\n``ModelForm``. This form then reads and writes its data from the Django model field\n:class:`cmsplugin_cascade.models.CascadeElement.glossary`, just as it always did. This means that we still\nhave the advantage of using a JSON field to store arbitrary data, preventing us from having to create a Django\nmodel for each plugin in our database.\n\nThe code for reading and writing JSON data from and to this special Django model field (ie. ``glossary``),\nhas been moved out of **dangocms-cascade** and into a new Django app named\n[django-entangled](https://github.com/jrief/django-entangled). The reason for this code separation is greater\nreusability.\n"
  },
  {
    "path": "docs/source/render-template.rst",
    "content": "========================================\nChoose an alternative rendering template\n========================================\n\nSometimes you must render a plugin with a slightly different template, other than the given default.\nA possible solution is to create a new plugin, inheriting from the given one and overriding\nthe ``render_template`` attribute with a customized template. This however adds another plugin to\nthe list of registered CMS plugins.\n\nA simpler solution to solve this problem, is to allow a plugin to be rendered with a customized\ntemplate out of a set of alternatives.\n\n\nChange the path for template lookups\n====================================\n\nSome Bootstrap Plugins are shipped with templates, which are optimized to be rendered by Angular-UI_\nrather than the default jQuery. These alternative templates are located in the folder\n``cascade/bootstrap3/angular-ui``. If your project uses AngularJS instead of jQuery, then configure\nthe lookup path in ``settings.py`` with\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'bootstrap3': {\n\t        ...\n\t        'template_basedir': 'angular-ui',\n\t    },\n\t}\n\nThis lookup path is applied only to the Plugin's field ``render_template`` prepared for it. Such a\ntemplate contains the placeholder ``{}``, which is expanded to the configured ``template_basedir``.\n\nFor instance, the **CarouselPlugin** defines its ``render_template`` such as:\n\n.. code-block:: python\n\n\tclass CarouselPlugin(BootstrapPluginBase):\n\t    ...\n\t    render_template = 'cascade/bootstrap3/{}/carousel.html'\n\t    ...\n\n.. _Angular-UI: http://angular-ui.github.io/bootstrap/versioned-docs/0.13.4/\n\n\nConfigure Cascade Plugins to be rendered using alternative templates\n====================================================================\n\nAll plugins which offer more than one rendering template, shall be added in the projects\n``settings.py`` to the dictionary ``CMSPLUGIN_CASCADE['plugins_with_extra_render_templates']``.\nEach item in this dictionary consists of a key, naming the plugin, and a value containing a list of\ntwo-tuples. The first element of this two-tuple must be the templates filename, while the second\nelement shall contain an arbitrary name to identify that template.\n\nExample:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_extra_render_templates': {\n\t        'TextLinkPlugin': (\n\t            ('cascade/link/text-link.html', _(\"default\")),\n\t            ('cascade/link/text-link-linebreak.html', _(\"with linebreak\")),\n\t        )\n\t    },\n\t    ...\n\t}\n\n\nUsage\n-----\n\nWhen editing a **djangoCMS** plugins with alternative rendering templates, the plugin editor\nadds a select box containing choices for alternative rendering templates. Choose one other than the\ndefault, and the plugin will be rendered using that template.\n"
  },
  {
    "path": "docs/source/section.rst",
    "content": "=================\nSection Bookmarks\n=================\n\nIf you have a long page, and you want to allow the visitors of your site to quickly navigate to\ndifferent sections, then you can use bookmarks and create links to the different sections of any\nHTML page.\n\nWhen a user clicks on a bookmark link, then that page will load as usual but will scroll down\nimmediately, so that the bookmark is at the very top of the page. Bookmarks are also known as\nanchors. They can be added to any HTML element using the attribute ``id``. For example:\n\n.. code-block:: html\n\n\t<section id=\"unique-identifier-for-that-page\">\n\nFor obvious reasons, this identifier must be unambiguous, otherwise the browser does not know\nwhere to jump to. Therefore **djangocms-cascade** enforces the uniqueness of all bookmarks used on\neach CMS page.\n\n\nConfiguration\n=============\n\nThe HTML standard allows the usage of the ``id`` attribute on any element, but in practice it only\nmakes sense on ``<section>``, ``<article>`` and the heading elements ``<h1>``...``<h6>``.\nCascade by default is configured to allow bookmarks on the **SimpleWrapperPlugin** and the\n**HeadingPlugin**. This can be overridden in the project's configuration settings using:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_bookmark': [list-of-plugins],\n\t    ...\n\t}\n\n\nHashbang Mode\n-------------\n\nLinks onto bookmarks do not work properly in hashbang mode. Depending on the HTML settings, you may\nhave to prefix them with ``/`` or ``!``. Therefore **djangocms-cascade** offers a configuration\ndirective:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'bookmark_prefix': '/',\n\t    ...\n\t}\n\nwhich automatically prefixes the used bookmark.\n\n\nUsage\n=====\n\nWhen editing a plugin that is eligible for adding a bookmark, an extra input field is shown:\n\n|section-bookmark|\n\n.. |section-bookmark| image:: /_static/section-bookmark.png\n\nYou may add any identifier to this field, as long as it is unique on that page. Otherwise the\nplugin's editor will be reject the given inputs, while saving.\n\n\nHyperlinking to a Bookmark\n==========================\n\nWhen editing a **TextLink**, **BootstrapButton** or the link fields inside the **Image** or\n**Picture** plugins, the user gets an additional drop-down menu to choose one of the bookmarks for\nthe given page. This additional drop-down is only available if the **Link** is of type *CMS page*.\n\n|link-bookmark|\n\n.. |link-bookmark| image:: /_static/link-bookmark.png\n\nIf no bookmarks have been associated with the chosen CMS page, the drop-down menu displays only\n*Page root*, which is the default.\n"
  },
  {
    "path": "docs/source/segmentation.rst",
    "content": "=======================\nSegmentation of the DOM\n=======================\n\nThe **SegmentationPlugin** allows to personalize the DOM structure, depending on the context used to\nrender the corresponding page. Since **django-CMS** always uses a RequestContext_ while rendering\nits pages, we always have access onto the request object. Some use cases are:\n\n* Depending on the user, show a different portion of the DOM, if he is a certain user or not logged\n  in at all.\n* Show different parts of the DOM, depending on the browsers estimated geolocation. Useful to\n  render different content depending on the visitors country.\n* Show different parts of the DOM, depending on the supplied marketing channel.\n* Show different parts of the DOM, depending on the content in the session objects from previous\n  visits of the users.\n* Segment visitors into different groups used for A/B-testing.\n\n\nConfiguration\n=============\n\nThe **SegmentationPlugin** must be activated separately on top of other **djangocms-cascade**\nplugins. In ``settings.py``, add to\n\n.. code-block:: python\n\n\tINSTALLED_APPS = (\n\t    ...\n\t    'cmsplugin_cascade',\n\t    'cmsplugin_cascade.segmentation',\n\t    ...\n\t)\n\nThen, depending on what kind of data shall be emulated, add a list of two-tuples to the\nconfiguration settings ``CMSPLUGIN_CASCADE['segmentation_mixins']``. The first entry of each\ntwo-tuple specifies the mixin class added the the proxy model for the ``SegmentationPlugin``. The\nsecond entry specifies the mixin class added the model admin class for the ``SegmentationPlugin``.\n\n.. code-block:: python\n\n\t# this entry is optional:\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'segmentation_mixins': (\n\t        ('cmsplugin_cascade.segmentation.mixins.EmulateUserModelMixin', 'cmsplugin_cascade.segmentation.mixins.EmulateUserAdminMixin',),  # the default\n\t        # other segmentation plugin classes\n\t    ),\n\t    ...\n\t}\n\n\nUsage\n=====\n\nWhen editing **djangoCMS** plugins in **Structure** mode, below the section **Generic** a new plugin\ntype appears, named **Segment**.\n\n|segment-plugin|\n\n.. |segment-plugin| image:: _static/segment-plugin.png\n\nThis plugin now behaves as an ``if`` block, which is rendered only, if the specified condition\nevaluates to true. The syntax used to specify the condition, is the same as used in the Django\ntemplate language. Therefore it is possible to evaluate against more than one condition and combine\nthem with ``and``, ``or`` and ``not`` as described in `boolean operators`_ in the Django docs\n\nImmediately below a segmentation block using the condition tag ``if``, it is possible to use the\ntags ``elif`` or ``else``. This kind of conditional blocks is well known to Python programmers.\n\nNote, that when rendering pages in djangoCMS, a RequestContext_- rather than a Context-object is used.\nThis RequestContext is populated by the ``user`` object if ``'django.contrib.auth.context_processors.auth'``\nis added to your settings.py ``TEMPLATE_CONTEXT_PROCESSORS``. This therefore is a prerequisite\nwhen the Segmentation plugin evaluates conditions such as ``user.username == \"john\"``.\n\n.. _RequestContext: https://docs.djangoproject.com/en/1.8/ref/templates/api/#django.template.RequestContext\n.. _boolean operators: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#boolean-operators\n.. _request object: https://docs.djangoproject.com/en/dev/ref/request-response/#httprequest-objects\n\n\nEmulating Users\n===============\n\nOnly staff users or administrators can emulate the currently logged in user. Staff-only users must\npossess the four permissions `cmsplugin_cascade.add_segmentation`,\n`cmsplugin_cascade.change_segmentation`, `cmsplugin_cascade.delete_segmentation` and\n`cmsplugin_cascade.view_segmentation`.\n\nIf this plugin is activated and the permissions are set, then in the CMS toolbar a new menu\ntag appears named “Segmentation”. Here the currently logged in staff user can select another user.\nAll evaluation conditions then evaluate against this selected user, instead of the currently logged\nin user.\n\nIt is quite simple to add other overriding emulations. Have a look at the class\n``cmsplugin_cascade.segmentation.mixins.EmulateUserMixin``. This class then has to be added to\nyour configuration settings ``CMSPLUGIN_CASCADE_SEGMENTATION_MIXINS``. It then overrides the\nevaluation conditions and the toolbar menu.\n"
  },
  {
    "path": "docs/source/sharable-fields.rst",
    "content": ".. _sharable-fields:\n\n============================\nWorking with sharable fields\n============================\n\nSometime you'd want to remember sizes, links or any other options for rendering a plugin instance\nacross the project. In order to not have to do this job for each managed entity, you can remember\nthese settings using a name of your choice, controllable in a special section of the administration\nbackend.\n\nNow, whenever someone adds a new instance using this plugin, a select box with these remembered\nsettings appears. He then can choose from one of the remembered settings, which frees him to\nreenter all the values.\n\n\nConfigure a Cascade Plugins to optionally share some fields\n===========================================================\n\nConfiguring a plugin to share specific fields with other plugins of the same type is very easy.\nIn the projects ``settings.py``, assure that ``'cmsplugin_cascade.sharable'`` is part of your\n``INSTALLED_APPS``.\n\nThen add a dictionary of Cascade plugins, with a list of fields which shall be sharable. For\nexample, with this settings, the image plugin can be configured to share its sizes and rendering\noptions among each other.\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'plugins_with_sharables': {\n\t        'BootstrapImagePlugin': ['image-shapes', 'image-width-responsive', 'image-width-fixed', 'image-height', 'resize-options'],\n\t    },\n\t    ...\n\t}\n\n\nControl some named settings\n===========================\n\nWhenever a plugin is configured to allow to share fields, at the bottom of the plugin editor a\nspecial field appears:\n\n|remember-settings|\n\n.. |remember-settings| image:: /_static/remember-settings.png\n\nBy activating the checkbox, adding an arbitrary name next to it and saving the plugin, an entity\nof sharable fields is saved in the database. Now, whenever someone starts to edit a plugin of this\ntype, a select box appears on the top of the editor:\n\n|use-shared-settings|\n\n.. |use-shared-settings| image:: /_static/use-shared-settings.png\n\nBy choosing a previously named shared settings, the configured fields are disabled for input and\nreplaced by their shared field's counterparts.\n\nIn order to edit these shared fields in the administration backend, one must access\n**Home › Cmsplugin_cascade › Shared between Plugins**. By choosing a named shared setting, one can\nenter into the shared field's editor. This editor auto adopts to the fields declared as shared,\nhence will change from entity to entity. For the above example, it may look like this:\n\n|edit-shared-fields|\n\n.. |edit-shared-fields| image:: /_static/edit-shared-fields.png\n\nIn this editor one can change these shared settings globally, for all plugin instances where this\nnamed shared settings have been applied to.\n"
  },
  {
    "path": "docs/source/sphinx.rst",
    "content": "==============================\nIntegrate Sphinx Documentation\n==============================\n\nRestructured Text (ReST) is the de facto standard for documenting Python projects and is even widely\nused outside of this realm, by applications written in other languages. Sphinx_ is a compiler to\ngenerate HTML, Latex, PDF, e-books, etc. out of sources written in ReST.\n\nHTML rendered by Sphinx, typically is rendered as static content by the web server. This makes it\ndifficult to serve documentation, side by side with content from **django-CMS**, because these are\ncompletely different technologies. Furthermore, since Sphinx uses Jinja2 templates, but **django-CMS**'s\ninternal templatetags are not available for Jinja2, template sharing is not possible.\n\nTherefore **djangocms-cascade** offers an integration service, which makes it possible to integrate\ndocumentation generated by Sphinx, unintrusively inside the menu tree of **django-CMS**.\n\n\nConfiguration\n=============\n\nTo the project's ``settings.py``, add these options to the configuration directives:\n\n.. code-block:: python\n\n\tINSTALLED_APPS = [\n\t    ...\n\t    'cmsplugin_cascade',\n\t    'cmsplugin_cascade.sphinx',\n\t    ...\n\t]\n\n\tCMS_TEMPLATES = [\n\t    ...\n\t    ('path/to/documentation.html', \"Documentation Page\"),\n\t    ...\n\t]\n\n\tSPHINX_DOCS_ROOT = '/path/to/docs/_build/fragments'\n\nReplace ``'/path/to/documentation.html'`` with a filename pointing to your documentation\nroot template (see below).\n\nPoint ``SPHINX_DOCS_ROOT`` onto the directory, into which the HTML page fragments are generated.\n\n\nConfigure Sphinx Builder\n------------------------\n\nLocate the file ``conf.py`` and add:\n\n.. code-block:: python\n\n\textensions = [\n\t    ...\n\t    'cmsplugin_cascade.sphinx.fragmentsbuilder',\n\t]\n\nBy invoking ``make fragments``, Sphinx generates a HTML fragment for each page inside the\ndocumentation folder, typically into ``docs/build/fragments``. Later we use these fragments\nand include them using a normal Django view.\n\n\nIntegration with the CMS\n========================\n\nIn Django's admin backend, add a page as the starting point for the documentation inside\nthe CMS menu tree. Typically, one would name that page \"*Documentation*\" using ``docs`` or\n``documentation`` as its slug.\n\nIn the *Advanced Settings* tab, choose **Documentation Page** as the template. This settings\nhas been configured using the directive ``CMS_TEMPLATES``, as shown above.\n\nAs *Application*, select **Sphinx Documentation** from the pull down menu. This attaches the\ncomplete documentation tree just below the chosen slug.\n\nOptionally select **Documentation Menu** from the pull down menu as the *Attached menu*. It adds\na submenu for each main chapter of the documentation. If omitted, only **Documentation** is added\nthe the CMS menu tree.\n\n\nThe Documentation Template\n--------------------------\n\nYou must provide a template to be used by the documentation view. This template typically extends\na base CMS page template, providing a header, the navigation bar and the footer. In the block,\nresponsible for rendering the main content, add this template code:\n\n.. code-block:: django\n\n\t{% extends \"path/to/base.html\" %}\n\t{% load static cascade_tags %}\n\t...\n\t{% block head %}\n\t{{ block.super }}\n\t<link href=\"{% static 'cascade/sphinx/css/bootstrap-sphinx.css' %}\" rel=\"stylesheet\" type=\"text/css\" />\n\t{% endblock %}\n\t...\n\t{% block main-content %}\n\t    {% if page_content %}\n\t        {{ page_content }}\n\t    {% else %}\n\t        {% sphinx_docs_include \"index.html\" %}\n\t    {% endif %}\n\t{% endblock %}\n\nThis Django template now includes the HTML fragments compiled by Sphinx. This allows us to use\n**django-CMS** and combine it with Sphinx. In the URL, the part behind the documentation's slug\ncorresponds 1:1 to the name of the ReST document.\n\nIn this example we add a stylesheet to adopt the output to the `Bootstrap theme`_ for Sphinx_.\nDepending on your template layout, the way you import this may vary.\n\n.. _Sphinx: http://www.sphinx-doc.org/\n.. _Bootstrap theme: http://ryan-roemer.github.io/sphinx-bootstrap-theme/README.html\n\n\nLinking onto Documentation Pages\n--------------------------------\n\nBy overriding the :ref:`link-plugin` with a special target named **Documentation**, we can\neven add links onto our documentation pages symbolically. This means, that whenever we open the\n**LinkPlugin** editor, an additional target is added. It offers a select box showing all\npages from our documentation tree. This prevents us, having to hard code the URL pointing\nonto the documentation.\n\nThis feature has to be configured in the project's ``settings.py``, by replacing the LinkPlugin\nwith a modified version of itself:\n\n.. code-block:: python\n\n\tCMSPLUGIN_CASCADE = {\n\t    ...\n\t    'link_plugin_classes': [\n\t        'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkPlugin',\n\t        'cmsplugin_cascade.link.plugin_base.LinkElementMixin',\n\t        'cmsplugin_cascade.sphinx.link_plugin.SphinxDocsLinkForm',\n\t    ],\n\t    ...\n\t}\n"
  },
  {
    "path": "docs/source/strides.rst",
    "content": ".. _strides:\n\n==============================\nUse Cascade outside of the CMS\n==============================\n\nOne of the most legitimate points **djangocms-cascade** can be criticised for, is the lack of\nstatic content rendering. Specially in projects, where we want to work with static pages instead\nof CMS pages, one might fall back to handcrafting HTML, giving up all the benefits of rapid\nprototyping as provided by the Cascade plugin system.\n\nSince version 0.14 of **djangocms-cascade**, one can prototype the page content and export it as\nJSON file using :ref:`clipboard`. Later on, one can reuse that persisted data and create the same\ncontent outside of a CMS page. This is specially useful, if you must persist the page content\nin the project's version control system.\n\n\nUsage\n=====\n\nAfter the placeholder of a CMS page, is filled up with plugins from **djangocms-cascade**,\nswitch into *Structure Mode*, go to the context menu of that placeholder and click *Copy all*.\n\nNext, inside Django's administration backend, go to\n\n\tHome › Django CMS Cascade › Persited Clipboard Content\n\nand click onto *Add Persisted Clipboard Content*. The *Data* field will now be filled with a\ncascade of plugins serialized as JSON data. Copy that data and paste it into a file locatable\nby Django's static file finders, for example ``myproject/static/myapp/cascades/slug.json``.\n\n\nIn Templates\n============\n\nCreate a Django template, where instead of adding a Django-CMS placeholder, use the templatetag\n``render_cascade``. Example:\n\n.. code-block:: Django\n\n\t{% load cascade_tags %}\n\n\t{% render_cascade \"myapp/cascades/slug.json\" %}\n\nThis templatetag now renders the content just as if it would be rendered by the CMS. This means\nthat changing the template of a **djangocms-cascade** plugin, immediately has effect on the rendered\noutput. This is so to say **Model View Control**, where the Model is the content peristed as JSON,\nand the View is the template provided by the plugin. It separates the composition of HTML components\nfrom their actual representation, allowing a much better division of work during the page creation.\n\n\nCaveats when creating your own Plugins\n======================================\n\nWhen developing your own plugins, consider the following precautions:\n\n\nInvoking ``super``\n------------------\n\nInstead of invoking ``super(MyPlugin, self).some_method()`` use\n``self.super(MyPlugin, self).some_method()``. This is required because **djangocms-cascade**\ncreates a list of \"shadow\" plugins, which do not inherit from ``CMSPluginBase``.\n\n\nTemplatetag ``render_plugin``\n-----------------------------\n\nDjango-CMS provides a templatetag ``render_plugin``. Don't use it in templates provided by\n**djangocms-cascade** plugins. Instead use the templatetag named ``render_plugin`` from\nCascade. Example:\n\n.. code-block:: Django\n\n\t{% load cascade_tags %}\n\t<div class=\"some-css-class\">\n\t{% for plugin in instance.child_plugin_instances %}\n\t    {% render_plugin plugin %}\n\t{% endfor %}\n\t<div>\n\n\nCaching\n=======\n\nEven though rendering using this templatetag is slightly faster than the classic ``placeholder``\ntag provided by the CMS (because we don't hit the database for each plugin instance), combining\neach plugin template with its context also takes its time. Therefore plugins rendered by\n``render_cascade``, by default are cached as well, just as their CMS counterparts.\n\nThis caching is disabled for plugins containing the attribute ``cache = False``. It can be turned\noff globally using the directive ``CMSPLUGIN_CASCADE['cache_strides'] = True`` in the project's\n``settings.py``.\n"
  },
  {
    "path": "examples/bs4demo/.coveragerc",
    "content": "[run]\nbranch = True\nsource =\n    cmsplugin_cascade\n[report]\nprecision = 2\nomit =\n    ../*migrations*\n    gs960   \n"
  },
  {
    "path": "examples/bs4demo/bs4demo/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/cms_plugins.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.forms import widgets\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.fields import GlossaryField\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\nclass Badge(CascadePluginBase):\n    \"\"\"\n    This is a simple example of a plugin suitable for the djangocms-cascade system.\n    It contains one single field: `content` rendered via the template `bs4demo/badge.html`.\n    \"\"\"\n    name = _(\"Badge\")\n    require_parent = False\n    allow_children = False\n    render_template = 'bs4demo/badge.html'\n\n    content = GlossaryField(\n        widgets.TextInput(),\n        label=_(\"Content\"),\n    )\n\nplugin_pool.register_plugin(Badge)\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/context_processors.py",
    "content": "# -*- coding: utf-8 -*-\nfrom django.conf import settings\n\n\ndef cascade(request):\n    \"\"\"\n    Adds additional context variables to the default context.\n    \"\"\"\n    context = {\n        'DJANGO_CLIENT_FRAMEWORK': settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'),\n    }\n    return context\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/models.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/settings.py",
    "content": "# Django settings for unit test project.\nfrom __future__ import unicode_literals\n\nimport os\nimport sys\n\nfrom django.urls import reverse_lazy\n\nfrom cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig\nfrom django.utils.text import format_lazy\n\nDEBUG = True\n\nBASE_DIR = os.path.dirname(__file__)\n\n# Root directory for this Django project\nPROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.path.pardir))\n\n# Directory where working files, such as media and databases are kept\nWORK_DIR = os.path.join(PROJECT_ROOT, 'workdir')\nif not os.path.isdir(WORK_DIR):\n    os.makedirs(WORK_DIR)\n\nSITE_ID = 1\n\nROOT_URLCONF = 'bs4demo.urls'\n\nSECRET_KEY = 'secret'\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': os.path.join(WORK_DIR, 'db.sqlite3'),\n    },\n}\n\nINSTALLED_APPS = [\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.sites',\n    'django.contrib.messages',\n    'django.contrib.admin',\n    'django.contrib.staticfiles',\n    'django.contrib.sitemaps',\n    #'reversion',\n    'djangocms_text_ckeditor',\n    'django_select2',\n    'cmsplugin_cascade',\n    'cmsplugin_cascade.clipboard',\n    'cmsplugin_cascade.extra_fields',\n    'cmsplugin_cascade.icon',\n    'cmsplugin_cascade.sharable',\n    'cmsplugin_cascade.segmentation',\n    'cms',\n    'cms_bootstrap',\n    'adminsortable2',\n    'menus',\n    'treebeard',\n    'filer',\n    'easy_thumbnails',\n    'sass_processor',\n    'sekizai',\n    'bs4demo',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n    'django.middleware.locale.LocaleMiddleware',\n    'django.middleware.gzip.GZipMiddleware',\n    'cms.middleware.page.CurrentPageMiddleware',\n    'cms.middleware.user.CurrentUserMiddleware',\n    'cms.middleware.toolbar.ToolbarMiddleware',\n    'cms.middleware.language.LanguageCookieMiddleware',\n]\n\n# silence false-positive warning 1_6.W001\n# https://docs.djangoproject.com/en/1.8/ref/checks/#backwards-compatibility\n#TEST_RUNNER = 'django.test.runner.DiscoverRunner'\n\n# Absolute path to the directory that holds media.\n# Example: \"/home/media/media.lawrence.com/\"\nMEDIA_ROOT = os.path.join(WORK_DIR, 'media')\n\n# URL that handles the media served from MEDIA_ROOT. Make sure to use a\n# trailing slash.\n# Examples: \"http://media.lawrence.com/media/\", \"http://example.com/media/\"\nMEDIA_URL = '/media/'\n\n# Absolute path to the directory that holds static files.\n# Example: \"/home/media/media.lawrence.com/static/\"\nSTATIC_ROOT = os.path.join(WORK_DIR, 'static')\n\n# URL that handles the static files served from STATIC_ROOT.\n# Example: \"http://media.lawrence.com/static/\"\nSTATIC_URL = '/static/'\n\nSTATICFILES_FINDERS = [\n    'django.contrib.staticfiles.finders.FileSystemFinder',\n    'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n    'sass_processor.finders.CssFinder',\n]\n\nSTATICFILES_DIRS = [\n    ('node_modules', os.path.join(PROJECT_ROOT, 'node_modules')),\n]\n\nTEMPLATES = [{\n    'BACKEND': 'django.template.backends.django.DjangoTemplates',\n    'APP_DIRS': True,\n    'OPTIONS': {\n        'context_processors': (\n            'django.contrib.auth.context_processors.auth',\n            'django.template.context_processors.debug',\n            'django.template.context_processors.i18n',\n            'django.template.context_processors.media',\n            'django.template.context_processors.static',\n            'django.template.context_processors.tz',\n            'django.template.context_processors.csrf',\n            'django.template.context_processors.request',\n            'django.contrib.messages.context_processors.messages',\n            'sekizai.context_processors.sekizai',\n            'cms.context_processors.cms_settings',\n            'bs4demo.context_processors.cascade',\n        ),\n    },\n}]\n\n# If you set this to False, Django will make some optimizations so as not\n# to load the internationalization machinery.\nUSE_I18N = True\n\n# If you set this to False, Django will not format dates, numbers and\n# calendars according to the current locale.\nUSE_L10N = True\n\n# If you set this to False, Django will not use timezone-aware datetimes.\nUSE_TZ = True\n\nLANGUAGE_CODE = 'en'\n\nLANGUAGES = (\n    ('en', 'English'),\n)\n\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': True,\n    'filters': {\n         'require_debug_false': {\n             '()': 'django.utils.log.RequireDebugFalse',\n         }\n    },\n    'formatters': {\n        'simple': {\n            'format': '[%(asctime)s %(module)s] %(levelname)s: %(message)s'\n        },\n    },\n    'handlers': {\n        'console': {\n            'level': 'INFO',\n            'class': 'logging.StreamHandler',\n            'formatter': 'simple',\n        },\n    },\n    'loggers': {\n        'django': {\n            'handlers': ['console'],\n            'level': 'INFO',\n            'propagate': True,\n        },\n    },\n}\n\nX_FRAME_OPTIONS = 'SAMEORIGIN'\n\nXS_SHARING_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']\n\n#############################################################\n# Application specific settings\n\nif sys.argv[1] == 'test':\n    CMS_TEMPLATES = (\n         ('testing.html', \"Default Page\"),\n    )\nelse:\n    CMS_TEMPLATES = (\n         ('bs4demo/main.html', \"Main Content\"),\n         ('bs4demo/wrapped.html', \"Wrapped Bootstrap Column\"),\n    )\n\nCMS_SEO_FIELDS = True\n\nCMS_CACHE_DURATIONS = {\n    'content': 3600,\n    'menus': 3600,\n    'permissions': 86400,\n}\n\nCMSPLUGIN_CASCADE_PLUGINS = (\n    'cmsplugin_cascade.segmentation',\n    'cmsplugin_cascade.generic',\n    'cmsplugin_cascade.leaflet',\n    'cmsplugin_cascade.link',\n    'cmsplugin_cascade.bootstrap4',\n    'bs4demo',\n)\n\nCMSPLUGIN_CASCADE = {\n    'alien_plugins': ('TextPlugin', 'TextLinkPlugin',),\n    'plugins_with_sharables': {\n        'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed',\n                                 'image_height', 'resize_options',),\n        'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',),\n    },\n    'exclude_hiding_plugin': ('SegmentPlugin', 'Badge'),\n    'allow_plugin_hiding': True,\n    'leaflet': {'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6}},\n    'cache_strides': True,\n}\n\nCMS_PLACEHOLDER_CONF = {\n    # this placeholder is used in templates/main.html, it shows how to\n    # scaffold a djangoCMS page starting with an empty placeholder\n    'Main Content': {\n        'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],\n        'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None},\n    },\n    # this placeholder is used in templates/wrapped.html, it shows how to\n    # add content to an existing Bootstrap column\n    'Bootstrap Column': {\n        'plugins': ['BootstrapRowPlugin', 'TextPlugin', ],\n        'parent_classes': {'BootstrapRowPlugin': None},\n        'require_parent': False,\n    },\n}\n\nCKEDITOR_SETTINGS = {\n    'language': '{{ language }}',\n    'skin': 'moono-lisa',\n    'toolbar': 'CMS',\n    'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),\n}\n\nSELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'\nSELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'\n\nFILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS = True\n\nFILER_DUMP_PAYLOAD = True\n\nTHUMBNAIL_PROCESSORS = (\n    'easy_thumbnails.processors.colorspace',\n    'easy_thumbnails.processors.autocrop',\n    'filer.thumbnail_processors.scale_and_crop_with_subject_location',\n    'easy_thumbnails.processors.filters',\n)\n\nTHUMBNAIL_HIGH_RESOLUTION = False\n\nTHUMBNAIL_PRESERVE_EXTENSIONS = True\n\nTHUMBNAIL_OPTIMIZE_COMMAND = {\n    'png': '/opt/local/bin/optipng {filename}',\n    'gif': '/opt/local/bin/optipng {filename}',\n    'jpeg': '/opt/local/bin/jpegoptim {filename}',\n}\n\nSASS_PROCESSOR_INCLUDE_DIRS = [\n    os.path.join(PROJECT_ROOT, 'node_modules'),\n]\n\nSASS_PROCESSOR_ROOT = STATIC_ROOT\n\n# to access files such as fonts via staticfiles finders\nNODE_MODULES_URL = STATIC_URL + 'node_modules/'\n\ntry:\n    from .private_settings import *\nexcept ImportError:\n    pass\n\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/static/bs4demo/cascades/strides.json",
    "content": "{\n  \"plugins\":[\n    [\n      \"BootstrapJumbotronPlugin\",\n      {\n        \"glossary\":{\n          \"background_width_height\":{\n            \"width\":\"\",\n            \"height\":\"\"\n          },\n          \"background_vertical_position\":\"center\",\n          \"media_queries\":{\n            \"xs\":[\n              \"(max-width: 768px)\"\n            ],\n            \"lg\":[\n              \"(min-width: 1200px)\"\n            ],\n            \"sm\":[\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"md\":[\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ]\n          },\n          \"background_attachment\":\"scroll\",\n          \"image\":{\n            \"pk\":5,\n            \"model\":\"filer.Image\"\n          },\n          \"background_repeat\":\"no-repeat\",\n          \"hide_plugin\":\"\",\n          \"fluid\":true,\n          \"container_max_heights\":{\n            \"xs\":\"100%\",\n            \"md\":\"100%\",\n            \"sm\":\"100%\",\n            \"lg\":\"100%\"\n          },\n          \"extra_inline_styles:Paddings\":{\n            \"padding-top\":\"500px\",\n            \"padding-bottom\":\"\"\n          },\n          \"background_size\":\"cover\",\n          \"resize_options\":[\n            \"crop\",\n            \"subject_location\",\n            \"high_resolution\"\n          ],\n          \"container_max_widths\":{\n            \"xs\":768,\n            \"lg\":1980,\n            \"sm\":992,\n            \"md\":1200\n          },\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"background_color\":[\n            \"\",\n            \"#12308b\"\n          ],\n          \"background_horizontal_position\":\"center\"\n        },\n        \"pk\":900\n      },\n      [\n        [\n          \"TextPlugin\",\n          {\n            \"body\":\"<h1 style=\\\"text-align: center;\\\"><span style=\\\"color: #800080;\\\">Django-CMS Cascade</span></h1>\",\n            \"pk\":901\n          },\n          []\n        ]\n      ]\n    ],\n    [\n      \"BootstrapContainerPlugin\",\n      {\n        \"glossary\":{\n          \"media_queries\":{\n            \"xs\":[\n              \"(max-width: 768px)\"\n            ],\n            \"lg\":[\n              \"(min-width: 1200px)\"\n            ],\n            \"sm\":[\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"md\":[\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ]\n          },\n          \"container_max_widths\":{\n            \"xs\":750,\n            \"lg\":1170,\n            \"sm\":750,\n            \"md\":970\n          },\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"hide_plugin\":\"\",\n          \"fluid\":\"\"\n        },\n        \"pk\":902\n      },\n      [\n        [\n          \"HeadingPlugin\",\n          {\n            \"glossary\":{\n              \"content\":\"Cascade Demo Page\",\n              \"element_id\":\"heading-1\",\n              \"tag_type\":\"h1\",\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-right\":\"\",\n                \"margin-top\":\"\",\n                \"margin-left\":\"\",\n                \"margin-bottom\":\"\"\n              }\n            },\n            \"pk\":936\n          },\n          []\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"\"\n              }\n            },\n            \"pk\":903\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-width\":\"col-md-6\",\n                  \"hide_plugin\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"lg-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":555.0,\n                    \"sm\":720.0,\n                    \"md\":455.0\n                  },\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\"\n                },\n                \"pk\":904\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"glossary\":{\n                      \"extra_inline_styles:line-height\":\"2\",\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"min-height\":\"\",\n                        \"height\":\"367px\"\n                      },\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"15px\"\n                      },\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#474747\"\n                      ],\n                      \"extra_css_classes\":[],\n                      \"tag_type\":\"div\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"20px\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"20px\",\n                        \"padding-left\":\"50px\"\n                      }\n                    },\n                    \"pk\":905\n                  },\n                  [\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h1 style=\\\"text-align: center;\\\">What we do?</h1>\\n\\n<p style=\\\"text-align: center;\\\">Lorem 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.</p>\",\n                        \"pk\":906\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-width\":\"col-md-6\",\n                  \"hide_plugin\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"lg-responsive-utils\":\"\",\n                  \"extra_css_classes\":[],\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":555.0,\n                    \"sm\":720.0,\n                    \"md\":455.0\n                  },\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\"\n                },\n                \"pk\":907\n              },\n              [\n                [\n                  \"BootstrapImagePlugin\",\n                  {\n                    \"glossary\":{\n                      \"image_width_responsive\":\"100%\",\n                      \"target\":\"\",\n                      \"title\":\"\",\n                      \"image\":{\n                        \"pk\":11,\n                        \"model\":\"filer.Image\"\n                      },\n                      \"alt_tag\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"image_width_fixed\":\"\",\n                      \"image_height\":\"\",\n                      \"link\":{\n                        \"type\":\"none\"\n                      },\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"image_title\":\"\",\n                      \"image_shapes\":[\n                        \"img-responsive\"\n                      ]\n                    },\n                    \"pk\":908\n                  },\n                  []\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"\"\n              }\n            },\n            \"pk\":909\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"md-column-offset\":\"\",\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#42c8c6\"\n                  ],\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"container_max_widths\":{\n                    \"xs\":220.0,\n                    \"lg\":360.0,\n                    \"sm\":220.0,\n                    \"md\":293.33\n                  },\n                  \"md-column-width\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"min-height\":\"\",\n                    \"height\":\"360px\"\n                  },\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-width\":\"\"\n                },\n                \"pk\":910\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"glossary\":{\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"min-height\":\"\",\n                        \"height\":\"360px\"\n                      },\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_css_classes\":[],\n                      \"tag_type\":\"div\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#42c8c6\"\n                      ],\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      }\n                    },\n                    \"pk\":911\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"glossary\":{\n                          \"font_size\":\"10em\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"symbol\":\"wrench\",\n                          \"hide_plugin\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"3\",\n                          \"border_radius\":\"\",\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ]\n                        },\n                        \"pk\":912\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Quick Installs</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":913\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"md-column-offset\":\"\",\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#f5b10e\"\n                  ],\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"container_max_widths\":{\n                    \"xs\":220.0,\n                    \"lg\":360.0,\n                    \"sm\":220.0,\n                    \"md\":293.33\n                  },\n                  \"md-column-width\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"min-height\":\"\",\n                    \"height\":\"360px\"\n                  },\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-width\":\"\"\n                },\n                \"pk\":918\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"glossary\":{\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"min-height\":\"\",\n                        \"height\":\"360px\"\n                      },\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_css_classes\":[],\n                      \"tag_type\":\"div\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#f5b10e\"\n                      ],\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      }\n                    },\n                    \"pk\":919\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"glossary\":{\n                          \"font_size\":\"10em\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"symbol\":\"cog-alt\",\n                          \"hide_plugin\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"3\",\n                          \"border_radius\":\"\",\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ]\n                        },\n                        \"pk\":920\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Customizable</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":921\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"md-column-offset\":\"\",\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#56ba41\"\n                  ],\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"container_max_widths\":{\n                    \"xs\":220.0,\n                    \"lg\":360.0,\n                    \"sm\":220.0,\n                    \"md\":293.33\n                  },\n                  \"md-column-width\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"min-height\":\"\",\n                    \"height\":\"360px\"\n                  },\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-width\":\"\"\n                },\n                \"pk\":914\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"glossary\":{\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"min-height\":\"\",\n                        \"height\":\"360px\"\n                      },\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_css_classes\":[],\n                      \"tag_type\":\"div\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#56ba41\"\n                      ],\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      }\n                    },\n                    \"pk\":915\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"glossary\":{\n                          \"font_size\":\"10em\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"symbol\":\"bell-alt\",\n                          \"hide_plugin\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"3\",\n                          \"border_radius\":\"\",\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ]\n                        },\n                        \"pk\":916\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Support</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":917\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"\"\n              }\n            },\n            \"pk\":929\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":1140.0,\n                    \"sm\":720.0,\n                    \"md\":940.0\n                  },\n                  \"xs-column-width\":\"col-xs-12\"\n                },\n                \"pk\":930\n              },\n              [\n                [\n                  \"CarouselPlugin\",\n                  {\n                    \"glossary\":{\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"container_max_heights\":{\n                        \"xs\":\"200px\",\n                        \"md\":\"300px\",\n                        \"sm\":\"250px\",\n                        \"lg\":\"350px\"\n                      },\n                      \"interval\":\"5\",\n                      \"hide_plugin\":\"\",\n                      \"options\":[\n                        \"slide\",\n                        \"pause\",\n                        \"wrap\"\n                      ]\n                    },\n                    \"pk\":931\n                  },\n                  [\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image\":{\n                            \"pk\":4,\n                            \"model\":\"filer.Image\"\n                          },\n                          \"image_title\":\"\",\n                          \"hide_plugin\":\"\",\n                          \"alt_tag\":\"\"\n                        },\n                        \"pk\":932\n                      },\n                      [\n                        [\n                          \"TextPlugin\",\n                          {\n                            \"body\":\"<h1>Hallo Welt!</h1>\",\n                            \"pk\":933\n                          },\n                          []\n                        ]\n                      ]\n                    ],\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image\":{\n                            \"pk\":7,\n                            \"model\":\"filer.Image\"\n                          },\n                          \"image_title\":\"\",\n                          \"hide_plugin\":\"\",\n                          \"alt_tag\":\"\"\n                        },\n                        \"pk\":934\n                      },\n                      []\n                    ],\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image\":{\n                            \"pk\":6,\n                            \"model\":\"filer.Image\"\n                          },\n                          \"image_title\":\"\",\n                          \"hide_plugin\":\"\",\n                          \"alt_tag\":\"\"\n                        },\n                        \"pk\":935\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"20px\"\n              }\n            },\n            \"pk\":922\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"sm-column-width\":\"col-sm-6\",\n                  \"md-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-width\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"lg-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":555.0,\n                    \"sm\":345.0,\n                    \"md\":455.0\n                  },\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\"\n                },\n                \"pk\":923\n              },\n              [\n                [\n                  \"BootstrapImagePlugin\",\n                  {\n                    \"glossary\":{\n                      \"image_width_responsive\":\"100%\",\n                      \"target\":\"\",\n                      \"title\":\"\",\n                      \"image\":{\n                        \"pk\":12,\n                        \"model\":\"filer.Image\"\n                      },\n                      \"alt_tag\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"image_width_fixed\":\"\",\n                      \"image_height\":\"\",\n                      \"link\":{\n                        \"type\":\"none\"\n                      },\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"image_title\":\"\",\n                      \"image_shapes\":[\n                        \"img-responsive\"\n                      ]\n                    },\n                    \"pk\":924\n                  },\n                  []\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"sm-responsive-utils\":\"\",\n                  \"xs-responsive-utils\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"sm-column-width\":\"col-sm-6\",\n                  \"md-responsive-utils\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"md-column-width\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"lg-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":555.0,\n                    \"sm\":345.0,\n                    \"md\":455.0\n                  },\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\"\n                },\n                \"pk\":925\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"glossary\":{\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"min-height\":\"\",\n                        \"height\":\"370px\"\n                      },\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"130%\"\n                      },\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_css_classes\":[],\n                      \"tag_type\":\"div\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#ef3e42\"\n                      ],\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"50px\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      }\n                    },\n                    \"pk\":926\n                  },\n                  [\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Let us make<br>\\na difference in your<br>\\nweb design</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</p>\",\n                        \"pk\":927\n                      },\n                      []\n                    ],\n                    [\n                      \"BootstrapButtonPlugin\",\n                      {\n                        \"glossary\":{\n                          \"icon_align\":\"icon-right\",\n                          \"extra_inline_styles:Margins\":{\n                            \"margin-top\":\"20px\",\n                            \"margin-bottom\":\"\"\n                          },\n                          \"button_options\":[],\n                          \"button_size\":\"btn-lg\",\n                          \"quick_float\":\"pull-right\",\n                          \"hide_plugin\":\"\",\n                          \"icon_font\":\"3\",\n                          \"link_content\":\"Continue\",\n                          \"link\":{\n                            \"pk\":5,\n                            \"model\":\"cms.Page\",\n                            \"type\":\"cmspage\",\n                            \"section\":\"\"\n                          },\n                          \"button_type\":\"btn-default\",\n                          \"symbol\":\"right-open\"\n                        },\n                        \"pk\":928\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"20px\"\n              }\n            },\n            \"pk\":937\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"container_max_widths\":{\n                    \"xs\":720.0,\n                    \"lg\":1140.0,\n                    \"sm\":720.0,\n                    \"md\":940.0\n                  },\n                  \"xs-column-width\":\"col-xs-12\"\n                },\n                \"pk\":938\n              },\n              [\n                [\n                  \"LeafletPlugin\",\n                  {\n                    \"glossary\":{\n                      \"render_template\":\"cascade/plugins/leaflet.html\",\n                      \"map_position\":{\n                        \"lat\":47.27337658656428,\n                        \"lng\":11.399195194244387,\n                        \"zoom\":15\n                      },\n                      \"hide_plugin\":\"\",\n                      \"map_height\":\"400px\",\n                      \"map_width\":\"100%\"\n                    },\n                    \"pk\":939,\n                    \"inlines\":[\n                      {\n                        \"title\":\"Kirche St. Nikolaus\",\n                        \"image\":{\n                          \"pk\":15,\n                          \"model\":\"filer.Image\"\n                        },\n                        \"marker_width\":\"25px\",\n                        \"position\":{\n                          \"lat\":47.274337475394645,\n                          \"lng\":11.393036842346191\n                        },\n                        \"popup_text\":null,\n                        \"marker_anchor\":{\n                          \"top\":\"50%\",\n                          \"left\":\"50%\"\n                        }\n                      }\n                    ]\n                  },\n                  []\n                ]\n              ]\n            ]\n          ]\n        ]\n      ]\n    ]\n  ]\n}"
  },
  {
    "path": "examples/bs4demo/bs4demo/static/bs4demo/css/_footer.scss",
    "content": "// include this file when using a static footer\n@import \"variables\";\n\nhtml {\n\tposition: relative;\n\tmin-height: 100%;\n}\n\nbody {\n\tmargin-bottom: $body-footer-height;\n}\n\n#footer {\n\tposition: absolute;\n\tbottom: 0;\n\twidth: 100%;\n\theight: $body-footer-height;\n\tcolor: $body-footer-color;\n\tbackground: $body-footer-bg;\n\tpadding-top: 20px;\n\tpadding-bottom: 20px;\n}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/static/bs4demo/css/_variables.scss",
    "content": "@import \"bootstrap/scss/_functions\";\n@import \"bootstrap/scss/_variables\";\n@import \"bootstrap/scss/mixins/_breakpoints.scss\";\n\n// footer\n$body-footer-height: 200px;\n$body-footer-color: $yiq-text-light;\n$body-footer-bg: $dark;\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/static/bs4demo/css/badge.scss",
    "content": ".badge-ribbon {\n position: relative;\n background: lightgrey;\n font-size: 50px;\n height: 100px;\n width: 100px;\n -moz-border-radius:    50px;\n -webkit-border-radius: 50px;\n border-radius:         50px;\n text-align: center;\n padding-top: 12px;\n margin-bottom: 50px;\n &:before, &:after {\n  content: '';\n  position: absolute;\n  border-bottom: 70px solid lightgrey;\n  border-left: 40px solid transparent;\n  border-right: 40px solid transparent;\n  top: 90px;\n  left: -10px;\n  -webkit-transform: rotate(-150deg);\n  -moz-transform:    rotate(-150deg);\n  -ms-transform:     rotate(-150deg);\n  -o-transform:      rotate(-150deg);\n }\n &:after {\n  left: auto;\n  right: -10px;\n  -webkit-transform: rotate(150deg);\n  -moz-transform:    rotate(150deg);\n  -ms-transform:     rotate(150deg);\n  -o-transform:      rotate(150deg);\n }\n}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/static/bs4demo/css/main.scss",
    "content": "@import \"variables\";\n@import \"bootstrap/scss/bootstrap\";\n@import \"footer\";\n\nbody {\n\tbackground-color: #eee8e8;\n}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/templates/bs4demo/badge.html",
    "content": "{% load sekizai_tags sass_tags %}\n{% addtoblock \"css\" %}<link rel=\"stylesheet\" href=\"{% sass_src 'bs4demo/css/badge.scss' %}\" type=\"text/css\" />{% endaddtoblock %}\n{% with inline_styles=instance.inline_styles %}\n<div class=\"badge-ribbon\"{% if inline_styles %} style=\"{{ inline_styles }}\"{% endif %}>{{ instance.glossary.content }}</div>\n{% endwith %}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/templates/bs4demo/base.html",
    "content": "{% load static cms_tags sekizai_tags %}\n<!doctype html>\n<html lang=\"de\">\n\n<head>\n\t<title>{% block title %}Page Title{% endblock %}</title>\n\t<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t<meta name=\"description\" content=\"{% block meta-description %}{% endblock %}\" />\n\t{% block head %}{% endblock %}\n\t{% render_block \"css\" %}\n</head>\n\n<body{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %} ng-app=\"cascadeDemo\"{% endif %}>\n\t{% cms_toolbar %}\n\t<header id=\"header\">\n\t\t{% block header %}{% endblock %}\n\t</header>\n\t<main>\n\t\t{% block main %}{% endblock %}\n\t</main>\n\t<footer id=\"footer\" class=\"border border-light\">\n\t\t<div class=\"container\">\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-6\">\n\t\t\t\t\t<h3>About</h3>\n\t\t\t\t\t<p>Lorem Ipsum</p>\n\t\t\t\t\t<p>Incididunt ut labore</p>\n\t\t\t\t\t<p>Quis nostrud exercitat</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-6\">\n\t\t\t\t\t<h3>Services</h3>\n\t\t\t\t\t<p>Conse ctetur adipisicing</p>\n\t\t\t\t\t<p>Elit sed do eiusmod tempor</p>\n\t\t\t\t\t<p>Lorem Ipsum</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</footer>\n\t{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}\n\t<script src=\"{% static 'node_modules/angular/angular.min.js' %}\" type=\"text/javascript\"></script>\n\t<script src=\"{% static 'node_modules/angular-animate/angular-animate.min.js' %}\" type=\"text/javascript\"></script>\n\t<script src=\"{% static 'node_modules/ui-bootstrap4/dist/ui-bootstrap-tpls.js' %}\" type=\"text/javascript\"></script>\n\t<script type=\"text/javascript\">\n\tangular.module('cascadeDemo', ['ngAnimate', 'ui.bootstrap'])\n\t</script>\n\t<style>\n\t.nav, .pagination, .carousel, .card-title a { cursor: pointer; }\n\t</style>\n\t{% else %}\n\t<script src=\"{% static 'node_modules/jquery/dist/jquery.min.js' %}\" type=\"text/javascript\"></script>\n\t<script src=\"{% static 'node_modules/popper.js/dist/umd/popper.js' %}\" type=\"text/javascript\"></script>\n\t<script src=\"{% static 'node_modules/bootstrap/dist/js/bootstrap.min.js' %}\" type=\"text/javascript\"></script>\n\t{% endif %}\n\t{% render_block \"js\" %}\n</body>\n\n</html>\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/templates/bs4demo/main.html",
    "content": "{% extends \"bs4demo/base.html\" %}\n{% load static cms_tags bootstrap_tags sekizai_tags sass_tags %}\n\n{% block head %}\n{% addtoblock \"css\" %}<link rel=\"stylesheet\" href=\"{% sass_src 'bs4demo/css/main.scss' %}\" type=\"text/css\" />{% endaddtoblock %}\n{% endblock head %}\n\n{% block header %}\n\t{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %}\n\t\t{% include \"bootstrap4/includes/ng-nav-navbar.html\" with navbar_classes=\"navbar-expand-lg navbar-light bg-light fixed-top\" %}\n \t{% else %}\n\t\t{% include \"bootstrap4/includes/nav-navbar.html\" with navbar_classes=\"navbar-expand-lg navbar-light bg-light fixed-top\" role=\"navigation\" %}\n{% endif %}\n\n{% if cms_version >= \"3.5.0\" and request.toolbar %}\n\t<style>\n\tbody { padding-top:46px; }\n\t.fixed-top {  top: 46px !important;}\n\t</style>\n{% endif %}\n\n{% endblock header %}\n\n{% block main %}{% placeholder \"Main Content\" or %}\n<div class=\"container\">\n\t<div class=\"row starter-template\">\n\t\t<div class=\"col-12\">\n\t\t\t<h1>Add Bootstrap container here</h1>\n\t\t\t<p class=\"lead\">Use this placeholder as a quick way to start editing a new CMS page.</p>\n\t\t\t<p>All you have to do is to append <code>?edit</code> to the URL, switch to “Structure” mode\n\t\t\tand add a <strong>Bootstrap Container Plugin</strong> or <strong>Jumbotron Plugin</strong>.</p>\n\t\t</div>\n\t</div>\n</div>\n{% endplaceholder %}{% endblock main %}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/templates/bs4demo/strides.html",
    "content": "{% extends \"bs4demo/main.html\" %}\n{% load cascade_tags %}\n\n{% block main %}\n<div class=\"container\">\n\t<div class=\"row starter-template\">\n\t\t<div class=\"col-12\">\n\t\t\t<p>Content is rendered using the templatetag {% verbatim %}<code>{% render_cascade \"bs4demo/cascades/strides.json\" %}</code>{% endverbatim %}</p>\n\t\t</div>\n\t</div>\n</div>\n\t{% render_cascade \"bs4demo/cascades/strides.json\" %}\n{% endblock main %}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/templates/bs4demo/wrapped.html",
    "content": "{% extends \"bs4demo/main.html\" %}\n{% load cms_tags %}\n\n{% block main %}\n<div class=\"container\">\n\t<div class=\"row starter-template\">\n\t\t<div class=\"col-12\">\n\t\t\t{% placeholder \"Bootstrap Column\" or %}\n\t\t\t<h1>Add some Bootstrap plugins here</h1>\n\t\t\t<p class=\"lead\">Use this placeholder as a quick way to start editing a new CMS page.</p>\n\t\t\t<p>All you have to do is to append <code>?edit</code> to the URL and switch to “Structure” mode.<br/>\n\t\t\tInto this hard coded Bootstrap Column, add a <strong>Text Plugin</strong>.\n\t\t\tIf you want to further subdivide this column, add a <strong>Row Plugin</strong> with their own columns.\n\t\t\t</p>\n\t\t\t{% endplaceholder %}\n\t\t</div>\n\t</div>\n</div>\n{% endblock main %}\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/urls.py",
    "content": "# -*- coding: utf-8 -*-\nfrom django.conf import settings\nfrom django.urls import include, path, re_path\nfrom django.conf.urls.static import static\nfrom django.contrib import admin\nfrom django.views.generic import TemplateView\n\nclass CascadeDemoView(TemplateView):\n    template_name = 'bs4demo/strides.html'\n\n\nadmin.autodiscover()\n\nurlpatterns = [\n    path('admin/select2/', include('django_select2.urls')),\n    path('admin/', admin.site.urls),\n    path('cascade/', CascadeDemoView.as_view()),\n    path('', include('cms.urls')),\n]\nurlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))\n"
  },
  {
    "path": "examples/bs4demo/bs4demo/utils.py",
    "content": "\ndef find_django_migrations_module(module_name):\n    \"\"\" Tries to locate <module_name>.migrations_django (without actually importing it).\n    Appends either \".migrations_django\" or \".migrations\" to module_name.\n    For details why:\n      https://docs.djangoproject.com/en/1.7/topics/migrations/#libraries-third-party-apps\n    \"\"\"\n    import imp\n    try:\n        module_info = imp.find_module(module_name)\n        module = imp.load_module(module_name, *module_info)\n        imp.find_module('migrations_django', module.__path__)\n        return module_name + '.migrations_django'\n    except ImportError:\n        return module_name + '.migrations'  # conforms to Django 1.7 defaults\n"
  },
  {
    "path": "examples/bs4demo/manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath(os.path.pardir))\n\nif __name__ == \"__main__\":\n    from django.core.management import execute_from_command_line\n\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bs4demo.settings')\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "examples/bs4demo/package.json",
    "content": "{\n  \"name\": \"djangocms-cascade\",\n  \"version\": \"0.10.0\",\n  \"description\": \"DjangoCMS-Cascade is the Swiss army knife for working with Django CMS plugins\",\n  \"directories\": {\n    \"doc\": \"docs\",\n    \"example\": \"examples\",\n    \"test\": \"tests\"\n  },\n  \"dependencies\": {\n    \"angular\": \"^1.5.11\",\n    \"angular-animate\": \"^1.5.11\",\n    \"angular-sanitize\": \"^1.5.11\",\n    \"ui-bootstrap4\": \"^3.0.5\",\n    \"bootstrap\": \"^4.1.3\",\n    \"jquery\": \"^3.2.1\",\n    \"leaflet\": \"^1.2.0\",\n    \"leaflet-easybutton\": \"^2.2.0\",\n    \"picturefill\": \"^3.0.2\",\n    \"popper.js\": \"^1.12.9\",\n    \"select2\": \"^4.0.3\"\n  },\n  \"devDependencies\": {},\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/jrief/djangocms-cascade.git\"\n  },\n  \"author\": \"Jacob Rief\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/jrief/djangocms-cascade/issues\"\n  },\n  \"homepage\": \"https://github.com/jrief/djangocms-cascade#readme\"\n}\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\nDJANGO_SETTINGS_MODULE = tests.settings\naddopts = --tb native\n"
  },
  {
    "path": "requirements/base.txt",
    "content": "Pillow\nargparse\nrequests\ndjango-admin-sortable2<2\ndjango-classy-tags\ndjango-sekizai\ndjango-entangled\ndjango-filer\ndjango-select2\ndjangocms-text-ckeditor\neasy-thumbnails[svg]\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\nfrom setuptools import setup, find_packages\nfrom cmsplugin_cascade import __version__\n\nwith open('README.md', 'r') as fh:\n    long_description = fh.read()\n\nCLASSIFIERS = [\n    'Development Status :: 5 - Production/Stable',\n    'Environment :: Web Environment',\n    'Intended Audience :: Developers',\n    'License :: OSI Approved :: MIT License',\n    'Operating System :: OS Independent',\n    'Programming Language :: Python',\n    'Topic :: Internet :: WWW/HTTP :: Dynamic Content',\n    'Programming Language :: Python :: 3.8',\n    'Programming Language :: Python :: 3.9',\n    'Programming Language :: Python :: 3.10',\n    'Framework :: Django :: 3.2',\n    'Framework :: Django :: 4.0',\n]\n\nsetup(\n    name='djangocms-cascade',\n    version=__version__,\n    description='Build Single Page Applications using the Django-CMS plugin system',\n    author='Jacob Rief',\n    author_email='jacob.rief@gmail.com',\n    url='https://github.com/jrief/djangocms-cascade',\n    packages=find_packages(exclude=['examples', 'docs', 'tests']),\n    install_requires=[\n        'django>=3.2,<5',\n        'django-classy-tags>=1.0',\n        'django-cms>=3.10,<4',\n        'django-entangled>=0.5.3',\n        'djangocms-text-ckeditor>=4.0',\n        'django-select2>=7.7',\n        'requests',\n    ],\n    license='MIT',\n    platforms=['OS Independent'],\n    classifiers=CLASSIFIERS,\n    long_description=long_description,\n    long_description_content_type='text/markdown',\n    include_package_data=True,\n    zip_safe=False,\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "tests/bootstrap4/__init__.py",
    "content": ""
  },
  {
    "path": "tests/bootstrap4/conftest.py",
    "content": "import pytest\nfrom cms.api import add_plugin\nfrom cmsplugin_cascade.models import CascadeElement\nfrom cmsplugin_cascade.bootstrap4.container import BootstrapContainerPlugin, BootstrapRowPlugin, BootstrapColumnPlugin\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef bootstrap_container(admin_site, cms_placeholder):\n    # add a Bootstrap Container Plugin\n    glossary = {'breakpoints': ['xs', 'sm', 'md', 'lg', 'xl'], 'fluid': ''}\n    container_model = add_plugin(cms_placeholder, BootstrapContainerPlugin, 'en', glossary=glossary)\n    assert isinstance(container_model, CascadeElement)\n    container_plugin = container_model.get_plugin_class_instance(admin_site)\n    assert isinstance(container_plugin, BootstrapContainerPlugin)\n    return container_plugin, container_model\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef bootstrap_row(admin_site, bootstrap_container):\n    # add a Bootstrap Row Plugin to the given container\n    container_plugin, container_model = bootstrap_container\n    row_model = add_plugin(container_model.placeholder, BootstrapRowPlugin, 'en', target=container_model)\n    assert isinstance(row_model, CascadeElement)\n    row_plugin = row_model.get_plugin_class_instance()\n    assert isinstance(row_plugin, BootstrapRowPlugin)\n    return row_plugin, row_model\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef bootstrap_column(admin_site, bootstrap_row):\n    # add a Bootstrap Column Plugin to the given row\n    row_plugin, row_model = bootstrap_row\n    glossary = {'xs-column-width': 'col'}\n    column_model = add_plugin(row_model.placeholder, BootstrapColumnPlugin, 'en', target=row_model, glossary=glossary)\n    assert isinstance(column_model, CascadeElement)\n    column_plugin = column_model.get_plugin_class_instance()\n    assert isinstance(column_plugin, BootstrapColumnPlugin)\n    return column_plugin, column_model\n"
  },
  {
    "path": "tests/bootstrap4/test_accordion.py",
    "content": "import pytest\nfrom django.template.context import RequestContext\nfrom cms.api import add_plugin\nfrom cms.plugin_rendering import ContentRenderer\nfrom cms.utils.plugins import build_plugin_tree\nfrom cmsplugin_cascade.models import CascadeElement\nfrom cmsplugin_cascade.bootstrap4.accordion import BootstrapAccordionGroupPlugin, BootstrapAccordionPlugin\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef bootstrap_accordion(rf, admin_site, bootstrap_column):\n    request = rf.get('/')\n    column_plugin, column_model = bootstrap_column\n\n    # add accordion plugin\n    accordion_model = add_plugin(column_model.placeholder, BootstrapAccordionPlugin, 'en', target=column_model)\n    assert isinstance(accordion_model, CascadeElement)\n    accordion_plugin = accordion_model.get_plugin_class_instance(admin_site)\n    assert isinstance(accordion_plugin, BootstrapAccordionPlugin)\n    data = {'num_children': 2, 'close_others': 'on', 'first_is_open': 'on'}\n    ModelForm = accordion_plugin.get_form(request, accordion_model)\n    form = ModelForm(data, None, instance=accordion_model)\n    assert form.is_valid()\n    accordion_plugin.save_model(request, accordion_model, form, False)\n    assert accordion_model.glossary['close_others'] is True\n    assert accordion_model.glossary['first_is_open'] is True\n    for child in accordion_model.get_children():\n        assert isinstance(child.get_plugin_class_instance(admin_site), BootstrapAccordionGroupPlugin)\n    return accordion_plugin, accordion_model\n\n\n@pytest.mark.django_db\ndef test_edit_accordion_group(rf, admin_site, bootstrap_accordion):\n    request = rf.get('/')\n    accordion_plugin, accordion_model = bootstrap_accordion\n    first_group = accordion_model.get_first_child()\n    group_model, group_plugin = first_group.get_plugin_instance(admin_site)\n    data = {'heading': \"Hello\", 'body_padding': 'on'}\n    ModelForm = group_plugin.get_form(request, group_model)\n    form = ModelForm(data, None, instance=group_model)\n    assert form.is_valid()\n    group_plugin.save_model(request, group_model, form, False)\n    assert group_model.glossary['heading'] == \"Hello\"\n    assert group_model.glossary['body_padding'] is True\n\n    # render the plugin\n    build_plugin_tree([accordion_model, group_model])\n    context = RequestContext(request)\n    content_renderer = ContentRenderer(request)\n    html = content_renderer.render_plugin(accordion_model, context).strip()\n    html = html.replace('\\n', '').replace('\\t', '')\n    expected = \"\"\"<div id=\"cmsplugin_{accordion_id}\" class=\"accordion\"><div class=\"card\">\n<div class=\"card-header\" id=\"heading_{group_id}\"><h5 class=\"mb-0\">\n<button class=\"btn btn-link\" type=\"button\" data-toggle=\"collapse\" data-target=\"#collapse_{group_id}\" aria-expanded=\"true\" aria-controls=\"collapse_{group_id}\">\nHello</button></h5></div>\n<div id=\"collapse_{group_id}\" class=\"collapse\" aria-labelledby=\"heading_{group_id}\" data-parent=\"#cmsplugin_{accordion_id}\">\n<div class=\"card-body\"></div></div></div></div>\"\"\".format(accordion_id=accordion_model.id, group_id=group_model.id)\n    expected = expected.replace('\\n', '').replace('\\t', '')\n    assert html == expected\n"
  },
  {
    "path": "tests/bootstrap4/test_container.py",
    "content": "import pytest\nfrom bs4 import BeautifulSoup\nfrom django.utils.html import strip_spaces_between_tags\nfrom cms.plugin_rendering import ContentRenderer\nfrom cms.utils.plugins import build_plugin_tree\nfrom cmsplugin_cascade.models import CascadeElement\nfrom cmsplugin_cascade.bootstrap4.container import BootstrapColumnPlugin\n\n\n@pytest.mark.django_db\ndef test_edit_bootstrap_container(rf, bootstrap_container):\n    container_plugin, container_model = bootstrap_container\n    request = rf.get('/')\n    ModelForm = container_plugin.get_form(request, container_model)\n    data = {'breakpoints': ['sm', 'md']}\n    form = ModelForm(data, None, instance=container_model)\n    assert form.is_valid()\n    soup = BeautifulSoup(form.as_p(), features='lxml')\n    input_element = soup.find(id=\"id_breakpoints_0\")\n    assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'xs'}.items() <= input_element.attrs.items()\n    input_element = soup.find(id=\"id_breakpoints_2\")\n    assert {'type': 'checkbox', 'name': 'breakpoints', 'value': 'md', 'checked': ''}.items() <= input_element.attrs.items()\n    input_element = soup.find(id=\"id_fluid\")\n    assert {'type': 'checkbox', 'name': 'fluid'}.items() <= input_element.attrs.items()\n    container_plugin.save_model(request, container_model, form, False)\n    assert container_model.glossary['breakpoints'] == ['sm', 'md']\n    assert 'fluid' in container_model.glossary\n    assert str(container_model) == \"for Landscape Phones, Tablets\"\n\n\n@pytest.mark.django_db\ndef test_edit_bootstrap_row(rf, bootstrap_row):\n    row_plugin, row_model = bootstrap_row\n    request = rf.get('/')\n    ModelForm = row_plugin.get_form(request, row_model)\n    data = {'num_children': 3}\n    form = ModelForm(data, None, instance=row_model)\n    assert form.is_valid()\n    row_plugin.save_model(request, row_model, form, False)\n\n    container_model, container_plugin = row_model.parent.get_plugin_instance()\n    plugin_list = [container_model, row_model]\n\n    # we now should have three columns attached to the row\n    assert row_model.get_descendant_count() == 3\n    for cms_plugin in row_model.get_descendants():\n        column_model, column_plugin = cms_plugin.get_plugin_instance()\n        assert isinstance(column_model, CascadeElement)\n        assert isinstance(column_plugin, BootstrapColumnPlugin)\n        assert column_model.parent.id == row_model.id\n        plugin_list.append(column_model)\n\n    # change data inside the first column\n    cms_plugin = row_model.get_descendants().first()\n    column_model, column_plugin = cms_plugin.get_plugin_instance()\n    data = {'xs-column-width': 'col', 'sm-column-offset': 'offset-sm-1', 'sm-column-width': 'col-sm-3'}\n    ModelForm = column_plugin.get_form(request, column_model)\n    form = ModelForm(data, None, instance=column_model)\n    assert form.is_valid()\n    column_plugin.save_model(request, column_model, form, True)\n\n    # change data inside the last column\n    cms_plugin = row_model.get_descendants().last()\n    column_model, column_plugin = cms_plugin.get_plugin_instance()\n    data = {'xs-column-width': 'col', 'sm-responsive-utils': 'hidden-sm', 'sm-column-width': 'col-sm-4'}\n    ModelForm = column_plugin.get_form(request, column_model)\n    form = ModelForm(data, None, instance=column_model)\n    assert form.is_valid()\n    column_plugin.save_model(request, column_model, form, False)\n\n    # render the plugin and check the output\n    context = {\n        'request': request,\n    }\n    content_renderer = ContentRenderer(request)\n    row_model.parent.child_plugin_instances\n    for plugin in plugin_list:\n        plugin.refresh_from_db()\n    build_plugin_tree(plugin_list)\n    html = content_renderer.render_plugin(container_model, context)\n    html = strip_spaces_between_tags(html).strip()\n    assert html == '<div class=\"container\"><div class=\"row\"><div class=\"col col-sm-3 offset-sm-1\">' \\\n                   '</div><div class=\"col\"></div><div class=\"col col-sm-4 hidden-sm\"></div></div></div>'\n"
  },
  {
    "path": "tests/bootstrap4/test_grid.py",
    "content": "import pytest\nfrom cmsplugin_cascade.bootstrap4.grid import (Bootstrap4Container, Bootstrap4Row, Bootstrap4Column, BootstrapException,\n                                               Breakpoint, Bound, fluid_bounds)\n\ndef test_breakpoint_iter():\n    for k, bp in enumerate(Breakpoint):\n        if k == 0: assert bp == Breakpoint.xs\n        if k == 1: assert bp == Breakpoint.sm\n        if k == 2: assert bp == Breakpoint.md\n        if k == 3: assert bp == Breakpoint.lg\n        if k == 4: assert bp == Breakpoint.xl\n    assert k == 4\n\n\ndef test_breakpoint_range():\n    for k, bp in enumerate(Breakpoint.range(Breakpoint.xs, Breakpoint.xl)):\n        if k == 0: assert bp == Breakpoint.xs\n        if k == 1: assert bp == Breakpoint.sm\n        if k == 2: assert bp == Breakpoint.md\n        if k == 3: assert bp == Breakpoint.lg\n        if k == 4: assert bp == Breakpoint.xl\n    assert k == 4\n\n\ndef test_breakpoint_partial():\n    for k, bp in enumerate(Breakpoint.range(Breakpoint.sm, Breakpoint.lg)):\n        if k == 0: assert bp == Breakpoint.sm\n        if k == 1: assert bp == Breakpoint.md\n        if k == 2: assert bp == Breakpoint.lg\n    assert k == 2\n\n\ndef test_xs_cols():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col\"></div>\n            <div class=\"col\"></div>\n            <div class=\"col\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    for _ in range(3):\n        row.add_column(Bootstrap4Column('col'))\n    assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)\n    assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)\n    assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 190.7)\n    assert row[2].get_bound(Breakpoint.sm) == Bound(180.0, 180.0)\n    assert row[2].get_bound(Breakpoint.md) == Bound(240.0, 240.0)\n    assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)\n    assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)\n\n\ndef test_fluid_xs_cols():\n    \"\"\"\n    <div class=\"container-fluid\">\n        <div class=\"row\">\n            <div class=\"col\"></div>\n            <div class=\"col\"></div>\n            <div class=\"col\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container(bounds=fluid_bounds)\n    row = container.add_row(Bootstrap4Row())\n    for _ in range(3):\n        row.add_column(Bootstrap4Column('col'))\n    assert row[0].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)\n    assert row[1].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)\n    assert row[2].get_bound(Breakpoint.xs) == Bound(106.7, 192.0)\n    assert row[2].get_bound(Breakpoint.sm) == Bound(192.0, 256.0)\n    assert row[2].get_bound(Breakpoint.md) == Bound(256.0, 330.7)\n    assert row[2].get_bound(Breakpoint.lg) == Bound(330.7, 400.0)\n    assert row[2].get_bound(Breakpoint.xl) == Bound(400.0, 660.0)\n\n\ndef test_xs_cols_with_flex():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-3\"></div>\n            <div class=\"col\"></div>\n            <div class=\"col\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col-3'))\n    row.add_column(Bootstrap4Column('col'))\n    row.add_column(Bootstrap4Column('col'))\n    repr(row[0])\n    assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)\n    assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)\n    assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)\n    assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)\n    assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)\n    assert row[1].get_bound(Breakpoint.xs) == Bound(120.0, 214.5)\n    assert row[1].get_bound(Breakpoint.sm) == Bound(202.5, 202.5)\n    assert row[1].get_bound(Breakpoint.md) == Bound(270.0, 270.0)\n    assert row[1].get_bound(Breakpoint.lg) == Bound(360.0, 360.0)\n    assert row[1].get_bound(Breakpoint.xl) == Bound(427.5, 427.5)\n\n\ndef test_xs_cols_with_auto_and_flex():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-3\"></div>\n            <div class=\"col-auto\"></div>\n            <div class=\"col\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col-3'))\n    row.add_column(Bootstrap4Column('col-auto'))\n    row.add_column(Bootstrap4Column('col'))\n    assert row[0].get_bound(Breakpoint.xs) == Bound(80.0, 143.0)\n    assert row[0].get_bound(Breakpoint.sm) == Bound(135.0, 135.0)\n    assert row[0].get_bound(Breakpoint.md) == Bound(180.0, 180.0)\n    assert row[0].get_bound(Breakpoint.lg) == Bound(240.0, 240.0)\n    assert row[0].get_bound(Breakpoint.xl) == Bound(285.0, 285.0)\n\n    assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)\n    assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)\n    assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 480.0)\n    assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)\n    assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)\n\n    assert row[2].get_bound(Breakpoint.xs) == Bound(30.0, 369.0)\n    assert row[2].get_bound(Breakpoint.sm) == Bound(30.0, 345.0)\n    assert row[2].get_bound(Breakpoint.md) == Bound(30.0, 480.0)\n    assert row[2].get_bound(Breakpoint.lg) == Bound(30.0, 660.0)\n    assert row[2].get_bound(Breakpoint.xl) == Bound(30.0, 795.0)\n\n\ndef test_mix_flex_with_fixed():\n    row = Bootstrap4Row()\n    with pytest.raises(BootstrapException):\n        row.add_column(Bootstrap4Column('col col-1'))\n\n\ndef test_mix_flex_with_auto():\n    row = Bootstrap4Row()\n    with pytest.raises(BootstrapException):\n        row.add_column(Bootstrap4Column('col col-auto'))\n\n\ndef test_mix_fixed_with_auto():\n    row = Bootstrap4Row()\n    with pytest.raises(BootstrapException):\n        row.add_column(Bootstrap4Column('col-1 col-auto'))\n\n\ndef test_growing_columns():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-12 col-sm-6 col-lg-4\"></div>\n            <div class=\"col-12 col-sm-6 col-lg-4\"></div>\n            <div class=\"col-12 col-sm-12 col-lg-4\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))\n    row.add_column(Bootstrap4Column('col-12 col-sm-6 col-lg-4'))\n    row.add_column(Bootstrap4Column('col-12 col-sm-12 col-lg-4'))\n    assert row[0].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)\n    assert row[0].get_bound(Breakpoint.sm) == Bound(270.0, 270.0)\n    assert row[0].get_bound(Breakpoint.md) == Bound(360.0, 360.0)\n    assert row[0].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)\n    assert row[0].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)\n\n    assert row[2].get_bound(Breakpoint.xs) == Bound(320.0, 572.0)\n    assert row[2].get_bound(Breakpoint.sm) == Bound(540.0, 540.0)\n    assert row[2].get_bound(Breakpoint.md) == Bound(720.0, 720.0)\n    assert row[2].get_bound(Breakpoint.lg) == Bound(320.0, 320.0)\n    assert row[2].get_bound(Breakpoint.xl) == Bound(380.0, 380.0)\n\n\ndef test_haricot():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col\"></div>\n            <div class=\"col-auto\"></div>\n            <div class=\"col-2\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col'))\n    row.add_column(Bootstrap4Column('col-auto'))\n    row.add_column(Bootstrap4Column('col-2'))\n    assert row[0].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)\n    assert row[0].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)\n    assert row[0].get_bound(Breakpoint.md) == Bound(30.0, 540.0)\n    assert row[0].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)\n    assert row[0].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)\n\n    assert row[1].get_bound(Breakpoint.xs) == Bound(30.0, 416.7)\n    assert row[1].get_bound(Breakpoint.sm) == Bound(30.0, 390.0)\n    assert row[1].get_bound(Breakpoint.md) == Bound(30.0, 540.0)\n    assert row[1].get_bound(Breakpoint.lg) == Bound(30.0, 740.0)\n    assert row[1].get_bound(Breakpoint.xl) == Bound(30.0, 890.0)\n\n    assert row[2].get_bound(Breakpoint.xs) == Bound(53.3, 95.3)\n    assert row[2].get_bound(Breakpoint.sm) == Bound(90.0, 90.0)\n    assert row[2].get_bound(Breakpoint.md) == Bound(120.0, 120.0)\n    assert row[2].get_bound(Breakpoint.lg) == Bound(160.0, 160.0)\n    assert row[2].get_bound(Breakpoint.xl) == Bound(190.0, 190.0)\n\n\ndef test_nested_row():\n    \"\"\"\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col\">\n                <div class=\"row\">\n                    <div class=\"col-5\"></div>\n                    <div class=\"col-7\"></div>\n                </div>\n            </div>\n            <div class=\"col\"></div>\n        </div>\n    </div>\n    \"\"\"\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col'))\n    row.add_column(Bootstrap4Column('col'))\n    nested_row = row[0].add_row(Bootstrap4Row())\n    nested_row.add_column(Bootstrap4Column('col-5'))\n    nested_row.add_column(Bootstrap4Column('col-7'))\n\n    assert nested_row[0].get_bound(Breakpoint.xs) == Bound(66.7, 119.2)\n    assert nested_row[1].get_bound(Breakpoint.xs) == Bound(93.3, 166.8)\n    assert nested_row[0].get_bound(Breakpoint.sm) == Bound(112.5, 112.5)\n    assert nested_row[1].get_bound(Breakpoint.sm) == Bound(157.5, 157.5)\n    assert nested_row[0].get_bound(Breakpoint.md) == Bound(150.0, 150.0)\n    assert nested_row[1].get_bound(Breakpoint.md) == Bound(210.0, 210.0)\n    assert nested_row[0].get_bound(Breakpoint.lg) == Bound(200.0, 200.0)\n    assert nested_row[1].get_bound(Breakpoint.lg) == Bound(280.0, 280.0)\n    assert nested_row[0].get_bound(Breakpoint.xl) == Bound(237.5, 237.5)\n    assert nested_row[1].get_bound(Breakpoint.xl) == Bound(332.5, 332.5)\n\n\ndef test_repr():\n    container = Bootstrap4Container()\n    row = container.add_row(Bootstrap4Row())\n    row.add_column(Bootstrap4Column('col'))\n    row.compute_column_bounds()\n    assert repr(container) == '<Bootstrap4Container: <Bootstrap4Row: <Bootstrap4Column: <Break[xs]: fixed=0, flex=True, auto=False>, <Break[sm]: fixed=0, flex=True, auto=False>, <Break[md]: fixed=0, flex=True, auto=False>, <Break[lg]: fixed=0, flex=True, auto=False>, <Break[xl]: fixed=0, flex=True, auto=False>>>>'\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import factory.fuzzy\nimport pytest\nfrom pytest_factoryboy import register\nfrom django.contrib import admin\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.hashers import make_password\nfrom cms.api import create_page\nfrom cmsplugin_cascade.models import CascadePage\n\n\n@pytest.fixture\ndef admin_site():\n    return admin.sites.AdminSite()\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef cms_page():\n    home_page = create_page(title='HOME', template='testing.html', language='en')\n    if not home_page.is_home:\n        home_page.set_as_homepage()\n    CascadePage.assure_relation(home_page)\n    return home_page\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef cms_placeholder(cms_page):\n    placeholder = cms_page.placeholders.get(slot='Main Content')\n    return placeholder\n\n\n@register\nclass UserFactory(factory.django.DjangoModelFactory):\n    class Meta:\n        model = get_user_model()\n\n    @classmethod\n    def create(cls, **kwargs):\n        user = super().create(**kwargs)\n        assert isinstance(user, get_user_model())\n        assert user.is_authenticated == True\n        return user\n\n    username = factory.Sequence(lambda n: 'uid-{}'.format(n))\n    password = make_password('secret')\n    email = factory.fuzzy.FuzzyText(suffix='@example.com')\n"
  },
  {
    "path": "tests/requirements.txt",
    "content": "lxml\nbeautifulsoup4\npluggy\npy\npytest\npytest-django\ncoverage\ndjango-reversion\nfactory-boy\npytest-factoryboy\n"
  },
  {
    "path": "tests/settings.py",
    "content": "from django.utils.text import format_lazy\nfrom django.urls import reverse_lazy\n\nfrom cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig\n\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n    'django.middleware.locale.LocaleMiddleware',\n    'django.middleware.gzip.GZipMiddleware',\n    'cms.middleware.page.CurrentPageMiddleware',\n    'cms.middleware.user.CurrentUserMiddleware',\n    'cms.middleware.toolbar.ToolbarMiddleware',\n    'cms.middleware.language.LanguageCookieMiddleware',\n]\n\nROOT_URLCONF = 'tests.urls'\n\nSECRET_KEY = 'test'\n\nSITE_ID = 1\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': ':memory:',\n    }\n}\n\nSTATIC_URL = '/static/'\n\nMEDIA_URL = '/media/'\n\nTEMPLATES = [{\n    'BACKEND': 'django.template.backends.django.DjangoTemplates',\n    'APP_DIRS': True,\n    'DIRS': ['tests/templates'],\n    'OPTIONS': {\n        'context_processors': (\n            'django.contrib.auth.context_processors.auth',\n            'django.template.context_processors.debug',\n            'django.template.context_processors.i18n',\n            'django.template.context_processors.media',\n            'django.template.context_processors.static',\n            'django.template.context_processors.tz',\n            'django.template.context_processors.csrf',\n            'django.template.context_processors.request',\n            'django.contrib.messages.context_processors.messages',\n            'sekizai.context_processors.sekizai',\n            'cms.context_processors.cms_settings',\n        )\n    }\n}]\n\n\nINSTALLED_APPS = [\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.sites',\n    'django.contrib.messages',\n    'django.contrib.admin',\n    'django.contrib.staticfiles',\n    'filer',\n    'easy_thumbnails',\n    'treebeard',\n    'menus',\n    'sekizai',\n    'cms',\n    'adminsortable2',\n    'djangocms_text_ckeditor',\n    'django_select2',\n    'cmsplugin_cascade',\n    'cmsplugin_cascade.clipboard',\n    'cmsplugin_cascade.extra_fields',\n    'cmsplugin_cascade.icon',\n    'cmsplugin_cascade.sharable',\n    'cmsplugin_cascade.segmentation',\n    'tests',\n]\n\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\nLANGUAGES = [\n    ('en', 'English'),\n]\n\nLANGUAGE_CODE = 'en'\n\nCMS_TEMPLATES = [\n    ('testing.html', 'Default Page'),\n]\n\nCMSPLUGIN_CASCADE_PLUGINS = [\n    'cmsplugin_cascade.link',\n    'cmsplugin_cascade.bootstrap4',\n]\n\n\nCMSPLUGIN_CASCADE = {\n    'plugins_with_extra_fields': {\n        'BootstrapButtonPlugin': PluginExtraFieldsConfig(),\n        'BootstrapContainerPlugin': PluginExtraFieldsConfig(),\n        'BootstrapColumnPlugin': PluginExtraFieldsConfig(),\n        'BootstrapRowPlugin': PluginExtraFieldsConfig(),\n        'BootstrapPicturePlugin': PluginExtraFieldsConfig(),\n        'SimpleWrapperPlugin': PluginExtraFieldsConfig(),\n    },\n    'plugins_with_sharables': {\n        'BootstrapImagePlugin': (\n            'image_shapes',\n            'image_width_responsive',\n            'image_width_fixed',\n            'image_height',\n            'resize_options',\n        ),\n        'BootstrapPicturePlugin': (\n            'image_shapes',\n            'responsive_heights',\n            'image_size',\n            'resize_options',\n        ),\n    },\n}\n\nCMS_PLACEHOLDER_CONF = {\n    'Main Content': {\n        'plugins': ['BootstrapContainerPlugin'],\n        'parent_classes': {\n            'BootstrapContainerPlugin': None,\n            'TextLinkPlugin': ['TextPlugin'],\n        },\n    },\n}\n\nTHUMBNAIL_PROCESSORS = (\n    'easy_thumbnails.processors.colorspace',\n    'easy_thumbnails.processors.autocrop',\n    'filer.thumbnail_processors.scale_and_crop_with_subject_location',\n    'easy_thumbnails.processors.filters',\n)\n\nTHUMBNAIL_PRESERVE_EXTENSIONS = True,\n\nTHUMBNAIL_OPTIMIZE_COMMAND = {\n    'png': '/opt/local/bin/optipng {filename}',\n    'gif': '/opt/local/bin/optipng {filename}',\n    'jpeg': '/opt/local/bin/jpegoptim {filename}',\n}\n\nCKEDITOR_SETTINGS = {\n    'language': '{{ language }}',\n    'skin': 'moono',\n    'toolbar': 'CMS',\n    'toolbar_HTMLField': [\n        ['Undo', 'Redo'],\n        ['cmsplugins', '-', 'ShowBlocks'],\n        ['Format', 'Styles'],\n        ['TextColor', 'BGColor', '-', 'PasteText', 'PasteFromWord'],\n        ['Maximize', ''],\n        '/',\n        ['Bold', 'Italic', 'Underline', '-', 'Subscript', 'Superscript', '-', 'RemoveFormat'],\n        ['JustifyLeft', 'JustifyCenter', 'JustifyRight'],\n        ['HorizontalRule'],\n        ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Table'],\n        ['Source']\n    ],\n    'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')),\n}\n\nSILENCED_SYSTEM_CHECKS = ['2_0.W001']\n"
  },
  {
    "path": "tests/static/strides/bootstrap-button.json",
    "content": "{\n  \"plugins\":[\n    [\n      \"BootstrapContainerPlugin\",\n      {\n        \"glossary\":{\n          \"hide_plugin\":false,\n          \"padding_xs\":\"\",\n          \"padding_sm\":\"\",\n          \"padding_md\":\"\",\n          \"padding_lg\":\"\",\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\",\n            \"xl\"\n          ],\n          \"fluid\":\"\"\n        },\n        \"pk\":2511\n      },\n      [\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"glossary\":{\n              \"hide_plugin\":\"\"\n            },\n            \"pk\":2512\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"glossary\":{\n                  \"xs-column-width\":\"col\"\n                },\n                \"pk\":2513\n              },\n              [\n                [\n                  \"BootstrapButtonPlugin\",\n                  {\n                    \"glossary\":{\n                      \"hide_plugin\":\"\",\n                      \"link_type\":\"\",\n                      \"cms_page\":null,\n                      \"section\":\"\",\n                      \"download_file\":null,\n                      \"ext_url\":\"\",\n                      \"mail_to\":\"\",\n                      \"link_target\":\"\",\n                      \"link_title\":\"\",\n                      \"icon_font\":{\n                        \"model\":\"cmsplugin_cascade.iconfont\",\n                        \"pk\":1\n                      },\n                      \"symbol\":\"\",\n                      \"link_content\":\"button_content\",\n                      \"button_type\":\"btn-secondary\",\n                      \"button_size\":\"\",\n                      \"button_options\":[],\n                      \"icon_align\":\"icon-right\",\n                      \"stretched_link\":false\n                    },\n                    \"pk\":2514\n                  },\n                  []\n                ]\n              ]\n            ]\n          ]\n        ]\n      ]\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/bootstrap-column.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"BootstrapColumnPlugin\",\n      {\n        \"pk\":1539,\n        \"glossary\":{\n          \"container_max_widths\":{\n            \"xs\":720.0,\n            \"md\":940.0,\n            \"sm\":720.0,\n            \"lg\":1140.0\n          },\n          \"xs-column-width\":\"col-xs-12\"\n        }\n      },\n      []\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/bootstrap-container.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"BootstrapContainerPlugin\",\n      {\n        \"pk\":1503,\n        \"glossary\":{\n          \"container_max_widths\":{\n            \"xs\":750,\n            \"md\":970,\n            \"sm\":750,\n            \"lg\":1170\n          },\n          \"media_queries\":{\n            \"xs\":[\n              \"(max-width: 768px)\"\n            ],\n            \"md\":[\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ],\n            \"sm\":[\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"lg\":[\n              \"(min-width: 1200px)\"\n            ]\n          },\n          \"hide_plugin\":\"\",\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"fluid\":\"\",\n          \"extra_css_classes\": \"foo bar\"\n        }\n      },\n      []\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/bootstrap-jumbotron.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"BootstrapJumbotronPlugin\",\n      {\n        \"pk\": 1501,\n        \"glossary\": {\n          \"background_vertical_position\": \"center\",\n          \"extra_inline_styles:Paddings\": {\n            \"padding-top\": \"500px\",\n            \"padding-bottom\": \"\"\n          },\n          \"resize_options\": [\n            \"crop\",\n            \"subject_location\",\n            \"high_resolution\"\n          ],\n          \"breakpoints\": [\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"hide_plugin\": \"\",\n          \"background_color\": [\n            \"\",\n            \"#12308b\"\n          ],\n          \"fluid\": true,\n          \"container_max_widths\": {\n            \"xs\": 768,\n            \"md\": 1200,\n            \"sm\": 992,\n            \"lg\": 1980\n          },\n          \"media_queries\": {\n            \"xs\": [\n              \"(max-width: 768px)\"\n            ],\n            \"md\": [\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ],\n            \"sm\": [\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"lg\": [\n              \"(min-width: 1200px)\"\n            ]\n          },\n          \"image\": {\n            \"pk\": 5,\n            \"model\": \"filer.Image\"\n          },\n          \"background_size\": \"cover\",\n          \"background_horizontal_position\": \"center\",\n          \"background_repeat\": \"no-repeat\",\n          \"container_max_heights\": {\n            \"xs\": \"100%\",\n            \"md\": \"100%\",\n            \"lg\": \"100%\",\n            \"sm\": \"100%\"\n          },\n          \"background_width_height\": {\n            \"height\": \"\",\n            \"width\": \"\"\n          },\n          \"background_attachment\": \"scroll\"\n        }\n      },\n      [\n        [\n          \"TextPlugin\",\n          {\n            \"body\": \"<h1 style=\\\"text-align: center;\\\"><span style=\\\"color: #ffffe0;\\\">Manage your website</span></h1>\\n\\n<p style=\\\"text-align: center;\\\"><span style=\\\"color: #ffffe0;\\\">with ease</span></p>\\n\\n<p style=\\\"text-align: center;\\\">\\u00a0</p>\\n\\n<p style=\\\"text-align: center;\\\">\\u00a0</p>\",\n            \"pk\": 1502\n          },\n          []\n        ]\n      ]\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/bootstrap-row.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"BootstrapRowPlugin\",\n      {\n        \"pk\":1538,\n        \"glossary\":{\n          \"hide_plugin\":\"\",\n          \"extra_css_classes\":\"\",\n          \"extra_inline_styles:Margins\":{\n            \"margin-bottom\":\"20px\",\n            \"margin-top\":\"\"\n          },\n          \"extra_element_id\":\"\"\n        }\n      },\n      []\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/carousel-plugin.json",
    "content": "{\n  \"plugins\": [\n                [\n                  \"BootstrapCarouselPlugin\",\n                  {\n                    \"glossary\":{\n                      \"hide_plugin\":false,\n                      \"margins_xs\":\"\",\n                      \"margins_sm\":\"\",\n                      \"margins_md\":\"\",\n                      \"margins_lg\":\"\",\n                      \"interval\":5,\n                      \"options\":[\n                        \"slide\",\n                        \"pause\",\n                        \"wrap\"\n                      ],\n                      \"container_max_heights\":{\n                        \"xs\":\"9rem\",\n                        \"sm\":\"9rem\",\n                        \"md\":\"9rem\",\n                        \"lg\":\"9rem\",\n                        \"xl\":\"9rem\"\n                      },\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ]\n                    },\n                    \"pk\":229\n                  },\n                  [\n                    [\n                      \"BootstrapCarouselSlidePlugin\",\n                      {\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image\":{\n                            \"pk\":4,\n                            \"model\":\"filer.Image\"\n                          },\n                          \"media_queries\":{\n                            \"xs\":{\n                              \"width\":572,\n                              \"media\":\"(max-width: 575.98px)\"\n                            },\n                            \"sm\":{\n                              \"width\":540,\n                              \"media\":\"(min-width: 576px) and (max-width: 767.98px)\"\n                            },\n                            \"md\":{\n                              \"width\":720,\n                              \"media\":\"(min-width: 768px) and (max-width: 991.98px)\"\n                            },\n                            \"lg\":{\n                              \"width\":960,\n                              \"media\":\"(min-width: 992px) and (max-width: 1199.98px)\"\n                            },\n                            \"xl\":{\n                              \"width\":1140,\n                              \"media\":\"(min-width: 1200px)\"\n                            }\n                          }\n                        },\n                        \"pk\":1526\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n}\n"
  },
  {
    "path": "tests/static/strides/framed-icon.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"FramedIconPlugin\",\n      {\n        \"pk\":1513,\n        \"glossary\":{\n          \"symbol\":\"umbrella\",\n          \"border_radius\":\"\",\n          \"color\":[\n            \"#ffffff\",\n             \"\"\n          ],\n          \"text_align\":\"text-center\",\n          \"hide_plugin\":\"\",\n          \"icon_font\":\"1\",\n          \"font_size\":\"10em\",\n          \"background_color\":[\n            \"#ffffff\",\n             \"true\"\n          ],\n          \"border\":[\n            \"0px\",\n            \"none\",\n            \"#000000\"\n          ]\n        }\n      },\n      []\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/simple-wrapper.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"SimpleWrapperPlugin\",\n      {\n        \"pk\":1512,\n        \"glossary\":{\n          \"tag_type\":\"div\",\n          \"extra_inline_styles:background-color\":[\n            \"#42c8c6\",\n            \"\"\n          ],\n          \"extra_inline_styles:line-height\":\"\",\n          \"extra_css_classes\":[],\n          \"hide_plugin\":\"\",\n          \"extra_inline_styles:color\":[\n            \"#ffffff\",\n            \"\"\n          ],\n          \"element_id\":\"\",\n          \"extra_inline_styles:Paddings\":{\n            \"padding-left\":\"50px\",\n            \"padding-bottom\":\"\",\n            \"padding-top\":\"\",\n            \"padding-right\":\"50px\"\n          },\n          \"extra_inline_styles:Font Size\":{\n            \"font-size\":\"\"\n          },\n          \"extra_inline_styles:Heights\":{\n            \"height\":\"360px\",\n            \"min-height\":\"\",\n            \"max-height\":\"\"\n          }\n        }\n      },\n      []\n    ]\n  ]\n}\n"
  },
  {
    "path": "tests/static/strides/text-plugin.json",
    "content": "{\n  \"plugins\": [\n    [\n      \"TextPlugin\",\n      {\n        \"body\": \"<h2 style=\\\"text-align: center;\\\">Customizable</h2>\\n\\n<p>Lorem ipsum dolor</p>\",\n        \"pk\": 1518\n      },\n      []\n    ]\n  ]\n}"
  },
  {
    "path": "tests/static/strides.json",
    "content": "{\n  \"plugins\":[\n    [\n      \"BootstrapJumbotronPlugin\",\n      {\n        \"pk\":1584,\n        \"glossary\":{\n          \"background_repeat\":\"no-repeat\",\n          \"image\":{\n            \"model\":\"filer.Image\",\n            \"pk\":5\n          },\n          \"background_size\":\"cover\",\n          \"fluid\":true,\n          \"resize_options\":[\n            \"crop\",\n            \"subject_location\",\n            \"high_resolution\"\n          ],\n          \"hide_plugin\":\"\",\n          \"container_max_heights\":{\n            \"sm\":\"100%\",\n            \"md\":\"100%\",\n            \"lg\":\"100%\",\n            \"xs\":\"100%\"\n          },\n          \"background_horizontal_position\":\"center\",\n          \"media_queries\":{\n            \"sm\":[\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"md\":[\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ],\n            \"lg\":[\n              \"(min-width: 1200px)\"\n            ],\n            \"xs\":[\n              \"(max-width: 768px)\"\n            ]\n          },\n          \"background_vertical_position\":\"center\",\n          \"container_max_widths\":{\n            \"sm\":992,\n            \"md\":1200,\n            \"lg\":1980,\n            \"xs\":768\n          },\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"background_color\":[\n            \"\",\n            \"#12308b\"\n          ],\n          \"background_attachment\":\"scroll\",\n          \"extra_inline_styles:Paddings\":{\n            \"padding-bottom\":\"\",\n            \"padding-top\":\"500px\"\n          },\n          \"background_width_height\":{\n            \"width\":\"\",\n            \"height\":\"\"\n          }\n        }\n      },\n      [\n        [\n          \"TextPlugin\",\n          {\n            \"body\":\"<h1 style=\\\"text-align: center;\\\"><span style=\\\"color: #ffffe0;\\\">Manage your website</span></h1>\\n\\n<p style=\\\"text-align: center;\\\"><span style=\\\"color: #ffffe0;\\\">with ease</span></p>\\n\\n<p style=\\\"text-align: center;\\\">\\u00a0</p>\\n\\n<p style=\\\"text-align: center;\\\">\\u00a0</p>\",\n            \"pk\":1585\n          },\n          []\n        ]\n      ]\n    ],\n    [\n      \"BootstrapContainerPlugin\",\n      {\n        \"pk\":1586,\n        \"glossary\":{\n          \"fluid\":\"\",\n          \"media_queries\":{\n            \"sm\":[\n              \"(min-width: 768px)\",\n              \"(max-width: 992px)\"\n            ],\n            \"md\":[\n              \"(min-width: 992px)\",\n              \"(max-width: 1200px)\"\n            ],\n            \"lg\":[\n              \"(min-width: 1200px)\"\n            ],\n            \"xs\":[\n              \"(max-width: 768px)\"\n            ]\n          },\n          \"container_max_widths\":{\n            \"sm\":750,\n            \"md\":970,\n            \"lg\":1170,\n            \"xs\":750\n          },\n          \"breakpoints\":[\n            \"xs\",\n            \"sm\",\n            \"md\",\n            \"lg\"\n          ],\n          \"hide_plugin\":\"\"\n        }\n      },\n      [\n        [\n          \"HeadingPlugin\",\n          {\n            \"pk\":1624,\n            \"glossary\":{\n              \"element_id\":\"heading-1\",\n              \"content\":\"Voluptate velit esse cillum dolore\",\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"\",\n                \"margin-left\":\"50px\",\n                \"margin-right\":\"\"\n              },\n              \"tag_type\":\"h1\"\n            }\n          },\n          []\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"pk\":1587,\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"\"\n              }\n            }\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1588,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":720.0,\n                    \"md\":455.0,\n                    \"lg\":555.0,\n                    \"xs\":720.0\n                  },\n                  \"md-column-width\":\"col-md-6\",\n                  \"lg-column-width\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"lg-responsive-utils\":\"\",\n                  \"sm-responsive-utils\":\"\"\n                }\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"pk\":1589,\n                    \"glossary\":{\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"height\":\"347px\"\n                      },\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#474747\"\n                      ],\n                      \"extra_inline_styles:line-height\":\"2\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"20px\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"20px\",\n                        \"padding-left\":\"50px\"\n                      },\n                      \"tag_type\":\"div\"\n                    }\n                  },\n                  [\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h1 style=\\\"text-align: center;\\\">What we do?</h1>\\n\\n<p style=\\\"text-align: center;\\\">Lorem 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.</p>\",\n                        \"pk\":1590\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1591,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":720.0,\n                    \"md\":455.0,\n                    \"lg\":555.0,\n                    \"xs\":720.0\n                  },\n                  \"md-column-width\":\"col-md-6\",\n                  \"lg-column-width\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"sm-responsive-utils\":\"\"\n                }\n              },\n              [\n                [\n                  \"BootstrapImagePlugin\",\n                  {\n                    \"pk\":1592,\n                    \"glossary\":{\n                      \"target\":\"\",\n                      \"image_width_responsive\":\"100%\",\n                      \"image\":{\n                        \"model\":\"filer.Image\",\n                        \"pk\":8\n                      },\n                      \"image_height\":\"\",\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"alt_tag\":\"\",\n                      \"image_shapes\":[\n                        \"img-responsive\"\n                      ],\n                      \"link\":{\n                        \"type\":\"none\"\n                      },\n                      \"title\":\"\",\n                      \"image_width_fixed\":\"\",\n                      \"image_title\":\"\"\n                    }\n                  },\n                  []\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"pk\":1593,\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"\"\n              }\n            }\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1594,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"height\":\"360px\",\n                    \"min-height\":\"\"\n                  },\n                  \"md-column-width\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"sm-responsive-utils\":\"\",\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#42c8c6\"\n                  ],\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"lg-column-ordering\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":220.0,\n                    \"md\":293.33,\n                    \"lg\":360.0,\n                    \"xs\":220.0\n                  },\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"sm-column-offset\":\"\"\n                }\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"pk\":1595,\n                    \"glossary\":{\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"height\":\"360px\",\n                        \"min-height\":\"\"\n                      },\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"extra_css_classes\":[],\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#42c8c6\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      },\n                      \"tag_type\":\"div\"\n                    }\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"pk\":1596,\n                        \"glossary\":{\n                          \"border_radius\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"1\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"font_size\":\"10em\",\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ],\n                          \"hide_plugin\":\"\",\n                          \"symbol\":\"umbrella\"\n                        }\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">\\u00a0\\u00a0Quick Installs</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":1597\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1598,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"height\":\"360px\",\n                    \"min-height\":\"\"\n                  },\n                  \"md-column-width\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"sm-responsive-utils\":\"\",\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#f5b10e\"\n                  ],\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"lg-column-ordering\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":220.0,\n                    \"md\":293.33,\n                    \"lg\":360.0,\n                    \"xs\":220.0\n                  },\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"sm-column-offset\":\"\"\n                }\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"pk\":1599,\n                    \"glossary\":{\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"height\":\"360px\",\n                        \"min-height\":\"\"\n                      },\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"extra_css_classes\":[],\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#f5b10e\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      },\n                      \"tag_type\":\"div\"\n                    }\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"pk\":1600,\n                        \"glossary\":{\n                          \"border_radius\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"1\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"font_size\":\"10em\",\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ],\n                          \"hide_plugin\":\"\",\n                          \"symbol\":\"cog-alt\"\n                        }\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Customizable</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":1601\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1602,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-width\":\"col-xs-4\",\n                  \"extra_inline_styles:Font Size\":{\n                    \"font-size\":\"\"\n                  },\n                  \"extra_inline_styles:line-height\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"extra_inline_styles:Heights\":{\n                    \"max-height\":\"\",\n                    \"height\":\"360px\",\n                    \"min-height\":\"\"\n                  },\n                  \"md-column-width\":\"\",\n                  \"md-column-offset\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"extra_css_classes\":[],\n                  \"lg-responsive-utils\":\"\",\n                  \"extra_inline_styles:Paddings\":{\n                    \"padding-top\":\"\",\n                    \"padding-right\":\"\",\n                    \"padding-bottom\":\"\",\n                    \"padding-left\":\"\"\n                  },\n                  \"sm-responsive-utils\":\"\",\n                  \"lg-column-width\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"extra_inline_styles:background-color\":[\n                    \"\",\n                    \"#56ba41\"\n                  ],\n                  \"hide_plugin\":\"\",\n                  \"extra_inline_styles:color\":[\n                    \"\",\n                    \"#ffffff\"\n                  ],\n                  \"lg-column-ordering\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":220.0,\n                    \"md\":293.33,\n                    \"lg\":360.0,\n                    \"xs\":220.0\n                  },\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"\",\n                  \"sm-column-offset\":\"\"\n                }\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"pk\":1603,\n                    \"glossary\":{\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"height\":\"360px\",\n                        \"min-height\":\"\"\n                      },\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"\"\n                      },\n                      \"extra_css_classes\":[],\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#56ba41\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      },\n                      \"tag_type\":\"div\"\n                    }\n                  },\n                  [\n                    [\n                      \"FramedIconPlugin\",\n                      {\n                        \"pk\":1604,\n                        \"glossary\":{\n                          \"border_radius\":\"\",\n                          \"text_align\":\"text-center\",\n                          \"icon_font\":\"1\",\n                          \"color\":[\n                            \"\",\n                            \"#ffffff\"\n                          ],\n                          \"font_size\":\"10em\",\n                          \"background_color\":[\n                            \"disabled\",\n                            \"#ffffff\"\n                          ],\n                          \"border\":[\n                            \"0px\",\n                            \"none\",\n                            \"#000000\"\n                          ],\n                          \"hide_plugin\":\"\",\n                          \"symbol\":\"bell-alt\"\n                        }\n                      },\n                      []\n                    ],\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Support</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore.</p>\",\n                        \"pk\":1605\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"pk\":1606,\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"\"\n              }\n            }\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1607,\n                \"glossary\":{\n                  \"container_max_widths\":{\n                    \"sm\":720.0,\n                    \"md\":940.0,\n                    \"lg\":1140.0,\n                    \"xs\":720.0\n                  },\n                  \"xs-column-width\":\"col-xs-12\"\n                }\n              },\n              [\n                [\n                  \"CarouselPlugin\",\n                  {\n                    \"pk\":1608,\n                    \"glossary\":{\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"options\":[\n                        \"slide\",\n                        \"pause\",\n                        \"wrap\"\n                      ],\n                      \"container_max_heights\":{\n                        \"sm\":\"250px\",\n                        \"md\":\"300px\",\n                        \"lg\":\"350px\",\n                        \"xs\":\"200px\"\n                      },\n                      \"interval\":\"5\",\n                      \"hide_plugin\":\"\"\n                    }\n                  },\n                  [\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"pk\":1609,\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image_title\":\"\",\n                          \"image\":{\n                            \"model\":\"filer.Image\",\n                            \"pk\":4\n                          },\n                          \"alt_tag\":\"\",\n                          \"hide_plugin\":\"\"\n                        }\n                      },\n                      [\n                        [\n                          \"TextPlugin\",\n                          {\n                            \"body\":\"<h1>Hallo Welt!</h1>\",\n                            \"pk\":1610\n                          },\n                          []\n                        ]\n                      ]\n                    ],\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"pk\":1611,\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image_title\":\"\",\n                          \"image\":{\n                            \"model\":\"filer.Image\",\n                            \"pk\":7\n                          },\n                          \"alt_tag\":\"\",\n                          \"hide_plugin\":\"\"\n                        }\n                      },\n                      []\n                    ],\n                    [\n                      \"CarouselSlidePlugin\",\n                      {\n                        \"pk\":1612,\n                        \"glossary\":{\n                          \"resize_options\":[\n                            \"upscale\",\n                            \"crop\",\n                            \"subject_location\",\n                            \"high_resolution\"\n                          ],\n                          \"image_title\":\"\",\n                          \"image\":{\n                            \"model\":\"filer.Image\",\n                            \"pk\":6\n                          },\n                          \"alt_tag\":\"\",\n                          \"hide_plugin\":\"\"\n                        }\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"pk\":1614,\n            \"glossary\":{\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"20px\",\n                \"margin-bottom\":\"20px\"\n              }\n            }\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1615,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":345.0,\n                    \"md\":455.0,\n                    \"lg\":555.0,\n                    \"xs\":720.0\n                  },\n                  \"md-column-width\":\"\",\n                  \"lg-column-width\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"col-sm-6\",\n                  \"md-column-offset\":\"\",\n                  \"lg-responsive-utils\":\"\",\n                  \"sm-responsive-utils\":\"\"\n                }\n              },\n              [\n                [\n                  \"BootstrapImagePlugin\",\n                  {\n                    \"pk\":1616,\n                    \"glossary\":{\n                      \"target\":\"\",\n                      \"image_width_responsive\":\"100%\",\n                      \"image\":{\n                        \"model\":\"filer.Image\",\n                        \"pk\":9\n                      },\n                      \"image_height\":\"\",\n                      \"resize_options\":[\n                        \"upscale\",\n                        \"crop\",\n                        \"subject_location\",\n                        \"high_resolution\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"alt_tag\":\"\",\n                      \"image_shapes\":[\n                        \"img-responsive\"\n                      ],\n                      \"link\":{\n                        \"type\":\"none\"\n                      },\n                      \"title\":\"\",\n                      \"image_width_fixed\":\"\",\n                      \"image_title\":\"\"\n                    }\n                  },\n                  []\n                ]\n              ]\n            ],\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1617,\n                \"glossary\":{\n                  \"xs-responsive-utils\":\"\",\n                  \"xs-column-ordering\":\"\",\n                  \"xs-column-offset\":\"\",\n                  \"xs-column-width\":\"col-xs-12\",\n                  \"sm-column-offset\":\"\",\n                  \"lg-column-offset\":\"\",\n                  \"hide_plugin\":\"\",\n                  \"md-column-ordering\":\"\",\n                  \"lg-column-ordering\":\"\",\n                  \"md-responsive-utils\":\"\",\n                  \"container_max_widths\":{\n                    \"sm\":345.0,\n                    \"md\":455.0,\n                    \"lg\":555.0,\n                    \"xs\":720.0\n                  },\n                  \"md-column-width\":\"\",\n                  \"lg-column-width\":\"\",\n                  \"sm-column-ordering\":\"\",\n                  \"sm-column-width\":\"col-sm-6\",\n                  \"md-column-offset\":\"\",\n                  \"lg-responsive-utils\":\"\",\n                  \"sm-responsive-utils\":\"\"\n                }\n              },\n              [\n                [\n                  \"SimpleWrapperPlugin\",\n                  {\n                    \"pk\":1618,\n                    \"glossary\":{\n                      \"element_id\":\"\",\n                      \"extra_inline_styles:Heights\":{\n                        \"max-height\":\"\",\n                        \"height\":\"370px\",\n                        \"min-height\":\"\"\n                      },\n                      \"extra_inline_styles:color\":[\n                        \"\",\n                        \"#ffffff\"\n                      ],\n                      \"extra_inline_styles:Font Size\":{\n                        \"font-size\":\"130%\"\n                      },\n                      \"extra_css_classes\":[],\n                      \"extra_inline_styles:line-height\":\"\",\n                      \"extra_inline_styles:background-color\":[\n                        \"\",\n                        \"#ef3e42\"\n                      ],\n                      \"hide_plugin\":\"\",\n                      \"extra_inline_styles:Paddings\":{\n                        \"padding-top\":\"50px\",\n                        \"padding-right\":\"50px\",\n                        \"padding-bottom\":\"\",\n                        \"padding-left\":\"50px\"\n                      },\n                      \"tag_type\":\"div\"\n                    }\n                  },\n                  [\n                    [\n                      \"TextPlugin\",\n                      {\n                        \"body\":\"<h2 style=\\\"text-align: center;\\\">Let us make<br>\\na difference in your<br>\\nweb design</h2>\\n\\n<p style=\\\"text-align: center;\\\">Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</p>\",\n                        \"pk\":1619\n                      },\n                      []\n                    ],\n                    [\n                      \"BootstrapButtonPlugin\",\n                      {\n                        \"pk\":1620,\n                        \"glossary\":{\n                          \"link\":{\n                            \"section\":\"\",\n                            \"model\":\"cms.Page\",\n                            \"type\":\"cmspage\",\n                            \"pk\":5\n                          },\n                          \"button_options\":[],\n                          \"icon_font\":\"3\",\n                          \"extra_inline_styles:Margins\":{\n                            \"margin-top\":\"20px\",\n                            \"margin-bottom\":\"\"\n                          },\n                          \"link_content\":\"Continue\",\n                          \"button_size\":\"btn-lg\",\n                          \"symbol\":\"right-open\",\n                          \"icon_align\":\"icon-right\",\n                          \"quick_float\":\"pull-right\",\n                          \"button_type\":\"btn-default\",\n                          \"hide_plugin\":\"\"\n                        }\n                      },\n                      []\n                    ]\n                  ]\n                ]\n              ]\n            ]\n          ]\n        ],\n        [\n          \"HeadingPlugin\",\n          {\n            \"pk\":1613,\n            \"glossary\":{\n              \"element_id\":\"heading\",\n              \"content\":\"Where we are\",\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"\",\n                \"margin-left\":\"\",\n                \"margin-right\":\"\"\n              },\n              \"tag_type\":\"h3\"\n            }\n          },\n          []\n        ],\n        [\n          \"BootstrapRowPlugin\",\n          {\n            \"pk\":1621,\n            \"glossary\":{\n              \"extra_element_id\":\"\",\n              \"extra_css_classes\":\"\",\n              \"hide_plugin\":\"\",\n              \"extra_inline_styles:Margins\":{\n                \"margin-top\":\"\",\n                \"margin-bottom\":\"20px\"\n              }\n            }\n          },\n          [\n            [\n              \"BootstrapColumnPlugin\",\n              {\n                \"pk\":1622,\n                \"glossary\":{\n                  \"container_max_widths\":{\n                    \"sm\":720.0,\n                    \"md\":940.0,\n                    \"lg\":1140.0,\n                    \"xs\":720.0\n                  },\n                  \"xs-column-width\":\"col-xs-12\"\n                }\n              },\n              [\n                [\n                  \"LeafletPlugin\",\n                  {\n                    \"inlines\":[\n                      {\n                        \"marker_width\":\"75px\",\n                        \"title\":\"Kirche St. Nikolaus\",\n                        \"image\":{\n                          \"model\":\"filer.Image\",\n                          \"pk\":3\n                        },\n                        \"marker_anchor\":{\n                          \"top\":\"35px\",\n                          \"left\":\"35px\"\n                        },\n                        \"popup_text\":\"<h1>Kirche in St. Nikolaus</h1><p>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.</p>\",\n                        \"position\":{\n                          \"lng\":11.392865180969238,\n                          \"lat\":47.27430835780767\n                        }\n                      },\n                      {\n                        \"popup_text\":null,\n                        \"marker_width\":\"\",\n                        \"title\":\"SOHO 2.0\",\n                        \"position\":{\n                          \"lng\":11.44007205963135,\n                          \"lat\":47.26479535533907\n                        },\n                        \"marker_anchor\":{\n                          \"top\":\"\",\n                          \"left\":\"\"\n                        }\n                      }\n                    ],\n                    \"pk\":1623,\n                    \"glossary\":{\n                      \"map_width\":\"100%\",\n                      \"map_position\":{\n                        \"zoom\":16,\n                        \"lat\":47.263227953097356,\n                        \"lng\":11.434611082077026\n                      },\n                      \"map_height\":\"400px\",\n                      \"hide_plugin\":\"\",\n                      \"render_template\":\"cascade/plugins/googlemap.html\"\n                    }\n                  },\n                  []\n                ]\n              ]\n            ]\n          ]\n        ]\n      ]\n    ]\n  ]\n}"
  },
  {
    "path": "tests/templates/testing.html",
    "content": "{% load cms_tags sekizai_tags %}\n\n<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<title>Test Template</title>\n\t\t{% render_block \"css\" %}\n\t</head>\n\n\t<body>\n\t\t{% placeholder \"Main Content\" %}\n\t</body>\n</html>\n"
  },
  {
    "path": "tests/test_base.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.contrib import admin\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.hashers import make_password\nfrom django.template.context import Context\n\nfrom cms.api import create_page\nfrom cms.test_utils.testcases import CMSTestCase\nfrom cmsplugin_cascade.models import CascadePage\n\n\nclass CascadeTestCase(CMSTestCase):\n    home_page = None\n\n    def setUp(self):\n        self.home_page = create_page(title='HOME', template='testing.html', language='en')\n        if not self.home_page.is_home:\n            # >= Django CMS v3.5.x\n            self.home_page.set_as_homepage()\n        CascadePage.assure_relation(self.home_page)\n\n        self.placeholder = self.home_page.placeholders.get(slot='Main Content')\n\n        self.request = self.get_request(self.home_page, 'en')\n        self.admin_site = admin.sites.AdminSite()\n\n        UserModel = get_user_model()\n        UserModel.objects.get_or_create(\n            username='admin',\n            is_staff=True,\n            is_superuser=True,\n            is_active=True,\n            password=make_password('admin'),\n        )\n        UserModel.objects.get_or_create(\n            username='staff',\n            is_staff=True,\n            is_superuser=False,\n            is_active=True,\n            password=make_password('staff'),\n        )\n\n    def get_request_context(self):\n        context = {}\n        context['request'] = self.request\n        context['user'] = self.request.user\n        try:\n            # >= Django CMS v3.4.x\n            context['cms_content_renderer'] = self.get_content_renderer(request=self.request)\n        except AttributeError:\n            # < Django CMS v3.4.x\n            pass\n        return Context(context)\n\n    def get_html(self, model_instance, context):\n        try:\n            # >= Django CMS v3.4.x\n            return context['cms_content_renderer'].render_plugin(model_instance, context)\n        except KeyError:\n            # < Django CMS v3.4.x\n            return model_instance.render_plugin(context)\n"
  },
  {
    "path": "tests/test_customplugin.py",
    "content": "from django.test import TestCase\n\nfrom cms.plugin_pool import plugin_pool\nfrom cmsplugin_cascade.plugin_base import CascadePluginBase\n\n\nclass CustomPlugin(CascadePluginBase):\n    name = 'Custom Element'\n    render_template = 'cascade/generic/naked.html'\n\n\nclass CustomPluginTest(TestCase):\n\n    def test_register(self):\n        plugin_pool.register_plugin(CustomPlugin)\n\n    def test_proxy_model_has_correct_app_label(self):\n        self.assertEqual(CustomPlugin.model._meta.app_label, 'cmsplugin_cascade')\n"
  },
  {
    "path": "tests/test_http.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\nimport json\nfrom django.conf import settings\nfrom django.contrib import admin\nfrom django.test.utils import override_settings\nfrom cms.models import Page\nfrom cms.utils.compat.dj import is_installed\nfrom cms.test_utils.testcases import CMSTestCase, URL_CMS_PAGE_ADD, URL_CMS_PLUGIN_ADD\nimport pytest\n\nAPPS_WITHOUT_REVERSION = [app for app in settings.INSTALLED_APPS if app != 'reversion']\n\n\nclass ContainerPluginTest(CMSTestCase):\n    def setUp(self):\n        self.admin_site = admin.sites.AdminSite()\n        self.user = self.get_superuser()\n        self.password = \"top_secret\"\n        self.user.set_password(self.password)\n        self.user.save()\n        self.client.login(username=self.user.username, password=self.password)\n        self.language = 'en'\n        self.site_id = settings.SITE_ID\n\n        # create page\n        response = self.client.post(\n            '/' + self.language + URL_CMS_PAGE_ADD[3:],\n            data={\n                'language': self.language,\n                'site': self.site_id,\n                'template': 'INHERIT',\n                'title': 'HOME',\n                'slug': 'home',\n                '_save': 'Save',\n            },\n            follow=True,\n        )\n        self.assertEqual(response.status_code, 200)\n\n        # get page and placeholder\n        self.page = Page.objects.get(publisher_is_draft=True, is_home=True)\n        self.placeholder = self.page.placeholders.get(slot='Main Content')\n\n    def _create_and_configure_a_container_plugin(self):\n        # create a plugin\n        response = self.client.post(\n            '/' + self.language + URL_CMS_PLUGIN_ADD[3:],\n            data={\n                'plugin_parent': '',\n                'plugin_type': 'BootstrapContainerPlugin',\n                'plugin_language': self.language,\n                'placeholder_id': self.placeholder.id,\n            },\n            HTTP_X_REQUESTED_WITH='XMLHttpRequest',\n        )\n        self.assertEqual(response.status_code, 200)\n        response_data = json.loads(response.content.decode('utf-8'))\n        plugin_url = response_data['url']\n\n        # configure that plugin\n        response = self.client.post(\n            plugin_url,\n            data={\n                '_popup': '1',\n                'breakpoints': ['xs', 'lg'],\n                '_save': 'Save',\n            },\n        )\n        self.assertEqual(response.status_code, 200)\n\n    @override_settings(INSTALLED_APPS=APPS_WITHOUT_REVERSION)\n    @pytest.mark.skip(reason=\"no way of currently testing this\")\n    def test_without_reversion(self):\n        self.assertFalse(is_installed('reversion'))\n        self._create_and_configure_a_container_plugin()\n\n    @pytest.mark.skip(reason=\"no way of currently testing this\")\n    def test_with_reversion(self):\n        self.assertTrue(is_installed('reversion'))\n        self._create_and_configure_a_container_plugin()\n"
  },
  {
    "path": "tests/test_iconfont.py",
    "content": "import os\nfrom bs4 import BeautifulSoup\nimport pytest\nimport factory.fuzzy\nfrom pytest_factoryboy import register\nfrom django import VERSION as DJANGO_VERSION\nfrom django.forms.models import ModelForm\nfrom django.urls import reverse, resolve\nfrom django.core.files import File as DjangoFile\nfrom django.template.context import RequestContext\nfrom filer.models.filemodels import File as FilerFileModel\nfrom cms.api import add_plugin\nfrom cms.plugin_rendering import ContentRenderer\nfrom cmsplugin_cascade.models import CascadeElement, IconFont\nfrom cmsplugin_cascade.icon.forms import IconFormMixin\nfrom cmsplugin_cascade.icon.simpleicon import SimpleIconPlugin\nfrom .conftest import UserFactory\n\n\n@register\nclass IconFileFactory(factory.django.DjangoModelFactory):\n    class Meta:\n        model = FilerFileModel\n\n    @classmethod\n    def create(cls, **kwargs):\n        filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')\n        fileobj = DjangoFile(open(filename, 'rb'), name='fontello-b504201f.zip')\n        owner = UserFactory(is_active=True, is_staff=True)\n        filer_fileobj = FilerFileModel.objects.create(\n            owner=owner,\n            original_filename=fileobj.name,\n            file=fileobj,\n        )\n        return filer_fileobj\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef icon_font(admin_client, icon_file_factory):\n    icon_file = icon_file_factory()\n    data = {\n        'identifier': \"Fontellico\",\n        'zip_file': icon_file.id,\n        'is_default': 'on',\n        '_continue': \"Save and continue editing\",\n    }\n    add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')\n    response = admin_client.post(add_iconfont_url, data)\n    assert response.status_code == 302\n    resolver_match = resolve(response.url)\n    assert resolver_match.url_name == 'cmsplugin_cascade_iconfont_change'\n\n    # check the content of the uploaded file\n    if DJANGO_VERSION >= (2, 0):\n        icon_font = IconFont.objects.get(pk=resolver_match.kwargs['object_id'])\n    else:\n        icon_font = IconFont.objects.get(pk=resolver_match.args[0])\n    assert icon_font.identifier == \"Fontellico\"\n    assert icon_font.config_data['name'] == 'fontelico'\n    assert len(icon_font.config_data['glyphs']) == 34\n    return icon_font\n\n\n@pytest.mark.django_db\ndef test_iconfont_change_view(admin_client, icon_font):\n    # check if the uploaded fonts are rendered inside Preview Icons\n    change_url = reverse('admin:cmsplugin_cascade_iconfont_change', args=[icon_font.id])\n    response = admin_client.get(change_url)\n    assert response.status_code == 200\n    soup = BeautifulSoup(response.content, 'lxml')\n    css_prefix = soup.find('div', class_='field-css_prefix').find('div', class_='readonly')\n    assert css_prefix.text == 'icon-'\n    preview_iconfont = soup.find('div', class_='preview-iconfont')\n    icon_items = preview_iconfont.ul.find_all('li')\n    assert len(icon_items) == 34\n    assert icon_items[0].i.attrs['class'] == ['icon-emo-happy']\n    assert icon_items[33].i.attrs['class'] == ['icon-marquee']\n\n\n@pytest.fixture\n@pytest.mark.django_db\ndef simple_icon(admin_site, cms_placeholder, icon_font):\n    \"\"\"Create and edit a SimpleIconPlugin\"\"\"\n    class IconFontForm(IconFormMixin, ModelForm):\n        class Meta(IconFormMixin.Meta):\n            model = CascadeElement\n\n    # add simple icon plugin\n    simple_icon_model = add_plugin(cms_placeholder, SimpleIconPlugin, 'en')\n    assert isinstance(simple_icon_model, CascadeElement)\n\n    # edit simple icon plugin\n    data = {'icon_font': str(icon_font.id), 'symbol': 'icon-skiing'}\n    form = IconFontForm(data=data, instance=simple_icon_model)\n    assert form.is_valid()\n    simple_icon_model = form.save()\n    assert simple_icon_model.glossary['icon_font']['model'] == 'cmsplugin_cascade.iconfont'\n    assert simple_icon_model.glossary['symbol'] == 'icon-skiing'\n    simple_icon_plugin = simple_icon_model.get_plugin_class_instance(admin_site)\n    assert isinstance(simple_icon_plugin, SimpleIconPlugin)\n    return simple_icon_plugin, simple_icon_model\n\n\n@pytest.mark.django_db\ndef test_simple_icon(rf, simple_icon):\n    \"\"\"Render a SimpleIconPlugin\"\"\"\n    simple_icon_plugin, simple_icon_model = simple_icon\n    request = rf.get('/')\n    context = RequestContext(request)\n    content_renderer = ContentRenderer(request)\n    html = content_renderer.render_plugin(simple_icon_model, context).strip()\n    assert html == '<i class=\"icon-icon-skiing\"></i>'\n"
  },
  {
    "path": "tests/test_missingmigrations.py",
    "content": "try:\n    from cStringIO import StringIO\nexcept ImportError:\n    from io import StringIO\nimport pytest\nfrom django.core.management import call_command\n\n\n@pytest.mark.django_db\ndef test_for_missing_migrations():\n    out = StringIO()\n\n    call_command('makemigrations', '--dry-run', 'cmsplugin_cascade', verbosity=3, interactive=False, stdout=out)\n    assert out.getvalue() == \"No changes detected in app 'cmsplugin_cascade'\\n\"\n"
  },
  {
    "path": "tests/test_segmentation.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom bs4 import BeautifulSoup\n\nfrom django.contrib.auth.models import AnonymousUser\nfrom django.contrib.auth import get_user_model\n\nfrom cms.api import add_plugin\nfrom cms.utils.plugins import build_plugin_tree\nfrom djangocms_text_ckeditor.cms_plugins import TextPlugin\nfrom cmsplugin_cascade.generic.simple_wrapper import SimpleWrapperPlugin\nfrom cmsplugin_cascade.segmentation.cms_plugins import SegmentPlugin\n\nfrom .test_base import CascadeTestCase\n\n\nclass SegmentationPluginTest(CascadeTestCase):\n    def setUp(self):\n        super(SegmentationPluginTest, self).setUp()\n        UserModel = get_user_model()\n        self.admin_user = UserModel.objects.get(username='admin')\n        self.staff_user = UserModel.objects.get(username='staff')\n\n    def test_plugin_context(self):\n        # create container\n        wrapper_model = add_plugin(self.placeholder, SimpleWrapperPlugin, 'en',\n            glossary={'tag_type': 'naked'})\n        wrapper_plugin = wrapper_model.get_plugin_class_instance(self.admin_site)\n        self.assertIsInstance(wrapper_plugin, SimpleWrapperPlugin)\n\n        # add an `if`-segment with some text as child\n        if_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,\n                               glossary={'open_tag': 'if', 'condition': 'user.is_superuser'})\n        self.assertIsInstance(if_segment_model.get_plugin_class_instance(), SegmentPlugin)\n        text_model_admin = add_plugin(self.placeholder, TextPlugin, 'en', target=if_segment_model,\n            body='<p>User is admin</p>')\n        self.assertIsInstance(text_model_admin.get_plugin_class_instance(), TextPlugin)\n\n        # add an `elif`-segment with some text as child\n        elif_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,\n                               glossary={'open_tag': 'elif', 'condition': 'user.is_authenticated'})\n        self.assertIsInstance(elif_segment_model.get_plugin_class_instance(), SegmentPlugin)\n        text_model_staff = add_plugin(self.placeholder, TextPlugin, 'en', target=elif_segment_model,\n            body='<p>User is staff</p>')\n        self.assertIsInstance(text_model_staff.get_plugin_class_instance(), TextPlugin)\n\n        # add an `else`-segment with some text as child\n        else_segment_model = add_plugin(self.placeholder, SegmentPlugin, 'en', target=wrapper_model,\n                               glossary={'open_tag': 'else'})\n        self.assertIsInstance(else_segment_model.get_plugin_class_instance(), SegmentPlugin)\n        text_model_anon = add_plugin(self.placeholder, TextPlugin, 'en', target=else_segment_model,\n            body='<p>User is anonymous</p>')\n        self.assertIsInstance(text_model_anon.get_plugin_class_instance(), TextPlugin)\n\n        # build the DOM\n        plugin_list = [wrapper_model, if_segment_model, text_model_admin, elif_segment_model,\n                       text_model_staff, else_segment_model, text_model_anon]\n        build_plugin_tree(plugin_list)\n\n        # test for if-segment (render the plugins as admin user)\n        self.request.user = self.admin_user\n        soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')\n        self.assertHTMLEqual(soup.p.text, 'User is admin')\n\n        # test for elif-segment (render the plugins as staff user)\n        self.request.user = self.staff_user\n        soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')\n        self.assertHTMLEqual(soup.p.text, 'User is staff')\n\n        # test for else-segment (render the plugins as anonymous user)\n        self.request.user = AnonymousUser\n        soup = BeautifulSoup(self.get_html(wrapper_model, self.get_request_context()), 'html.parser')\n        self.assertHTMLEqual(soup.p.text, 'User is anonymous')\n"
  },
  {
    "path": "tests/test_strides.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nimport json\nimport django\nimport os\n\nfrom bs4 import BeautifulSoup\n\nfrom django.contrib.auth import get_user_model\nfrom django.core.files.uploadedfile import SimpleUploadedFile\nfrom django.urls import reverse\nfrom django.template import RequestContext, Template\nfrom django.test import RequestFactory\n\nfrom cmsplugin_cascade.models import IconFont\nfrom filer.admin.clipboardadmin import ajax_upload\n\nfrom .test_base import CascadeTestCase\n\n\nclass StridePluginTest(CascadeTestCase):\n    def setUp(self):\n        super().setUp()\n        request = RequestFactory().get('/')\n        self.context = RequestContext(request, {})\n\n    def assertStyleEqual(self, provided, expected):\n        styles = dict((pair.split(':')[0].strip(), pair.split(':')[1].strip())\n                      for pair in provided.split(';') if ':' in pair)\n        self.assertDictEqual(styles, expected)\n\n    def skiptest_bootstrap_jumbotron(self):\n        template = Template('{% load cascade_tags sekizai_tags %}{% render_block \"css\" %}{% render_cascade \"strides/bootstrap-jumbotron.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        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 )\n        element = soup.find(id='cascadeelement_id-1501')\n        self.assertEqual(element.h1.text, \"Manage your website\")\n        self.assertStyleEqual(element.h1.attrs['style'], {'text-align': 'center'})\n\n    def test_bootstrap_container(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/bootstrap-container.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        element = soup.find(class_='container')\n        self.assertSetEqual(set(element.attrs['class']), {'foo', 'bar', 'container'})\n\n    def skiptest_bootstrap_row(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/bootstrap-row.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        self.assertEqual(str(soup.div), '<div class=\"row\" style=\"margin-bottom: 20px;\">\\n</div>')\n\n    def skiptest_bootstrap_column(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/bootstrap-column.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        self.assertEqual(str(soup.div), '<div class=\"col-xs-12\">\\n</div>')\n\n    def skiptest_simple_wrapper(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/simple-wrapper.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        expected_styles = {\n            'background-color':'#42c8c6',\n            'color':'#ffffff',\n            'height':'360px',\n            'padding-left':'50px',\n            'padding-right':'50px',\n        }\n        self.assertStyleEqual(soup.div.attrs['style'], expected_styles)\n\n    def upload_icon_font(self):\n        UserModel = get_user_model()\n        admin_user = UserModel.objects.get(username='admin')\n        with self.login_user_context(admin_user):\n            filename = os.path.join(os.path.dirname(__file__), 'assets/fontello-b504201f.zip')\n            with open(filename, 'rb') as zipfile:\n                uploaded_file = SimpleUploadedFile('fontello-b504201f.zip', zipfile.read(), content_type='application/zip')\n            request = self.get_request(reverse('admin:filer-ajax_upload'))\n            request.FILES.update(file=uploaded_file)\n            response = ajax_upload(request)\n            self.assertEqual(response.status_code, 200)\n            content = json.loads(response.content.decode('utf-8'))\n\n            # save the form and submit the remaining fields\n            add_iconfont_url = reverse('admin:cmsplugin_cascade_iconfont_add')\n            data = {\n                'identifier': \"Fontellico\",\n                'zip_file': content['file_id'],\n                '_continue': \"Save and continue editing\",\n            }\n            response = self.client.post(add_iconfont_url, data)\n            self.assertEqual(response.status_code, 302)\n        self.assertEqual(IconFont.objects.count(), 1)\n\n    def test_framed_icon(self):\n        self.upload_icon_font()\n        icon_font = IconFont.objects.first()\n        icon_font.id = 1  # to match id in fixture \"strides/framed-icon.json\"\n        icon_font.save()\n\n        template = Template('{% load cascade_tags sekizai_tags %}{% render_block \"css\" %}{% render_cascade \"strides/framed-icon.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        self.assertSetEqual(set(soup.div.attrs['class']), {'text-center'})\n        self.assertStyleEqual(soup.div.attrs['style'], {'font-size': '10em'})\n        expected_style = {\n            'color': '#ffffff',\n            'display': 'inline-block',\n        }\n        self.assertStyleEqual(soup.div.span.attrs['style'], expected_style)\n\n    def test_text_plugin(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/text-plugin.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        self.assertEqual(soup.h2.text, \"Customizable\")\n        self.assertStyleEqual(soup.h2.attrs['style'], {'text-align': 'center'})\n        self.assertEqual(soup.p.text, \"Lorem ipsum dolor\")\n\n    def test_carousel_plugin(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/carousel-plugin.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        carousel = soup.find(class_='carousel')\n        self.assertSetEqual(set(carousel.attrs['class']), {'carousel', 'slide', 'pause', 'wrap', 'slide'})\n        self.assertListEqual(carousel.ol.attrs['class'], ['carousel-indicators'])\n        self.assertListEqual(carousel.ol.li.attrs['class'], ['active'])\n        slide = carousel.find(class_='carousel-inner')\n        self.assertSetEqual(set(slide.div.attrs['class']), {'carousel-item', 'active'})       \n\n    def test_button_plugin(self):\n        template = Template('{% load cascade_tags %}{% render_cascade \"strides/bootstrap-button.json\" %}')\n        html = template.render(self.context)\n        soup = BeautifulSoup(html, features='lxml')\n        button = soup.find(class_='btn')\n        self.assertSetEqual(set(button.attrs['class']), {'btn', 'btn-secondary'})\n"
  },
  {
    "path": "tests/urls.py",
    "content": "from django import VERSION as DJANGO_VERSION\nfrom django.conf.urls.i18n import i18n_patterns\nfrom django.contrib import admin\n\nif DJANGO_VERSION < (2, 0):\n    from django.conf.urls import url, include\n\n    urlpatterns = i18n_patterns(\n        url(r'^admin/', admin.site.urls),\n        url(r'^', include('cms.urls')),\n    )\n\nif DJANGO_VERSION >= (2, 0):\n    from django.urls import path, include\n\n    urlpatterns = i18n_patterns(\n        path('admin/', admin.site.urls),\n        path('', include('cms.urls')),\n    )\n"
  },
  {
    "path": "tests/utils.py",
    "content": "from django.template import RequestContext\n\n\ndef get_request_context(request, extra_context=None):\n    if extra_context is None:\n        extra_context = {}\n    context = RequestContext(request, extra_context)\n    # XXX: Workaround for an issue with django-cms that we do not fully\n    # understand yet. It seems that the template context processors are not\n    # run early enough, so context['request'] is not available when\n    # django-cms tries to access it. We should do further analysis on this.\n    context['request'] = request\n    # The same problem seems to exist with request.user.\n    context['user'] = request.user\n    return context\n"
  }
]