pax_global_header00006660000000000000000000000064144751717200014522gustar00rootroot0000000000000052 comment=59fec9e388eca203a3d73a5133f8a1130aee07bd django-jinja-2.11.0/000077500000000000000000000000001447517172000141365ustar00rootroot00000000000000django-jinja-2.11.0/.github/000077500000000000000000000000001447517172000154765ustar00rootroot00000000000000django-jinja-2.11.0/.github/workflows/000077500000000000000000000000001447517172000175335ustar00rootroot00000000000000django-jinja-2.11.0/.github/workflows/ci.yml000066400000000000000000000010361447517172000206510ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master schedule: - cron: 33 3 * * MON # Run Mondays at 03:33 UTC jobs: Build: runs-on: ubuntu-20.04 strategy: matrix: python-version: - '3.8' - '3.9' - '3.10' - '3.11' steps: - uses: actions/setup-python@v2 with: python-version: '${{ matrix.python-version }}' - uses: actions/checkout@v2 - run: pip install tox tox-gh-actions - run: tox django-jinja-2.11.0/.gitignore000066400000000000000000000002631447517172000161270ustar00rootroot00000000000000*.pyc .*swp build/ dist/ superview.egg-info/ django_superview.egg-info versiontools-*.egg *.egg-info .project .pydevproject .idea .settings /testing/venv doc/index.html .tox env/ django-jinja-2.11.0/CHANGES.adoc000066400000000000000000000217621447517172000160460ustar00rootroot00000000000000Changelog ========= Version 2.11.0 -------------- _Released September 3rd, 2023_ - Drop Django 2.2 support, now require >=3.2. - Drop Python 3.6 and 3.7 support, now require >=3.8. - Add Django 4.1 and 4.2 support. - Add Python 3.11 support. - Provide a better default template engine NAME than 'backend' (#303): Previously, when configuring `TEMPLATES` in Django's settings, `NAME` had to be set to avoid the template engine's name becoming `"backend"`: [source,python] ---- TEMPLATES = [ { "NAME": "jinja2", "BACKEND": "django_jinja.backend.Jinja2", ---- If your code matches that pattern, it can now be simplified to: [source,python] ---- TEMPLATES = [ { "BACKEND": "django_jinja.jinja2.Jinja2", ---- There are no plans to remove support for the old `backend` import path, for consideration of existing projects. Also, be careful if you've set `NAME` to `"jinja"` (not `"jinja2"`)! Version 2.10.2 -------------- _Released May 12th, 2022_ - Fix compatibility with Jinja2 3.1 in tests (#296). Version 2.10.1 -------------- _Released May 12th, 2022_ - Update links in documentation to point to new home of Jinja2 docs (#295). Version 2.10.0 -------------- _Released December 10th, 2021_ - Fix usage of `unittest` to be ready for Python 3.11 (#288). - Fix `default_app_config` deprecation warning for Django 3.2 (#289). - Replace Travis with GitHub Actions, dropping `ppc64le` arch tests (#290). * Added Python 3.10 to tests (Django supports it if >=3.2.9). - Run pyupgrade, remove compatibility code (#291). - Add support for Django 4.0, with more old-code cleanup (#292). * Remove use of `django.conf.urls.url` in test app (gone in Django 4.0). * Set `DEFAULT_AUTO_FIELD` to avoid warnings in test app. * Remove `django_jinja.contrib._pipeline` package, which was deprecated in 2015. * Drop formal Django 3.0 and 3.1 support. ** Projects using these versions should still function until support for 2.2 is dropped. - Django 2.2 and Python 3.6 will likely be dropped in the next non-patch release. Version 2.9.1 ------------- _Released September 4th, 2021_ - Complete config example in the docs now reflects actual project defaults (#286). - `get_template` no longer calls `template_name.endswith` twice under the default setup. - Rewrite usage and template matching config sections in the docs, to fully explain behavior. Version 2.9.0 ------------- _Released July 2nd, 2021_ - Jinja2 policies now settable via `TEMPLATES[n]['OPTIONS']['policies']` (#285). - `makemessages` now respects the `ext.i18n.trimmed` policy, will automatically trim jinja `{% trans %}` blocks when generating `.po` files. Version 2.8.0 ------------- _Released May 24th, 2021_ - Upgrade to jinja2 3.0, dropping support for 2.11 and below (#279). - Dropped Python 3.5 support. Version 2.7.1 ------------- _Released April 16th, 2021_ - Fixed `makemessages` command, which in 2.7.0 could not detect `{% trans %}` tags in Django templates (#272). - Fixed `{% cache %}` tag to allow a timeout of `None` (to cache forever), which Django's tag added in 2.0 (#274). - Fixed README not displaying in project description at PyPI (#276). - Added Django 3.2 support. Version 2.7.0 ------------- _Released August 20th, 2020_ - Fixed loading template names with backslashes on Windows (#249). - Added Django's `json_script` filter for Django 2.1 and higher. - Fixed docs site stylesheet. - Added Django 3.1 support. - Removed Django 1.11 support. - Added Python 3.9 (rc1) to test suite. - Clarified "not recommended" usage of context processors with django-jinja in the docs. Version 2.6.0 ------------- _Released February 1st, 2020_ - Documented compatibility changes made in version 2.5.0. - Cut new release to reflect this in package metadata. Version 2.5.0 ------------- - Fix compatibility issues with Django 3.0, minimum version now 1.11. - Dropped support for Python 2.7, 3.4, adding support through 3.8. Version 2.4.2 ------------- - Added `Template.stream` method to use with StreamingHttpResponse. Version 2.4.1 ------------- - Minor improvements on `makemessages`. Version 2.4.0 ------------- - Revert the 2.3.1 change because importing jinja templates from django is not intended feature and that change breaks the django template object signature. Version 2.3.1 ------------- - Add minor fixes allowing creating templatetags that can load jinja2 templates into django templates (refer to issues #94 and #201 for more information). Version 2.3.0 ------------- - Fix compatibility issues with django 1.11 Version 2.2.2 ------------- - Fix many warnings for django 2.0 Version 2.2.1 ------------- - Fix compatibility issues with django 1.9. Version 2.2.0 ------------- - Remove `removetags` filter (making compatible with django 1.10). Version 2.1.3 ------------- - Fix support passing in django template contexts. - Fix template name matching on tests. Version 2.1.2 ------------- - Fix compatibility issues with django 1.8 - Fix unicode decode error on cache tag. Version 2.1.1 ------------- - Improve makemessages command. Version 2.1.0 ------------- - Fix support for django debug toolbar 1.4 - Improve syntax error reporting. - Improve debug instrumentation. Version 2.0.0 ------------- - Remove django < 1.8 compatibility. - Major code refactor for make it now more simpler. Version 1.4.2 ------------- - Minor fix on extensions. Version 1.4.1 ------------- - Add missing import on setup.py of views. Version 1.4.0 ------------- - Add generic views helpers (thanks to @sbutler). - Minor fixes on imports. - Add helper for set the "undefined" parameter in a easy way. - Add a simple way to add extensions from apps. - Fixed bug related to csrf_token. Version 1.3.3 ------------- - Fix django 1.8 compatibilities. - Fix documentation issues. - Remove obsolete code. Version 1.3.2 ------------- - Do not load django < 1.7 setup related settings for django 1.8 backend. - Fix django-debug-toolbar compatibility. Version 1.3.1 ------------- - Fix bug related to doble inclusion of DEFAULT_EXTENSIONS. - Remove the extra django filters extension and document the change. Version 1.3.0 ------------- - Now all builtin filters, and global functions are implemented using jinja2 extensions. - Breaking change: JINJA2_FILTERS_REPLACE_FROM_DJANGO is removed Version 1.2.1 ------------- - Improved JINJA2_LOADER handling for django <= 1.7 - Add documentation entry for JINJA2_LOADER. Version 1.2.0 ------------- - Allow set custom module as translation engine. (by @toshka) Version 1.1.1 ------------- - Fix typos on function names. - Honor Django's `setting_changed` signal to reinitialize the Jinja2 environment. Thanks to @akx Version 1.1.0 ------------- - Code base refactored. - Add django 1.8 support. - Remove `fix_ampersands` filter. - Fix linebreaksbr autoescape bug. - Start using `jinja2.DebugUndefined` when settings.DEBUG is True. Version 1.0.5 ------------- - Fix template loaders order. - Convert documentation to asciidoctor. - Move changelog to separated file. Version 1.0.4 ------------- - Add render_with decorator as replacement for django inclusion_tag. - Reorder how builtin functions/filters are setted making easy overwrite them. Version 1.0.3 ------------- - Add timezone template filters and template global functions: localtime, tz and timezone. Version 1.0.2 ------------- - Fix bug with application loading with django < 1.7 Version 1.0.1 ------------- - Fix bug introduced in previous version on `easy_thumbnails` contrib app. Version 1.0.0 ------------- - Major code cleanup. - Full django 1.7+ support - Add JINJA2_CONSTANTS settings. Version 0.25 ------------ - Enable newstyle gettext by default. - Add settings for easy disable newstyle gettext. Version 0.24 ------------ - Fix django 1.7 warnings on run tests. - Add all rest methods to error views (403, 404, 500). Version 0.23 ------------ - Add settings JINJA2_FILTERS_REPLACE_FROM_DJANGO - Add settings JINJA2_MUTE_URLRESOLVE_EXCEPTIONS - Improvements on cache tag. - Other bugfixes. Version 0.22 ------------ - Change template order selection. - New contrib: subdomains - New contrib: dajax-ice - Documentation fixes. - Minor improvements. Version 0.21 ------------ - Remove obsolete __version__ variable from __init__.py file. - Add bytecode cache with django cache framework support. Version 0.20 ------------ - Introduce backward incompatible change: all contrib apps are renamed (prepened _ on each module name) for avoid name conflicts with the original package. Version 0.19 ------------ - Bugfixes related to autoescape. Version 0.18 ------------ - Test singnal when stream template method is used. Version 0.17 ------------ - Add 4xx/500 django special views. Version 0.16 ------------ - Remove distribute dependency. Version 0.15 ------------ - Put autoescape ON by default. - Add easy_thumbnails contrib app - Add django humanize contrib app Version 0.14 ------------ - Add jinja2 extensions loading by default Version 0.13 ------------ - New intercept method by regex is added. - Documentation improvements. django-jinja-2.11.0/LICENSE000066400000000000000000000027221447517172000151460ustar00rootroot00000000000000Copyright (c) 2012-2013 Andrey Antukh Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-jinja-2.11.0/README.rst000066400000000000000000000014411447517172000156250ustar00rootroot00000000000000django-jinja ============ Simple and nonobstructive jinja2 integration with Django. .. image:: https://github.com/niwinz/django-jinja/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/niwinz/django-jinja/actions .. image:: https://img.shields.io/pypi/v/django-jinja.svg?style=flat :target: https://pypi.python.org/pypi/django-jinja **Documentation:** http://niwinz.github.io/django-jinja/latest/ How to install? --------------- You can install it with pip: .. code-block:: shell pip install django-jinja How to run tests as a developer ------------------------------- Install the Tox automation tool (outside a virtualenv), then .. code-block:: shell tox Tox will create virtualenvs for different interpreter versions and run the test suite. django-jinja-2.11.0/django_jinja/000077500000000000000000000000001447517172000165535ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/__init__.py000066400000000000000000000000001447517172000206520ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/apps.py000066400000000000000000000003471447517172000200740ustar00rootroot00000000000000from django.apps import AppConfig from django_jinja import base class DjangoJinjaAppConfig(AppConfig): name = "django_jinja" verbose_name = "Django Jinja" def ready(self): base.patch_django_for_autoescape() django-jinja-2.11.0/django_jinja/backend.py000066400000000000000000000307031447517172000205170ustar00rootroot00000000000000""" Since django 1.8.x, django comes with native multiple template engine support. It also comes with jinja2 backend, but it is slightly unflexible, and it does not support by default all django filters and related stuff. This is an implementation of django backend inteface for use django_jinja easy with django 1.8. """ import sys import os import os.path as path import functools from importlib import import_module import jinja2 from django.conf import settings from django.core import signals from django.core.exceptions import ImproperlyConfigured from django.dispatch import receiver from django.middleware import csrf from django.template import TemplateDoesNotExist from django.template import TemplateSyntaxError from django.template.backends.base import BaseEngine from django.template.context import BaseContext from django.utils.encoding import smart_str from django.utils.functional import SimpleLazyObject from django.utils.functional import cached_property from django.utils.module_loading import import_string from django.utils.safestring import mark_safe from . import base from . import builtins from . import library from . import utils class Origin: """ A container to hold debug information as described in the template API documentation. """ def __init__(self, name, template_name): self.name = name self.template_name = template_name class Template: def __init__(self, template, backend): self.template = template self.backend = backend self.name = template.name self.origin = Origin( name=template.filename, template_name=template.name ) def render(self, context=None, request=None): return mark_safe(self._process_template(self.template.render, context, request)) def stream(self, context=None, request=None): return self._process_template(self.template.stream, context, request) def _process_template(self, handler, context=None, request=None): if context is None: context = {} context = base.dict_from_context(context) if request is not None: def _get_val(): token = csrf.get_token(request) if token is None: return 'NOTPROVIDED' else: return smart_str(token) context["request"] = request context["csrf_token"] = SimpleLazyObject(_get_val) # Support for django context processors for processor in self.backend.context_processors: context.update(processor(request)) if self.backend._tmpl_debug: from django.test import signals # Define a "django" like context for emitatet the multi # layered context object. This is mainly for apps like # django-debug-toolbar that are very coupled to django's # internal implementation of context. if not isinstance(context, BaseContext): class CompatibilityContext(dict): @property def dicts(self): return [self] context = CompatibilityContext(context) signals.template_rendered.send(sender=self, template=self, context=context) return handler(context) class Jinja2(BaseEngine): app_dirname = "templates" @staticmethod @functools.lru_cache() def get_default(): """ When only one django-jinja backend is configured, returns it. Raises ImproperlyConfigured otherwise. This is required for finding the match extension where the developer does not specify a template_engine on a TemplateResponseMixin subclass. """ from django.template import engines jinja_engines = [engine for engine in engines.all() if isinstance(engine, Jinja2)] if len(jinja_engines) == 1: # Unwrap the Jinja2 engine instance. return jinja_engines[0] elif len(jinja_engines) == 0: raise ImproperlyConfigured( "No Jinja2 backend is configured.") else: raise ImproperlyConfigured( "Several Jinja2 backends are configured. " "You must select one explicitly.") def __init__(self, params): params = params.copy() options = params.pop("OPTIONS", {}).copy() self.app_dirname = options.pop("app_dirname", "templates") super().__init__(params) newstyle_gettext = options.pop("newstyle_gettext", True) context_processors = options.pop("context_processors", []) match_extension = options.pop("match_extension", ".jinja") match_regex = options.pop("match_regex", None) environment_clspath = options.pop("environment", "jinja2.Environment") extra_filters = options.pop("filters", {}) extra_tests = options.pop("tests", {}) extra_globals = options.pop("globals", {}) extra_constants = options.pop("constants", {}) translation_engine = options.pop("translation_engine", "django.utils.translation") policies = options.pop("policies", {}) tmpl_debug = options.pop("debug", settings.DEBUG) bytecode_cache = options.pop("bytecode_cache", {}) bytecode_cache.setdefault("name", "default") bytecode_cache.setdefault("enabled", False) bytecode_cache.setdefault("backend", "django_jinja.cache.BytecodeCache") undefined = options.pop("undefined", None) if undefined is not None: if isinstance(undefined, str): options["undefined"] = utils.load_class(undefined) else: options["undefined"] = undefined if settings.DEBUG: options.setdefault("undefined", jinja2.DebugUndefined) else: options.setdefault("undefined", jinja2.Undefined) environment_cls = import_string(environment_clspath) if isinstance(options.get("loader"), str): # Allow to specify a loader as string loader_cls = import_string(options.pop("loader")) else: # Backward compatible default loader_cls = jinja2.FileSystemLoader options.setdefault("loader", loader_cls(self.template_dirs)) options.setdefault("extensions", builtins.DEFAULT_EXTENSIONS) options.setdefault("auto_reload", settings.DEBUG) options.setdefault("autoescape", True) self.env = environment_cls(**options) # Initialize i18n support if settings.USE_I18N: translation = import_module(translation_engine) self.env.install_gettext_translations(translation, newstyle=newstyle_gettext) else: self.env.install_null_translations(newstyle=newstyle_gettext) self._context_processors = context_processors self._match_regex = match_regex self._match_extension = match_extension self._tmpl_debug = tmpl_debug self._bytecode_cache = bytecode_cache self._initialize_builtins(filters=extra_filters, tests=extra_tests, globals=extra_globals, constants=extra_constants) self._initialize_policies(policies) self._initialize_thirdparty() self._initialize_bytecode_cache() def _initialize_bytecode_cache(self): if self._bytecode_cache["enabled"]: cls = utils.load_class(self._bytecode_cache["backend"]) self.env.bytecode_cache = cls(self._bytecode_cache["name"]) def _initialize_thirdparty(self): """ Iterate over all available apps in searching and preloading available template filters or functions for jinja2. """ for app_path, mod_path in base._iter_templatetags_modules_list(): if not path.isdir(mod_path): continue for filename in filter(lambda x: x.endswith(".py") or x.endswith(".pyc"), os.listdir(mod_path)): # Exclude __init__.py files if filename == "__init__.py" or filename == "__init__.pyc": continue file_mod_path = f"{app_path}.templatetags.{filename.rsplit('.', 1)[0]}" try: import_module(file_mod_path) except ImportError: pass library._update_env(self.env) def _initialize_builtins(self, filters=None, tests=None, globals=None, constants=None): def insert(data, name, value): if isinstance(value, str): data[name] = import_string(value) else: data[name] = value if filters: for name, value in filters.items(): insert(self.env.filters, name, value) if tests: for name, value in tests.items(): insert(self.env.tests, name, value) if globals: for name, value in globals.items(): insert(self.env.globals, name, value) if constants: for name, value in constants.items(): self.env.globals[name] = value def _initialize_policies(self, policies): # Set policies like those in jinja2.defaults.DEFAULT_POLICIES for name, value in policies.items(): self.env.policies[name] = value @cached_property def context_processors(self): return tuple(import_string(path) for path in self._context_processors) @property def match_extension(self): return self._match_extension def from_string(self, template_code): return Template(self.env.from_string(template_code), self) def match_template(self, template_name): return base.match_template(template_name, self._match_extension, self._match_regex) def get_template(self, template_name): if not self.match_template(template_name): message = f"Template {template_name} does not exists" raise TemplateDoesNotExist(message) try: return Template(self.env.get_template(template_name), self) except jinja2.TemplateNotFound as exc: # Unlike django's template engine, jinja2 doesn't like windows-style path separators. # But because django does, its docs encourage the usage of os.path.join(). # Rather than insisting that our users switch to posixpath.join(), this try block # will attempt to retrieve the template path again with forward slashes on windows: if os.name == 'nt' and '\\' in template_name: try: return self.get_template(template_name.replace("\\", "/")) except jinja2.TemplateNotFound: pass exc = TemplateDoesNotExist(exc.name, backend=self) utils.reraise( TemplateDoesNotExist, exc, sys.exc_info()[2], ) except jinja2.TemplateSyntaxError as exc: new = TemplateSyntaxError(exc.args) new.template_debug = get_exception_info(exc) utils.reraise(TemplateSyntaxError, new, sys.exc_info()[2]) @receiver(signals.setting_changed) def _setting_changed(sender, setting, *args, **kwargs): """ Reset the Jinja2.get_default() cached when TEMPLATES changes. """ if setting == "TEMPLATES": Jinja2.get_default.cache_clear() def get_exception_info(exception): """ Formats exception information for display on the debug page using the structure described in the template API documentation. """ context_lines = 10 lineno = exception.lineno if exception.source is None: if os.path.exists(exception.filename): with open(exception.filename) as f: source = f.read() else: source = exception.source lines = list(enumerate(source.strip().split("\n"), start=1)) during = lines[lineno - 1][1] total = len(lines) top = max(0, lineno - context_lines - 1) bottom = min(total, lineno + context_lines) return { 'name': exception.filename, 'message': exception.message, 'source_lines': lines[top:bottom], 'line': lineno, 'before': '', 'during': during, 'after': '', 'total': total, 'top': top, 'bottom': bottom, } django-jinja-2.11.0/django_jinja/base.py000066400000000000000000000064271447517172000200500ustar00rootroot00000000000000import re import os.path as path from importlib import import_module from django.template.context import BaseContext def dict_from_context(context): """ Converts context to native python dict. """ if isinstance(context, BaseContext): new_dict = {} for i in reversed(list(context)): new_dict.update(dict_from_context(i)) return new_dict return dict(context) def _iter_templatetags_modules_list(): """ Get list of modules that contains templatetags submodule. """ from django.apps import apps all_modules = [x.name for x in apps.get_app_configs()] for app_path in all_modules: try: mod = import_module(app_path + ".templatetags") # Empty folders can lead to unexpected behavior with Python 3. # We make sure to have the `__file__` attribute. if getattr(mod, '__file__', None) is not None: yield (app_path, path.dirname(mod.__file__)) except ImportError: pass def patch_django_for_autoescape(): """ Patch django modules for make them compatible with jinja autoescape implementation. """ from django.utils import safestring from django.forms.boundfield import BoundField from django.forms.utils import ErrorList from django.forms.utils import ErrorDict if hasattr(safestring, "SafeText"): if not hasattr(safestring.SafeText, "__html__"): safestring.SafeText.__html__ = lambda self: str(self) if hasattr(safestring, "SafeString"): if not hasattr(safestring.SafeString, "__html__"): safestring.SafeString.__html__ = lambda self: str(self) if hasattr(safestring, "SafeUnicode"): if not hasattr(safestring.SafeUnicode, "__html__"): safestring.SafeUnicode.__html__ = lambda self: str(self) if hasattr(safestring, "SafeBytes"): if not hasattr(safestring.SafeBytes, "__html__"): safestring.SafeBytes.__html__ = lambda self: str(self) if not hasattr(BoundField, "__html__"): BoundField.__html__ = lambda self: str(self) if not hasattr(ErrorList, "__html__"): ErrorList.__html__ = lambda self: str(self) if not hasattr(ErrorDict, "__html__"): ErrorDict.__html__ = lambda self: str(self) def get_match_extension(using=None): """ Gets the extension that the template loader will match for django-jinja. This returns Jinja2.match_extension. The "using" parameter selects with Jinja2 backend to use if you have multiple ones configured in settings.TEMPLATES. If it is None and only one Jinja2 backend is defined then it will use that, otherwise an ImproperlyConfigured exception is thrown. """ from .backend import Jinja2 from django.template import engines if using is None: engine = Jinja2.get_default() else: engine = engines[using] return engine.match_extension def match_template(template_name, extension, regex): if extension: matches_extension = template_name.endswith(extension) if regex: return matches_extension and re.match(regex, template_name) else: return matches_extension elif regex: return re.match(regex, template_name) else: return True django-jinja-2.11.0/django_jinja/builtins/000077500000000000000000000000001447517172000204045ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/builtins/__init__.py000066400000000000000000000007641447517172000225240ustar00rootroot00000000000000DEFAULT_EXTENSIONS = [ "jinja2.ext.do", "jinja2.ext.loopcontrols", "jinja2.ext.i18n", "django_jinja.builtins.extensions.DebugExtension", "django_jinja.builtins.extensions.CsrfExtension", "django_jinja.builtins.extensions.CacheExtension", "django_jinja.builtins.extensions.TimezoneExtension", "django_jinja.builtins.extensions.UrlsExtension", "django_jinja.builtins.extensions.StaticFilesExtension", "django_jinja.builtins.extensions.DjangoFiltersExtension", ] django-jinja-2.11.0/django_jinja/builtins/extensions.py000066400000000000000000000234101447517172000231550ustar00rootroot00000000000000import logging import pprint from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key from django.urls import NoReverseMatch from django.urls import reverse from django.utils.encoding import force_str from jinja2.nodes import ContextReference from jinja2 import TemplateSyntaxError from jinja2 import pass_context from jinja2 import nodes from jinja2.ext import Extension from markupsafe import Markup JINJA2_MUTE_URLRESOLVE_EXCEPTIONS = getattr(settings, "JINJA2_MUTE_URLRESOLVE_EXCEPTIONS", False) logger = logging.getLogger(__name__) class CsrfExtension(Extension): tags = {'csrf_token'} def __init__(self, environment): self.environment = environment def parse(self, parser): lineno = parser.stream.expect('name:csrf_token').lineno call = self.call_method( '_render', [nodes.Name('csrf_token', 'load', lineno=lineno)], lineno=lineno ) return nodes.Output([nodes.MarkSafe(call)]) def _render(self, csrf_token): if csrf_token: if csrf_token == 'NOTPROVIDED': return Markup("") return Markup(f"") if settings.DEBUG: import warnings warnings.warn("A {% csrf_token %} was used in a template, but the context " "did not provide the value. This is usually caused by not " "using RequestContext.") return '' class CacheExtension(Extension): """ Exactly like Django's own tag, but supports full Jinja2 expressiveness for all arguments. {% cache gettimeout()*2 "foo"+options.cachename %} ... {% endcache %} General Syntax: {% cache [expire_time] [fragment_name] [var1] [var2] .. %} .. some expensive processing .. {% endcache %} Available by default (does not need to be loaded). Partly based on the ``FragmentCacheExtension`` from the Jinja2 docs. """ tags = {'cache'} def parse(self, parser): lineno = next(parser.stream).lineno expire_time = parser.parse_expression() fragment_name = parser.parse_expression() vary_on = [] while not parser.stream.current.test('block_end'): vary_on.append(parser.parse_expression()) body = parser.parse_statements(['name:endcache'], drop_needle=True) return nodes.CallBlock( self.call_method('_cache_support', [expire_time, fragment_name, nodes.List(vary_on), nodes.Const(lineno)]), [], [], body).set_lineno(lineno) def _cache_support(self, expire_time, fragm_name, vary_on, lineno, caller): try: if expire_time is not None: expire_time = int(expire_time) except (ValueError, TypeError): raise TemplateSyntaxError( f'"{list(self.tags)[0]}" tag got a non-integer timeout value: {expire_time!r}', lineno, ) cache_key = make_template_fragment_key(fragm_name, vary_on) value = cache.get(cache_key) if value is None: value = caller() cache.set(cache_key, force_str(value), expire_time) else: value = force_str(value) return value class DebugExtension(Extension): """ A ``{% debug %}`` tag that dumps the available variables, filters and tests. Typical usage like this: .. codeblock:: html+jinja
{% debug %}
produces output like this: :: {'context': {'_': , 'csrf_token': , 'cycler': , ... 'view': }, 'filters': ['abs', 'add', 'addslashes', 'attr', 'batch', 'bootstrap', 'bootstrap_classes', 'bootstrap_horizontal', 'bootstrap_inline', ... 'yesno'], 'tests': ['callable', 'checkbox_field', 'defined', 'divisibleby', 'escaped', 'even', 'iterable', 'lower', 'mapping', 'multiple_checkbox_field', ... 'string', 'undefined', 'upper']} """ tags = {'debug'} def __init__(self, environment): super().__init__(environment) def parse(self, parser): lineno = parser.stream.expect('name:debug').lineno context = ContextReference() call = self.call_method('_render', [context], lineno=lineno) return nodes.Output([nodes.MarkSafe(call)]) def _render(self, context): result = { 'filters': sorted(self.environment.filters.keys()), 'tests': sorted(self.environment.tests.keys()), 'context': context.get_all() } # # We set the depth since the intent is basically to show the top few # names. TODO: provide user control over this? # text = pprint.pformat(result, depth=3, compact=True) return Markup.escape(text) class StaticFilesExtension(Extension): def __init__(self, environment): super().__init__(environment) environment.globals["static"] = self._static def _static(self, path): return staticfiles_storage.url(path) class UrlsExtension(Extension): def __init__(self, environment): super().__init__(environment) environment.globals["url"] = self._url_reverse @pass_context def _url_reverse(self, context, name, *args, **kwargs): try: current_app = context["request"].current_app except AttributeError: try: current_app = context["request"].resolver_match.namespace except AttributeError: current_app = None except KeyError: current_app = None try: return reverse(name, args=args, kwargs=kwargs, current_app=current_app) except NoReverseMatch as exc: logger.error('Error: %s', exc) if not JINJA2_MUTE_URLRESOLVE_EXCEPTIONS: raise return '' return reverse(name, args=args, kwargs=kwargs) from . import filters class TimezoneExtension(Extension): def __init__(self, environment): super().__init__(environment) environment.globals["utc"] = filters.utc environment.globals["timezone"] = filters.timezone environment.globals["localtime"] = filters.localtime class DjangoFiltersExtension(Extension): def __init__(self, environment): super().__init__(environment) environment.filters["static"] = filters.static environment.filters["reverseurl"] = filters.reverse environment.filters["addslashes"] = filters.addslashes environment.filters["capfirst"] = filters.capfirst environment.filters["escapejs"] = filters.escapejs_filter environment.filters["floatformat"] = filters.floatformat environment.filters["iriencode"] = filters.iriencode environment.filters["linenumbers"] = filters.linenumbers environment.filters["make_list"] = filters.make_list environment.filters["slugify"] = filters.slugify environment.filters["stringformat"] = filters.stringformat environment.filters["truncatechars"] = filters.truncatechars environment.filters["truncatechars_html"] = filters.truncatechars_html environment.filters["truncatewords"] = filters.truncatewords environment.filters["truncatewords_html"] = filters.truncatewords_html environment.filters["urlizetrunc"] = filters.urlizetrunc environment.filters["ljust"] = filters.ljust environment.filters["rjust"] = filters.rjust environment.filters["cut"] = filters.cut environment.filters["linebreaksbr"] = filters.linebreaksbr environment.filters["linebreaks"] = filters.linebreaks_filter environment.filters["striptags"] = filters.striptags environment.filters["add"] = filters.add environment.filters["date"] = filters.date environment.filters["time"] = filters.time environment.filters["timesince"] = filters.timesince_filter environment.filters["timeuntil"] = filters.timeuntil_filter environment.filters["default_if_none"] = filters.default_if_none environment.filters["divisibleby"] = filters.divisibleby environment.filters["yesno"] = filters.yesno environment.filters["pluralize"] = filters.pluralize environment.filters["localtime"] = filters.localtime environment.filters["utc"] = filters.utc environment.filters["timezone"] = filters.timezone environment.filters["json_script"] = filters.json_script class DjangoExtraFiltersExtension(Extension): def __init__(self, environment): super().__init__(environment) environment.filters["title"] = filters.title environment.filters["upper"] = filters.upper environment.filters["lower"] = filters.lower environment.filters["urlencode"] = filters.urlencode environment.filters["urlize"] = filters.urlize environment.filters["wordcount"] = filters.wordcount environment.filters["wordwrap"] = filters.wordwrap environment.filters["center"] = filters.center environment.filters["join"] = filters.join environment.filters["length"] = filters.length environment.filters["random"] = filters.random environment.filters["default"] = filters.default environment.filters["filesizeformat"] = filters.filesizeformat environment.filters["pprint"] = filters.pprint django-jinja-2.11.0/django_jinja/builtins/filters.py000066400000000000000000000063521447517172000224340ustar00rootroot00000000000000from django.utils.encoding import force_str from django.urls import reverse as django_reverse from django.contrib.staticfiles.storage import staticfiles_storage def reverse(value, *args, **kwargs): """ Shortcut filter for reverse url on templates. Is a alternative to django {% url %} tag, but more simple. Usage example: {{ 'web:timeline'|reverse(userid=2) }} This is a equivalent to django: {% url 'web:timeline' userid=2 %} """ return django_reverse(value, args=args, kwargs=kwargs) def static(path): return staticfiles_storage.url(path) from django.template.defaultfilters import addslashes from django.template.defaultfilters import capfirst from django.utils.html import escapejs as escapejs_filter # from django.utils.html import fix_ampersands as fix_ampersands_filter from django.template.defaultfilters import floatformat from django.template.defaultfilters import iriencode from django.template.defaultfilters import linenumbers from django.template.defaultfilters import make_list from django.template.defaultfilters import stringformat from django.template.defaultfilters import title from django.template.defaultfilters import truncatechars from django.template.defaultfilters import truncatechars_html from django.template.defaultfilters import truncatewords from django.template.defaultfilters import truncatewords_html from django.template.defaultfilters import upper from django.template.defaultfilters import lower from django.template.defaultfilters import urlencode from django.template.defaultfilters import urlize from django.template.defaultfilters import urlizetrunc from django.template.defaultfilters import wordcount from django.template.defaultfilters import wordwrap from django.template.defaultfilters import ljust from django.template.defaultfilters import rjust from django.template.defaultfilters import center from django.template.defaultfilters import cut from django.template.defaultfilters import linebreaks_filter from django.template.defaultfilters import linebreaksbr from django.template.defaultfilters import striptags from django.template.defaultfilters import join from django.template.defaultfilters import length from django.template.defaultfilters import random from django.template.defaultfilters import add from django.template.defaultfilters import date from django.template.defaultfilters import time from django.template.defaultfilters import timesince_filter from django.template.defaultfilters import timeuntil_filter from django.template.defaultfilters import default from django.template.defaultfilters import default_if_none from django.template.defaultfilters import divisibleby from django.template.defaultfilters import yesno from django.template.defaultfilters import filesizeformat from django.template.defaultfilters import pprint from django.template.defaultfilters import pluralize from django.template.defaultfilters import json_script from django.utils.text import slugify as djslugify def slugify(value): return djslugify(force_str(value)) from functools import partial linebreaksbr = partial(linebreaksbr, autoescape=True) # TZ from django.templatetags.tz import do_timezone as timezone from django.templatetags.tz import localtime from django.templatetags.tz import utc django-jinja-2.11.0/django_jinja/cache.py000066400000000000000000000013641447517172000201740ustar00rootroot00000000000000from django.core.cache import caches from django.utils.functional import cached_property from jinja2 import BytecodeCache as _BytecodeCache class BytecodeCache(_BytecodeCache): """ A bytecode cache for Jinja2 that uses Django's caching framework. """ def __init__(self, cache_name): self._cache_name = cache_name @cached_property def backend(self): return caches[self._cache_name] def load_bytecode(self, bucket): key = f'jinja2_{str(bucket.key)}' bytecode = self.backend.get(key) if bytecode: bucket.bytecode_from_string(bytecode) def dump_bytecode(self, bucket): key = f'jinja2_{str(bucket.key)}' self.backend.set(key, bucket.bytecode_to_string()) django-jinja-2.11.0/django_jinja/contrib/000077500000000000000000000000001447517172000202135ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/__init__.py000066400000000000000000000000001447517172000223120ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/000077500000000000000000000000001447517172000235415ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/__init__.py000066400000000000000000000000001447517172000256400ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/models.py000066400000000000000000000000001447517172000253640ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/templatetags/000077500000000000000000000000001447517172000262335ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/templatetags/__init__.py000066400000000000000000000000001447517172000303320ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_easy_thumbnails/templatetags/thumbnails.py000066400000000000000000000022761447517172000307620ustar00rootroot00000000000000import logging from easy_thumbnails.conf import settings from easy_thumbnails.templatetags import thumbnail as _thumbnail from django_jinja import library from functools import wraps logger = logging.getLogger(__name__) def debug_silence(error_output=''): def inner(fn): @wraps(fn) def wrapper(*args, **kwargs): try: return fn(*args, **kwargs) except Exception as exc: if settings.THUMBNAIL_DEBUG: raise logger.error('Error: %s', exc) return error_output return wrapper return inner @library.filter @debug_silence(error_output='') def thumbnail_url(source, alias): return _thumbnail.thumbnail_url(source, alias) @library.global_function @debug_silence(error_output=None) def thumbnailer_passive(obj): return _thumbnail.thumbnailer_passive(obj) @library.global_function @debug_silence(error_output=None) def thumbnailer(obj): return _thumbnail.thumbnailer(obj) @library.global_function @debug_silence(error_output='') def thumbnail(source, **kwargs): thumbnail = _thumbnail.get_thumbnailer(source).get_thumbnail(kwargs) return thumbnail.url django-jinja-2.11.0/django_jinja/contrib/_humanize/000077500000000000000000000000001447517172000221725ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_humanize/__init__.py000066400000000000000000000000001447517172000242710ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_humanize/models.py000066400000000000000000000000001447517172000240150ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_humanize/templatetags/000077500000000000000000000000001447517172000246645ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_humanize/templatetags/__init__.py000066400000000000000000000000001447517172000267630ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_humanize/templatetags/_humanize.py000066400000000000000000000011271447517172000272160ustar00rootroot00000000000000from django.contrib.humanize.templatetags import humanize from django_jinja import library @library.filter def ordinal(source): return humanize.ordinal(source) @library.filter def intcomma(source, use_l10n=True): return humanize.intcomma(source, use_l10n) @library.filter def intword(source): return humanize.intword(source) @library.filter def apnumber(source): return humanize.apnumber(source) @library.filter def naturalday(source, arg=None): return humanize.naturalday(source, arg) @library.filter def naturaltime(source): return humanize.naturaltime(source) django-jinja-2.11.0/django_jinja/contrib/_subdomains/000077500000000000000000000000001447517172000225165ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_subdomains/__init__.py000066400000000000000000000000001447517172000246150ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_subdomains/models.py000066400000000000000000000000001447517172000243410ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_subdomains/templatetags/000077500000000000000000000000001447517172000252105ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_subdomains/templatetags/__init__.py000066400000000000000000000000001447517172000273070ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/contrib/_subdomains/templatetags/subdomainurls.py000066400000000000000000000004071447517172000304520ustar00rootroot00000000000000from django_jinja import library from jinja2 import pass_context from subdomains.templatetags.subdomainurls import url as subdomain_url @library.global_function @pass_context def url(context, *args, **kwargs): return subdomain_url(context, *args, **kwargs) django-jinja-2.11.0/django_jinja/jinja2.py000066400000000000000000000003601447517172000203010ustar00rootroot00000000000000""" This import enables the import path of the django-jinja template backend to have a sane default NAME in your TEMPLATES setting. See: https://github.com/niwinz/django-jinja/pull/303 """ from .backend import Jinja2 __all__ = ["Jinja2"] django-jinja-2.11.0/django_jinja/library.py000066400000000000000000000042701447517172000205740ustar00rootroot00000000000000import functools from django.template.loader import render_to_string from django.utils.safestring import mark_safe # Global register dict for third party # template functions, filters and extensions. _local_env = { "globals": {}, "tests": {}, "filters": {}, "extensions": set(), } def _update_env(env): """ Given a jinja environment, update it with third party collected environment extensions. """ env.globals.update(_local_env["globals"]) env.tests.update(_local_env["tests"]) env.filters.update(_local_env["filters"]) for extension in _local_env["extensions"]: env.add_extension(extension) def _attach_function(attr, func, name=None): if name is None: name = func.__name__ global _local_env _local_env[attr][name] = func return func def _register_function(attr, name=None, fn=None): if name is None and fn is None: def dec(func): return _attach_function(attr, func) return dec elif name is not None and fn is None: if callable(name): return _attach_function(attr, name) else: def dec(func): return _register_function(attr, name, func) return dec elif name is not None and fn is not None: return _attach_function(attr, fn, name) raise RuntimeError("Invalid parameters") def extension(extension): global _local_env _local_env["extensions"].add(extension) return extension def global_function(*args, **kwargs): return _register_function("globals", *args, **kwargs) def test(*args, **kwargs): return _register_function("tests", *args, **kwargs) def filter(*args, **kwargs): return _register_function("filters", *args, **kwargs) def render_with(template, fn=None): """ Makes simple function works like django's default inclusion_tag: render specified template with context returned by decorated function. """ if fn is None: return functools.partial(render_with, template) @functools.wraps(fn) def _wrapper(*args, **kwargs): data = render_to_string(template, fn(*args, **kwargs)) return mark_safe(data) return _wrapper django-jinja-2.11.0/django_jinja/management/000077500000000000000000000000001447517172000206675ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/management/__init__.py000066400000000000000000000000001447517172000227660ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/management/commands/000077500000000000000000000000001447517172000224705ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/management/commands/__init__.py000066400000000000000000000000001447517172000245670ustar00rootroot00000000000000django-jinja-2.11.0/django_jinja/management/commands/makemessages.py000066400000000000000000000114521447517172000255120ustar00rootroot00000000000000"""Jinja2's i18n functionality is not exactly the same as Django's. In particular, the tags names and their syntax are different: 1. The Django ``trans`` tag is replaced by a _() global. 2. The Django ``blocktrans`` tag is called ``trans``. (1) isn't an issue, since the whole ``makemessages`` process is based on converting the template tags to ``_()`` calls. However, (2) means that those Jinja2 ``trans`` tags will not be picked up my Django's ``makemessage`` command. There aren't any nice solutions here. While Jinja2's i18n extension does come with extraction capabilities built in, the code behind ``makemessages`` unfortunately isn't extensible, so we can: * Duplicate the command + code behind it. * Offer a separate command for Jinja2 extraction. * Try to get Django to offer hooks into makemessages(). * Monkey-patch. We are currently doing that last thing. It turns out there we are lucky for once: It's simply a matter of extending two regular expressions. Credit for the approach goes to: http://stackoverflow.com/questions/2090717/getting-translation-strings-for-jinja2-templates-integrated-with-django-1-x """ import re from django.core.management.commands import makemessages from django.template import engines from django.template.base import BLOCK_TAG_START, BLOCK_TAG_END from django.utils.translation import template as trans_real strip_whitespace_right = re.compile(fr"({BLOCK_TAG_START}-?\s*(trans|pluralize).*?-{BLOCK_TAG_END})\s+", re.U) strip_whitespace_left = re.compile(fr"\s+({BLOCK_TAG_START}-\s*(endtrans|pluralize).*?-?{BLOCK_TAG_END})", re.U) def strip_whitespaces(src): src = strip_whitespace_left.sub(r'\1', src) src = strip_whitespace_right.sub(r'\1', src) return src # this regex looks for {% trans %} blocks that don't have 'trimmed' or 'notrimmed' set. # capturing {% endtrans %} ensures this doesn't affect DTL {% trans %} tags. trans_block_re = re.compile( fr"({BLOCK_TAG_START}-?\s*trans)(?!\s+(?:no)?trimmed)" fr"(.*?{BLOCK_TAG_END}.*?{BLOCK_TAG_START}-?\s*?endtrans\s*?-?{BLOCK_TAG_END})", re.U | re.DOTALL ) def apply_i18n_trimmed_policy(src, engine): # if env.policies["ext.i18n.trimmed"]: insert 'trimmed' flag on jinja {% trans %} blocks. i18n_trim_policy = engine.get("OPTIONS", {}).get("policies", {}).get("ext.i18n.trimmed") if not i18n_trim_policy: return src return trans_block_re.sub(r"\1 trimmed \2", src) class Command(makemessages.Command): def add_arguments(self, parser): super().add_arguments(parser) parser.add_argument('--jinja2-engine-name', default=None, dest='jinja_engine') def _get_default_jinja_template_engine(self): # dev's note: i would love to have this easy default: --jinja2-engine-name=jinja2 # but due to historical reasons, django-jinja's engine's name can default to either `jinja2` or 'backend'. # see: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-NAME # the default engine will be the first one exactly matching either of the new or old import paths. supported_import_paths = ["django_jinja.backend.Jinja2", "django_jinja.jinja2.Jinja2"] return [e for e in engines.templates.values() if e["BACKEND"] in supported_import_paths][0] def handle(self, *args, **options): old_endblock_re = trans_real.endblock_re old_block_re = trans_real.block_re old_constant_re = trans_real.constant_re old_templatize = trans_real.templatize # Extend the regular expressions that are used to detect # translation blocks with an "OR jinja-syntax" clause. trans_real.endblock_re = re.compile( trans_real.endblock_re.pattern + '|' + r"""^-?\s*endtrans\s*-?$""") trans_real.block_re = re.compile( trans_real.block_re.pattern + '|' + r"""^-?\s*trans(?:\s+(?:no)?trimmed)?(?:\s+(?!'|")(?=.*?=.*?)|\s*-?$)""") trans_real.plural_re = re.compile( trans_real.plural_re.pattern + '|' + r"""^-?\s*pluralize(?:\s+.+|-?$)""") trans_real.constant_re = re.compile(r""".*?_\(((?:".*?(? p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; } .subheader, .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { line-height: 1.45; color: #7a2518; font-weight: normal; margin-top: 0; margin-bottom: 0.25em; } /* Typography resets */ div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; } /* Default Link Styles */ a { color: #2156a5; text-decoration: none; line-height: inherit; } a:hover, a:focus { color: #1d4b8f; } a img { border: none; } /* Default paragraph styles */ p { font-family: inherit; font-weight: normal; font-size: 1em; line-height: 1.6; margin-bottom: 1.25em; text-rendering: optimizeLegibility; } p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; } /* Default header styles */ h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-weight: 300; font-style: normal; font-family: "Roboto Slab", "ff-tisa-web-pro","Georgia",Arial,sans-serif; color: #3E4349; color: #9C1500; text-rendering: optimizeLegibility; margin-top: 1em; margin-bottom: 0.5em; line-height: 1.0125em; } h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #527bbd; line-height: 0; } h1 { font-size: 2.125em; } h2 { font-size: 1.6875em; } h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; } h4 { font-size: 1.125em; } h5 { font-size: 1.125em; } h6 { font-size: 1em; } hr { border: solid #ddddd8; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; } /* Helpful Typography Defaults */ em, i { font-style: italic; line-height: inherit; } strong, b { font-weight: bold; line-height: inherit; } small { font-size: 60%; line-height: inherit; } code { font-family: "Droid Sans Mono", "DejaVu Sans Mono", monospace; font-family: "Liberation Mono",Menlo,Courier,monospace; font-weight: normal; color: rgba(0, 0, 0, 0.9); } /* Lists */ ul, ol, dl { font-size: 1em; line-height: 1.6; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; } ul, ol { margin-left: 1.5em; } ul.no-bullet, ol.no-bullet { margin-left: 1.5em; } /* Unordered Lists */ ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ } ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; } ul.square { list-style-type: square; } ul.circle { list-style-type: circle; } ul.disc { list-style-type: disc; } ul.no-bullet { list-style: none; } /* Ordered Lists */ ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; } /* Definition Lists */ dl dt { margin-bottom: 0.3125em; font-weight: bold; } dl dd { margin-bottom: 1.25em; } /* Abbreviations */ abbr, acronym { text-transform: uppercase; font-size: 90%; color: rgba(0, 0, 0, 0.8); border-bottom: 1px dotted #dddddd; cursor: help; } abbr { text-transform: none; } /* Blockquotes */ blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; } blockquote cite { display: block; font-size: 0.9375em; color: rgba(0, 0, 0, 0.6); } blockquote cite:before { content: "\2014 \0020"; } blockquote cite a, blockquote cite a:visited { color: rgba(0, 0, 0, 0.6); } blockquote, blockquote p { line-height: 1.6; color: rgba(0, 0, 0, 0.85); } /* Microformats */ .vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #dddddd; padding: 0.625em 0.75em; } .vcard li { margin: 0; display: block; } .vcard .fn { font-weight: bold; font-size: 0.9375em; } .vevent .summary { font-weight: bold; } .vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; } @media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.2; } h1 { font-size: 2.75em; } h2 { font-size: 2.3125em; } h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; } h4 { font-size: 1.4375em; } } /* Tables */ table { background: white; margin-bottom: 1.25em; border: solid 1px #dedede; } table thead, table tfoot { background: #f7f8f7; font-weight: bold; } table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 0.5em 0.625em 0.625em; font-size: inherit; color: rgba(0, 0, 0, 0.8); text-align: left; } table tr th, table tr td { padding: 0.5625em 0.625em; font-size: inherit; color: rgba(0, 0, 0, 0.8); } table tr.even, table tr.alt, table tr:nth-of-type(even) { background: #f8f8f7; } table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.6; } h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.2; word-spacing: -0.05em; } h1 strong, h2 strong, h3 strong, #toctitle strong, .sidebarblock > .content > .title strong, h4 strong, h5 strong, h6 strong { font-weight: 400; } .clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; } .clearfix:after, .float-group:after { clear: both; } *:not(pre) > code { font-size: 0.9375em; font-style: normal !important; letter-spacing: 0; padding: 0.1em 0.5ex; word-spacing: -0.15em; background-color: #f7f7f8; -webkit-border-radius: 4px; border-radius: 4px; line-height: 1.45; text-rendering: optimizeSpeed; } pre, pre > code { line-height: 1.45; color: rgba(0, 0, 0, 0.9); font-family: "Droid Sans Mono", "DejaVu Sans Mono", "Monospace", monospace; font-family: "Liberation Mono",Menlo,Courier,monospace; font-weight: normal; text-rendering: optimizeSpeed; } .keyseq { color: rgba(51, 51, 51, 0.8); } kbd { display: inline-block; color: rgba(0, 0, 0, 0.8); font-size: 0.75em; line-height: 1.4; background-color: #f7f7f7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; margin: -0.15em 0.15em 0 0.15em; padding: 0.2em 0.6em 0.2em 0.5em; vertical-align: middle; white-space: nowrap; } .keyseq kbd:first-child { margin-left: 0; } .keyseq kbd:last-child { margin-right: 0; } .menuseq, .menu { color: rgba(0, 0, 0, 0.8); } b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; } b.button:before { content: "["; padding: 0 3px 0 2px; } b.button:after { content: "]"; padding: 0 2px 0 3px; } p a > code:hover { color: rgba(0, 0, 0, 0.9); } #header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; } #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; } #header:after, #content:after, #footnotes:after, #footer:after { clear: both; } #content { margin-top: 1.25em; } #content:before { content: none; } #header > h1:first-child { color: rgba(0, 0, 0, 0.85); margin-top: 2.25rem; margin-bottom: 0; font-weight: 700; } #header > h1:first-child + #toc { margin-top: 8px; border-top: 1px solid #ddddd8; } #header > h1:only-child, body.toc2 #header > h1:nth-last-child(2) { border-bottom: 1px solid #ddddd8; padding-bottom: 8px; } #header .details { border-bottom: 1px solid #ddddd8; line-height: 1.45; padding-top: 0.25em; padding-bottom: 0.25em; padding-left: 0.25em; color: rgba(0, 0, 0, 0.6); display: -ms-flexbox; display: -webkit-flex; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; } #header .details span:first-child { margin-left: -0.125em; } #header .details span.email a { color: rgba(0, 0, 0, 0.85); } #header .details br { display: none; } #header .details br + span:before { content: "\00a0\2013\00a0"; } #header .details br + span.author:before { content: "\00a0\22c5\00a0"; color: rgba(0, 0, 0, 0.85); } #header .details br + span#revremark:before { content: "\00a0|\00a0"; } #header #revnumber { text-transform: capitalize; } #header #revnumber:after { content: "\00a0"; } #content > h1:first-child:not([class]) { color: rgba(0, 0, 0, 0.85); border-bottom: 1px solid #ddddd8; padding-bottom: 8px; margin-top: 0; padding-top: 1rem; margin-bottom: 1.25rem; } #toc { border-bottom: 1px solid #efefed; padding-bottom: 0.5em; } #toc > ul { margin-left: 0.125em; } #toc ul.sectlevel0 > li > a { font-style: italic; } #toc ul.sectlevel0 ul.sectlevel1 { margin: 0.5em 0; } #toc ul { font-family: "Open Sans", "DejaVu Sans", sans-serif; list-style-type: none; } #toc a { text-decoration: none; color: #3E4349; } #toc a:active { text-decoration: underline; } #toctitle { font-size: 1.2em; font-weight: 700; } @media only screen and (min-width: 768px) { #toctitle { font-size: 1.375em; } body.toc2 { padding-left: 15em; padding-right: 0; } #toc.toc2 { margin-top: 0 !important; background-color: #f8f8f7; position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #efefed; border-top-width: 0 !important; border-bottom-width: 0 !important; z-index: 1000; padding: 1.25em 1em; height: 100%; overflow: auto; } #toc.toc2 #toctitle { margin-top: 0; font-size: 1.2em; } #toc.toc2 > ul { font-size: 0.9em; margin-bottom: 0; } #toc.toc2 ul ul { margin-left: 0; padding-left: 1em; } #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } body.toc2.toc-right { padding-left: 0; padding-right: 15em; } body.toc2.toc-right #toc.toc2 { border-right-width: 0; border-left: 1px solid #efefed; left: auto; right: 0; } } @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 25em; padding-right: 0; } #toc.toc2 { width: 25em; font-size: 16px; } #toc.toc2 #toctitle { font-size: 1.375em; } #toc.toc2 > ul { font-size: 0.95em; } #toc.toc2 ul ul { padding-left: 1.25em; } body.toc2.toc-right { padding-left: 0; padding-right: 20em; } } #content #toc { border-style: solid; border-width: 1px; border-color: #e0e0dc; margin-bottom: 1.25em; padding: 1.25em; background: #f8f8f7; -webkit-border-radius: 4px; border-radius: 4px; } #content #toc > :first-child { margin-top: 0; } #content #toc > :last-child { margin-bottom: 0; } #footer { max-width: 100%; background-color: rgba(0, 0, 0, 0.8); padding: 1.25em; } #footer-text { color: rgba(255, 255, 255, 0.8); line-height: 1.44; } .sect1 { padding-bottom: 0.625em; } @media only screen and (min-width: 768px) { .sect1 { padding-bottom: 1.25em; } } .sect1 + .sect1 { border-top: 1px solid #efefed; } #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; z-index: 1001; width: 1.5ex; margin-left: -1.5ex; display: block; text-decoration: none !important; visibility: hidden; text-align: center; font-weight: normal; } #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: "\00A7"; font-size: 0.85em; display: block; padding-top: 0.1em; } #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; } #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { text-decoration: none; color: #3E4349; color: #ba3925; color: #9C1500; font-weight: 700; } #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #3E4349; color: #9C1500; } .audioblock, .imageblock, .literalblock, .listingblock, .stemblock, .videoblock { margin-bottom: 1.25em; } .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-rendering: optimizeLegibility; text-align: left; font-family: "Noto Serif", "DejaVu Serif", serif; font-size: 1rem; font-style: italic; } table.tableblock > caption.title { white-space: nowrap; overflow: visible; max-width: 0; } .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { color: rgba(0, 0, 0, 0.85); } table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; } .admonitionblock > table { border-collapse: separate; border: 0; background: none; width: 100%; } .admonitionblock > table td.icon { text-align: center; width: 80px; } .admonitionblock > table td.icon img { max-width: none; } .admonitionblock > table td.icon .title { font-weight: bold; font-family: "Open Sans", "DejaVu Sans", sans-serif; text-transform: uppercase; } .admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #ddddd8; color: rgba(0, 0, 0, 0.6); } .admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; } .exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: white; -webkit-border-radius: 4px; border-radius: 4px; } .exampleblock > .content > :first-child { margin-top: 0; } .exampleblock > .content > :last-child { margin-bottom: 0; } .sidebarblock { border-style: solid; border-width: 1px; border-color: #e0e0dc; margin-bottom: 1.25em; padding: 1.25em; background: #f8f8f7; -webkit-border-radius: 4px; border-radius: 4px; } .sidebarblock > :first-child { margin-top: 0; } .sidebarblock > :last-child { margin-bottom: 0; } .sidebarblock > .content > .title { color: #7a2518; margin-top: 0; text-align: center; } .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; } .literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { background: #f7f7f8; } .sidebarblock .literalblock pre, .sidebarblock .listingblock pre:not(.highlight), .sidebarblock .listingblock pre[class="highlight"], .sidebarblock .listingblock pre[class^="highlight "], .sidebarblock .listingblock pre.CodeRay, .sidebarblock .listingblock pre.prettyprint { background: #f2f1f1; } .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { -webkit-border-radius: 4px; border-radius: 4px; word-wrap: break-word; padding: 1em; font-size: 0.8125em; } .literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; } @media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.90625em; } } @media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 1em; } } .literalblock.output pre { color: #f7f7f8; background-color: rgba(0, 0, 0, 0.9); } .listingblock pre.highlightjs { padding: 0; } .listingblock pre.highlightjs > code { padding: 1em; -webkit-border-radius: 4px; border-radius: 4px; } .listingblock pre.prettyprint { border-width: 0; } .listingblock > .content { position: relative; } .listingblock code[data-lang]:before { display: none; content: attr(data-lang); position: absolute; font-size: 0.75em; top: 0.425rem; right: 0.5rem; line-height: 1; text-transform: uppercase; color: #999; } .listingblock:hover code[data-lang]:before { display: block; } .listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; } .listingblock.terminal pre .command:not([data-prompt]):before { content: "$"; } table.pyhltable { border-collapse: separate; border: 0; margin-bottom: 0; background: none; } table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; } table.pyhltable td.code { padding-left: .75em; padding-right: 0; } pre.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #ddddd8; } pre.pygments .lineno { display: inline-block; margin-right: .25em; } table.pyhltable .linenodiv { background: none !important; padding-right: 0 !important; } .quoteblock { margin: 0 1em 1.25em 1.5em; display: table; } .quoteblock > .title { margin-left: -1.5em; margin-bottom: 0.75em; } .quoteblock blockquote, .quoteblock blockquote p { color: rgba(0, 0, 0, 0.85); font-size: 1.15rem; line-height: 1.75; word-spacing: 0.1em; letter-spacing: 0; font-style: italic; text-align: justify; } .quoteblock blockquote { margin: 0; padding: 0; border: 0; } .quoteblock blockquote:before { content: "\201c"; float: left; font-size: 2.75em; font-weight: bold; line-height: 0.6em; margin-left: -0.6em; color: #7a2518; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; } .quoteblock .attribution { margin-top: 0.5em; margin-right: 0.5ex; text-align: right; } .quoteblock .quoteblock { margin-left: 0; margin-right: 0; padding: 0.5em 0; border-left: 3px solid rgba(0, 0, 0, 0.6); } .quoteblock .quoteblock blockquote { padding: 0 0 0 0.75em; } .quoteblock .quoteblock blockquote:before { display: none; } .verseblock { margin: 0 1em 1.25em 1em; } .verseblock pre { font-family: "Open Sans", "DejaVu Sans", sans; font-size: 1.15rem; color: rgba(0, 0, 0, 0.85); font-weight: 300; text-rendering: optimizeLegibility; } .verseblock pre strong { font-weight: 400; } .verseblock .attribution { margin-top: 1.25rem; margin-left: 0.5ex; } .quoteblock .attribution, .verseblock .attribution { font-size: 0.9375em; line-height: 1.45; font-style: italic; } .quoteblock .attribution br, .verseblock .attribution br { display: none; } .quoteblock .attribution cite, .verseblock .attribution cite { display: block; letter-spacing: -0.05em; color: rgba(0, 0, 0, 0.6); } .quoteblock.abstract { margin: 0 0 1.25em 0; display: block; } .quoteblock.abstract blockquote, .quoteblock.abstract blockquote p { text-align: left; word-spacing: 0; } .quoteblock.abstract blockquote:before, .quoteblock.abstract blockquote p:first-of-type:before { display: none; } table.tableblock { max-width: 100%; border-collapse: separate; } table.tableblock td > .paragraph:last-child p > p:last-child, table.tableblock th > p:last-child, table.tableblock td > p:last-child { margin-bottom: 0; } table.spread { width: 100%; } table.tableblock, th.tableblock, td.tableblock { border: 0 solid #dedede; } table.grid-all th.tableblock, table.grid-all td.tableblock { border-width: 0 1px 1px 0; } table.grid-all tfoot > tr > th.tableblock, table.grid-all tfoot > tr > td.tableblock { border-width: 1px 1px 0 0; } table.grid-cols th.tableblock, table.grid-cols td.tableblock { border-width: 0 1px 0 0; } table.grid-all * > tr > .tableblock:last-child, table.grid-cols * > tr > .tableblock:last-child { border-right-width: 0; } table.grid-rows th.tableblock, table.grid-rows td.tableblock { border-width: 0 0 1px 0; } table.grid-all tbody > tr:last-child > th.tableblock, table.grid-all tbody > tr:last-child > td.tableblock, table.grid-all thead:last-child > tr > th.tableblock, table.grid-rows tbody > tr:last-child > th.tableblock, table.grid-rows tbody > tr:last-child > td.tableblock, table.grid-rows thead:last-child > tr > th.tableblock { border-bottom-width: 0; } table.grid-rows tfoot > tr > th.tableblock, table.grid-rows tfoot > tr > td.tableblock { border-width: 1px 0 0 0; } table.frame-all { border-width: 1px; } table.frame-sides { border-width: 0 1px; } table.frame-topbot { border-width: 1px 0; } th.halign-left, td.halign-left { text-align: left; } th.halign-right, td.halign-right { text-align: right; } th.halign-center, td.halign-center { text-align: center; } th.valign-top, td.valign-top { vertical-align: top; } th.valign-bottom, td.valign-bottom { vertical-align: bottom; } th.valign-middle, td.valign-middle { vertical-align: middle; } table thead th, table tfoot th { font-weight: bold; } tbody tr th { display: table-cell; line-height: 1.6; background: #f7f8f7; } tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: rgba(0, 0, 0, 0.8); font-weight: bold; } p.tableblock > code:only-child { background: none; padding: 0; } p.tableblock { font-size: 1em; } td > div.verse { white-space: pre; } ol { margin-left: 1.75em; } ul li ol { margin-left: 1.5em; } dl dd { margin-left: 1.125em; } dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; } ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; } ul.unstyled, ol.unnumbered, ul.checklist, ul.none { list-style-type: none; } ul.unstyled, ol.unnumbered, ul.checklist { margin-left: 0.625em; } ul.checklist li > p:first-child > .fa-square-o:first-child, ul.checklist li > p:first-child > .fa-check-square-o:first-child { width: 1em; font-size: 0.85em; } ul.checklist li > p:first-child > input[type="checkbox"]:first-child { width: 1em; position: relative; top: 1px; } ul.inline { margin: 0 auto 0.625em auto; margin-left: -1.375em; margin-right: 0; padding: 0; list-style: none; overflow: hidden; } ul.inline > li { list-style: none; float: left; margin-left: 1.375em; display: block; } ul.inline > li > * { display: block; } .unstyled dl dt { font-weight: normal; font-style: normal; } ol.arabic { list-style-type: decimal; } ol.decimal { list-style-type: decimal-leading-zero; } ol.loweralpha { list-style-type: lower-alpha; } ol.upperalpha { list-style-type: upper-alpha; } ol.lowerroman { list-style-type: lower-roman; } ol.upperroman { list-style-type: upper-roman; } ol.lowergreek { list-style-type: lower-greek; } .hdlist > table, .colist > table { border: 0; background: none; } .hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; } td.hdlist1 { padding-right: .75em; font-weight: bold; } td.hdlist1, td.hdlist2 { vertical-align: top; } .literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; } .colist > table tr > td:first-of-type { padding: 0 0.75em; line-height: 1; } .colist > table tr > td:last-of-type { padding: 0.25em 0; } .thumb, .th { line-height: 0; display: inline-block; border: solid 4px white; -webkit-box-shadow: 0 0 0 1px #dddddd; box-shadow: 0 0 0 1px #dddddd; } .imageblock.left, .imageblock[style*="float: left"] { margin: 0.25em 0.625em 1.25em 0; } .imageblock.right, .imageblock[style*="float: right"] { margin: 0.25em 0 1.25em 0.625em; } .imageblock > .title { margin-bottom: 0; } .imageblock.thumb, .imageblock.th { border-width: 6px; } .imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; } .image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; } .image.left { margin-right: 0.625em; } .image.right { margin-left: 0.625em; } a.image { text-decoration: none; } span.footnote, span.footnoteref { vertical-align: super; font-size: 0.875em; } span.footnote a, span.footnoteref a { text-decoration: none; } span.footnote a:active, span.footnoteref a:active { text-decoration: underline; } #footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; } #footnotes hr { width: 20%; min-width: 6.25em; margin: -.25em 0 .75em 0; border-width: 1px 0 0 0; } #footnotes .footnote { padding: 0 0.375em; line-height: 1.3; font-size: 0.875em; margin-left: 1.2em; text-indent: -1.2em; margin-bottom: .2em; } #footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; } #footnotes .footnote:last-of-type { margin-bottom: 0; } #content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; } .gist .file-data > table { border: 0; background: #fff; width: 100%; margin-bottom: 0; } .gist .file-data > table td.line-data { width: 99%; } div.unbreakable { page-break-inside: avoid; } .big { font-size: larger; } .small { font-size: smaller; } .underline { text-decoration: underline; } .overline { text-decoration: overline; } .line-through { text-decoration: line-through; } .aqua { color: #00bfbf; } .aqua-background { background-color: #00fafa; } .black { color: black; } .black-background { background-color: black; } .blue { color: #0000bf; } .blue-background { background-color: #0000fa; } .fuchsia { color: #bf00bf; } .fuchsia-background { background-color: #fa00fa; } .gray { color: #606060; } .gray-background { background-color: #7d7d7d; } .green { color: #006000; } .green-background { background-color: #007d00; } .lime { color: #00bf00; } .lime-background { background-color: #00fa00; } .maroon { color: #600000; } .maroon-background { background-color: #7d0000; } .navy { color: #000060; } .navy-background { background-color: #00007d; } .olive { color: #606000; } .olive-background { background-color: #7d7d00; } .purple { color: #600060; } .purple-background { background-color: #7d007d; } .red { color: #bf0000; } .red-background { background-color: #fa0000; } .silver { color: #909090; } .silver-background { background-color: #bcbcbc; } .teal { color: #006060; } .teal-background { background-color: #007d7d; } .white { color: #bfbfbf; } .white-background { background-color: #fafafa; } .yellow { color: #bfbf00; } .yellow-background { background-color: #fafa00; } span.icon > .fa { cursor: default; } .admonitionblock td.icon [class^="fa icon-"] { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; } .admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #19407c; } .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; } .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; } .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; } .admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; } .conum[data-value] { display: inline-block; color: #fff !important; background-color: rgba(0, 0, 0, 0.8); -webkit-border-radius: 100px; border-radius: 100px; text-align: center; font-size: 0.75em; width: 1.67em; height: 1.67em; line-height: 1.67em; font-family: "Open Sans", "DejaVu Sans", sans-serif; font-style: normal; font-weight: bold; } .conum[data-value] * { color: #fff !important; } .conum[data-value] + b { display: none; } .conum[data-value]:after { content: attr(data-value); } pre .conum[data-value] { position: relative; top: -0.125em; } b.conum * { color: inherit !important; } .conum:not([data-value]):empty { display: none; } h1, h2 { letter-spacing: -0.01em; } dt, th.tableblock, td.content { text-rendering: optimizeLegibility; } p, td.content { letter-spacing: -0.01em; } p strong, td.content strong { letter-spacing: -0.005em; } p, blockquote, dt, td.content { font-size: 1.0625rem; } p { margin-bottom: 1.25rem; } .sidebarblock p, .sidebarblock dt, .sidebarblock td.content, p.tableblock { font-size: 1em; } .exampleblock > .content { background-color: #fffef7; border-color: #e0e0dc; -webkit-box-shadow: 0 1px 4px #e0e0dc; box-shadow: 0 1px 4px #e0e0dc; } .print-only { display: none !important; } @media print { @page { margin: 1.25cm 0.75cm; } * { -webkit-box-shadow: none !important; box-shadow: none !important; text-shadow: none !important; } a { color: inherit !important; text-decoration: underline !important; } a.bare, a[href^="#"], a[href^="mailto:"] { text-decoration: none !important; } a[href^="http:"]:not(.bare):after, a[href^="https:"]:not(.bare):after { content: "(" attr(href) ")"; display: inline-block; font-size: 0.875em; padding-left: 0.25em; } abbr[title]:after { content: " (" attr(title) ")"; } pre, blockquote, tr, img { page-break-inside: avoid; } thead { display: table-header-group; } img { max-width: 100% !important; } p, blockquote, dt, td.content { font-size: 1em; orphans: 3; widows: 3; } h2, h3, #toctitle, .sidebarblock > .content > .title, #toctitle, .sidebarblock > .content > .title { page-break-after: avoid; } #toc, .sidebarblock, .exampleblock > .content { background: none !important; } #toc { border-bottom: 1px solid #ddddd8 !important; padding-bottom: 0 !important; } .sect1 { padding-bottom: 0 !important; } .sect1 + .sect1 { border: 0 !important; } #header > h1:first-child { margin-top: 1.25rem; } body.book #header { text-align: center; } body.book #header > h1:first-child { border: 0 !important; margin: 2.5em 0 1em 0; } body.book #header .details { border: 0 !important; display: block; padding: 0 !important; } body.book #header .details span:first-child { margin-left: 0 !important; } body.book #header .details br { display: block; } body.book #header .details br + span:before { content: none !important; } body.book #toc { border: 0 !important; text-align: left !important; padding: 0 !important; margin: 0 !important; } body.book #toc, body.book #preamble, body.book h1.sect0, body.book .sect1 > h2 { page-break-before: always; } .listingblock code[data-lang]:before { display: block; } #footer { background: none !important; padding: 0 0.9375em; } #footer-text { color: rgba(0, 0, 0, 0.6) !important; font-size: 0.9em; } .hide-on-print { display: none !important; } .print-only { display: block !important; } .hide-for-print { display: none !important; } .show-for-print { display: inherit !important; } } django-jinja-2.11.0/doc/content-docinfo.html000066400000000000000000000003541447517172000206640ustar00rootroot00000000000000 django-jinja-2.11.0/doc/content.adoc000066400000000000000000000454521447517172000172170ustar00rootroot00000000000000= django-jinja - jinja2 backend for Django Andrey Antukh, 2.11.0 :toc: left :!numbered: :source-highlighter: pygments :pygments-style: friendly :sectlinks: == Introduction django-jinja is a xref:license[BSD Licensed], simple and non-obstructive jinja2 backend for Django. === Rationale Jinja2 provides certain advantages over the native system of Django, for example, explicit calls to callable from templates, has better performance and has a plugin system, etc ... Django comes with a jinja backend, why should I use *django-jinja*? The Django builtin backend has a very limited set of features if we compare it with the django template engine and in my opinion is not very usable because it does not integrate well with the rest of django such as its filters, template tags and preloading of templatetags, among others. *django-jinja* comes to the rescue and adds everything missing. This is a brief list of differences with django's built-in backend: - Auto-load templatetags compatible with Jinja2 the same way as Django. - Find the templates as usual in `"/templates"` directory instead of `"/jinja2"` directory (you can overwrite this behavior, see below). - Django templates can coexist with Jinja2 templates without any problems. It works as middleware, intercepts Jinja templates by file path pattern. - Django template filters and tags can mostly be used in Jinja2 templates. - I18n subsystem adapted for Jinja2 (makemessages now collects messages from Jinja templates, respects the `ext.i18n.trimmed` policy) - jinja2 bytecode cache adapted for using django's cache subsystem. - Support for django context processors. [NOTE] ==== The usage of context processors is available for compatibility and migrations, but creating new context processors is not the recommended way to set global context. With *django-jinja*, you can do this by setting global data or global constants. See below, in the "Custom filters, globals, constants and tests" section. ==== === Requirements - Python >= 3.8 - Django >= 3.2 - jinja2 >= 3.0 If you are using older versions of Django or Python, you need an older version of django-jinja: |=== |django-jinja |supported python versions |supported django versions |==1.4.2 |2.7, 3.3, 3.4 |1.5, 1.6, 1.7, 1.8 |==2.4.1 |2.7, 3.4, 3.5 |1.8, 1.11 |==2.6.0 |3.5 (not django 3.0), 3.6, 3.7, 3.8 |1.11, 2.2, 3.0 |>=2.7.0 |3.5 (django 2.2 only), 3.6, 3.7, 3.8, 3.9 |2.2, 3.0, 3.1, 3.2 |>=2.8.0 |3.6, 3.7, 3.8, 3.9 |2.2, 3.0, 3.1, 3.2 |>=2.10.0 |3.6, 3.7, 3.8, 3.9, 3.10 |2.2, 3.2, 4.0 |>=2.11.0 |3.8, 3.9, 3.10, 3.11 |3.2, 4.0, 4.1, 4.2 |=== === Installation The simplest way to install **django-jinja** is using **pip**: [source, bash] ---- pip install django-jinja ---- == Quick Start Add it to Django's installed apps list: [source, python] ---- INSTALLED_APPS += ('django_jinja',) ---- Followed by the basic template engine configuration: [source, python] ---- TEMPLATES = [ { "BACKEND": "django_jinja.jinja2.Jinja2", "DIRS": [], "APP_DIRS": True, "OPTIONS": {} }, { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": {} }, ] ---- [NOTE] ==== If you are using the default value for the app templates directory of the django-jinja backend, take care of the template engines order, because the django-jinja backend by default uses the same directory for the templates as the django template engine. If you put the django engine first every jinja template will be found by the django engine. ==== To read more on the logic of the `DIRS` and `APP_DIRS` settings, and how the engines resolve template paths, check out link:https://docs.djangoproject.com/en/dev/topics/templates/#support-for-template-engines[Django's section on setting up template engines]. == User Guide === Using Jinja2 templates in your views By default, *django-jinja*'s template backend matches files with the extension `.jinja`, and (if using the `APP_DIRS` template loader) it crawls the same `templates` folders within your apps as the Django Template Language (DTL) engine does. So, all you have to do to switch your template renderer is to change the file extension of the template. Make sure your templates use the right engine's syntax corresponding to their file extensions! As an example, these class-based views work for both Jinja2 and DTL in a project set up like in xref:_quick_start[Quick Start]: [source, python] ---- """ app layout: myapp/ ├── __init__.py ├── apps.py ├── templates │ ├── bar.html │ └── foo.jinja └── views.py <--(you are here) """ from django.views.generic import TemplateView class FooView(TemplateView): template_name = 'foo.jinja' # renders with Jinja2 class BarView(TemplateView): template_name = 'bar.html' # renders with DTL ---- [NOTE] ==== Jinja2 and DTL templates can't call each other with `{% extends %}` or `{% include %}`. If you mix them up, django will raise `django.template.TemplateDoesNotExist` or `TemplateSyntaxError`. If you use template inheritance in your project to keep every page looking the same, you may end up needing to maintain two versions of your commonly used templates, like `base.jinja` and `base.html`, that render the same, each using their own template language. ==== For advice on converting from a DTL to a Jinja2 template, see xref:_differences_with_django_template_engine[Differences with Django Template Engine]. === Advanced template pattern matching If the above default behavior is not to your liking, you can tune it using these `OPTIONS`: [source, python] ---- "OPTIONS": { # django-jinja defaults "match_extension": ".jinja", "match_regex": None, "app_dirname": "templates", } ---- - To match only file paths that end with a certain string, use `match_extension`. - To use regular expressions to match or exclude certain paths, use `match_regex`. - If both are set, both tests must pass for the backend to try and render the file. - If both are disabled with `None`, the backend will try and render *any* file it finds (and preclude any subsequent engines in `TEMPLATES`). This example matches `.html` files instead of `.jinja` across the entire project, but uses a regular expression to exclude matching DTL templates used by the admin interface. [source, python] ---- "OPTIONS": { # Match the template names ending in .html but not the ones in the admin folder. "match_extension": ".html", "match_regex": r"^(?!admin/).*", } ---- As said previously, when using `APP_DIRS`, django-jinja's backend uses the same `templates` directory as the django template engine. To change it to use another directory in your apps, you can use the `app_dirname` option: [source, python] ---- "OPTIONS": { # Match the templates at /jinja2/*.html`, leaving /templates/ for DTL. "match_extension": ".html", "app_dirname": "jinja2", } ---- === Context processors support It is a helper to use django's context processors with jinja2 backend for django 1.8. .Example: set up a bunch of context processors: [source, python] ---- "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", ], } ---- As with the django template engine, this is a default list of context processors, and you can skip setting them if you do not have your own. Furthermore, the purpose of django-jinja's context processor support is to help with migrations, but context processors are no longer the recommended way to set global variables and functions. For the recommended way, see the next section. [NOTE] ==== Remember that django (1.8.x and 1.9.x) is backward compatible with the old template api and this has its own trade-offs. If you find yourself using functions like `render_to_string` or `render_to_response` from django, do not forget to pass the request parameter in order to make context processors work. ==== === Custom filters, globals, constants and tests This is the recommended way to set up additional jinja variables, tests, and filters, in your settings. [source, python] ---- "OPTIONS": { "tests": { "mytest": "path.to.tests.mytestfn", }, "filters": { "myfilter": "path.to.filters.myfilterfn", }, "constants": { "hello": "hello world", }, "globals": { "somefn": "path.to.functions.somefn", } } ---- === Set policies To set link:https://jinja.palletsprojects.com/en/latest/api/#policies[environment policies] introduced in Jinja2 2.9: [source, python] ---- "OPTIONS": { "policies": { "ext.i18n.trimmed": True, }, } ---- === Add additional extensions django-jinja, by default sets up a great amount of extensions to make your experience using jinja in django painless. But if you want to add more extensions, you can do it using the `extensions` entry of the backend options: [source, python] ---- from django_jinja.builtins import DEFAULT_EXTENSIONS "OPTIONS": { "extensions": DEFAULT_EXTENSIONS + [ # Your extensions here... "path.to.your.Extension" ] } ---- === Setting the template engine name Keep in mind that the automatically inferred `NAME` for any template backend link:https://docs.djangoproject.com/en/dev/ref/settings/#std-setting-TEMPLATES-NAME[will depend on the backend's import path]. This name is used when you need to specify a template engine by name (e.g. `render_to_string("myapp/template.jinja", context, using="jinja2")`). In previous versions of django-jinja, the backend's import path was `django_jinja.backend.Jinja2`, providing the unhelpful default engine name, `"backend"`. Now, the import path `django_jinja.jinja2.Jinja2` can be used, which provides a more meaningful engine name, `"jinja2"`. The old import path is still available for compatability with existing Django projects. === Gettext Style Jinja2 implements two styles of gettext. You can read about it here: https://jinja.palletsprojects.com/en/latest/extensions/#new-style-gettext You can switch to concrete style using the `newstyle_gettext` entry on backend options: [source, python] ---- "OPTIONS": { "newstyle_gettext": True, } ---- === Complete example This is a complete configuration example with django-jinja's defaults: [source, python] ---- TEMPLATES = [ { "BACKEND": "django_jinja.jinja2.Jinja2", "APP_DIRS": True, "OPTIONS": { "match_extension": ".jinja", "match_regex": None, "app_dirname": "templates", # Can be set to "jinja2.Undefined" or any other subclass. "undefined": None, "newstyle_gettext": True, "tests": { # "mytest": "path.to.my.test", }, "filters": { # "myfilter": "path.to.my.filter", }, "globals": { # "myglobal": "path.to.my.globalfunc", }, "constants": { # "foo": "bar", }, "policies": { # "ext.i18n.trimmed": True, }, "extensions": [ "jinja2.ext.do", "jinja2.ext.loopcontrols", "jinja2.ext.i18n", "django_jinja.builtins.extensions.CsrfExtension", "django_jinja.builtins.extensions.CacheExtension", "django_jinja.builtins.extensions.DebugExtension", "django_jinja.builtins.extensions.TimezoneExtension", "django_jinja.builtins.extensions.UrlsExtension", "django_jinja.builtins.extensions.StaticFilesExtension", "django_jinja.builtins.extensions.DjangoFiltersExtension", ], "bytecode_cache": { "name": "default", "backend": "django_jinja.cache.BytecodeCache", "enabled": False, }, "autoescape": True, "auto_reload": settings.DEBUG, "translation_engine": "django.utils.translation", } }, ] ---- == Differences with Django Template Engine === Url reversing django-jinja comes with helpers for reverse urls. Instead of using django's approach, it uses a simple function called `url`. .Reverse urls in templates [source, html+jinja] ---- {{ url('ns:name', pk=obj.pk) }} ---- This approach is very flexible, because we do not need additional options to set a result if executing url in one variable. With jinja2 you can use the set template tag for it: [source, html+jinja] ---- {% set myurl=url("ns:name", pk=obj.pk) %} ---- === Static files Like urls, static files can be resolved with the simple `static` function available globally in jinja context: .Example resolving static files [source, html+jinja] ---- {{ static("js/lib/foo.js") }} ---- === i18n support django-jinja inherits the jinja2 approach for handling translation strings. You can read more about it here: https://jinja.palletsprojects.com/en/latest/templates/#i18n [source, html+jinja] ---- {{ _('Hello %(name)s', name=user.name) }} {% trans name=user.name %} Hello {{ name }} {% endtrans %} ---- Additionally, django-jinja extends django's `makemessages` command to make it work with jinja2 i18n tags. If you want more django-like i18n-related tags, you can use extensions from https://github.com/MoritzS/jinja2-django-tags. === Replace jinja filters with django versions Django and Jinja overlap in a little subset of template filters. To properly handle this, django-jinja uses the jinja versions by default. But if you want a django version of them, you should use the "django_jinja.builtins.extensions.DjangoExtraFiltersExtension" extension. The affected filters are: title, upper, lower, urlencode, urlize, wordcount, wordwrap, center join, length, random, default, filesizeformat, pprint. === Registering filters in a "django" way. django-jinja comes with facilities for loading template filters, globals and tests from django applications. Here an example: [source, python] ---- # /templatetags/.py # don't forget to create __init__.py in templatetags dir from django_jinja import library import jinja2 @library.test(name="one") def is_one(n): """ Usage: {% if m is one %}Foo{% endif %} """ return n == 1 @library.filter def mylower(name): """ Usage: {{ 'Hello'|mylower() }} """ return name.lower() @library.filter @jinja2.pass_context def replace(context, value, x, y): """ Filter with template context. Usage: {{ 'Hello'|replace('H','M') }} """ return value.replace(x, y) @library.global_function def myecho(data): """ Usage: {{ myecho('foo') }} """ return data @library.global_function @library.render_with("test-render-with.jinja") def myrenderwith(*args, **kwargs): """ Render result with jinja template. Usage: {{ myrenderwith() }} """ return {"name": "Foo"} from .myextensions import MyExtension library.extension(MyExtension) ---- This only works within a Django app. If you don't have an app for your project, create an app specifically for this purpose and put your templatetags there. === Render 4xx/500 pages with jinja django-jinja also provides a set of views for easy render 4xx/500 pages using jinja engine: [source, python] ---- # yourproject/urls.py from django_jinja import views handler400 = views.BadRequest.as_view() handler403 = views.PermissionDenied.as_view() handler404 = views.PageNotFound.as_view() handler500 = views.ServerError.as_view() ---- == Known Issues - The above handler500 is broken as of django 2.2. see: https:github.com/niwinz/django-jinja/issues/234 == Builtin contrib modules *django-jinja* comes with some additional contrib modules that adapt a limited set of external django apps for easy use from jinja templates. Please note that in order to use any of these contrib modules, you'll need to install the relevant dependent packages yourself first. [NOTE] ==== In django, creating new tags is simpler than in Jinja2. You should remember that in jinja tags are really extensions and have a different purpose than the django template tags. Thus for many things that the django template system uses tags, django-jinja will provide functions with the same functionality. ==== easy-thumbnails ~~~~~~~~~~~~~~~ Easy Thumbnails is a thumbnail generation library for Django. .Activate plugin (settings.py) [source, python] ---- INSTALLED_APPS += ('django_jinja.contrib._easy_thumbnails',) ---- .Usage [source, html+jinja] ---- {{ thumbnail(file, size=(400, 400)) }} {{ user.avatar|thumbnail_url("alias") }} ---- django-subdomains ~~~~~~~~~~~~~~~~~ Subdomain helpers for the Django framework, including subdomain-based URL routing. .Activate plugin (settings.py) [source, python] ---- INSTALLED_APPS += ('django_jinja.contrib._subdomains',) ---- .Usage [source, html+jinja] ---- {{ url('homepage', subdomain='wildcard') }} ---- humanize ~~~~~~~~ Django comes with the humanize library that exposes some useful template filters. .Activate plugin (settings.py) [source, python] ---- INSTALLED_APPS += ('django_jinja.contrib._humanize',) ---- link:https://docs.djangoproject.com/en/dev/ref/contrib/humanize/[Complete list of available filters] .[[license]] License ------- [source,text] ---- Copyright (c) 2011-2017 Andre Antukh All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- django-jinja-2.11.0/setup.py000066400000000000000000000040301447517172000156450ustar00rootroot00000000000000#!/usr/bin/env python3 from setuptools import setup setup( name = "django-jinja", version = "2.11.0", description = "Jinja2 templating language integrated in Django.", long_description = open("README.rst").read(), long_description_content_type='text/x-rst', keywords = "django, jinja2", author = "Andrey Antukh", author_email = "niwi@niwi.be", maintainer = "Asif Saif Uddin", maintainer_email = "auvipy@gmail.com", url = "https://github.com/niwinz/django-jinja", license = "BSD", packages = [ "django_jinja", "django_jinja.builtins", "django_jinja.management", "django_jinja.management.commands", "django_jinja.contrib", "django_jinja.contrib._easy_thumbnails", "django_jinja.contrib._easy_thumbnails.templatetags", "django_jinja.contrib._humanize", "django_jinja.contrib._humanize.templatetags", "django_jinja.contrib._subdomains", "django_jinja.contrib._subdomains.templatetags", "django_jinja.views", "django_jinja.views.generic", ], include_package_data = True, python_requires = ">=3.8", install_requires = [ "jinja2>=3", "django>=3.2", ], tests_require = [ "pytz", ], classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Framework :: Django :: 4.1", "Framework :: Django :: 4.2", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Internet :: WWW/HTTP", ] ) django-jinja-2.11.0/testing/000077500000000000000000000000001447517172000156135ustar00rootroot00000000000000django-jinja-2.11.0/testing/locale/000077500000000000000000000000001447517172000170525ustar00rootroot00000000000000django-jinja-2.11.0/testing/locale/en/000077500000000000000000000000001447517172000174545ustar00rootroot00000000000000django-jinja-2.11.0/testing/locale/en/.hidden000066400000000000000000000000001447517172000206760ustar00rootroot00000000000000django-jinja-2.11.0/testing/locale/en/LC_MESSAGES/000077500000000000000000000000001447517172000212415ustar00rootroot00000000000000django-jinja-2.11.0/testing/locale/en/LC_MESSAGES/django.po000066400000000000000000000033571447517172000230530ustar00rootroot00000000000000#: testapp/templates/i18n_test.html:1 #, python-format msgid "" "\n" "Foo %(bar)s\n" msgstr "" #: testapp/templates/i18n_test.html:6 testapp/templates/i18n_test.jinja:6 msgid "Year" msgstr "" #: testapp/templates/i18n_test.html:9 testapp/templates/i18n_test.jinja:8 #, python-format msgid "1 Hello %(user)s!" msgstr "" #: testapp/templates/i18n_test.html:11 testapp/templates/i18n_test.jinja:10 #, python-format msgid "2 Hello %(user)s!" msgstr "" #: testapp/templates/i18n_test.html:14 testapp/templates/i18n_test.jinja:13 #, python-format msgid "" "\n" "3 This is %(book_title)s by %(author)s\n" msgstr "" #: testapp/templates/i18n_test.html:20 #, python-format msgid "" "\n" "4 There is %(count)s %(name)s object.\n" msgid_plural "" "\n" "4 There are %(count)s %(name)s objects.\n" msgstr[0] "" msgstr[1] "" #: testapp/templates/i18n_test.html:27 testapp/templates/i18n_test.jinja:26 msgid "5 Hello World!" msgstr "" #: testapp/templates/i18n_test.html:33 #, python-format msgid "" "\n" " #%(invoice_count)s invoice\n" msgid_plural "" "\n" " #%(invoice_count)s invoices\n" msgstr[0] "" msgstr[1] "" #: testapp/templates/i18n_test.html:41 testapp/templates/i18n_test.jinja:40 #, python-format msgid "#%(trimmed_invoice_count)s trimmed invoice" msgid_plural "#%(trimmed_invoice_count)s trimmed invoices" msgstr[0] "" msgstr[1] "" #: testapp/templates/i18n_test.jinja:1 #, python-format msgid "Foo %(bar)s" msgstr "" #: testapp/templates/i18n_test.jinja:19 #, python-format msgid "4 There is %(count)s %(name)s object." msgid_plural "4 There are %(count)s %(name)s objects." msgstr[0] "" msgstr[1] "" #: testapp/templates/i18n_test.jinja:32 #, python-format msgid "#%(invoice_count)s invoice" msgid_plural "#%(invoice_count)s invoices" msgstr[0] "" msgstr[1] "" django-jinja-2.11.0/testing/manage.py000077500000000000000000000003621447517172000174210ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-jinja-2.11.0/testing/runtests.py000066400000000000000000000005141447517172000200540ustar00rootroot00000000000000#!/usr/bin/env python import os, sys os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") if __name__ == "__main__": from django.core.management import execute_from_command_line args = sys.argv args.insert(1, "test") if len(args) == 2: args.insert(2, "testapp") execute_from_command_line(args) django-jinja-2.11.0/testing/settings.py000066400000000000000000000064531447517172000200350ustar00rootroot00000000000000import os, sys sys.path.insert(0, "..") BASE_DIR = os.path.dirname(__file__) DEBUG = False TEMPLATE_DEBUG = False ALLOWED_HOSTS = ["*"] DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "foobar.db" } } DEFAULT_AUTO_FIELD = "django.db.models.AutoField" MIDDLEWARE_CLASSES = [ # "django.middleware.csrf.CsrfViewMiddleware", "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ] ROOT_URLCONF = "testapp.urls" USE_I18N = True USE_TZ = True TIME_ZONE = "UTC" LANGUAGE_CODE = "en" ADMIN_MEDIA_PREFIX = "/static/admin/" INTERNAL_IPS = ("127.0.0.1",) STATIC_ROOT = os.path.join(BASE_DIR, "static") STATIC_URL = "/static/" SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" SESSION_EXPIRE_AT_BROWSER_CLOSE = True STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) # TEMPLATE_DIRS = () SECRET_KEY = "di!n($kqa3)nd%ikad#kcjpkd^uw*h%*kj=*pm7$vbo6ir7h=l" INSTALLED_APPS = ( "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.staticfiles", "django.contrib.messages", "django_jinja", "testapp", ) from django_jinja.builtins import DEFAULT_EXTENSIONS JINJA2_MUTE_URLRESOLVE_EXCEPTIONS = True TEMPLATES = [ { "BACKEND": "django_jinja.jinja2.Jinja2", "APP_DIRS": True, "OPTIONS": { "debug": True, "context_processors": [ "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", ], "constants": { "foo": "bar", }, "policies": { "ext.i18n.trimmed": True, }, "extensions": DEFAULT_EXTENSIONS + [ "django_jinja.builtins.extensions.DjangoExtraFiltersExtension", ] } }, { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True } ] TEST_RUNNER = "django.test.runner.DiscoverRunner" LOGGING = { "version": 1, "disable_existing_loggers": False, "filters": { "require_debug_false": { "()": "django.utils.log.RequireDebugFalse" } }, "formatters": { "simple": { "format": "%(asctime)s: %(message)s", } }, "handlers": { "console":{ "level":"DEBUG", "class":"logging.StreamHandler", "formatter": "simple" }, }, "loggers": { "django.request": { "handlers": ["console"], "level": "ERROR", "propagate": True, }, "arandomtable.custom": { "handlers": ["console"], "level": "DEBUG", }, } } django-jinja-2.11.0/testing/testapp/000077500000000000000000000000001447517172000172735ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/__init__.py000066400000000000000000000000001447517172000213720ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/forms.py000066400000000000000000000001371447517172000207740ustar00rootroot00000000000000from django import forms class TestForm(forms.Form): name = forms.CharField(max_length=2) django-jinja-2.11.0/testing/testapp/jinja2/000077500000000000000000000000001447517172000204505ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/jinja2/hola_mundo.html000066400000000000000000000000311447517172000234550ustar00rootroot00000000000000hola mundo de {{ name }} django-jinja-2.11.0/testing/testapp/migrations/000077500000000000000000000000001447517172000214475ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/migrations/0001_initial.py000066400000000000000000000006601447517172000241140ustar00rootroot00000000000000from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ] operations = [ migrations.CreateModel( name='TestModel', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ], options={ }, bases=(models.Model,), ), ] django-jinja-2.11.0/testing/testapp/migrations/0002_testmodel_date.py000066400000000000000000000005501447517172000254570ustar00rootroot00000000000000from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ('testapp', '0001_initial'), ] operations = [ migrations.AddField( model_name='testmodel', name='date', field=models.DateTimeField(null=True), preserve_default=True, ), ] django-jinja-2.11.0/testing/testapp/migrations/__init__.py000066400000000000000000000000001447517172000235460ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/models.py000066400000000000000000000001601447517172000211250ustar00rootroot00000000000000from django.db.models import Model, DateTimeField class TestModel(Model): date = DateTimeField(null=True) django-jinja-2.11.0/testing/testapp/static/000077500000000000000000000000001447517172000205625ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/static/script.js000066400000000000000000000000341447517172000224210ustar00rootroot00000000000000console.log("Hello world"); django-jinja-2.11.0/testing/testapp/static/style.css000066400000000000000000000000321447517172000224270ustar00rootroot00000000000000body { font-size: 12px; } django-jinja-2.11.0/testing/testapp/templates/000077500000000000000000000000001447517172000212715ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/templates/403.jinja000066400000000000000000000000041447517172000226060ustar00rootroot00000000000000403 django-jinja-2.11.0/testing/testapp/templates/404.jinja000066400000000000000000000000041447517172000226070ustar00rootroot00000000000000404 django-jinja-2.11.0/testing/testapp/templates/500.jinja000066400000000000000000000000041447517172000226040ustar00rootroot00000000000000500 django-jinja-2.11.0/testing/testapp/templates/hello_world.jinja000066400000000000000000000002651447517172000246230ustar00rootroot00000000000000 test

