6 | %block>
--------------------------------------------------------------------------------
/tests_project/homepage/templates/filters.html:
--------------------------------------------------------------------------------
1 | <%block filter="django_syntax(local)">
2 | {{ django_var }}
3 | %block>
4 |
5 | <%block filter="jinja2_syntax(local)">
6 | {{ jinja2_var }}
7 | %block>
8 |
9 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/scripts/index.js:
--------------------------------------------------------------------------------
1 | (function(context) {
2 |
3 | // utc_epoch comes from index.py
4 | console.log('Current epoch in UTC is ' + context.utc_epoch);
5 |
6 | })(DMP_CONTEXT.get());
7 |
--------------------------------------------------------------------------------
/django_mako_plus/version.py:
--------------------------------------------------------------------------------
1 | # This file should have NO imports and be entirely standalone.
2 | # This allows it to import into the runtime DMP as well as
3 | # setup.py during installation.
4 |
5 | __version__ = '5.11.2'
6 |
--------------------------------------------------------------------------------
/django_mako_plus/template/__init__.py:
--------------------------------------------------------------------------------
1 | from .adapter import MakoTemplateAdapter
2 | from .loader import MakoTemplateLoader
3 | from .lexer import ExpressionPostProcessor
4 | from .util import template_inheritance, create_mako_context
5 |
--------------------------------------------------------------------------------
/tests_project/errorsapp/templates/syntax_error.html:
--------------------------------------------------------------------------------
1 | <%inherit file="/homepage/templates/base.htm" />
2 |
3 | <%block name="content">
4 | %for i in range(5):
5 | This throws a syntax error:
6 | missing endfor
7 | %block>
8 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Django-Mako-Plus
2 | site_name: Django-Mako-Plus
3 | site_url: http://doconix.github.io/django-mako-plus/
4 | site_description: Routing Django to Mako since 2013
5 | site_author: Conan C. Albrecht
6 |
7 | docs_dir: docs-rtd
8 |
--------------------------------------------------------------------------------
/docs/.buildinfo:
--------------------------------------------------------------------------------
1 | # Sphinx build info version 1
2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3 | config: ef33e1ec039c28f08e046b53b2de334a
4 | tags: 645f666f9bcd5a90fca523b33c5a78b7
5 |
--------------------------------------------------------------------------------
/django_mako_plus/util/__init__.py:
--------------------------------------------------------------------------------
1 | # set up the logger
2 | import logging
3 | log = logging.getLogger('django_mako_plus')
4 |
5 |
6 | # public functions
7 | from .base58 import b58enc, b58dec
8 | from .datastruct import merge_dicts, flatten, crc32
9 | from .reflect import qualified_name, import_qualified
10 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/templates/index.html:
--------------------------------------------------------------------------------
1 | <%inherit file="base.htm" />
2 |
3 | <%block name="content">
4 |
5 |
Congratulations -- you've successfully created a new DMP app!
6 |
Current time in UTC: ${ utc_time }
7 |
8 | %block>
9 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Routing Django to Mako since 2013.
2 |
3 | **IMPORTANT: This project is in the deep freezer. I have moved to React clients (still with Django back end), so I'm no longer actively maintaining this project.**
4 |
5 | Please visit http://doconix.github.io/django-mako-plus/ for tutorials, examples, and other documentation topics.
6 |
--------------------------------------------------------------------------------
/tests_project/homepage/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # some models for testing
4 |
5 | class IceCream(models.Model):
6 | name = models.TextField(null=True, blank=True)
7 | rating = models.IntegerField(default=0)
8 |
9 |
10 |
11 | class MyInt(int):
12 | '''Used in testing for specialized types'''
13 | pass
14 |
--------------------------------------------------------------------------------
/docs-src/static.rst:
--------------------------------------------------------------------------------
1 | .. _static:
2 |
3 | Static Files
4 | ==========================
5 |
6 |
7 | One of the primary tasks of DMP is to connect your static files to your templates. Read deeper to see what DMP can do for your static files...
8 |
9 |
10 | .. toctree::
11 | :maxdepth: 1
12 |
13 | static_overview
14 | static_links
15 | static_compilers
16 | static_webpack
17 | static_faq
18 |
--------------------------------------------------------------------------------
/tests_project/homepage/templates/base.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Testing_App
7 |
8 | ## render the static file links with the same name as this template
9 | ${ django_mako_plus.links(self) }
10 |
11 |
12 |
13 | <%block name="content">
14 | %block>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/_sources/static.rst.txt:
--------------------------------------------------------------------------------
1 | .. _static:
2 |
3 | Static Files
4 | ==========================
5 |
6 |
7 | One of the primary tasks of DMP is to connect your static files to your templates. Read deeper to see what DMP can do for your static files...
8 |
9 |
10 | .. toctree::
11 | :maxdepth: 1
12 |
13 | static_overview
14 | static_links
15 | static_compilers
16 | static_webpack
17 | static_faq
18 |
--------------------------------------------------------------------------------
/tests_project/homepage/views/static_files.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponse
3 | from django.views.generic import View
4 |
5 | from django_mako_plus import view_function, jscontext
6 |
7 |
8 | @view_function
9 | def process_request(request):
10 | return request.dmp.render('static_files.html', {
11 | jscontext('key1'): 'value1',
12 | 'key2': 'value2',
13 | })
14 |
15 |
--------------------------------------------------------------------------------
/django_mako_plus/converter/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | # public items in this package
3 | from .parameter import ViewParameter
4 | from .decorators import parameter_converter
5 | from .base import ParameterConverter
6 |
7 |
8 | # import the default converters
9 | # this must come at the end of the file so view_function above is loaded
10 | # it doesn't matter what's imported -- the file just needs to load
11 | from .converters import __name__ as _
12 |
--------------------------------------------------------------------------------
/django_mako_plus/converter/decorators.py:
--------------------------------------------------------------------------------
1 | from .base import ParameterConverter
2 |
3 | ### Decorator that denotes a converter function ###
4 |
5 | def parameter_converter(*convert_types):
6 | '''
7 | Decorator that denotes a function as a url parameter converter.
8 | '''
9 | def inner(func):
10 | for ct in convert_types:
11 | ParameterConverter._register_converter(func, ct)
12 | return func
13 | return inner
14 |
--------------------------------------------------------------------------------
/tests_project/homepage/tests/test_filters.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from django_mako_plus import render_template
4 |
5 |
6 |
7 |
8 | class Tester(TestCase):
9 |
10 | def test_filters(self):
11 | html = render_template(None, 'homepage', 'filters.html', {
12 | 'django_var': '::django::',
13 | 'jinja2_var': '~~jinja2~~',
14 | })
15 | self.assertTrue('::django::' in html)
16 | self.assertTrue('~~jinja2~~' in html)
17 |
--------------------------------------------------------------------------------
/tests_project/tests_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for fomo project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests_project.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/templates/base_ajax.htm:
--------------------------------------------------------------------------------
1 | ## this is the skeleton of all *ajax* pages on our site - page snippets that are retrieved with Ajax.
2 | ## it's primary function is to insert the CSS and JS for the ajax file template inheritance
3 |
4 | ## render the static file links with the same name as this template
5 | ${ django_mako_plus.links(self) }
6 |
7 | ## render the ajax content
8 | <%block name="content">
9 | Sub-templates should place their ajax content here.
10 | %block>
11 |
12 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/views/index.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django_mako_plus import view_function, jscontext
3 | from datetime import datetime, timezone
4 |
5 | @view_function
6 | def process_request(request):
7 | utc_time = datetime.utcnow()
8 | context = {
9 | # sent to index.html:
10 | 'utc_time': utc_time,
11 | # sent to index.html and index.js:
12 | jscontext('utc_epoch'): utc_time.timestamp(),
13 | }
14 | return request.dmp.render('index.html', context)
--------------------------------------------------------------------------------
/stats-download.txt:
--------------------------------------------------------------------------------
1 | # Download stats query for Google BigQuery:
2 | https://bigquery.cloud.google.com/table/the-psf:pypi.downloads
3 |
4 |
5 | SELECT
6 | STRFTIME_UTC_USEC(timestamp, "%Y-%m") AS yyyymm,
7 | COUNT(*) as download_count
8 | FROM
9 | TABLE_DATE_RANGE (
10 | [the-psf:pypi.downloads],
11 | DATE_ADD(CURRENT_TIMESTAMP(), -1, "year"),
12 | CURRENT_TIMESTAMP()
13 | )
14 | WHERE
15 | file.project="django-mako-plus"
16 | GROUP BY
17 | yyyymm
18 | ORDER BY
19 | yyyymm DESC
20 |
--------------------------------------------------------------------------------
/docs-src/converters.rst:
--------------------------------------------------------------------------------
1 | .. _converters:
2 |
3 | Parameter Conversion
4 | ==========================
5 |
6 | In the `initial tutorial `_, you learned that any extra parameters in the URL are sent to your view function as parameters. The following pages provide further information on how converters work.
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | converters_types
12 | converters_adding
13 | converters_replacing
14 | converters_errors
15 | converters_raw
16 | converters_decorators
17 |
--------------------------------------------------------------------------------
/django_mako_plus/project_template/project_name/wsgi.py-tpl:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for {{ project_name }} project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/docs/_sources/converters.rst.txt:
--------------------------------------------------------------------------------
1 | .. _converters:
2 |
3 | Parameter Conversion
4 | ==========================
5 |
6 | In the `initial tutorial `_, you learned that any extra parameters in the URL are sent to your view function as parameters. The following pages provide further information on how converters work.
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | converters_types
12 | converters_adding
13 | converters_replacing
14 | converters_errors
15 | converters_raw
16 | converters_decorators
17 |
--------------------------------------------------------------------------------
/docs-src/deploy_tutorials.rst:
--------------------------------------------------------------------------------
1 | .. _deploy_tutorials:
2 |
3 | Deployment Tutorials
4 | ======================
5 |
6 | The following tutorials, written by DMP users, describe deployment to different servers. Note that some might be for earlier versions of DMP (so slight modifications might be necessary).
7 |
8 | - http://www.duckcode.me/article/how-to-deploy-to-heroku
9 | - http://blog.tworivershosting.com/2014/11/ubuntu-server-setup-for-django-mako-plus.html
10 |
11 | Let us know if you write or find additional tutorials. They are generally very helpful!
12 |
--------------------------------------------------------------------------------
/docs/_sources/deploy_tutorials.rst.txt:
--------------------------------------------------------------------------------
1 | .. _deploy_tutorials:
2 |
3 | Deployment Tutorials
4 | ======================
5 |
6 | The following tutorials, written by DMP users, describe deployment to different servers. Note that some might be for earlier versions of DMP (so slight modifications might be necessary).
7 |
8 | - http://www.duckcode.me/article/how-to-deploy-to-heroku
9 | - http://blog.tworivershosting.com/2014/11/ubuntu-server-setup-for-django-mako-plus.html
10 |
11 | Let us know if you write or find additional tutorials. They are generally very helpful!
12 |
--------------------------------------------------------------------------------
/docs-src/deploy.rst:
--------------------------------------------------------------------------------
1 | .. _deploy:
2 |
3 | Deployment
4 | ==========================
5 |
6 |
7 | For the most part, DMP-based projects deploy with the same process and considerations as normal Django projects. Therefore, we recommend reading the `standard Django documentation `_ first.
8 |
9 | Once you understand the general process of deploying Django, the following pages describe specific DMP deployment considerations:
10 |
11 | .. toctree::
12 | :maxdepth: 1
13 |
14 | deploy_static
15 | deploy_recommendations
16 | deploy_tutorials
17 |
--------------------------------------------------------------------------------
/docs-src/tutorial.rst:
--------------------------------------------------------------------------------
1 | .. _tutorial:
2 |
3 | Tutorial
4 | ==========================
5 |
6 | These five tutorial pages are the best way to learn about DMP. Assuming you have gone through the installation instructions, this is your jumping off point.
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | tutorial_meet_dmp
12 | tutorial_views
13 | tutorial_parameters
14 | tutorial_css_js
15 | tutorial_ajax
16 |
17 | Although I agree that T2 was the best movie in the series (although personally, I *loved* the twist at the end of the underrated T3), my favorite part of the tutorial is T4 (css and js).
18 |
--------------------------------------------------------------------------------
/docs/_sources/deploy.rst.txt:
--------------------------------------------------------------------------------
1 | .. _deploy:
2 |
3 | Deployment
4 | ==========================
5 |
6 |
7 | For the most part, DMP-based projects deploy with the same process and considerations as normal Django projects. Therefore, we recommend reading the `standard Django documentation `_ first.
8 |
9 | Once you understand the general process of deploying Django, the following pages describe specific DMP deployment considerations:
10 |
11 | .. toctree::
12 | :maxdepth: 1
13 |
14 | deploy_static
15 | deploy_recommendations
16 | deploy_tutorials
17 |
--------------------------------------------------------------------------------
/docs/_sources/tutorial.rst.txt:
--------------------------------------------------------------------------------
1 | .. _tutorial:
2 |
3 | Tutorial
4 | ==========================
5 |
6 | These five tutorial pages are the best way to learn about DMP. Assuming you have gone through the installation instructions, this is your jumping off point.
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | tutorial_meet_dmp
12 | tutorial_views
13 | tutorial_parameters
14 | tutorial_css_js
15 | tutorial_ajax
16 |
17 | Although I agree that T2 was the best movie in the series (although personally, I *loved* the twist at the end of the underrated T3), my favorite part of the tutorial is T4 (css and js).
18 |
--------------------------------------------------------------------------------
/django_mako_plus/webroot/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 |
4 | module.exports = (env, argv) => {
5 | let DEBUG = argv.mode != 'production';
6 | return {
7 | entry: [
8 | './dmp-common.src.js',
9 | ],
10 | output: {
11 | path: path.resolve(__dirname),
12 | filename: DEBUG ? './dmp-common.js' : './dmp-common.min.js',
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | use: 'babel-loader',
19 | },
20 | ],
21 | },
22 | optimization: {
23 | minimize: !DEBUG,
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests_project/homepage/views/__init__.py:
--------------------------------------------------------------------------------
1 | from django_mako_plus.converter import ParameterConverter
2 | from django_mako_plus import view_function
3 | from django.http import HttpRequest
4 |
5 |
6 | class RecordingConverter(ParameterConverter):
7 | '''Converter that also records the converted variables for inspecting during testing'''
8 | def convert_parameters(self, *args, **kwargs):
9 | # request is usually args[0], but it can be args[1] when using functools.partial in the decorator
10 | request = args[1] if len(args) >= 2 and isinstance(args[1], HttpRequest) else args[0]
11 | args, kwargs = super().convert_parameters(*args, **kwargs)
12 | request.dmp.converted_params = kwargs
13 | return args, kwargs
14 |
--------------------------------------------------------------------------------
/docs-src/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | # -E :: don't use a saved environment, always read all files
6 | # -a :: write all files (not just changed)
7 | # -q :: no stdout, just stderr
8 | SPHINXOPTS = -E -a -q
9 | SPHINXBUILD = sphinx-build
10 | SPHINXPROJ = Django-Mako-Plus
11 | SOURCEDIR = .
12 | BUILDDIR = ../docs/
13 |
14 | # Put it first so that "make" without argument is like "make help".
15 | help:
16 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
17 |
18 | .PHONY: help Makefile
19 |
20 | html: Makefile
21 | @python3 make_page_labels.py
22 | @$(SPHINXBUILD) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
23 | @touch ${BUILDDIR}.nojekyll
24 |
--------------------------------------------------------------------------------
/docs-src/install_subdirectory.rst:
--------------------------------------------------------------------------------
1 | .. _install_subdirectory:
2 |
3 | Installing in a Subdirectory: /mysite/
4 | ==========================================
5 |
6 | This section is for those that need Django is a subdirectory, such as ``/mysite``. If your Django installation is at the root of your domain, skip this section.
7 |
8 | In other words, suppose your Django site isn't the only thing on your server. Instead of the normal url pattern, ``http://www.yourdomain.com/``, your Django installation is at ``http://www.yourdomain.com/mysite/``. All apps are contained within this ``mysite/`` directory.
9 |
10 | This is accomplished in the normal Django way. Adjust your ``urls.py`` file to include the prefix:
11 |
12 | .. code-block:: python
13 |
14 | url('^mysite/', include('django_mako_plus.urls')),
15 |
--------------------------------------------------------------------------------
/docs/_sources/install_subdirectory.rst.txt:
--------------------------------------------------------------------------------
1 | .. _install_subdirectory:
2 |
3 | Installing in a Subdirectory: /mysite/
4 | ==========================================
5 |
6 | This section is for those that need Django is a subdirectory, such as ``/mysite``. If your Django installation is at the root of your domain, skip this section.
7 |
8 | In other words, suppose your Django site isn't the only thing on your server. Instead of the normal url pattern, ``http://www.yourdomain.com/``, your Django installation is at ``http://www.yourdomain.com/mysite/``. All apps are contained within this ``mysite/`` directory.
9 |
10 | This is accomplished in the normal Django way. Adjust your ``urls.py`` file to include the prefix:
11 |
12 | .. code-block:: python
13 |
14 | url('^mysite/', include('django_mako_plus.urls')),
15 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.chdir('./tests_project')
7 | sys.path.insert(0, os.getcwd())
8 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests_project.settings'
9 | import django
10 | django.setup()
11 | from django.conf import settings
12 | from django.test.utils import get_runner
13 | TestRunner = get_runner(settings)
14 | test_runner = TestRunner()
15 | failures = test_runner.run_tests(sys.argv[1:])
16 | print()
17 | print('Note: some of the tests produce exceptions and stack traces in the output, but these are the expected exceptions resulting from tests. Focus on whether the tests ran without failures (not on the expected exceptions).')
18 | print()
19 | sys.exit(bool(failures))
20 |
--------------------------------------------------------------------------------
/tests_project/homepage/fixtures/ice_cream.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "model": "homepage.icecream",
4 | "pk": 1,
5 | "fields": {
6 | "name": "Mint Chocolate Chip",
7 | "rating": 10
8 | }
9 | },
10 | {
11 | "model": "homepage.icecream",
12 | "pk": 2,
13 | "fields": {
14 | "name": "Burnt Almond Fudge",
15 | "rating": 10
16 | }
17 | },
18 | {
19 | "model": "homepage.icecream",
20 | "pk": 3,
21 | "fields": {
22 | "name": "Cherry",
23 | "rating": 3
24 | }
25 | },
26 | {
27 | "model": "homepage.icecream",
28 | "pk": 4,
29 | "fields": {
30 | "name": "Sherbet",
31 | "rating": 1
32 | }
33 | }
34 | ]
35 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/styles/base.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | color: #777788;
6 | }
7 |
8 | .clearfix {
9 | clear: both;
10 | }
11 |
12 | header {
13 | padding: 50px 20px;
14 | text-align: center;
15 | }
16 |
17 | header > .title {
18 | display: inline-block;
19 | color: #3771A1;
20 | font-size: 40px;
21 | font-weight: bold;
22 | text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.2);
23 | vertical-align: middle;
24 | }
25 |
26 | header img {
27 | vertical-align: middle;
28 | margin-right: 24px;
29 | }
30 |
31 | main {
32 | margin: 0;
33 | padding: 15px;
34 | }
35 |
36 | footer {
37 | margin-top: 40px;
38 | border-top: 1px solid #CCCCCC;
39 | text-align: right;
40 | }
41 |
42 | footer a {
43 | display: inline-block;
44 | color: #777788;
45 | margin: 15px 10% 0 0;
46 | }
47 |
--------------------------------------------------------------------------------
/tests_project/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests_project.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError:
10 | # The above import may fail for some other reason. Ensure that the
11 | # issue is really that Django is missing to avoid masking other
12 | # exceptions on Python 2.
13 | try:
14 | import django
15 | except ImportError:
16 | raise ImportError(
17 | "Couldn't import Django. Are you sure it's installed and "
18 | "available on your PYTHONPATH environment variable? Did you "
19 | "forget to activate a virtual environment?"
20 | )
21 | raise
22 | execute_from_command_line(sys.argv)
23 |
--------------------------------------------------------------------------------
/django_mako_plus/project_template/manage.py-tpl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError:
10 | # The above import may fail for some other reason. Ensure that the
11 | # issue is really that Django is missing to avoid masking other
12 | # exceptions on Python 2.
13 | try:
14 | import django
15 | except ImportError:
16 | raise ImportError(
17 | "Couldn't import Django. Are you sure it's installed and "
18 | "available on your PYTHONPATH environment variable? Did you "
19 | "forget to activate a virtual environment?"
20 | )
21 | raise
22 | execute_from_command_line(sys.argv)
23 |
--------------------------------------------------------------------------------
/docs-src/make_page_labels.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import glob, os, re
4 |
5 | EXISTING_RE = re.compile(r'^\.\.\s+_\w+:\n+')
6 |
7 | for fname in glob.glob('*.rst'):
8 | with open(fname) as fin:
9 | contents = fin.read()
10 | prevcontents = contents
11 | label = '.. _{}:\n\n'.format(os.path.splitext(fname)[0])
12 | EXACT_RE = re.compile(r'^\.\. _{}:\n\n[^\n]'.format(os.path.splitext(fname)[0]))
13 | if not EXACT_RE.search(contents):
14 | if EXISTING_RE.search(contents): # existing line is wrong, so replace
15 | contents = EXISTING_RE.sub(label, contents)
16 | else: # none there, so insert
17 | contents = label + contents
18 | if prevcontents != contents:
19 | print('Fixed label in {}'.format(fname))
20 | with open(fname, 'w') as fout:
21 | fout.write(''.join(contents))
22 |
--------------------------------------------------------------------------------
/django_mako_plus/util/reflect.py:
--------------------------------------------------------------------------------
1 | from importlib import import_module
2 |
3 |
4 | def qualified_name(obj):
5 | '''Returns the fully-qualified name of the given object'''
6 | if not hasattr(obj, '__module__'):
7 | obj = obj.__class__
8 | module = obj.__module__
9 | if module is None or module == str.__class__.__module__:
10 | return obj.__qualname__
11 | return '{}.{}'.format(module, obj.__qualname__)
12 |
13 |
14 | def import_qualified(name):
15 | '''
16 | Imports a fully-qualified name from a module:
17 |
18 | cls = import_qualified('homepage.views.index.MyForm')
19 |
20 | Raises an ImportError if it can't be ipmorted.
21 | '''
22 | parts = name.rsplit('.', 1)
23 | if len(parts) != 2:
24 | raise ImportError('Invalid fully-qualified name: {}'.format(name))
25 | try:
26 | return getattr(import_module(parts[0]), parts[1])
27 | except AttributeError:
28 | raise ImportError('{} not found in module {}'.format(parts[1], parts[0]))
29 |
--------------------------------------------------------------------------------
/docs/_static/sphinx_tabs/tabs.css:
--------------------------------------------------------------------------------
1 | .sphinx-tabs {
2 | margin-bottom: 2em;
3 | }
4 |
5 | .sphinx-tabs .sphinx-menu a.item {
6 | color: #2980b9 !important;
7 | }
8 |
9 | .sphinx-tabs .sphinx-menu {
10 | border-bottom-color: #a0b3bf !important;
11 | display: flex;
12 | flex-direction: row;
13 | flex-wrap: wrap;
14 | }
15 |
16 | .sphinx-tabs .sphinx-menu a.active.item {
17 | border-color: #a0b3bf !important;
18 | }
19 |
20 | .sphinx-tab {
21 | border-color: #a0b3bf !important;
22 | box-sizing: border-box;
23 | }
24 |
25 | .tab div[class^='highlight']:last-child {
26 | margin-bottom: 0;
27 | }
28 |
29 | .tab .wy-plain-list-disc:last-child,
30 | .rst-content .section ul:last-child,
31 | .rst-content .toctree-wrapper ul:last-child,
32 | article ul:last-child {
33 | margin-bottom: 0;
34 | }
35 |
36 | /* Code tabs don't need the code-block border */
37 | .code-tab.tab {
38 | padding: 0.4em !important;
39 | }
40 |
41 | .code-tab.tab div[class^='highlight'] {
42 | border: none;
43 | }
44 |
--------------------------------------------------------------------------------
/django_mako_plus/context_processors.py:
--------------------------------------------------------------------------------
1 | ################################################################
2 | ### A set of request processors that add variables to the
3 | ### context (parameters) when templates are rendered.
4 | ###
5 |
6 | from django.conf import settings as conf_settings
7 | from django.template.backends.utils import csrf_input_lazy, csrf_token_lazy
8 |
9 |
10 |
11 | def settings(request):
12 | '''Adds the settings dictionary to the request'''
13 | return { 'settings': conf_settings }
14 |
15 |
16 | def csrf(request):
17 | '''
18 | Adds the "csrf_input" and "csrf_token" variables to the request.
19 |
20 | Following Django's lead, this processor is included in DMP's
21 | default context processors list. It does not need to be listed
22 | in settings.py.
23 |
24 | To include the control in your forms,
25 | use ${ csrf_input }.
26 | '''
27 | return {
28 | 'csrf_input': csrf_input_lazy(request),
29 | 'csrf_token': csrf_token_lazy(request),
30 | }
31 |
--------------------------------------------------------------------------------
/docs/_static/sphinx_tabs/semantic-ui-2.2.10/tab.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.2.10 - Tab
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.tab{display:none}.ui.tab.active,.ui.tab.open{display:block}.ui.tab.loading{position:relative;overflow:hidden;display:block;min-height:250px}.ui.tab.loading *{position:relative!important;left:-10000px!important}.ui.tab.loading.segment:before,.ui.tab.loading:before{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.tab.loading.segment:after,.ui.tab.loading:after{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}
--------------------------------------------------------------------------------
/docs-src/install.rst:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installation
4 | ==============================
5 |
6 | This section shows how to install DMP in a new project as well as an existing project.
7 |
8 | What kind of project do you have?
9 |
10 | .. toctree::
11 | :maxdepth: 1
12 |
13 | install_new
14 | install_existing
15 |
16 |
17 | Minimal DMP
18 | -------------------------------------
19 |
20 | A common question from users is how to do a minimal installation with just one feature. For example, you might need to use DMP to render Mako templates in an otherwise vanilla Django project. Or you might only need DMP's routing-by-convention.
21 |
22 | This section shows how to go cafeteria-style with DMP.
23 |
24 | .. toctree::
25 | :maxdepth: 1
26 |
27 | install_as_renderer
28 | install_as_router
29 | install_app_specific
30 |
31 |
32 | Customizations
33 | -----------------------------------
34 |
35 | The following pages discuss additional installation topics:
36 |
37 | .. toctree::
38 | :maxdepth: 1
39 |
40 | install_custom_urls
41 | install_subdirectory
42 |
--------------------------------------------------------------------------------
/docs/_sources/install.rst.txt:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installation
4 | ==============================
5 |
6 | This section shows how to install DMP in a new project as well as an existing project.
7 |
8 | What kind of project do you have?
9 |
10 | .. toctree::
11 | :maxdepth: 1
12 |
13 | install_new
14 | install_existing
15 |
16 |
17 | Minimal DMP
18 | -------------------------------------
19 |
20 | A common question from users is how to do a minimal installation with just one feature. For example, you might need to use DMP to render Mako templates in an otherwise vanilla Django project. Or you might only need DMP's routing-by-convention.
21 |
22 | This section shows how to go cafeteria-style with DMP.
23 |
24 | .. toctree::
25 | :maxdepth: 1
26 |
27 | install_as_renderer
28 | install_as_router
29 | install_app_specific
30 |
31 |
32 | Customizations
33 | -----------------------------------
34 |
35 | The following pages discuss additional installation topics:
36 |
37 | .. toctree::
38 | :maxdepth: 1
39 |
40 | install_custom_urls
41 | install_subdirectory
42 |
--------------------------------------------------------------------------------
/django_mako_plus/project_template/project_name/urls.py-tpl:
--------------------------------------------------------------------------------
1 | """{{ project_name }} URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/{{ docs_version }}/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url, include
17 | from django.contrib import admin
18 |
19 | urlpatterns = [
20 | # the built-in Django administrator
21 | url(r'^admin/', admin.site.urls),
22 |
23 | # urls for any third-party apps go here
24 |
25 | # the DMP router - this should normally be the last URL listed
26 | url('', include('django_mako_plus.urls')),
27 | ]
28 |
--------------------------------------------------------------------------------
/django_mako_plus/router/urlparams.py:
--------------------------------------------------------------------------------
1 |
2 | ################################################################
3 | ### Special type of list used for url params
4 |
5 | class URLParamList(list):
6 | '''
7 | A simple extension to Python's list that returns '' for indices that don't exist.
8 | For example, if the object is ['a', 'b'] and you call obj[5], it will return ''
9 | rather than throwing an IndexError. This makes dealing with url parameters
10 | simpler since you don't have to check the length of the list.
11 | '''
12 | def __getitem__(self, idx):
13 | '''Returns the element at idx, or '' if idx is beyond the length of the list'''
14 | return self.get(idx, '')
15 |
16 | def get(self, idx, default=''):
17 | '''Returns the element at idx, or default if idx is beyond the length of the list'''
18 | # if the index is beyond the length of the list, return ''
19 | if isinstance(idx, int) and (idx >= len(self) or idx < -1 * len(self)):
20 | return default
21 | # else do the regular list function (for int, slice types, etc.)
22 | return super().__getitem__(idx)
23 |
--------------------------------------------------------------------------------
/django_mako_plus/app_template/templates/base.htm:
--------------------------------------------------------------------------------
1 | ## this is the skeleton of all pages on in this app - it defines the basic html tags
2 |
3 |
4 |
5 |
6 |
7 | DMP
8 |
9 | ## add any site-wide scripts or CSS here; for example, jquery:
10 |
11 |
12 | ## render the static file links with the same name as this template
13 |
14 | ${ django_mako_plus.links(self) }
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Welcome to DMP!
22 |
23 |
24 |
25 | <%block name="content">
26 | Site content goes here in sub-templates.
27 | %block>
28 |
29 |
30 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/livereload-docs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This starts a web server on http://localhost:5500/ where doc files are automatically recompiled.
4 | #
5 | # If you need to create the docs from scratch, run:
6 | # cd docs
7 | # make html
8 | #
9 |
10 | from livereload import Server
11 | from subprocess import Popen, PIPE
12 |
13 | # livereload's run_shell doesn't encode errors right (leaves them bytes)
14 | import logging
15 | logger = logging.getLogger('livereload')
16 | class Runner:
17 | def __init__(self, cmd, cwd):
18 | self.cmd = cmd
19 | self.cwd = cwd
20 | def __str__(self):
21 | return ' '.join(self.cmd)
22 | def __call__(self):
23 | p = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=self.cwd, shell=False)
24 | stdout, stderr = p.communicate()
25 | if stderr:
26 | logger.error('\n' + stderr.decode())
27 | return stderr
28 | if stdout:
29 | logger.info('\n' + stdout.decode())
30 | return stdout
31 |
32 | server = Server()
33 | server.watch('docs-src/*.rst', Runner(['make', 'html', '--always-make' ], cwd='docs-src'))
34 | server.watch('docs-src/_static/*.css', Runner(['make', 'html', '--always-make' ], cwd='docs-src'))
35 | print('PORT IS 5500')
36 | server.serve(root='docs/', restart_delay=1)
37 |
--------------------------------------------------------------------------------
/docs-src/topics.rst:
--------------------------------------------------------------------------------
1 | .. _topics:
2 |
3 | Basic Concepts
4 | ==========================
5 |
6 | After the tutorial, start through these topics to learn about DMP:
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | topics_settings
12 | topics_escaping
13 | topics_variables
14 | topics_modules
15 | topics_third_party
16 | topics_paths
17 | topics_convenience
18 | topics_redirecting
19 | topics_csrf
20 | topics_class_views
21 | topics_django
22 | topics_responses
23 | topics_view_function
24 | topics_partial_templates
25 | topics_other_syntax
26 | topics_signals
27 | topics_translation
28 |
29 |
30 |
31 | The following are further destinations to learn Mako and Django:
32 |
33 | - Go through the `Mako Templates `__ documentation. It will explain all the constructs you can use in your html templates.
34 | - Read or reread the `Django Tutorial `__. Just remember as you see the tutorial's Django template code (usually surrounded by ``{{ }}``) that you'll be using Mako syntax instead (``${ }``).
35 | - Link to this project in your blog or online comments. I'd love to see the Django people come around to the idea that Python isn't evil inside templates. Complex Python might be evil, but Python itself is just a tool within templates.
36 |
--------------------------------------------------------------------------------
/docs/_sources/topics.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics:
2 |
3 | Basic Concepts
4 | ==========================
5 |
6 | After the tutorial, start through these topics to learn about DMP:
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | topics_settings
12 | topics_escaping
13 | topics_variables
14 | topics_modules
15 | topics_third_party
16 | topics_paths
17 | topics_convenience
18 | topics_redirecting
19 | topics_csrf
20 | topics_class_views
21 | topics_django
22 | topics_responses
23 | topics_view_function
24 | topics_partial_templates
25 | topics_other_syntax
26 | topics_signals
27 | topics_translation
28 |
29 |
30 |
31 | The following are further destinations to learn Mako and Django:
32 |
33 | - Go through the `Mako Templates `__ documentation. It will explain all the constructs you can use in your html templates.
34 | - Read or reread the `Django Tutorial `__. Just remember as you see the tutorial's Django template code (usually surrounded by ``{{ }}``) that you'll be using Mako syntax instead (``${ }``).
35 | - Link to this project in your blog or online comments. I'd love to see the Django people come around to the idea that Python isn't evil inside templates. Complex Python might be evil, but Python itself is just a tool within templates.
36 |
--------------------------------------------------------------------------------
/django_mako_plus/util/datastruct.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import zlib
3 |
4 |
5 | def merge_dicts(*dicts):
6 | '''
7 | Shallow merges an arbitrary number of dicts, starting
8 | with the first argument and updating through the
9 | last argument (last dict wins on conflicting keys).
10 | '''
11 | merged = {}
12 | for d in dicts:
13 | if d:
14 | merged.update(d)
15 | return merged
16 |
17 |
18 | def flatten(*args):
19 | '''Generator that recursively flattens embedded lists, tuples, etc.'''
20 | for arg in args:
21 | if isinstance(arg, collections.Iterable) and not isinstance(arg, (str, bytes)):
22 | yield from flatten(*arg)
23 | else:
24 | yield arg
25 |
26 |
27 |
28 | def crc32(filename):
29 | '''
30 | Calculates the CRC checksum for a file.
31 | Using CRC32 because security isn't the issue and don't need perfect noncollisions.
32 | We just need to know if a file has changed.
33 |
34 | On my machine, crc32 was 20 times faster than any hashlib algorithm,
35 | including blake and md5 algorithms.
36 | '''
37 | result = 0
38 | with open(filename, 'rb') as fin:
39 | while True:
40 | chunk = fin.read(48)
41 | if len(chunk) == 0:
42 | break
43 | result = zlib.crc32(chunk, result)
44 | return result
45 |
--------------------------------------------------------------------------------
/tests_project/homepage/tests/test_static_files.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.test import TestCase
3 | from django.test.utils import override_settings
4 |
5 | class Tester(TestCase):
6 |
7 | def setUp(self):
8 | # resets all the Mako caches because we switch between debug and prod mode
9 | # during testing, and providers load differently for each
10 | dmp = apps.get_app_config('django_mako_plus')
11 | dmp.engine.template_loaders = {}
12 |
13 |
14 | @override_settings(DEBUG=True)
15 | def test_links(self):
16 | resp = self.client.get('/homepage/static_files/')
17 | self.assertEqual(resp.status_code, 200)
18 | # base
19 | self.assertTrue(b'/static/homepage/scripts/base.js' in resp.content)
20 | self.assertTrue(b'/static/homepage/styles/base.css' in resp.content)
21 | # static files
22 | self.assertTrue(b'homepage/scripts/static_files.js' in resp.content)
23 | self.assertTrue(b'homepage/styles/static_files.css' in resp.content)
24 | # jscontext output
25 | self.assertTrue(b'DMP_CONTEXT.set' in resp.content)
26 | self.assertTrue(b'data-context' in resp.content)
27 | self.assertTrue(b'key1' in resp.content)
28 | self.assertTrue(b'value1' in resp.content)
29 | self.assertTrue(b'key2' not in resp.content)
30 | self.assertTrue(b'value2' not in resp.content)
31 |
--------------------------------------------------------------------------------
/django_mako_plus/converter/parameter.py:
--------------------------------------------------------------------------------
1 |
2 | #####################################
3 | ### ViewParameter
4 |
5 | class ViewParameter(object):
6 | '''
7 | A data class that represents a view parameter on a view function.
8 | An instance of this class is created for each parameter in a view function
9 | (except the initial request object argument).
10 | '''
11 | def __init__(self, name, position, kind, type, default):
12 | '''
13 | name: The name of the parameter.
14 | position: The position of this parameter.
15 | kind: The kind of argument (positional, keyword, etc.). See inspect module.
16 | type: The expected type of this parameter. Converters use this type to
17 | convert urlparam strings to the right type.
18 | default: Any default value, specified in function type hints. If no default is
19 | specified in the function, this is `inspect.Parameter.empty`.
20 | '''
21 | self.name = name
22 | self.position = position
23 | self.kind = kind
24 | self.type = type
25 | self.default = default
26 |
27 | def __repr__(self):
28 | return ''.format(
29 | self.name,
30 | self.type.__qualname__ if self.type is not None else '',
31 | self.default,
32 | )
33 |
--------------------------------------------------------------------------------
/docs-src/deploy_recommendations.rst:
--------------------------------------------------------------------------------
1 | .. _deploy_recommendations:
2 |
3 | Deployment Recommendations
4 | ==========================
5 |
6 | This section has nothing to do with the Django-Mako-Framework, but I want to address a couple issues in hopes that it will save you some headaches. One of the most difficult decisions in Django development is deciding how to deploy your system. In particular, there are several ways to connect Django to your web server: mod\_wsgi, FastCGI, etc.
7 |
8 | At MyEducator, we've been through all of them at various levels of testing and production. By far, we've had the best success with `uWSGI `__. It is a professional server, and it is stable.
9 |
10 | One other decision you'll have to make is which database use. I'm excluding the "big and beefies" like Oracle or DB2. Those with sites that need these databases already know who they are. Most of you will be choosing between MySQL, PostgreSQL, and perhaps another mid-level database.
11 |
12 | In choosing databases, you'll find that many, if not most, of the Django developers use PostgreSQL. The system is likely tested best and first on PG. We started on MySQL, and we moved to PG after experiencing a few problems. Since deploying on PG, things have been amazingly smooth.
13 |
14 | Your mileage may vary with everything in this section. Do your own testing and take it all as advice only. Best of luck.
15 |
--------------------------------------------------------------------------------
/django_mako_plus/templatetags/django_mako_plus.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.apps import apps
3 | from django.utils.safestring import mark_safe
4 |
5 |
6 | ###############################################################
7 | ### DJANGO template tag to include a Mako template
8 | ###
9 | ### This file is called "django_mako_plus.py" because it's
10 | ### the convention for creating Django template tags.
11 | ###
12 | ### See also django_mako_plus/filters.py
13 |
14 | register = template.Library()
15 |
16 | @register.simple_tag(takes_context=True)
17 | def dmp_include(context, template_name, def_name=None, **kwargs):
18 | '''
19 | Includes a DMP (Mako) template into a normal django template.
20 |
21 | context: automatically provided
22 | template_name: specified as "app/template"
23 | def_name: optional block to render within the template
24 |
25 | Example:
26 | {% load django_mako_plus %}
27 | {% dmp_include "homepage/bsnav_dj.html" %}
28 | or
29 | {% dmp_include "homepage/bsnav_dj.html" "blockname" %}
30 | '''
31 | dmp = apps.get_app_config('django_mako_plus')
32 | template = dmp.engine.get_template(template_name)
33 | dmpcontext = context.flatten()
34 | dmpcontext.update(kwargs)
35 | return mark_safe(template.render(
36 | context=dmpcontext,
37 | request=context.get('request'),
38 | def_name=def_name
39 | ))
40 |
--------------------------------------------------------------------------------
/docs/_sources/deploy_recommendations.rst.txt:
--------------------------------------------------------------------------------
1 | .. _deploy_recommendations:
2 |
3 | Deployment Recommendations
4 | ==========================
5 |
6 | This section has nothing to do with the Django-Mako-Framework, but I want to address a couple issues in hopes that it will save you some headaches. One of the most difficult decisions in Django development is deciding how to deploy your system. In particular, there are several ways to connect Django to your web server: mod\_wsgi, FastCGI, etc.
7 |
8 | At MyEducator, we've been through all of them at various levels of testing and production. By far, we've had the best success with `uWSGI `__. It is a professional server, and it is stable.
9 |
10 | One other decision you'll have to make is which database use. I'm excluding the "big and beefies" like Oracle or DB2. Those with sites that need these databases already know who they are. Most of you will be choosing between MySQL, PostgreSQL, and perhaps another mid-level database.
11 |
12 | In choosing databases, you'll find that many, if not most, of the Django developers use PostgreSQL. The system is likely tested best and first on PG. We started on MySQL, and we moved to PG after experiencing a few problems. Since deploying on PG, things have been amazingly smooth.
13 |
14 | Your mileage may vary with everything in this section. Do your own testing and take it all as advice only. Best of luck.
15 |
--------------------------------------------------------------------------------
/django_mako_plus/util/base58.py:
--------------------------------------------------------------------------------
1 | ###########################################################################################
2 | ### Converter of Base10 (decimal) to Base58
3 | ### Ambiguous chars not used: 0, O, I, and l
4 | ### This uses the same alphabet as bitcoin.
5 |
6 | BASE58CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
7 | BASE58INDEX = { ch: i for i, ch in enumerate(BASE58CHARS) }
8 |
9 | def b58enc(uid):
10 | '''Encodes a UID to an 11-length string, encoded using base58 url-safe alphabet'''
11 | # note: i tested a buffer array too, but string concat was 2x faster
12 | if not isinstance(uid, int):
13 | raise ValueError('Invalid integer: {}'.format(uid))
14 | if uid == 0:
15 | return BASE58CHARS[0]
16 | enc_uid = ""
17 | while uid:
18 | uid, r = divmod(uid, 58)
19 | enc_uid = BASE58CHARS[r] + enc_uid
20 | return enc_uid
21 |
22 | def b58dec(enc_uid):
23 | '''Decodes a UID from base58, url-safe alphabet back to int.'''
24 | if isinstance(enc_uid, str):
25 | pass
26 | elif isinstance(enc_uid, bytes):
27 | enc_uid = enc_uid.decode('utf8')
28 | else:
29 | raise ValueError('Cannot decode this type: {}'.format(enc_uid))
30 | uid = 0
31 | try:
32 | for i, ch in enumerate(enc_uid):
33 | uid = (uid * 58) + BASE58INDEX[ch]
34 | except KeyError:
35 | raise ValueError('Invalid character: "{}" ("{}", index 5)'.format(ch, enc_uid, i))
36 | return uid
37 |
--------------------------------------------------------------------------------
/tests_project/homepage/tests/test_redirect.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 |
4 |
5 |
6 | class Tester(TestCase):
7 |
8 | def test_redirect_exception(self):
9 | resp = self.client.get('/homepage/redirects.redirect_exception/')
10 | self.assertEqual(resp.status_code, 302)
11 | self.assertEqual(resp['Location'], 'new_location')
12 |
13 |
14 | def test_permanent_redirect_exception(self):
15 | resp = self.client.get('/homepage/redirects.permanent_redirect_exception/')
16 | self.assertEqual(resp.status_code, 301)
17 | self.assertEqual(resp['Location'], 'permanent_new_location')
18 |
19 |
20 | def test_javascript_redirect_exception(self):
21 | resp = self.client.get('/homepage/redirects.javascript_redirect_exception/')
22 | self.assertEqual(resp.status_code, 200)
23 | self.assertTrue(b'javascript_new_location' in resp.content)
24 |
25 |
26 | def test_internal_redirect_exception(self):
27 | resp = self.client.get('/homepage/redirects.internal_redirect_exception/')
28 | self.assertEqual(resp.status_code, 200)
29 | self.assertEqual(resp.content, b'new_location2')
30 |
31 |
32 | def test_bad_internal_redirect_exception(self):
33 | resp = self.client.get('/homepage/redirects.bad_internal_redirect_exception/')
34 | self.assertEqual(resp.status_code, 404)
35 | resp = self.client.get('/homepage/redirects.bad_internal_redirect_exception2/')
36 | self.assertEqual(resp.status_code, 404)
37 |
--------------------------------------------------------------------------------
/tests_project/homepage/views/index.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponse
3 | from django.views.generic import View
4 |
5 | from django_mako_plus import view_function
6 |
7 | import datetime
8 |
9 |
10 |
11 |
12 | ### Function-based endpoints ###
13 |
14 | @view_function
15 | def process_request(request):
16 | return request.dmp.render('index.html', {
17 | 'current_time': datetime.datetime.now(),
18 | })
19 |
20 |
21 | @view_function
22 | def basic(request):
23 | return request.dmp.render('index.basic.html', {})
24 |
25 |
26 | @view_function(a=1, b=2)
27 | def decorated(request):
28 | return HttpResponse('This one is decorated')
29 |
30 |
31 |
32 | ### Class-based endpoints ###
33 |
34 | class class_based(View):
35 | # not decorated (this is ok with class-based views)
36 | def get(self, request):
37 | return HttpResponse('Get was called.')
38 |
39 | def post(self, request):
40 | return HttpResponse('Post was called.')
41 |
42 |
43 | class class_based_decorated(View):
44 | # decorated
45 | @view_function
46 | def get(self, request):
47 | return HttpResponse('Get was called.')
48 |
49 |
50 | class class_based_argdecorated(View):
51 | # decorated with arguments
52 | @view_function(a=1, b=2)
53 | def get(self, request):
54 | return HttpResponse('Get was called.')
55 |
56 |
57 | ### Doesn't return a response ###
58 |
59 | @view_function
60 | def bad_response(request):
61 | return 'Should have been HttpResponse.'''
62 |
--------------------------------------------------------------------------------
/django_mako_plus/management/commands/dmp.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.core.management.base import BaseCommand, CommandError
3 | from django.conf import settings
4 | from django_mako_plus.management.mixins import DMPCommandMixIn
5 |
6 | import os, os.path, shutil
7 | import sys
8 | import argparse
9 |
10 | # this command was placed here in Sept 2018 to help users know about the change.
11 | # it can probably be removed sometime in Summer, 2019.
12 |
13 |
14 | class Command(DMPCommandMixIn, BaseCommand):
15 | help = 'Message to inform users of the change back to dmp_* commands.'
16 |
17 | def add_arguments(self, parser):
18 | super().add_arguments(parser)
19 | parser.add_argument(dest='all', nargs=argparse.REMAINDER, help='Wildcard to catch all remaining arguments')
20 |
21 | def handle(self, *args, **options):
22 | try:
23 | pos = sys.argv.index('dmp') # should be 1
24 | guess = 'Our guess at the right command is:\n\n {}'.format(' '.join(
25 | sys.argv[:pos] + \
26 | [ sys.argv[pos] + '_' + sys.argv[pos+1] ] + \
27 | sys.argv[pos+2:]
28 | ))
29 | except: # `dmp` not there, or no subcommand
30 | guess = ''
31 | raise CommandError('''
32 |
33 | DMP command usage changed in v5.6: `manage.py dmp *` commands are now `manage.py dmp_*`.
34 |
35 | As much as we liked the former syntax, it had to be tied to Django internals. It broke
36 | whenever Django changed its internal command structure. Apologies for the change.
37 |
38 | {}
39 | '''.format(guess))
40 |
--------------------------------------------------------------------------------
/django_mako_plus/converter/info.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.core.exceptions import ImproperlyConfigured
3 |
4 |
5 | import inspect
6 | import sys
7 |
8 |
9 |
10 | class ConverterFunctionInfo(object):
11 | '''Holds information about a converter function'''
12 | def __init__(self, convert_func, convert_type, source_order):
13 | self.convert_func = convert_func
14 | self.convert_type = convert_type
15 | self.source_order = source_order
16 | self.sort_key = 0
17 |
18 |
19 | def prepare_sort_key(self):
20 | '''
21 | Triggered by view_function._sort_converters when our sort key should be created.
22 | This can't be called in the constructor because Django models might not be ready yet.
23 | '''
24 | if isinstance(self.convert_type, str):
25 | try:
26 | app_name, model_name = self.convert_type.split('.')
27 | except ValueError:
28 | raise ImproperlyConfigured('"{}" is not a valid converter type. String-based converter types must be specified in "app.Model" format.'.format(self.convert_type))
29 | try:
30 | self.convert_type = apps.get_model(app_name, model_name)
31 | except LookupError as e:
32 | raise ImproperlyConfigured('"{}" is not a valid model name. {}'.format(self.convert_type, e))
33 |
34 | # we reverse sort by ( len(mro), source code order ) so subclasses match first
35 | # on same types, last declared method sorts first
36 | self.sort_key = ( -1 * len(inspect.getmro(self.convert_type)), -1 * self.source_order )
37 |
--------------------------------------------------------------------------------
/django_mako_plus/middleware.py:
--------------------------------------------------------------------------------
1 |
2 | # try to import MiddlewareMixIn (Django 1.10+)
3 | try:
4 | from django.utils.deprecation import MiddlewareMixin
5 | except ImportError:
6 | # create a dummy MiddlewareMixin if older Django
7 | MiddlewareMixin = object
8 |
9 | from .router import RequestViewWrapper, RoutingData
10 |
11 |
12 |
13 |
14 | ##########################################################
15 | ### Middleware the prepares the request for
16 | ### use with the controller.
17 |
18 |
19 | class RequestInitMiddleware(MiddlewareMixin):
20 | '''
21 | A required middleware class that adds a RoutingData object to the request
22 | at the earliest possible moment.
23 |
24 | Note that VIEW middleware functions can not only read the RouteData variables, but they can
25 | adjust values as well. This power should be used with great responsibility, but it allows
26 | middleware to adjust the app, page, function, and url params if needed.
27 | '''
28 | # This singleton is set on the request object early in the request (during middleware).
29 | # Once urls.py has processed, request.dmp is changed to a populated RoutingData object.
30 | INITIAL_ROUTING_DATA = RoutingData()
31 |
32 |
33 | def process_request(self, request):
34 | request.dmp = self.INITIAL_ROUTING_DATA
35 |
36 | def process_view(self, request, view_func, view_args, view_kwargs):
37 | # view_func will be a RequestViewWrapper when our resolver (DMPResolver) matched
38 | if isinstance(view_func, RequestViewWrapper):
39 | view_func.routing_data.request = request
40 | request.dmp = view_func.routing_data
41 |
--------------------------------------------------------------------------------
/tests_project/homepage/views/redirects.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponse
3 | from django.views.generic import View
4 |
5 | from django_mako_plus import view_function
6 | from django_mako_plus import RedirectException
7 | from django_mako_plus import PermanentRedirectException
8 | from django_mako_plus import InternalRedirectException
9 | from django_mako_plus import JavascriptRedirectException
10 |
11 | import datetime
12 |
13 |
14 | ### Redirect exception ###
15 |
16 | @view_function
17 | def redirect_exception(request):
18 | raise RedirectException('new_location')
19 |
20 |
21 | ### Permanent redirect exception ###
22 |
23 | @view_function
24 | def permanent_redirect_exception(request):
25 | raise PermanentRedirectException('permanent_new_location')
26 |
27 |
28 | ### Permanent redirect exception ###
29 |
30 | @view_function
31 | def javascript_redirect_exception(request):
32 | raise JavascriptRedirectException('javascript_new_location')
33 |
34 |
35 | ### Internal redirect exceptions ###
36 |
37 | @view_function
38 | def internal_redirect_exception(request):
39 | raise InternalRedirectException('homepage.views.redirects', 'internal_redirect_exception2')
40 |
41 | @view_function
42 | def bad_internal_redirect_exception(request):
43 | raise InternalRedirectException('homepage.non_existent', 'internal_redirect_exception2')
44 |
45 | @view_function
46 | def bad_internal_redirect_exception2(request):
47 | raise InternalRedirectException('homepage.views.redirects', 'nonexistent_function')
48 |
49 | # should not be decorated with @view_function because a target of internal redirect
50 | def internal_redirect_exception2(request):
51 | return HttpResponse('new_location2')
52 |
--------------------------------------------------------------------------------
/docs-src/topics_responses.rst:
--------------------------------------------------------------------------------
1 | .. _topics_responses:
2 |
3 | Lazy Rendering with ``TemplateResponse``
4 | =======================================================
5 |
6 | The Django documentation describes two template-oriented responses: `TemplateResponse `_ and `SimpleTemplateResponse `_. These specialized responses support lazy rendering at the last possible moment. This allows decorators or middleware to modify the response after creating but before template rendering.
7 |
8 | DMP can be used with template responses, just like any other template engine.
9 |
10 | Method 1: Template String
11 | ------------------------------
12 |
13 | Specify the DMP template using ``app/template`` format. This method uses `Django-style format `_:
14 |
15 | .. code-block:: python
16 |
17 | from django_mako_plus import view_function
18 | from django.template.response import TemplateResponse
19 |
20 | @view_function
21 | def process_request(request):
22 | ...
23 | context = {...}
24 | return TemplateResponse(request, 'homepage/index.html', context)
25 |
26 |
27 |
28 | Method 2: Template Object
29 | --------------------------------
30 |
31 | Alternatively, use a template object in the current app. This method uses DMP-style format:
32 |
33 | .. code-block:: python
34 |
35 | from django_mako_plus import view_function
36 | from django.template.response import TemplateResponse
37 |
38 | @view_function
39 | def process_request(request):
40 | ...
41 | context = {...}
42 | template = request.dmp.get_template('index.html')
43 | return TemplateResponse(request, template, context)
44 |
--------------------------------------------------------------------------------
/docs/_sources/topics_responses.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_responses:
2 |
3 | Lazy Rendering with ``TemplateResponse``
4 | =======================================================
5 |
6 | The Django documentation describes two template-oriented responses: `TemplateResponse `_ and `SimpleTemplateResponse `_. These specialized responses support lazy rendering at the last possible moment. This allows decorators or middleware to modify the response after creating but before template rendering.
7 |
8 | DMP can be used with template responses, just like any other template engine.
9 |
10 | Method 1: Template String
11 | ------------------------------
12 |
13 | Specify the DMP template using ``app/template`` format. This method uses `Django-style format `_:
14 |
15 | .. code-block:: python
16 |
17 | from django_mako_plus import view_function
18 | from django.template.response import TemplateResponse
19 |
20 | @view_function
21 | def process_request(request):
22 | ...
23 | context = {...}
24 | return TemplateResponse(request, 'homepage/index.html', context)
25 |
26 |
27 |
28 | Method 2: Template Object
29 | --------------------------------
30 |
31 | Alternatively, use a template object in the current app. This method uses DMP-style format:
32 |
33 | .. code-block:: python
34 |
35 | from django_mako_plus import view_function
36 | from django.template.response import TemplateResponse
37 |
38 | @view_function
39 | def process_request(request):
40 | ...
41 | context = {...}
42 | template = request.dmp.get_template('index.html')
43 | return TemplateResponse(request, template, context)
44 |
--------------------------------------------------------------------------------
/django_mako_plus/webroot/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Conan Albrecht",
3 | "babel": {
4 | "presets": [
5 | [
6 | "@babel/env",
7 | {
8 | "loose": false,
9 | "modules": false,
10 | "targets": {
11 | "chrome": "58",
12 | "ie": "10"
13 | },
14 | "useBuiltIns": "usage"
15 | }
16 | ]
17 | ],
18 | "sourceMaps": "inline"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/doconix/django-mako-plus/issues"
22 | },
23 | "dependencies": {
24 | "@babel/polyfill": "^7.2.5"
25 | },
26 | "description": "Django Mako Plus helper script",
27 | "devDependencies": {
28 | "@babel/cli": "^7.2.3",
29 | "@babel/core": "^7.2.2",
30 | "@babel/preset-env": "^7.3.1",
31 | "babel-loader": "^8.0.5",
32 | "webpack": "^4.29.2",
33 | "webpack-cli": "^3.2.3"
34 | },
35 | "directories": {
36 | "doc": "docs"
37 | },
38 | "homepage": "https://github.com/doconix/django-mako-plus#readme",
39 | "license": "Apache-2.0",
40 | "main": "./dmp-common.js",
41 | "name": "django-mako-plus",
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/doconix/django-mako-plus.git"
45 | },
46 | "scripts": {
47 | "babel": "./node_modules/.bin/babel ./dmp-common.src.js --out-file ./dmp-common.js",
48 | "build": "./node_modules/.bin/webpack --mode development && ./node_modules/.bin/webpack --mode production",
49 | "watch": "./node_modules/.bin/webpack --mode development --watch"
50 | },
51 | "version": "5.11.2"
52 | }
--------------------------------------------------------------------------------
/django_mako_plus/command.py:
--------------------------------------------------------------------------------
1 | from .util import log
2 |
3 | import subprocess
4 | from collections import namedtuple
5 |
6 |
7 | ################################################################
8 | ### Run a shell command
9 |
10 | ReturnInfo = namedtuple('ReturnInfo', ( 'code', 'stdout', 'stderr' ))
11 |
12 |
13 | def run_command(*args, raise_exception=True, cwd=None):
14 | '''
15 | Runs a command, piping all output to the DMP log.
16 | The args should be separate arguments so paths and subcommands can have spaces in them:
17 |
18 | ret = run_command('ls', '-l', '/Users/me/My Documents')
19 | print(ret.code)
20 | print(ret.stdout)
21 | print(ret.stderr)
22 |
23 | On Windows, the PATH is not followed. This can be overcome with:
24 |
25 | import shutil
26 | run_command(shutil.which('program'), '-l', '/Users/me/My Documents')
27 | '''
28 | args = [ str(a) for a in args ]
29 | log.info('running %s', ' '.join(args))
30 | p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, cwd=cwd)
31 | stdout, stderr = p.communicate()
32 | returninfo = ReturnInfo(p.returncode, stdout.decode('utf8'), stderr.decode('utf8'))
33 | if stdout:
34 | log.info('%s', returninfo.stdout)
35 | if raise_exception and returninfo.code != 0:
36 | raise CommandError(' '.join(args), returninfo)
37 | return returninfo
38 |
39 |
40 | class CommandError(Exception):
41 | def __init__(self, command, returninfo):
42 | self.command = command
43 | self.returninfo = returninfo
44 | super().__init__('CommandError')
45 |
46 | def __str__(self):
47 | return '[return value: {}] {}; {}'.format(self.returninfo.code, self.returninfo.stdout[:1000], self.returninfo.stderr[:1000])
48 |
--------------------------------------------------------------------------------
/docs-src/install_app_specific.rst:
--------------------------------------------------------------------------------
1 | .. _install_app_specific:
2 |
3 | Limiting to Specific Apps
4 | =======================================================
5 |
6 | DMP normally registers patterns for all "local" apps in your project. That's the apps that are located beneath your project root.
7 |
8 | This happens when you include DMP's URL file in your project. DMP iterates your local apps, and it adds patterns for each using ``app_resolver()``. See these `methods (especially _dmp_paths_for_app()) in the source `_.
9 |
10 | You can disable the automatic registration of apps with DMP by removing the ``include('', 'django_mako_plus')`` line from ``urls.py``. With this line removed, DMP won't inject any convention-based patterns into your project.
11 |
12 | Now register specific apps by calling ``app_resolver()`` directly.
13 |
14 | An Example
15 | -----------------
16 |
17 | The following ``urls.py`` file enables DMP-style patterns on just two apps: ``polls`` and ``account``:
18 |
19 | .. code-block:: python
20 |
21 | from django.apps import apps
22 | from django.conf.urls import url, include
23 | from django.views.static import serve
24 |
25 | import os
26 |
27 | urlpatterns = [
28 |
29 | # dmp JS file (for DEBUG mode)
30 | url(
31 | r'^django_mako_plus/(?P[^/]+)',
32 | serve,
33 | { 'document_root': os.path.join(apps.get_app_config('django_mako_plus').path, 'webroot') },
34 | name='DMP webroot (for devel)',
35 | ),
36 |
37 | # manually register the polls and account apps
38 | apps.get_app_config('django_mako_plus').register_app('polls')
39 | apps.get_app_config('django_mako_plus').register_app('account')
40 |
41 | ]
42 |
--------------------------------------------------------------------------------
/docs/_sources/install_app_specific.rst.txt:
--------------------------------------------------------------------------------
1 | .. _install_app_specific:
2 |
3 | Limiting to Specific Apps
4 | =======================================================
5 |
6 | DMP normally registers patterns for all "local" apps in your project. That's the apps that are located beneath your project root.
7 |
8 | This happens when you include DMP's URL file in your project. DMP iterates your local apps, and it adds patterns for each using ``app_resolver()``. See these `methods (especially _dmp_paths_for_app()) in the source `_.
9 |
10 | You can disable the automatic registration of apps with DMP by removing the ``include('', 'django_mako_plus')`` line from ``urls.py``. With this line removed, DMP won't inject any convention-based patterns into your project.
11 |
12 | Now register specific apps by calling ``app_resolver()`` directly.
13 |
14 | An Example
15 | -----------------
16 |
17 | The following ``urls.py`` file enables DMP-style patterns on just two apps: ``polls`` and ``account``:
18 |
19 | .. code-block:: python
20 |
21 | from django.apps import apps
22 | from django.conf.urls import url, include
23 | from django.views.static import serve
24 |
25 | import os
26 |
27 | urlpatterns = [
28 |
29 | # dmp JS file (for DEBUG mode)
30 | url(
31 | r'^django_mako_plus/(?P[^/]+)',
32 | serve,
33 | { 'document_root': os.path.join(apps.get_app_config('django_mako_plus').path, 'webroot') },
34 | name='DMP webroot (for devel)',
35 | ),
36 |
37 | # manually register the polls and account apps
38 | apps.get_app_config('django_mako_plus').register_app('polls')
39 | apps.get_app_config('django_mako_plus').register_app('account')
40 |
41 | ]
42 |
--------------------------------------------------------------------------------
/django_mako_plus/management/commands/dmp_startproject.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.core.management.commands.startproject import Command as StartProjectCommand
3 | from django_mako_plus.management.mixins import DMPCommandMixIn
4 |
5 | import os, os.path, platform
6 |
7 |
8 | NOT_SET = object()
9 |
10 |
11 | class Command(DMPCommandMixIn, StartProjectCommand):
12 | help = (
13 | "Creates a DMP project directory structure for the given project "
14 | "name in the current directory or optionally in the given directory."
15 | )
16 | requires_system_checks = False
17 |
18 | def add_arguments(self, parser):
19 | super().add_arguments(parser)
20 | self.get_action_by_dest(parser, 'template').default = NOT_SET
21 |
22 | def handle(self, *args, **options):
23 | if options.get('template') is NOT_SET:
24 | # set the template to a DMP app
25 | options['template'] = 'http://cdn.rawgit.com/doconix/django-mako-plus/master/project_template.zip'
26 | # attempt to use a local DMP install instead of the online repo as specified above
27 | # this should work unless the installation type is not normal
28 | template_dir = os.path.join(self.get_dmp_path(), 'project_template')
29 | if os.path.exists(template_dir):
30 | options['template'] = template_dir
31 |
32 | # call the super
33 | StartProjectCommand.handle(self, *args, **options)
34 |
35 | # display a message to help the new kids
36 | pyexec = 'python' if platform.system() == 'Windows' else 'python3'
37 | self.message("""Project {name} created successfully!
38 |
39 | What's next?
40 | 1. cd {name}
41 | 2. {pyexec} manage.py dmp_startapp homepage
42 |
43 | """.format(name=options.get('name'), pyexec=pyexec))
44 |
--------------------------------------------------------------------------------
/django_mako_plus/urls.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.conf import settings
3 | try:
4 | from django.urls import re_path # Django 2.x
5 | except ImportError:
6 | from django.conf.urls import url as re_path # Django 1.x
7 | from django.views.static import serve
8 | from .router import app_resolver
9 | import os, os.path
10 |
11 |
12 | #########################################################
13 | ### The default DMP url patterns
14 | ###
15 | ### FYI, even though the valid python identifier is [_A-Za-z][_a-zA-Z0-9]*,
16 | ### I'm simplifying it to [_a-zA-Z0-9]+ because it works for our purposes
17 |
18 | app_name = 'django_mako_plus'
19 | dmp = apps.get_app_config('django_mako_plus')
20 | urlpatterns = []
21 |
22 | # start with the DMP web files - for development time
23 | # at production, serve this directly with Nginx/IIS/etc. instead
24 | # there is no "if debug mode" statement here because the web server will serve the file at production before urls.py happens,
25 | # but if this deployment step isn't done right, it will still work through this link
26 | urlpatterns.append(re_path(
27 | r'^django_mako_plus/(?P[^/]+)',
28 | serve,
29 | { 'document_root': os.path.join(apps.get_app_config('django_mako_plus').path, 'webroot') },
30 | name='DMP webroot (for devel)',
31 | ))
32 |
33 | # add a DMP-style resolver for each app in the project directory
34 | for config in apps.get_app_configs():
35 | if os.path.samefile(os.path.dirname(config.path), settings.BASE_DIR):
36 | urlpatterns.append(app_resolver(config.name))
37 |
38 | # add a DMP-style resolver for the default app
39 | if dmp.options['DEFAULT_APP']:
40 | try:
41 | apps.get_app_config(dmp.options['DEFAULT_APP'])
42 | urlpatterns.append(app_resolver())
43 | except LookupError:
44 | pass # the default app in dmp's TEMPLATES entry isn't an installed app, so skip it
45 |
--------------------------------------------------------------------------------
/docs-src/topics_csrf.rst:
--------------------------------------------------------------------------------
1 | .. _topics_csrf:
2 |
3 | CSRF Tokens
4 | ====================
5 |
6 | In support of the Django CSRF capability, DMP includes ``csrf_token`` and ``csrf_input`` in the context of every template. Following `Django's lead `__, this token is always available and cannot be disabled for security reasons.
7 |
8 | However, slightly different than Django's default templates (but following `Jinja2's lead `__), use ``csrf_input`` to render the CSRF input:
9 |
10 | ::
11 |
12 |
17 |
18 |
19 |
20 | Using Python Code
21 | -----------------------------
22 |
23 | The standard way to insert the CSRF token is with the template tag (as above). However, suppose you are creating your forms directly using Python code, like this next example does.
24 |
25 | The functions to create the token are actually right in Django (no need for DMP). Here are two options:
26 |
27 | 1. ``django.template.backends.utils.csrf_input`` creates the full tag: ````
28 | 2. ``django.middleware.csrf.get_token`` creates and returns the token value: ``YpaAqd8LjS5j2eG...``
29 |
30 | Example:
31 |
32 | .. code-block:: python
33 |
34 | from io import StringIO
35 | from django.template.backends.utils import csrf_input
36 |
37 | def render_form(request, form):
38 | '''Renders the form html'''
39 | buf = StringIO()
40 | buf.write('')
45 | return buf.getvalue()
46 |
--------------------------------------------------------------------------------
/docs/_sources/topics_csrf.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_csrf:
2 |
3 | CSRF Tokens
4 | ====================
5 |
6 | In support of the Django CSRF capability, DMP includes ``csrf_token`` and ``csrf_input`` in the context of every template. Following `Django's lead `__, this token is always available and cannot be disabled for security reasons.
7 |
8 | However, slightly different than Django's default templates (but following `Jinja2's lead `__), use ``csrf_input`` to render the CSRF input:
9 |
10 | ::
11 |
12 |
17 |
18 |
19 |
20 | Using Python Code
21 | -----------------------------
22 |
23 | The standard way to insert the CSRF token is with the template tag (as above). However, suppose you are creating your forms directly using Python code, like this next example does.
24 |
25 | The functions to create the token are actually right in Django (no need for DMP). Here are two options:
26 |
27 | 1. ``django.template.backends.utils.csrf_input`` creates the full tag: ````
28 | 2. ``django.middleware.csrf.get_token`` creates and returns the token value: ``YpaAqd8LjS5j2eG...``
29 |
30 | Example:
31 |
32 | .. code-block:: python
33 |
34 | from io import StringIO
35 | from django.template.backends.utils import csrf_input
36 |
37 | def render_form(request, form):
38 | '''Renders the form html'''
39 | buf = StringIO()
40 | buf.write('')
45 | return buf.getvalue()
46 |
--------------------------------------------------------------------------------
/tests_project/homepage/tests/test_engine.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.template import TemplateDoesNotExist
3 | from django.test import TestCase
4 |
5 | from django_mako_plus.template import MakoTemplateAdapter
6 | from django_mako_plus.template import MakoTemplateLoader
7 |
8 | import os
9 | import os.path
10 |
11 |
12 | class Tester(TestCase):
13 |
14 | @classmethod
15 | def setUpTestData(cls):
16 | cls.tests_app = apps.get_app_config('homepage')
17 |
18 | def test_from_string(self):
19 | dmp = apps.get_app_config('django_mako_plus')
20 | template = dmp.engine.from_string('${ 2 + 2 }')
21 | self.assertIsInstance(template, MakoTemplateAdapter)
22 | self.assertEqual(template.render(None), "4")
23 |
24 | def test_get_template(self):
25 | dmp = apps.get_app_config('django_mako_plus')
26 | template = dmp.engine.get_template('homepage/index.basic.html')
27 | self.assertIsInstance(template, MakoTemplateAdapter)
28 | self.assertRaises(TemplateDoesNotExist, dmp.engine.get_template, 'homepage/nonexistent_template.html')
29 |
30 | def test_get_template_loader(self):
31 | dmp = apps.get_app_config('django_mako_plus')
32 | loader = dmp.engine.get_template_loader('homepage', create=False)
33 | self.assertIsInstance(loader, MakoTemplateLoader)
34 | template = loader.get_template('index.basic.html')
35 | self.assertIsInstance(template, MakoTemplateAdapter)
36 |
37 | def test_get_template_loader_for_path(self):
38 | dmp = apps.get_app_config('django_mako_plus')
39 | path = os.path.join(self.tests_app.path, 'templates')
40 | loader = dmp.engine.get_template_loader_for_path(path, use_cache=False)
41 | self.assertIsInstance(loader, MakoTemplateLoader)
42 | template = loader.get_template('index.basic.html')
43 | self.assertIsInstance(template, MakoTemplateAdapter)
44 |
--------------------------------------------------------------------------------
/django_mako_plus/__main__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from django.core import management
3 | import os.path
4 | import sys
5 | import functools
6 |
7 | __doc__ = '''
8 | Starts a new DMP-style project. This is the DMP equivalent of "django-admin.py startproject".
9 |
10 | Example:
11 |
12 | django_mako_plus dmp_startproject [project name]
13 |
14 | if the above doesn't work, try:
15 |
16 | python -m django_mako_plus dmp_startproject [project name]
17 |
18 | Background: The dmp_startproject command has a bit of a chicken-and-egg problem.
19 | It creates the project, but the command isn't available until the project is created
20 | and DMP is in INSTALLED_APPS. Can't create until it is created...
21 | Django solves this with django-admin.py, a global Python script that can be
22 | executed directly from the command line.
23 | '''
24 |
25 | DMP_MANAGEMENT_PATH = os.path.join(os.path.dirname(__file__), 'management')
26 |
27 |
28 |
29 | def main():
30 | # Django is hard coded to return only its own commands when a project doesn't
31 | # exist yet. Since I want DMP to be able to create projects, I'm monkey-patching
32 | # Django's get_commands() function so DMP gets added. This is the least-offensive
33 | # way I could see to do this since Django really isn't built to allow other commands
34 | # pre-project. This only happens when `django_mako_plus` is run directly and not
35 | # when `manage.py` or `django-admin.py` are run.
36 | orig_get_commands = management.get_commands
37 | @functools.lru_cache(maxsize=None)
38 | def new_get_commands():
39 | commands = {}
40 | commands.update(orig_get_commands())
41 | commands.update({ name: 'django_mako_plus' for name in management.find_commands(DMP_MANAGEMENT_PATH) })
42 | return commands
43 | management.get_commands = new_get_commands
44 |
45 | # mimic the code in django-admin.py
46 | management.execute_from_command_line()
47 |
48 |
49 | ## runner!
50 | if __name__ == '__main__':
51 | main()
52 |
--------------------------------------------------------------------------------
/docs-src/editors.rst:
--------------------------------------------------------------------------------
1 | .. _editors:
2 |
3 | Editors
4 | ==========================
5 |
6 | This page contains ideas for customizing your favorite editor for DMP and Django development. If your editor isn't listed here, please contribute ideas for it!
7 |
8 | Note that templates can use any file extension. For example, if you prefer ``.mako`` instead of the conventional ``.html``, simply use this extension in view functions:
9 |
10 | .. code-block:: python
11 |
12 | @view_function
13 | def process_request(request):
14 | ...
15 | return request.dmp.render('mypage.mako', {...})
16 |
17 |
18 | VSCode
19 | -------------------------------------
20 |
21 | :Code Highlighting:
22 | A VSCode extension for Mako exists in the marketplace. Search "Mako" on the extensions tab and install.
23 |
24 | To activate highlighting, click the language in the bottom right of the vscode window (or type "Change Language Mode" in the command dropdown) and select Mako.
25 |
26 | If you want to make the association permanent, add the following to the vscode settings file. Open the command Command Palette and type ``Open settings (JSON)`` for the settings file.
27 | ::
28 |
29 | "files.associations": {
30 | "*.htm": "mako",
31 | "*.html": "mako"
32 | }
33 |
34 |
35 | Atom
36 | ----------------------
37 |
38 | :Code Highlighting:
39 | An Atom package for Mako can be downloaded from within the editor. Open Settings and search for "Mako" on the Install tab. Install the ``language-mako`` package. Once installed, click on it if you want to customize its settings.
40 |
41 | To activate highlighting, click the language in the bottom right of the atom window and select ``HTML (Mako)``.
42 |
43 | If you want to make the association stick, open the Atom ``config.cson`` and add the following:
44 |
45 | ::
46 |
47 | "*":
48 | core:
49 | customFileTypes:
50 | 'text.html.mako': [
51 | 'html',
52 | 'htm'
53 | ]
54 |
--------------------------------------------------------------------------------
/docs/_sources/editors.rst.txt:
--------------------------------------------------------------------------------
1 | .. _editors:
2 |
3 | Editors
4 | ==========================
5 |
6 | This page contains ideas for customizing your favorite editor for DMP and Django development. If your editor isn't listed here, please contribute ideas for it!
7 |
8 | Note that templates can use any file extension. For example, if you prefer ``.mako`` instead of the conventional ``.html``, simply use this extension in view functions:
9 |
10 | .. code-block:: python
11 |
12 | @view_function
13 | def process_request(request):
14 | ...
15 | return request.dmp.render('mypage.mako', {...})
16 |
17 |
18 | VSCode
19 | -------------------------------------
20 |
21 | :Code Highlighting:
22 | A VSCode extension for Mako exists in the marketplace. Search "Mako" on the extensions tab and install.
23 |
24 | To activate highlighting, click the language in the bottom right of the vscode window (or type "Change Language Mode" in the command dropdown) and select Mako.
25 |
26 | If you want to make the association permanent, add the following to the vscode settings file. Open the command Command Palette and type ``Open settings (JSON)`` for the settings file.
27 | ::
28 |
29 | "files.associations": {
30 | "*.htm": "mako",
31 | "*.html": "mako"
32 | }
33 |
34 |
35 | Atom
36 | ----------------------
37 |
38 | :Code Highlighting:
39 | An Atom package for Mako can be downloaded from within the editor. Open Settings and search for "Mako" on the Install tab. Install the ``language-mako`` package. Once installed, click on it if you want to customize its settings.
40 |
41 | To activate highlighting, click the language in the bottom right of the atom window and select ``HTML (Mako)``.
42 |
43 | If you want to make the association stick, open the Atom ``config.cson`` and add the following:
44 |
45 | ::
46 |
47 | "*":
48 | core:
49 | customFileTypes:
50 | 'text.html.mako': [
51 | 'html',
52 | 'htm'
53 | ]
54 |
--------------------------------------------------------------------------------
/django_mako_plus/management/commands/dmp_startapp.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.core.management.commands.startapp import Command as StartAppCommand
3 | from django_mako_plus.management.mixins import DMPCommandMixIn
4 |
5 | import os, os.path, platform
6 |
7 |
8 | NOT_SET = object()
9 |
10 |
11 | class Command(DMPCommandMixIn, StartAppCommand):
12 | help = (
13 | "Creates a DMP app directory structure for the given app name in "
14 | "the current directory or optionally in the given directory."
15 | )
16 | requires_system_checks = False
17 |
18 | def add_arguments(self, parser):
19 | super().add_arguments(parser)
20 | self.get_action_by_dest(parser, 'template').default = NOT_SET
21 |
22 |
23 | def handle(self, *args, **options):
24 | dmp = apps.get_app_config('django_mako_plus')
25 | if options.get('template') is NOT_SET:
26 | # set the template to a DMP app
27 | options['template'] = 'http://cdn.rawgit.com/doconix/django-mako-plus/master/app_template.zip'
28 | # attempt to use a local DMP install instead of the online repo as specified above
29 | dmp_dir = dmp.path
30 | if dmp_dir:
31 | template_dir = os.path.join(dmp_dir, 'app_template')
32 | if os.path.exists(template_dir):
33 | options['template'] = template_dir
34 |
35 | # ensure we have the extensions we need
36 | options['extensions'] = list(set(options.get('extensions') + [ 'py', 'htm', 'html' ]))
37 |
38 | # call the super
39 | StartAppCommand.handle(self, *args, **options)
40 |
41 | pyexec = 'python' if platform.system() == 'Windows' else 'python3'
42 | self.message("""App {name} created successfully!
43 |
44 | What's next?
45 | 1. Add your new app to the list in settings.py:
46 | INSTALLED_APPS = [
47 | ...
48 | '{name}',
49 | ]
50 | 2. {pyexec} manage.py runserver
51 | 3. Take a browser to http://localhost:8000/
52 |
53 | """.format(name=options.get('name'), pyexec=pyexec))
54 |
--------------------------------------------------------------------------------
/django_mako_plus/management/mixins.py:
--------------------------------------------------------------------------------
1 | import os, os.path
2 |
3 |
4 |
5 | #########################################
6 | ### Mixin for all DMP commands
7 |
8 | class DMPCommandMixIn(object):
9 | '''Some extra SWAG that all DMP commands get'''
10 | # needs to be true so Django initializes urls.py (which registers the dmp apps)
11 | requires_system_checks = True
12 |
13 | def add_arguments(self, parser):
14 | super().add_arguments(parser)
15 |
16 | # django also provides a verbosity parameter
17 | # these two are just convenience params to it
18 | parser.add_argument(
19 | '--verbose',
20 | action='store_true',
21 | dest='verbose',
22 | default=False,
23 | help='Set verbosity to level 3 (see --verbosity).',
24 | )
25 | parser.add_argument(
26 | '--quiet',
27 | action='store_true',
28 | dest='quiet',
29 | default=False,
30 | help='Set verbosity to level 0, which silences all messages (see --verbosity).',
31 | )
32 |
33 |
34 | def get_action_by_dest(self, parser, dest):
35 | '''Retrieves the given parser action object by its dest= attribute'''
36 | for action in parser._actions:
37 | if action.dest == dest:
38 | return action
39 | return None
40 |
41 |
42 | def execute(self, *args, **options):
43 | '''Placing this in execute because then subclass handle() don't have to call super'''
44 | if options['verbose']:
45 | options['verbosity'] = 3
46 | if options['quiet']:
47 | options['verbosity'] = 0
48 | self.verbosity = options.get('verbosity', 1)
49 | super().execute(*args, **options)
50 |
51 |
52 | def get_dmp_path(self):
53 | '''Returns the absolute path to DMP. Apps do not have to be loaded yet'''
54 | return os.path.dirname(os.path.dirname(__file__))
55 |
56 |
57 | def message(self, msg='', level=1, tab=0):
58 | '''Print a message to the console'''
59 | if self.verbosity >= level:
60 | self.stdout.write('{}{}'.format(' ' * tab, msg))
61 |
--------------------------------------------------------------------------------
/tests_project/homepage/views/converter.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponse
3 | from django.views.generic import View
4 | from django_mako_plus import view_function, parameter_converter
5 |
6 | from homepage.models import IceCream, MyInt
7 |
8 | from . import view_function
9 |
10 | import decimal, datetime
11 |
12 |
13 | ### View function endpoints ###
14 |
15 | @view_function
16 | def process_request(request, s:str='', i:int=1, f:float=2, b:bool=False, ic:IceCream=None):
17 | return HttpResponse('parameter conversion tests')
18 |
19 | @view_function
20 | def more_testing(request, d:decimal.Decimal=None, dt:datetime.date=None, dttm:datetime.datetime=None, mi:MyInt=None):
21 | return HttpResponse('more parameter conversion tests')
22 |
23 |
24 | ### Custom converter function ###
25 |
26 | class GeoLocation(object):
27 | def __init__(self, latitude, longitude):
28 | self.latitude = latitude
29 | self.longitude = longitude
30 |
31 |
32 | @parameter_converter(GeoLocation)
33 | def convert_geo_location(value, parameter):
34 | parts = value.split(',')
35 | if len(parts) < 2:
36 | raise ValueError('Both latitude and longitude are required')
37 | # the float constructor will raise ValueError if invalid
38 | return GeoLocation(float(parts[0]), float(parts[1]))
39 |
40 |
41 | @view_function
42 | def geo_location_endpoint(request, loc:GeoLocation):
43 | return HttpResponse('{}, {}'.format(loc.latitude, loc.longitude))
44 |
45 |
46 | ### Class-based views ###
47 |
48 | class class_based(View):
49 | def get(self, request, s:str='', i:int=1, f:float=2, b:bool=False, ic:IceCream=None):
50 | return HttpResponse('Get was called.')
51 |
52 |
53 | class class_based_decorated(View):
54 | # decorated
55 | @view_function
56 | def get(self, request, s:str='', i:int=1, f:float=2, b:bool=False, ic:IceCream=None):
57 | return HttpResponse('Get was called.')
58 |
59 |
60 | class class_based_argdecorated(View):
61 | # decorated with arguments
62 | @view_function(a=1, b=2)
63 | def get(self, request, s:str='', i:int=1, f:float=2, b:bool=False, ic:IceCream=None):
64 | return HttpResponse('Get was called.')
65 |
--------------------------------------------------------------------------------
/django_mako_plus/convenience.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | import os, os.path
3 |
4 |
5 | ##############################################################
6 | ### Convenience functions
7 | ### These are imported into __init__.py
8 |
9 | def get_template_loader(app, subdir='templates'):
10 | '''
11 | Convenience method that calls get_template_loader() on the DMP
12 | template engine instance.
13 | '''
14 | dmp = apps.get_app_config('django_mako_plus')
15 | return dmp.engine.get_template_loader(app, subdir, create=True)
16 |
17 |
18 | def get_template(app, template_name, subdir="templates"):
19 | '''
20 | Convenience method that retrieves a template given the app and
21 | name of the template.
22 | '''
23 | dmp = apps.get_app_config('django_mako_plus')
24 | return dmp.engine.get_template_loader(app, subdir, create=True).get_template(template_name)
25 |
26 |
27 | def render_template(request, app, template_name, context=None, subdir="templates", def_name=None):
28 | '''
29 | Convenience method that directly renders a template, given the app and template names.
30 | '''
31 | return get_template(app, template_name, subdir).render(context, request, def_name)
32 |
33 |
34 | def get_template_loader_for_path(path, use_cache=True):
35 | '''
36 | Convenience method that calls get_template_loader_for_path() on the DMP
37 | template engine instance.
38 | '''
39 | dmp = apps.get_app_config('django_mako_plus')
40 | return dmp.engine.get_template_loader_for_path(path, use_cache)
41 |
42 |
43 | def get_template_for_path(path, use_cache=True):
44 | '''
45 | Convenience method that retrieves a template given a direct path to it.
46 | '''
47 | dmp = apps.get_app_config('django_mako_plus')
48 | app_path, template_name = os.path.split(path)
49 | return dmp.engine.get_template_loader_for_path(app_path, use_cache=use_cache).get_template(template_name)
50 |
51 |
52 | def render_template_for_path(request, path, context=None, use_cache=True, def_name=None):
53 | '''
54 | Convenience method that directly renders a template, given a direct path to it.
55 | '''
56 | return get_template_for_path(path, use_cache).render(context, request, def_name)
57 |
--------------------------------------------------------------------------------
/django_mako_plus/provider/webpack.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.core.management import call_command
3 | from django.forms.utils import flatatt
4 | import os
5 | import os.path
6 | import posixpath
7 | from ..util import merge_dicts
8 | from .link import CssLinkProvider, JsLinkProvider
9 | from ..management.commands.dmp_webpack import Command as WebpackCommand
10 |
11 |
12 |
13 | class WebpackJsLinkProvider(JsLinkProvider):
14 | '''Generates a JS '.format(flatatt(attrs))
50 |
51 | def provide(self):
52 | # this must come after the regular JsLinkProvider script because the JsLinkProvider doesn't always
53 | # output a link (duplicates get skipped)
54 | super().provide()
55 | if self.is_last():
56 | self.write(''.format(
57 | uid=self.provider_run.uid,
58 | ))
59 |
--------------------------------------------------------------------------------
/release_version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from django_mako_plus.version import __version__
4 | from django_mako_plus.command import run_command
5 |
6 | import sys
7 | import os
8 | import re
9 | import shutil
10 | import json
11 |
12 |
13 | # set the version number in package.json
14 | print('Updating the version in package.json...')
15 | PACKAGE_JSON = 'django_mako_plus/webroot/package.json'
16 | with open(PACKAGE_JSON) as fin:
17 | data = json.load(fin)
18 | data['version'] = __version__
19 | with open(PACKAGE_JSON, 'w') as fout:
20 | json.dump(data, fout, sort_keys=True, indent=4)
21 |
22 | # set the version number in dmp-common.src.js
23 | print('Updating the version in JS...')
24 | VERSION_PATTERN = re.compile("__version__\ = '\w+\.\w+\.\w+'")
25 | DMP_COMMON = 'django_mako_plus/webroot/dmp-common.src.js'
26 | with open(DMP_COMMON) as fin:
27 | content = fin.read()
28 | match = VERSION_PATTERN.search(content)
29 | if not match:
30 | raise RuntimeError('Version pattern did not match. Aborting because the version number cannot be updated in dmp-common.src.js.')
31 | content = VERSION_PATTERN.sub("__version__ = '{}'".format(__version__), content)
32 | with open(DMP_COMMON, 'w') as fout:
33 | fout.write(content)
34 |
35 | # backport and minify dmp-common.src.js
36 | print('Backporting and minifying JS...')
37 | run_command('npm', 'run', 'build', cwd='./django_mako_plus/webroot/')
38 |
39 | # update the archives
40 | print('Creating the archives...')
41 | shutil.make_archive('app_template', 'zip', root_dir='./django_mako_plus/app_template')
42 | shutil.make_archive('project_template', 'zip', root_dir='./django_mako_plus/project_template')
43 |
44 | # make the documentation, since GitHub Pages reads it as static HTML
45 | print('Making the documentation...')
46 | run_command('make', 'html', cwd='./docs-src')
47 |
48 | # run the setup and upload
49 | print()
50 | if input('Ready to upload to PyPi. Continue? ')[:1].lower() == 'y':
51 | ret = run_command('python3', 'setup.py', 'sdist')
52 | print(ret.stdout)
53 | ret = run_command('twine', 'upload', 'dist/*')
54 | print(ret.stdout)
55 | run_command('rm', '-rf', 'dist/', 'django_mako_plus.egg-info/')
56 |
57 | ret = run_command('npm', 'publish', cwd='./django_mako_plus/webroot/')
58 | print(ret.stdout)
59 |
--------------------------------------------------------------------------------
/docs-src/topics_translation.rst:
--------------------------------------------------------------------------------
1 | .. _topics_translation:
2 |
3 | Internationalization
4 | ----------------------------------
5 |
6 | If your site needs to be translated into other languages, this section is for you. I'm sure you are aware that Django has full support for translation to other languages. If not, you should first read the standard Translation documentation at http://docs.djangoproject.com/en/dev/topics/i18n/translation/.
7 |
8 | DMP supports Django's translation functions--with one caveat. Since Django doesn't know about Mako, it can't translate strings in your Mako files. DMP fixes this with the ``dmp_makemessages`` command. Instead of running ``python3 manage.py makemessages`` like the Django tutorial shows, run ``python3 manage.py dmp_makemessages``. Since the DMP version is an extension of the standard version, the same command line options apply to both.
9 |
10 | Internally, ``dmp_makemessages`` literally extends the ``makemessages`` class. Since Mako templates are compiled into .py files at runtime (which makes them discoverable by ``makemessages``), the DMP version of the command simply finds all your templates, compiles them, and calls the standard command. Django finds your translatable strings within the cached\_templates directory (which holds the compiled Mako templates).
11 |
12 | Suppose you have a template with a header you want translated. Simply use the following in your template:
13 |
14 | .. code-block:: html+mako
15 |
16 | <%! from django.utils.translation import ugettext as _ %>
17 |
${ _("World History") }
18 |
19 | Run the following at the command line:
20 |
21 | ::
22 |
23 | python3 manage.py dmp_makemessages
24 |
25 | Assuming you have translations set up the way Django's documentation tells you to, you'll get a new language.po file. Edit this file and add the translation. Then compile your translations:
26 |
27 | ::
28 |
29 | python3 manage.py compilemessages
30 |
31 | Your translation file (language.mo) is now ready, and assuming you've set the language in your session, you'll now see the translations in your template.
32 |
33 | FYI, the ``dmp_makemessages`` command does everything the regular command does, so it will also find translatable strings in your regular view files as well. You don't need to run both ``dmp_makemessages`` and ``makemessages``.
34 |
--------------------------------------------------------------------------------
/docs/_sources/topics_translation.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_translation:
2 |
3 | Internationalization
4 | ----------------------------------
5 |
6 | If your site needs to be translated into other languages, this section is for you. I'm sure you are aware that Django has full support for translation to other languages. If not, you should first read the standard Translation documentation at http://docs.djangoproject.com/en/dev/topics/i18n/translation/.
7 |
8 | DMP supports Django's translation functions--with one caveat. Since Django doesn't know about Mako, it can't translate strings in your Mako files. DMP fixes this with the ``dmp_makemessages`` command. Instead of running ``python3 manage.py makemessages`` like the Django tutorial shows, run ``python3 manage.py dmp_makemessages``. Since the DMP version is an extension of the standard version, the same command line options apply to both.
9 |
10 | Internally, ``dmp_makemessages`` literally extends the ``makemessages`` class. Since Mako templates are compiled into .py files at runtime (which makes them discoverable by ``makemessages``), the DMP version of the command simply finds all your templates, compiles them, and calls the standard command. Django finds your translatable strings within the cached\_templates directory (which holds the compiled Mako templates).
11 |
12 | Suppose you have a template with a header you want translated. Simply use the following in your template:
13 |
14 | .. code-block:: html+mako
15 |
16 | <%! from django.utils.translation import ugettext as _ %>
17 |
${ _("World History") }
18 |
19 | Run the following at the command line:
20 |
21 | ::
22 |
23 | python3 manage.py dmp_makemessages
24 |
25 | Assuming you have translations set up the way Django's documentation tells you to, you'll get a new language.po file. Edit this file and add the translation. Then compile your translations:
26 |
27 | ::
28 |
29 | python3 manage.py compilemessages
30 |
31 | Your translation file (language.mo) is now ready, and assuming you've set the language in your session, you'll now see the translations in your template.
32 |
33 | FYI, the ``dmp_makemessages`` command does everything the regular command does, so it will also find translatable strings in your regular view files as well. You don't need to run both ``dmp_makemessages`` and ``makemessages``.
34 |
--------------------------------------------------------------------------------
/docs-src/topics_variables.rst:
--------------------------------------------------------------------------------
1 | .. _topics_variables:
2 |
3 | Metadata about the Request
4 | =====================================
5 |
6 | As you saw in the tutorial, DMP adds an object to each request as ``request.dmp``. This object supports the inner workings of the DMP router and provides convenient access to render functions. It also contains routing information for the current request.
7 |
8 | These variables are set during `Django's URL resolution stage `_. That means the variables aren't available during pre-request middleware, but they are set by the time view middleware runs.
9 |
10 | Available Variables
11 | ------------------------------
12 |
13 | ``request.dmp.app``
14 | The Django application specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.app is the string "calculator".
15 |
16 | ``request.dmp.page``
17 | The name of the Python module specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.page is the string "index". In the URL ``http://www.server.com/calculator/index.somefunc/1/2/3``, request.dmp.page is still the string "index".
18 |
19 | ``request.dmp.function``
20 | The name of the function within the module that will be called, even if it is not specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.function is the string "process\_request" (the default function). In the URL ``http://www.server.com/calculator/index.somefunc/1/2/3``, request.dmp.function is the string "somefunc".
21 |
22 | ``request.dmp.module``
23 | The name of the real Python module specified in the URL, as it will be imported into the runtime module space. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.module is the string "calculator.views.index".
24 |
25 | ``request.dmp.callable``
26 | A reference to the view function the url resolved to.s
27 |
28 | ``request.dmp.view_type``
29 | The type of view: function (regular view function), class (class-based view), or template (direct template render).
30 |
31 | ``request.dmp.urlparams``
32 | A list of parameters specified in the URL. These are normally sent to your view functions based on their signatures, but the raw values are available here as a list of strings. See the the topic on `Parameter Conversion `_ for more information.
33 |
--------------------------------------------------------------------------------
/docs/_sources/topics_variables.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_variables:
2 |
3 | Metadata about the Request
4 | =====================================
5 |
6 | As you saw in the tutorial, DMP adds an object to each request as ``request.dmp``. This object supports the inner workings of the DMP router and provides convenient access to render functions. It also contains routing information for the current request.
7 |
8 | These variables are set during `Django's URL resolution stage `_. That means the variables aren't available during pre-request middleware, but they are set by the time view middleware runs.
9 |
10 | Available Variables
11 | ------------------------------
12 |
13 | ``request.dmp.app``
14 | The Django application specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.app is the string "calculator".
15 |
16 | ``request.dmp.page``
17 | The name of the Python module specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.page is the string "index". In the URL ``http://www.server.com/calculator/index.somefunc/1/2/3``, request.dmp.page is still the string "index".
18 |
19 | ``request.dmp.function``
20 | The name of the function within the module that will be called, even if it is not specified in the URL. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.function is the string "process\_request" (the default function). In the URL ``http://www.server.com/calculator/index.somefunc/1/2/3``, request.dmp.function is the string "somefunc".
21 |
22 | ``request.dmp.module``
23 | The name of the real Python module specified in the URL, as it will be imported into the runtime module space. In the URL ``http://www.server.com/calculator/index/1/2/3``, request.dmp.module is the string "calculator.views.index".
24 |
25 | ``request.dmp.callable``
26 | A reference to the view function the url resolved to.s
27 |
28 | ``request.dmp.view_type``
29 | The type of view: function (regular view function), class (class-based view), or template (direct template render).
30 |
31 | ``request.dmp.urlparams``
32 | A list of parameters specified in the URL. These are normally sent to your view functions based on their signatures, but the raw values are available here as a list of strings. See the the topic on `Parameter Conversion `_ for more information.
33 |
--------------------------------------------------------------------------------
/django_mako_plus/provider/__init__.py:
--------------------------------------------------------------------------------
1 | from django.apps import apps
2 | from django.template import Context
3 | from django.utils.safestring import mark_safe
4 |
5 | from .runner import ProviderRun
6 | from ..template import create_mako_context
7 |
8 |
9 | #########################################################
10 | ### Primary functions
11 |
12 | def links(tself, group=None):
13 | '''Returns the HTML for the given provider group (or all groups if None)'''
14 | pr = ProviderRun(tself, group)
15 | pr.run()
16 | return mark_safe(pr.getvalue())
17 |
18 |
19 | def template_links(request, app, template_name, context=None, group=None, force=True):
20 | '''
21 | Returns the HTML for the given provider group, using an app and template name.
22 | This method should not normally be used (use links() instead). The use of
23 | this method is when provider need to be called from regular python code instead
24 | of from within a rendering template environment.
25 | '''
26 | if isinstance(app, str):
27 | app = apps.get_app_config(app)
28 | if context is None:
29 | context = {}
30 | dmp = apps.get_app_config('django_mako_plus')
31 | template_obj = dmp.engine.get_template_loader(app, create=True).get_mako_template(template_name, force=force)
32 | return template_obj_links(request, template_obj, context, group)
33 |
34 |
35 | def template_obj_links(request, template_obj, context=None, group=None):
36 | '''
37 | Returns the HTML for the given provider group, using a template object.
38 | This method should not normally be used (use links() instead). The use of
39 | this method is when provider need to be called from regular python code instead
40 | of from within a rendering template environment.
41 | '''
42 | # the template_obj can be a MakoTemplateAdapter or a Mako Template
43 | # if our DMP-defined MakoTemplateAdapter, switch to the embedded Mako Template
44 | template_obj = getattr(template_obj, 'mako_template', template_obj)
45 | # create a mako context so it seems like we are inside a render
46 | context_dict = {
47 | 'request': request,
48 | }
49 | if isinstance(context, Context):
50 | for d in context:
51 | context_dict.update(d)
52 | elif context is not None:
53 | context_dict.update(context)
54 | mako_context = create_mako_context(template_obj, **context_dict)
55 | return links(mako_context['self'], group=group)
56 |
--------------------------------------------------------------------------------
/docs-src/topics_convenience.rst:
--------------------------------------------------------------------------------
1 | .. _topics_convenience:
2 |
3 | Convenience Functions
4 | ===========================================
5 |
6 | .. contents::
7 | :depth: 2
8 |
9 |
10 | You might be wondering: Can I use a dynamically-found app? What if I need a template object? Can I render a file directly?
11 |
12 | Use the DMP convenience functions to be more dynamic, to interact directly with template objects, or to render a file of your choosing:
13 |
14 | :Render a file from any app's template directory:
15 | .. code-block:: python
16 |
17 | from django_mako_plus import render_template
18 | mystr = render_template(request, 'homepage', 'index.html', context)
19 |
20 | :Render a file from a custom directory within an app:
21 | .. code-block:: python
22 |
23 | from django_mako_plus import render_template
24 | mystr = render_template(request, 'homepage', 'custom.html', context, subdir="customsubdir")
25 |
26 | :Render a file at any location, even outside of your project:
27 | .. code-block:: python
28 |
29 | from django_mako_plus import render_template_for_path
30 | mystr = render_template_for_path(request, '/var/some/dir/template.html', context)
31 |
32 | :Get a template object for a template in an app:
33 | .. code-block:: python
34 |
35 | from django_mako_plus import get_template
36 | template = get_template('homepage', 'index.html')
37 |
38 | :Get a template object at any location, even outside your project:
39 | .. code-block:: python
40 |
41 | from django_mako_plus import get_template_for_path
42 | template = get_template_for_path('/var/some/dir/template.html')
43 |
44 | :Get a lower-level Mako template object (without the Django template wrapper):
45 | .. code-block:: python
46 |
47 | from django_mako_plus import get_template_for_path
48 | template = get_template_for_path('/var/some/dir/template.html')
49 | mako_template = template.mako_template
50 |
51 | See the `Mako documentation `__ for more information on working directly with Mako template objects. Mako has many features that go well beyond the DMP interface.
52 |
53 | The convenience functions are not Django-API compliant. To use the normal API, see `Django-Style Template Rendering `_.
54 |
55 | The convenience functions are perfectly fine to use, but ``request.dmp.render(...)``, described in the tutorial, is likely the best choice because it doesn't hard code the app name.
56 |
--------------------------------------------------------------------------------
/docs/_sources/topics_convenience.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_convenience:
2 |
3 | Convenience Functions
4 | ===========================================
5 |
6 | .. contents::
7 | :depth: 2
8 |
9 |
10 | You might be wondering: Can I use a dynamically-found app? What if I need a template object? Can I render a file directly?
11 |
12 | Use the DMP convenience functions to be more dynamic, to interact directly with template objects, or to render a file of your choosing:
13 |
14 | :Render a file from any app's template directory:
15 | .. code-block:: python
16 |
17 | from django_mako_plus import render_template
18 | mystr = render_template(request, 'homepage', 'index.html', context)
19 |
20 | :Render a file from a custom directory within an app:
21 | .. code-block:: python
22 |
23 | from django_mako_plus import render_template
24 | mystr = render_template(request, 'homepage', 'custom.html', context, subdir="customsubdir")
25 |
26 | :Render a file at any location, even outside of your project:
27 | .. code-block:: python
28 |
29 | from django_mako_plus import render_template_for_path
30 | mystr = render_template_for_path(request, '/var/some/dir/template.html', context)
31 |
32 | :Get a template object for a template in an app:
33 | .. code-block:: python
34 |
35 | from django_mako_plus import get_template
36 | template = get_template('homepage', 'index.html')
37 |
38 | :Get a template object at any location, even outside your project:
39 | .. code-block:: python
40 |
41 | from django_mako_plus import get_template_for_path
42 | template = get_template_for_path('/var/some/dir/template.html')
43 |
44 | :Get a lower-level Mako template object (without the Django template wrapper):
45 | .. code-block:: python
46 |
47 | from django_mako_plus import get_template_for_path
48 | template = get_template_for_path('/var/some/dir/template.html')
49 | mako_template = template.mako_template
50 |
51 | See the `Mako documentation `__ for more information on working directly with Mako template objects. Mako has many features that go well beyond the DMP interface.
52 |
53 | The convenience functions are not Django-API compliant. To use the normal API, see `Django-Style Template Rendering `_.
54 |
55 | The convenience functions are perfectly fine to use, but ``request.dmp.render(...)``, described in the tutorial, is likely the best choice because it doesn't hard code the app name.
56 |
--------------------------------------------------------------------------------
/docs-src/converters_replacing.rst:
--------------------------------------------------------------------------------
1 | .. _converters_replacing:
2 |
3 | Customizing the Converter
4 | ===================================
5 |
6 | There may be situations where you need to specialize or even replace the converter. This is done by subclassing the ``ParameterConverter`` class and referencing your subclass in ``settings.py``.
7 |
8 | Note that we already discussed creating a custom converter class to `handle converter errors `_.
9 |
10 | Suppose you need to convert the first url parameter in a standard way, regardless of its type. The following code looks for this parameter by position:
11 |
12 | .. code-block:: python
13 |
14 | from django_mako_plus import ParameterConverter
15 |
16 | class SiteConverter(ParameterConverter):
17 | '''Customized converter that always converts the first parameter in a standard way, regardless of type'''
18 |
19 | def convert_value(self, value, parameter, request):
20 | # in the view function signature, request is position 0
21 | # and the first url parameter is position 1
22 | if parameter.position == 1:
23 | return some_custom_converter(value, parameter, request)
24 |
25 | # any other url params convert the normal way
26 | return super().convert_value(value, parameter, request)
27 |
28 |
29 | We'll assume you placed the class in ``myproject/lib/converters.py``. Activate your new converter in DMP's section of ``settings.py``:
30 |
31 | .. code-block:: python
32 |
33 | TEMPLATES = [
34 | {
35 | 'NAME': 'django_mako_plus',
36 | 'BACKEND': 'django_mako_plus.MakoTemplates',
37 | 'OPTIONS': {
38 | 'PARAMETER_CONVERTER': 'lib.converters.SiteConverter',
39 | ...
40 | }
41 | }
42 | ]
43 |
44 | All parameters in the system will now use your customization rather than the standard DMP converter.
45 |
46 |
47 |
48 |
49 | Disabling the Converter
50 | ------------------------------
51 |
52 | If you want to entirely disable parameter conversion, set DMP's converter setting to None in ``settings.py``. This will result in a slight processing speedup.
53 |
54 | .. code-block:: python
55 |
56 | TEMPLATES = [
57 | {
58 | 'NAME': 'django_mako_plus',
59 | 'BACKEND': 'django_mako_plus.MakoTemplates',
60 | 'OPTIONS': {
61 | 'PARAMETER_CONVERTER': None,
62 | ...
63 | }
64 | }
65 | ]
66 |
--------------------------------------------------------------------------------
/docs/_sources/converters_replacing.rst.txt:
--------------------------------------------------------------------------------
1 | .. _converters_replacing:
2 |
3 | Customizing the Converter
4 | ===================================
5 |
6 | There may be situations where you need to specialize or even replace the converter. This is done by subclassing the ``ParameterConverter`` class and referencing your subclass in ``settings.py``.
7 |
8 | Note that we already discussed creating a custom converter class to `handle converter errors `_.
9 |
10 | Suppose you need to convert the first url parameter in a standard way, regardless of its type. The following code looks for this parameter by position:
11 |
12 | .. code-block:: python
13 |
14 | from django_mako_plus import ParameterConverter
15 |
16 | class SiteConverter(ParameterConverter):
17 | '''Customized converter that always converts the first parameter in a standard way, regardless of type'''
18 |
19 | def convert_value(self, value, parameter, request):
20 | # in the view function signature, request is position 0
21 | # and the first url parameter is position 1
22 | if parameter.position == 1:
23 | return some_custom_converter(value, parameter, request)
24 |
25 | # any other url params convert the normal way
26 | return super().convert_value(value, parameter, request)
27 |
28 |
29 | We'll assume you placed the class in ``myproject/lib/converters.py``. Activate your new converter in DMP's section of ``settings.py``:
30 |
31 | .. code-block:: python
32 |
33 | TEMPLATES = [
34 | {
35 | 'NAME': 'django_mako_plus',
36 | 'BACKEND': 'django_mako_plus.MakoTemplates',
37 | 'OPTIONS': {
38 | 'PARAMETER_CONVERTER': 'lib.converters.SiteConverter',
39 | ...
40 | }
41 | }
42 | ]
43 |
44 | All parameters in the system will now use your customization rather than the standard DMP converter.
45 |
46 |
47 |
48 |
49 | Disabling the Converter
50 | ------------------------------
51 |
52 | If you want to entirely disable parameter conversion, set DMP's converter setting to None in ``settings.py``. This will result in a slight processing speedup.
53 |
54 | .. code-block:: python
55 |
56 | TEMPLATES = [
57 | {
58 | 'NAME': 'django_mako_plus',
59 | 'BACKEND': 'django_mako_plus.MakoTemplates',
60 | 'OPTIONS': {
61 | 'PARAMETER_CONVERTER': None,
62 | ...
63 | }
64 | }
65 | ]
66 |
--------------------------------------------------------------------------------
/docs-src/topics_django.rst:
--------------------------------------------------------------------------------
1 | .. _topics_django:
2 |
3 | Using the Django API
4 | =====================================
5 |
6 | In the `tutorial `_ , you may have noticed that we didn't use the "normal" Django shortcuts like ``render`` and ``render_to_response``. DMP provides the shortcuts like ``request.dmp.render`` because its renderers are tied to apps (which is different than Django).
7 |
8 | But that doesn't mean you can't use the standard Django shortcuts and template classes with DMP. As a template engine, DMP conforms to the Django standard. If you want to use Django's shortcuts and be more standard, here's how to do it.
9 |
10 | ``render(request, template, context)``
11 | ---------------------------------------------------
12 |
13 | The following imports only from ``django.shortcuts``:
14 |
15 | .. code-block:: python
16 |
17 | # doin' the django way:
18 | from django.shortcuts import render
19 | return render(request, 'homepage/index.html', context)
20 |
21 | # or to be more explicit with Django, you can specify the engine:
22 | from django.shortcuts import render
23 | return render(request, 'homepage/index.html', context, using='django_mako_plus')
24 |
25 |
26 | Specifying the Template Name
27 | -----------------------------------
28 |
29 | All right, the above code actually doesn't perfectly match the Django docs. In normal Django, you'd only specify the filename as simply ``index.html`` instead of ``homepage/index.html`` as we did above. Django's template loaders look for ``index.html`` in your "template directories" as specified in your settings. In the case of the ``app_directories`` loader, it even searches the same setup as DMP: the ``app/templates/`` directories.
30 |
31 | But unlike DMP, Django searches **all** templates directories, not just the current app. As it searches through your various ``templates`` folders, it uses the first ``index.html`` file it finds. In other words, it's not really app-aware. In real projects, this often necessitates longer template filenames or additional template subdirectories. Yuck.
32 |
33 | Since app-awareness is at the core of DMP, the template should be specified in the format ``app/template``. This allows DMP load your template from the right app.
34 |
35 |
36 | ``TemplateResponse`` and ``SimpleTemplateResponse``
37 | ---------------------------------------------------------
38 |
39 | The topic on `Django's lazy-rendering of templates `_ shows how DMP supports these responses.
40 |
41 |
42 | Further Reading about Template Locations
43 | ------------------------------------------
44 |
45 | The topic on `Template Location/Import `_ describes the nuances of template directories, inheritance, and discovery.
46 |
--------------------------------------------------------------------------------
/docs/_sources/topics_django.rst.txt:
--------------------------------------------------------------------------------
1 | .. _topics_django:
2 |
3 | Using the Django API
4 | =====================================
5 |
6 | In the `tutorial `_ , you may have noticed that we didn't use the "normal" Django shortcuts like ``render`` and ``render_to_response``. DMP provides the shortcuts like ``request.dmp.render`` because its renderers are tied to apps (which is different than Django).
7 |
8 | But that doesn't mean you can't use the standard Django shortcuts and template classes with DMP. As a template engine, DMP conforms to the Django standard. If you want to use Django's shortcuts and be more standard, here's how to do it.
9 |
10 | ``render(request, template, context)``
11 | ---------------------------------------------------
12 |
13 | The following imports only from ``django.shortcuts``:
14 |
15 | .. code-block:: python
16 |
17 | # doin' the django way:
18 | from django.shortcuts import render
19 | return render(request, 'homepage/index.html', context)
20 |
21 | # or to be more explicit with Django, you can specify the engine:
22 | from django.shortcuts import render
23 | return render(request, 'homepage/index.html', context, using='django_mako_plus')
24 |
25 |
26 | Specifying the Template Name
27 | -----------------------------------
28 |
29 | All right, the above code actually doesn't perfectly match the Django docs. In normal Django, you'd only specify the filename as simply ``index.html`` instead of ``homepage/index.html`` as we did above. Django's template loaders look for ``index.html`` in your "template directories" as specified in your settings. In the case of the ``app_directories`` loader, it even searches the same setup as DMP: the ``app/templates/`` directories.
30 |
31 | But unlike DMP, Django searches **all** templates directories, not just the current app. As it searches through your various ``templates`` folders, it uses the first ``index.html`` file it finds. In other words, it's not really app-aware. In real projects, this often necessitates longer template filenames or additional template subdirectories. Yuck.
32 |
33 | Since app-awareness is at the core of DMP, the template should be specified in the format ``app/template``. This allows DMP load your template from the right app.
34 |
35 |
36 | ``TemplateResponse`` and ``SimpleTemplateResponse``
37 | ---------------------------------------------------------
38 |
39 | The topic on `Django's lazy-rendering of templates `_ shows how DMP supports these responses.
40 |
41 |
42 | Further Reading about Template Locations
43 | ------------------------------------------
44 |
45 | The topic on `Template Location/Import `_ describes the nuances of template directories, inheritance, and discovery.
46 |
--------------------------------------------------------------------------------
/docs-src/converters_adding.rst:
--------------------------------------------------------------------------------
1 | .. _converters_adding:
2 |
3 | Adding a New Type
4 | =================================
5 |
6 | It's easy to add converter functions for new, specialized types.
7 |
8 | Remember that DMP already knows how to convert all of your models -- you probably don't need to add new converter functions for specific model classes.
9 |
10 | Suppose we want to use geographic locations in the format "20.4,-162.0". The URL might looks something like this:
11 |
12 | ``http://localhost:8000/homepage/index/20.4,-162.0/``
13 |
14 |
15 | Let's place our new class and converter function in ``homepage/apps.py`` (you can actually place these in any file that loads with Django). Decorate the function with ``@parameter_converter``, with the type(s) as arguments.
16 |
17 | .. code-block:: python
18 |
19 | from django.apps import AppConfig
20 | from django_mako_plus import parameter_converter
21 |
22 | class HomepageConfig(AppConfig):
23 | name = 'homepage'
24 |
25 |
26 | class GeoLocation(object):
27 | def __init__(self, latitude, longitude):
28 | self.latitude = latitude
29 | self.longitude = longitude
30 |
31 | @parameter_converter(GeoLocation)
32 | def convert_geo_location(value, parameter):
33 | parts = value.split(',')
34 | if len(parts) < 2:
35 | raise ValueError('Both latitude and longitude are required')
36 | # the float constructor will raise ValueError if invalid
37 | return GeoLocation(float(parts[0]), float(parts[1]))
38 |
39 | The function must do one of the following:
40 |
41 | 1. Return the converted type.
42 | 2. Raise one of these exceptions:
43 |
44 | * ``ValueError``, which triggers the Http 404 page. This should be done for "expected" conversion errors (e.g. bad data in url).
45 | * DMP's `RedirectException `_ or `InternalRedirectException `_.
46 | * Any other exception, which triggers the Http 500 page. This should be done for unexpected errors.
47 |
48 | When Django starts up, the ``parameter_converter`` decorator registers our new function as a converter.
49 |
50 |
51 | Using the New Type
52 | --------------------------
53 |
54 | In ``homepage/views/index.py``, use our custom ``GeoLocation`` class as the type hint in the index file.
55 |
56 | .. code-block:: python
57 |
58 | from homepage.apps import GeoLocation
59 |
60 | @view_function
61 | def process_request(request, loc:GeoLocation):
62 | print(loc.latitude)
63 | print(loc.longitude)
64 | return request.dmp.render('index.html', {})
65 |
66 | Then during each request, DMP reads the signature on ``process_request``, looks up the ``GeoLocation`` type, and calls our function to convert the string to a GeoLocation object.
67 |
--------------------------------------------------------------------------------
/docs/_sources/converters_adding.rst.txt:
--------------------------------------------------------------------------------
1 | .. _converters_adding:
2 |
3 | Adding a New Type
4 | =================================
5 |
6 | It's easy to add converter functions for new, specialized types.
7 |
8 | Remember that DMP already knows how to convert all of your models -- you probably don't need to add new converter functions for specific model classes.
9 |
10 | Suppose we want to use geographic locations in the format "20.4,-162.0". The URL might looks something like this:
11 |
12 | ``http://localhost:8000/homepage/index/20.4,-162.0/``
13 |
14 |
15 | Let's place our new class and converter function in ``homepage/apps.py`` (you can actually place these in any file that loads with Django). Decorate the function with ``@parameter_converter``, with the type(s) as arguments.
16 |
17 | .. code-block:: python
18 |
19 | from django.apps import AppConfig
20 | from django_mako_plus import parameter_converter
21 |
22 | class HomepageConfig(AppConfig):
23 | name = 'homepage'
24 |
25 |
26 | class GeoLocation(object):
27 | def __init__(self, latitude, longitude):
28 | self.latitude = latitude
29 | self.longitude = longitude
30 |
31 | @parameter_converter(GeoLocation)
32 | def convert_geo_location(value, parameter):
33 | parts = value.split(',')
34 | if len(parts) < 2:
35 | raise ValueError('Both latitude and longitude are required')
36 | # the float constructor will raise ValueError if invalid
37 | return GeoLocation(float(parts[0]), float(parts[1]))
38 |
39 | The function must do one of the following:
40 |
41 | 1. Return the converted type.
42 | 2. Raise one of these exceptions:
43 |
44 | * ``ValueError``, which triggers the Http 404 page. This should be done for "expected" conversion errors (e.g. bad data in url).
45 | * DMP's `RedirectException `_ or `InternalRedirectException `_.
46 | * Any other exception, which triggers the Http 500 page. This should be done for unexpected errors.
47 |
48 | When Django starts up, the ``parameter_converter`` decorator registers our new function as a converter.
49 |
50 |
51 | Using the New Type
52 | --------------------------
53 |
54 | In ``homepage/views/index.py``, use our custom ``GeoLocation`` class as the type hint in the index file.
55 |
56 | .. code-block:: python
57 |
58 | from homepage.apps import GeoLocation
59 |
60 | @view_function
61 | def process_request(request, loc:GeoLocation):
62 | print(loc.latitude)
63 | print(loc.longitude)
64 | return request.dmp.render('index.html', {})
65 |
66 | Then during each request, DMP reads the signature on ``process_request``, looks up the ``GeoLocation`` type, and calls our function to convert the string to a GeoLocation object.
67 |
--------------------------------------------------------------------------------
/django_mako_plus/http.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 |
3 | # this redirect key is (hopefully) unique but generic so it doesn't signpost the use of DMP/Django.
4 | # not prefixing with X- because that's now deprecated.
5 | REDIRECT_HEADER_KEY = 'Redirect-Location'
6 |
7 |
8 | ###############################################################################
9 | ### Redirect with Javascript instead of 301/302
10 | ### See also exceptions.py for two additional redirect methods
11 |
12 | class HttpResponseJavascriptRedirect(HttpResponse):
13 | '''
14 | Sends a regular HTTP 200 OK response that contains Javascript to
15 | redirect the browser:
16 |
17 | .
18 |
19 | If redirect_to is empty, it redirects to the current location (essentially refreshing
20 | the current page):
21 |
22 | .
23 |
24 | Normally, redirecting should be done via HTTP 302 rather than Javascript.
25 | Use this class when your only choice is through Javascript.
26 |
27 | For example, suppose you need to redirect the top-level page from an Ajax response.
28 | Ajax redirects normally only redirects the Ajax itself (not the page that initiated the call),
29 | and this default behavior is usually what is needed. However, there are instances when the
30 | entire page must be redirected, even if the call is Ajax-based.
31 |
32 | After the redirect_to parameter, you can use any of the normal HttpResponse constructor arguments.
33 |
34 | If you need to omit the surrounding '.format(script)
53 | # call the super
54 | super().__init__(script, *args, **kwargs)
55 | # add the custom header
56 | self[REDIRECT_HEADER_KEY] = redirect_to or 'window.location.href'
57 |
--------------------------------------------------------------------------------
/django_mako_plus/provider/context.py:
--------------------------------------------------------------------------------
1 | from django.utils.module_loading import import_string
2 | import json
3 | import logging
4 | from ..version import __version__
5 | from ..util import log
6 | from .base import BaseProvider
7 |
8 | ###################################
9 | ### JS Context Provider
10 |
11 | class JsContextProvider(BaseProvider):
12 | '''
13 | Adds all js_context() variables to DMP_CONTEXT.
14 | '''
15 | DEFAULT_OPTIONS = {
16 | # the group this provider is part of. this only matters when
17 | # the html page limits the providers that will be called with
18 | # ${ django_mako_plus.links(group="...") }
19 | 'group': 'scripts',
20 | # the encoder to use for the JSON structure
21 | 'encoder': 'django.core.serializers.json.DjangoJSONEncoder',
22 | }
23 |
24 | def __init__(self, *args, **kwargs):
25 | super().__init__(*args, **kwargs)
26 | self.encoder = import_string(self.options['encoder'])
27 | if log.isEnabledFor(logging.DEBUG):
28 | log.debug('%s created', repr(self))
29 |
30 | def provide(self):
31 | # we output on the first run through - the context is only needed once
32 | if not self.is_first():
33 | return
34 |
35 | # generate the context dictionary
36 | data = {
37 | 'id': self.provider_run.uid,
38 | 'version': __version__,
39 | 'templates': [ '{}/{}'.format(p.app_config.name, p.template_relpath) for p in self.iter_related() ],
40 | 'app': self.provider_run.request.dmp.app if self.provider_run.request is not None else None,
41 | 'page': self.provider_run.request.dmp.page if self.provider_run.request is not None else None,
42 | 'log': log.isEnabledFor(logging.DEBUG),
43 | 'values': {
44 | 'id': self.provider_run.uid,
45 | },
46 | }
47 | for k in self.provider_run.context.keys():
48 | if isinstance(k, jscontext):
49 | value = self.provider_run.context[k]
50 | data['values'][k] = value.__jscontext__() if callable(getattr(value, '__jscontext__', None)) else value
51 |
52 | # output the script
53 | self.write('')
58 |
59 |
60 | class jscontext(str):
61 | '''
62 | Marks a key in the context dictionary as a JS context item.
63 | JS context items are sent to the template like normal,
64 | but they are also added to the runtime JS namespace.
65 |
66 | See the tutorial for more information on this function.
67 | '''
68 | # no code needed, just using the class for identity
69 |
--------------------------------------------------------------------------------
/docs/_static/sphinx_tabs/tabs.js:
--------------------------------------------------------------------------------
1 | // From http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
2 | function elementIsInView (el) {
3 | if (typeof jQuery === "function" && el instanceof jQuery) {
4 | el = el[0];
5 | }
6 |
7 | const rect = el.getBoundingClientRect();
8 |
9 | return (
10 | rect.top >= 0 &&
11 | rect.left >= 0 &&
12 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
13 | rect.right <= (window.innerWidth || document.documentElement.clientWidth)
14 | );
15 | }
16 |
17 | $(function() {
18 | // Change container tags