Hello World from {{ name }}

{% csrf_token %} django-jinja-2.11.0/testing/testapp/templates/i18n_test.html000066400000000000000000000020041447517172000237710ustar00rootroot00000000000000{% blocktrans with bar=foo %} Foo {{ bar }} {% endblocktrans %} {% trans v_index %} {% trans 'Year' as t_year %} {{ table_sort('date_start', t_year) }}

{% blocktrans %}1 Hello {{ user }}!{% endblocktrans %}

{% blocktrans user=user.username %}2 Hello {{ user }}!{% endblocktrans %}

{% blocktrans book_title=book.title, author=author.name %} 3 This is {{ book_title }} by {{ author }} {% endblocktrans %}

{% blocktrans count=list|length %} 4 There is {{ count }} {{ name }} object. {% plural %} 4 There are {{ count }} {{ name }} objects. {% endblocktrans %}

{% trans '5 Hello World!' %}

{# couldnt get this one to work..#}

{# _('6 Hello, %(user)s!6)|format(user=user.username) #}

{% blocktrans %} #{{ invoice_count }} invoice {% plural %} #{{ invoice_count }} invoices {% endblocktrans %}

{% blocktrans trimmed %} #{{ trimmed_invoice_count }} trimmed invoice {% plural %} #{{ trimmed_invoice_count }} trimmed invoices {% endblocktrans %}

django-jinja-2.11.0/testing/testapp/templates/i18n_test.jinja000066400000000000000000000016541447517172000241320ustar00rootroot00000000000000{% trans bar=foo %} Foo {{ bar }} {% endtrans %} {{ _(v_index) }} {{ table_sort('date_start', _('Year')) }}

{% trans %}1 Hello {{ user }}!{% endtrans %}

{% trans user=user.username %}2 Hello {{ user }}!{% endtrans %}

{% trans notrimmed book_title=book.title, author=author.name %} 3 This is {{ book_title }} by {{ author }} {% endtrans %}

{% trans count=list|length %} 4 There is {{ count }} {{ name }} object. {% pluralize %} 4 There are {{ count }} {{ name }} objects. {% endtrans %}

{{ _('5 Hello World!') }}

{# couldnt get this one to work..#}

{# _('6 Hello, %(user)s!6)|format(user=user.username) #}

{% trans %} #{{ invoice_count }} invoice {% pluralize %} #{{ invoice_count }} invoices {% endtrans %}

{% trans trimmed %} #{{ trimmed_invoice_count }} trimmed invoice {% pluralize %} #{{ trimmed_invoice_count }} trimmed invoices {% endtrans %}

django-jinja-2.11.0/testing/testapp/templates/streaming_test.jinja000066400000000000000000000002651447517172000253410ustar00rootroot00000000000000 Streaming test

Streaming to the World from {{ name }}

django-jinja-2.11.0/testing/testapp/templates/test-debug-var.jinja000066400000000000000000000000411447517172000251320ustar00rootroot00000000000000{% if debug %}foobar{% endif -%} django-jinja-2.11.0/testing/testapp/templates/test-render-with.jinja000066400000000000000000000000341447517172000255100ustar00rootroot00000000000000{{ name }} django-jinja-2.11.0/testing/testapp/templates/testapp/000077500000000000000000000000001447517172000227515ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive.html.jinja000066400000000000000000000000371447517172000304320ustar00rootroot00000000000000ArchiveIndexView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive_day.html.jinja000066400000000000000000000000351447517172000312650ustar00rootroot00000000000000DayArchiveView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive_month.html.jinja000066400000000000000000000000371447517172000316370ustar00rootroot00000000000000MonthArchiveView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive_today.html.jinja000066400000000000000000000000371447517172000316320ustar00rootroot00000000000000TodayArchiveView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive_week.html.jinja000066400000000000000000000000361447517172000314440ustar00rootroot00000000000000WeekArchiveView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_archive_year.html.jinja000066400000000000000000000000361447517172000314510ustar00rootroot00000000000000YearArchiveView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_confirm_delete.html.jinja000066400000000000000000000000311447517172000317620ustar00rootroot00000000000000DeleteView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_create.html.jinja000066400000000000000000000000311447517172000302460ustar00rootroot00000000000000CreateView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_date_detail.html.jinja000066400000000000000000000000351447517172000312460ustar00rootroot00000000000000DateDetailView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_detail.html.jinja000066400000000000000000000000311447517172000302450ustar00rootroot00000000000000DetailView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_list.html.jinja000066400000000000000000000000271447517172000277630ustar00rootroot00000000000000ListView Test Template django-jinja-2.11.0/testing/testapp/templates/testapp/testmodel_update.html.jinja000066400000000000000000000000311447517172000302650ustar00rootroot00000000000000UpdateView Test Template django-jinja-2.11.0/testing/testapp/templatetags/000077500000000000000000000000001447517172000217655ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/templatetags/__init__.py000066400000000000000000000000001447517172000240640ustar00rootroot00000000000000django-jinja-2.11.0/testing/testapp/templatetags/sample_addons.py000066400000000000000000000006321447517172000251510ustar00rootroot00000000000000from django_jinja import library import jinja2 @library.test(name="one") def is_one(n): return n == 1 @library.filter @jinja2.pass_context def replace(context, value, x, y): return value.replace(x, y) @library.global_function def myecho(data): return data @library.global_function @library.render_with("test-render-with.jinja") def myrenderwith(*args, **kwargs): return {"name": "Foo"} django-jinja-2.11.0/testing/testapp/tests.py000066400000000000000000000462021447517172000210130ustar00rootroot00000000000000import datetime from unittest import mock from django.conf import global_settings from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.urls import reverse from django.middleware import csrf from django.shortcuts import render from django.template import RequestContext from django.template import engines from django.template.loader import get_template from django.test import TestCase from django.test import override_settings from django.test.client import RequestFactory from django.utils import timezone from django_jinja.backend import Jinja2 from django_jinja.base import get_match_extension from django_jinja.base import match_template from django_jinja.views.generic.base import Jinja2TemplateResponseMixin from .forms import TestForm from .models import TestModel from .views import StreamingTestView class RenderTemplatesTests(TestCase): def setUp(self): self.env = engines["jinja2"] self.factory = RequestFactory() def test_template_filters(self): filters_data = [ ("{{ 'test-static.css'|static }}", {}, '/static/test-static.css'), ("{{ 'test-1'|reverseurl }}", {}, '/test1/'), ("{{ 'test-1'|reverseurl(data=2) }}", {}, '/test1/2/'), ("{{ num|floatformat }}", {'num': 34.23234}, '34.2'), ("{{ num|floatformat(3) }}", {'num': 34.23234}, '34.232'), ("{{ 'hola'|capfirst }}", {}, "Hola"), ("{{ 'hola mundo'|truncatechars(5) }}", {}, "hola…"), ("{{ 'hola mundo'|truncatechars_html(5) }}", {}, "hola…"), ("{{ 'hola mundo'|truncatewords(1) }}", {}, "hola …"), ("{{ 'hola mundo'|truncatewords_html(1) }}", {}, "hola …"), ("{{ 'hola mundo'|wordwrap(1) }}", {}, "hola\nmundo"), ("{{ 'hola mundo'|title }}", {}, "Hola Mundo"), ("{{ 'hola mundo'|slugify }}", {}, "hola-mundo"), ("{{ 'hello'|ljust(10) }}", {}, "hello "), ("{{ 'hello'|rjust(10) }}", {}, " hello"), ("{{ 'hello\nworld'|linebreaksbr }}", {}, "hello
world"), ("{{ '
hello
'|striptags }}", {}, "hello"), ("{{ list|join(',') }}", {'list':['a','b']}, 'a,b'), ("{{ 3|add(2) }}", {}, "5"), ("{{ now|date('n Y') }}", {"now": datetime.datetime(2012, 12, 20)}, "12 2012"), ("{{ url('test-1') }}", {}, '/test1/'), ("{{ foo }}", {}, "bar"), ] print() for template_str, kwargs, result in filters_data: print("- Testing: ", template_str, "with:", kwargs) template = self.env.from_string(template_str) _result = template.render(kwargs) self.assertEqual(_result, result) def test_string_interpolation(self): template = self.env.from_string("{{ 'Hello %s!' % name }}") self.assertEqual(template.render({"name": "foo"}), "Hello foo!") template = self.env.from_string("{{ _('Hello %s!').format(name) }}") self.assertEqual(template.render({"name": "foo"}), "Hello foo!") def test_urlresolve_exceptions(self): template = self.env.from_string("{{ url('adads') }}") template.render({}) def test_custom_addons_01(self): template = self.env.from_string("{{ 'Hello'|replace('H','M') }}") result = template.render({}) self.assertEqual(result, "Mello") def test_custom_addons_02(self): template = self.env.from_string("{% if m is one %}Foo{% endif %}") result = template.render({'m': 1}) self.assertEqual(result, "Foo") def test_custom_addons_03(self): template = self.env.from_string("{{ myecho('foo') }}") result = template.render({}) self.assertEqual(result, "foo") def test_render_with(self): template = self.env.from_string("{{ myrenderwith() }}") result = template.render({}) self.assertEqual(result, "Foo") def test_django_context(self): """ Test that Django context objects (which are stacks of dicts) can be passed directly to Jinja2 templates. """ template = self.env.from_string("{{ greeting }}, {{ name }}") request = self.factory.get('/') ctx = RequestContext(request, {"greeting": "Hello", "name": "stranger"}) with ctx.push(greeting="Hi"): with ctx.push(name="friend"): rendered1 = template.render(ctx) rendered2 = template.render(ctx) self.assertEqual(rendered1, "Hi, friend") self.assertEqual(rendered2, "Hello, stranger") def test_autoscape_with_form(self): form = TestForm() template = self.env.from_string("{{ form.as_p() }}") result = template.render({"form": form}) self.assertIn('maxlength="2"', result) self.assertIn("
  • Ensure this value """ """has at most 2 characters (it has 3).
  • """)) template = self.env.from_string("{{ form.errors }}") result = template.render({"form": form}) self.assertEqual(result, ("""
    • name
        """ """
      • Ensure this value has at most 2 characters (it """ """has 3).
    """)) def test_autoescape_01(self): template = self.env.from_string("{{ foo|safe }}") result = template.render({'foo': '

    Hellp

    '}) self.assertEqual(result, "

    Hellp

    ") def test_autoescape_02(self): template = self.env.from_string("{{ foo }}") result = template.render({'foo': '

    Hellp

    '}) self.assertEqual(result, "<h1>Hellp</h1>") def test_autoescape_03(self): template = self.env.from_string("{{ foo|linebreaksbr }}") result = template.render({"foo": "\nfoo"}) self.assertEqual(result, "<script>alert(1)</script>
    foo") def test_debug_var_when_render_shortcut_is_used(self): prev_debug_value = settings.DEBUG settings.DEBUG = True request = self.factory.get("/") response = render(request, "test-debug-var.jinja") self.assertEqual(response.content, b"foobar") settings.DEBUG = prev_debug_value def test_debug_tag(self): """Test for {% debug %}""" tmpl = self.env.from_string('''Hello{% debug %}Bye''') out = tmpl.render() out = out.replace(''', "'").replace('<', '<').replace('>', '>') # # Check that some of the built-in items exist in the debug output... # assert "'context'" in out assert "'cycler'" in out assert "'filters'" in out assert "'abs'" in out assert "'tests'" in out assert "'!='" in out def test_csrf_01(self): template_content = "{% csrf_token %}" with mock.patch("django.middleware.csrf.get_token", return_value="123123") as m: request = self.factory.get('/customer/details') token = csrf.get_token(request) template = self.env.from_string(template_content) result = template.render({}, request) expected = f"" self.assertEqual(token, "123123") self.assertEqual(result, expected) def test_cache_01(self): template_content = "{% cache 200 'fooo' %}fóäo bar{% endcache %}" request = self.factory.get('/customer/details') context = RequestContext(request) template = self.env.from_string(template_content) result = template.render(context) self.assertEqual(result, "fóäo bar") def test_cache_none(self): template_content1 = "{% cache none 'barr' %}foo foo berry{% endcache %}" template_content2 = "{% cache none 'barr' %}not rendered{% endcache %}" request = self.factory.get('/forgotten/flavor') context = RequestContext(request) template1 = self.env.from_string(template_content1) result1 = template1.render(context) template2 = self.env.from_string(template_content2) result2 = template2.render(context) self.assertEqual(result1, "foo foo berry") self.assertEqual(result2, "foo foo berry") def test_404_page(self): response = self.client.get(reverse("page-404")) self.assertEqual(response.status_code, 404) self.assertEqual(response.content, b"404") response = self.client.post(reverse("page-404")) self.assertEqual(response.status_code, 404) self.assertEqual(response.content, b"404") response = self.client.put(reverse("page-404")) self.assertEqual(response.status_code, 404) self.assertEqual(response.content, b"404") response = self.client.delete(reverse("page-404")) self.assertEqual(response.status_code, 404) self.assertEqual(response.content, b"404") def test_403_page(self): response = self.client.get(reverse("page-403")) self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b"403") def test_500_page(self): response = self.client.get(reverse("page-500")) self.assertEqual(response.status_code, 500) self.assertEqual(response.content, b"500") def test_get_default(self): Jinja2.get_default.cache_clear() self.assertEqual(Jinja2.get_default(), self.env) def test_get_default_multiple(self): setting = { "append": [ { "BACKEND": "django_jinja.backend.Jinja2", "NAME": "jinja2dup", "APP_DIRS": True, "OPTIONS": { "match_extension": ".jinjadup", } } ] } with self.modify_settings(TEMPLATES=setting): with self.assertRaisesRegex(ImproperlyConfigured, r'Several Jinja2 backends'): Jinja2.get_default() def test_get_default_none(self): with self.settings(TEMPLATES=global_settings.TEMPLATES): with self.assertRaisesRegex(ImproperlyConfigured, r'No Jinja2 backend is configured'): Jinja2.get_default() def test_overwrite_default_app_dirname(self): setting = [ { "BACKEND": "django_jinja.backend.Jinja2", "NAME": "jinja2", "APP_DIRS": True, "OPTIONS": { "match_extension": None, "match_regex": None, "app_dirname": "jinja2", } } ] with override_settings(TEMPLATES=setting): template = get_template("hola_mundo.html") data = template.render({"name": "jinja2"}) self.assertEqual(data, "hola mundo de jinja2") def test_context_manipulation(self): response = self.client.get(reverse("test-1")) self.assertEqual(response.context["name"], "Jinja2") self.assertTemplateUsed(response, 'hello_world.jinja') def test_streaming_response(self): template = "streaming_test.jinja" context = {"view": StreamingTestView, "name": "Streaming Jinja2"} response = self.client.get(reverse('streaming-test')) self.assertEqual(response.context["name"], context["name"]) self.assertEqual(response.context["view"], context["view"]) self.assertTemplateUsed(response, template) template = get_template(template) self.assertEqual( b''.join(response.streaming_content), template.render(context).encode() ) class BaseTests(TestCase): def setUp(self): self.env = engines["jinja2"] def test_match_template(self): self.assertTrue( match_template('admin/foo.html', regex=None, extension=None)) self.assertFalse( match_template('admin/foo.html', regex=None, extension='.jinja')) self.assertTrue( match_template('admin/foo.html', regex=None, extension='.html')) self.assertTrue( match_template('admin/foo.html', regex=r'.*\.html', extension=None)) self.assertFalse( match_template('admin/foo.html', regex=r"^(?!admin/.*)", extension=None)) def test_get_match_extension(self): self.assertEqual(Jinja2.get_default().match_extension, get_match_extension()) def test_get_match_extension_using(self): setting = { "append": [ { "BACKEND": "django_jinja.backend.Jinja2", "NAME": "jinja2dup", "APP_DIRS": True, "OPTIONS": { "match_extension": ".jinjadup", } } ] } with self.modify_settings(TEMPLATES=setting): self.assertEqual(".jinja", get_match_extension(using='jinja2')) self.assertEqual(".jinjadup", get_match_extension(using="jinja2dup")) class TemplateResponseTests(TestCase): class _BaseView: def get_template_names(self): return [ 'name1.html', 'name2.html', 'name3.html.jinja', ] def setUp(self): self.obj1 = TestModel.objects.create() def test_get_template_names(self): class _View(Jinja2TemplateResponseMixin, self._BaseView): pass view = _View() self.assertEqual( ['name1.html.jinja', 'name2.html.jinja', 'name3.html.jinja'], view.get_template_names() ) def test_get_template_names_classext(self): class _View(Jinja2TemplateResponseMixin, self._BaseView): jinja2_template_extension = '.foo' view = _View() self.assertEqual( ['name1.html.foo', 'name2.html.foo', 'name3.html.jinja.foo'], view.get_template_names() ) def test_get_template_names_using(self): class _View(Jinja2TemplateResponseMixin, self._BaseView): template_engine = 'jinja2dup' setting = { "append": [ { "BACKEND": "django_jinja.backend.Jinja2", "NAME": "jinja2dup", "APP_DIRS": True, "OPTIONS": { "match_extension": ".jinjadup", } } ] } with self.modify_settings(TEMPLATES=setting): view = _View() self.assertEqual( ['name1.html.jinjadup', 'name2.html.jinjadup', 'name3.html.jinja.jinjadup'], view.get_template_names() ) class GenericViewTests(TestCase): def setUp(self): self.obj1 = TestModel.objects.create(date=timezone.now()) self.obj2 = TestModel.objects.create(date=timezone.now()) def test_detailview(self): self.assertContains( self.client.get(f'/testmodel/{self.obj1.pk}/detail'), 'DetailView Test Template', status_code=200 ) def test_createview(self): self.assertContains( self.client.get('/testmodel/create'), 'CreateView Test Template', status_code=200 ) def test_deleteview(self): self.assertContains( self.client.get(f'/testmodel/{self.obj1.pk}/delete'), 'DeleteView Test Template', status_code=200 ) def test_updateview(self): self.assertContains( self.client.get(f'/testmodel/{self.obj1.pk}/update'), 'UpdateView Test Template', status_code=200 ) def test_listview(self): self.assertContains( self.client.get('/testmodel/'), 'ListView Test Template', status_code=200 ) def test_archiveindexview(self): self.assertContains( self.client.get('/testmodel/archive/'), 'ArchiveIndexView Test Template', status_code=200 ) def test_yeararchiveview(self): self.assertContains( self.client.get(f'/testmodel/archive/{self.obj1.date:%Y}/'), 'YearArchiveView Test Template', status_code=200 ) def test_montharchiveview(self): self.assertContains( self.client.get(f'/testmodel/archive/{self.obj1.date:%Y/%b}/'), 'MonthArchiveView Test Template', status_code=200 ) def test_weekarchiveview(self): self.assertContains( self.client.get(f'/testmodel/archive/{self.obj1.date:%Y/week/%U}/'), 'WeekArchiveView Test Template', status_code=200 ) def test_dayarchiveview(self): self.assertContains( self.client.get(f'/testmodel/archive/{self.obj1.date:%Y/%b/%d}/'), 'DayArchiveView Test Template', status_code=200 ) def test_todayarchiveview(self): self.assertContains( self.client.get('/testmodel/archive/today/'), 'TodayArchiveView Test Template', status_code=200 ) def test_datedetailview(self): self.assertContains( self.client.get(f'/testmodel/archive/{self.obj1.date:%Y/%b/%d}/{self.obj1.pk}'), 'DateDetailView Test Template', status_code=200 ) # ==== Special imports ==== def test_import_archiveindexview(self): from django_jinja.views.generic import ArchiveIndexView def test_import_yeararchiveview(self): from django_jinja.views.generic import YearArchiveView def test_import_montharchiveview(self): from django_jinja.views.generic import MonthArchiveView def test_import_dayarchiveview(self): from django_jinja.views.generic import DayArchiveView def test_import_weekarchiveview(self): from django_jinja.views.generic import WeekArchiveView def test_import_todayarchiveview(self): from django_jinja.views.generic import TodayArchiveView def test_import_datedetailview(self): from django_jinja.views.generic import DateDetailView def test_import_detailview(self): from django_jinja.views.generic import DetailView def test_import_createview(self): from django_jinja.views.generic import CreateView def test_import_updateview(self): from django_jinja.views.generic import UpdateView def test_import_deleteview(self): from django_jinja.views.generic import DeleteView def test_import_listview(self): from django_jinja.views.generic import ListView django-jinja-2.11.0/testing/testapp/urls.py000066400000000000000000000037741447517172000206450ustar00rootroot00000000000000from django.urls import re_path as url from django_jinja import views from .views import BasicTestView from .views import I18nTestView, I18nTestViewDTL from .views import StreamingTestView from .views import CreateTestView, DeleteTestView, DetailTestView, UpdateTestView from .views import ListTestView from .views import ArchiveIndexTestView, YearArchiveTestView, MonthArchiveTestView, WeekArchiveTestView, DayArchiveTestView, TodayArchiveTestView, DateDetailTestView urlpatterns = [ url(r"^test1/$", BasicTestView.as_view(), name="test-1"), url(r"^test1/(?P\d+)/$", BasicTestView.as_view(), name="test-1"), url(r"^test-i18n/$", I18nTestView.as_view(), name="i18n-test"), url(r"^test-i18n-dtl/$", I18nTestViewDTL.as_view(), name="i18n-dtl-test"), url(r"^test/404$", views.PageNotFound.as_view(), name="page-404"), url(r"^test/403$", views.PermissionDenied.as_view(), name="page-403"), url(r"^test/500$", views.ServerError.as_view(), name="page-500"), url(r"^test-streaming/$", StreamingTestView.as_view(), name='streaming-test'), url(r"^testmodel/$", ListTestView.as_view()), url(r"^testmodel/create$", CreateTestView.as_view()), url(r"^testmodel/(?P\d+)/delete$", DeleteTestView.as_view()), url(r"^testmodel/(?P\d+)/detail$", DetailTestView.as_view()), url(r"^testmodel/(?P\d+)/update$", UpdateTestView.as_view()), url(r"^testmodel/archive/$", ArchiveIndexTestView.as_view()), url(r"^testmodel/archive/(?P\d{4})/$", YearArchiveTestView.as_view()), url(r"^testmodel/archive/(?P\d{4})/week/(?P\d+)/$", WeekArchiveTestView.as_view()), url(r"^testmodel/archive/(?P\d{4})/(?P[\w-]+)/$", MonthArchiveTestView.as_view()), url(r"^testmodel/archive/(?P\d{4})/(?P[\w-]+)/(?P\d+)/$", DayArchiveTestView.as_view()), url(r"^testmodel/archive/today/$", TodayArchiveTestView.as_view()), url(r"^testmodel/archive/(?P\d{4})/(?P[\w-]+)/(?P\d+)/(?P\d+)$", DateDetailTestView.as_view()) ] django-jinja-2.11.0/testing/testapp/views.py000066400000000000000000000057671447517172000210210ustar00rootroot00000000000000from django.views.generic import View from django.http import HttpResponse, StreamingHttpResponse from django.shortcuts import render from django.template import loader from django.template.loader import render_to_string from django_jinja.views.generic.detail import DetailView from django_jinja.views.generic.edit import CreateView, DeleteView, UpdateView from django_jinja.views.generic.list import ListView from django_jinja.views.generic.dates import ArchiveIndexView, YearArchiveView, MonthArchiveView, WeekArchiveView, DayArchiveView, TodayArchiveView, DateDetailView from .models import TestModel class BasicTestView(View): def get(self, request, data=None): data = render_to_string("hello_world.jinja", {"name": "Jinja2"}, request=request) return HttpResponse(data) class I18nTestView(View): template_name = "i18n_test.jinja" def get(self, request, data=None): class Author: name = "Freddy Fred" class Book: title = "Big 'ol Book" return render(request, self.template_name, { "v_index": "Index", "table_sort": lambda x, y: f"{x} {y}", "invoice_count": 1, "trimmed_invoice_count": 2, "author": Author(), "book": Book(), }) class I18nTestViewDTL(I18nTestView): template_name = "i18n_test.html" class ContextManipulationTestView(View): def get(self, request): return render(request, "hello_world.jinja", {"name": "Jinja2"}) # ==== generic.detail ==== class DetailTestView(DetailView): model = TestModel # ==== generic.edit ==== class CreateTestView(CreateView): model = TestModel fields = [] template_name_suffix = '_create' class DeleteTestView(DeleteView): model = TestModel class UpdateTestView(UpdateView): model = TestModel fields = [] template_name_suffix = '_update' # ==== generic.list ==== class ListTestView(ListView): model = TestModel # ==== generic.dates ==== class ArchiveIndexTestView(ArchiveIndexView): model = TestModel date_field = 'date' class YearArchiveTestView(YearArchiveView): model = TestModel date_field = 'date' class MonthArchiveTestView(MonthArchiveView): model = TestModel date_field = 'date' class WeekArchiveTestView(WeekArchiveView): model = TestModel date_field = 'date' class DayArchiveTestView(DayArchiveView): model = TestModel date_field = 'date' class TodayArchiveTestView(TodayArchiveView): model = TestModel date_field = 'date' template_name_suffix = '_archive_today' class DateDetailTestView(DateDetailView): model = TestModel date_field = 'date' template_name_suffix = '_date_detail' class StreamingTestView(View): def get(self, request, *args, **kwargs): context = {"name": "Streaming Jinja2", "view": type(self)} template = loader.get_template('streaming_test.jinja') return StreamingHttpResponse(template.stream(context, request), content_type='text/html') django-jinja-2.11.0/testing/uwsgi.ini000066400000000000000000000003131447517172000174470ustar00rootroot00000000000000[uwsgi] http-socket = 127.0.0.1:8001 ;pyhome = /home/niwi/.virtualenvs/djorm enable-threads = true wsgi-file = wsgi.py processes = 2 threads = 2 master = true max-requests = 2 ;lazy = true ;cheap = true django-jinja-2.11.0/testing/wsgi.py000066400000000000000000000021521447517172000171360ustar00rootroot00000000000000""" WSGI config for arandomtable project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) django-jinja-2.11.0/tox.ini000066400000000000000000000005551447517172000154560ustar00rootroot00000000000000[tox] envlist = py{38,39,310}-django{32,40} py{38,39,310,311}-django{41,42} [gh-actions] python = 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12-dev: py312 [testenv] changedir=testing commands=python runtests.py deps= django32: Django~=3.2 django40: Django~=4.0 django41: Django~=4.1 django42: Django~=4.2 jinja2