├── docs-src ├── .nojekyll ├── _static │ ├── webpack.png │ └── webpack.pptx ├── static.rst ├── converters.rst ├── deploy_tutorials.rst ├── deploy.rst ├── tutorial.rst ├── Makefile ├── install_subdirectory.rst ├── make_page_labels.py ├── install.rst ├── topics.rst ├── deploy_recommendations.rst ├── topics_responses.rst ├── install_app_specific.rst ├── topics_csrf.rst ├── editors.rst ├── topics_translation.rst ├── topics_variables.rst ├── topics_convenience.rst ├── converters_replacing.rst ├── topics_django.rst ├── converters_adding.rst ├── topics_class_views.rst ├── converters_errors.rst ├── topics_signals.rst └── converters_decorators.rst ├── docs ├── .nojekyll ├── objects.inv ├── _static │ ├── up.png │ ├── down.png │ ├── file.png │ ├── plus.png │ ├── comment.png │ ├── minus.png │ ├── webpack.png │ ├── webpack.pptx │ ├── ajax-loader.gif │ ├── down-pressed.png │ ├── up-pressed.png │ ├── comment-bright.png │ ├── comment-close.png │ ├── fonts │ │ ├── Inconsolata.ttf │ │ ├── Lato-Bold.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Inconsolata-Bold.ttf │ │ ├── Lato │ │ │ ├── lato-bold.eot │ │ │ ├── lato-bold.ttf │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-italic.eot │ │ │ ├── lato-italic.ttf │ │ │ ├── lato-italic.woff │ │ │ ├── lato-italic.woff2 │ │ │ ├── lato-regular.eot │ │ │ ├── lato-regular.ttf │ │ │ ├── lato-regular.woff │ │ │ ├── lato-bolditalic.eot │ │ │ ├── lato-bolditalic.ttf │ │ │ ├── lato-bolditalic.woff │ │ │ ├── lato-regular.woff2 │ │ │ └── lato-bolditalic.woff2 │ │ ├── RobotoSlab-Bold.ttf │ │ ├── RobotoSlab-Regular.ttf │ │ ├── Inconsolata-Regular.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ └── RobotoSlab │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ └── roboto-slab-v7-regular.woff2 │ ├── sphinx_tabs │ │ ├── tabs.css │ │ ├── semantic-ui-2.2.10 │ │ │ └── tab.min.css │ │ └── tabs.js │ └── css │ │ └── badge_only.css ├── _images │ └── webpack.png ├── .doctrees │ ├── faq.doctree │ ├── deploy.doctree │ ├── index.doctree │ ├── static.doctree │ ├── topics.doctree │ ├── compare.doctree │ ├── editors.doctree │ ├── install.doctree │ ├── tutorial.doctree │ ├── converters.doctree │ ├── environment.pickle │ ├── install_new.doctree │ ├── static_faq.doctree │ ├── topics_csrf.doctree │ ├── deploy_static.doctree │ ├── static_links.doctree │ ├── topics_django.doctree │ ├── topics_paths.doctree │ ├── tutorial_ajax.doctree │ ├── upgrade_notes.doctree │ ├── converters_raw.doctree │ ├── converters_types.doctree │ ├── deploy_tutorials.doctree │ ├── install_existing.doctree │ ├── static_compilers.doctree │ ├── static_overview.doctree │ ├── static_webpack.doctree │ ├── topics_escaping.doctree │ ├── topics_modules.doctree │ ├── topics_responses.doctree │ ├── topics_settings.doctree │ ├── topics_signals.doctree │ ├── topics_variables.doctree │ ├── tutorial_css_js.doctree │ ├── tutorial_views.doctree │ ├── converters_adding.doctree │ ├── converters_errors.doctree │ ├── install_as_router.doctree │ ├── topics_class_views.doctree │ ├── topics_convenience.doctree │ ├── topics_redirecting.doctree │ ├── topics_third_party.doctree │ ├── topics_translation.doctree │ ├── tutorial_meet_dmp.doctree │ ├── converters_decorators.doctree │ ├── converters_replacing.doctree │ ├── install_app_specific.doctree │ ├── install_as_renderer.doctree │ ├── install_custom_urls.doctree │ ├── install_subdirectory.doctree │ ├── topics_other_syntax.doctree │ ├── topics_view_function.doctree │ ├── tutorial_parameters.doctree │ ├── deploy_recommendations.doctree │ └── topics_partial_templates.doctree ├── .buildinfo └── _sources │ ├── static.rst.txt │ ├── converters.rst.txt │ ├── deploy_tutorials.rst.txt │ ├── deploy.rst.txt │ ├── tutorial.rst.txt │ ├── install_subdirectory.rst.txt │ ├── install.rst.txt │ ├── topics.rst.txt │ ├── deploy_recommendations.rst.txt │ ├── topics_responses.rst.txt │ ├── install_app_specific.rst.txt │ ├── topics_csrf.rst.txt │ ├── editors.rst.txt │ ├── topics_translation.rst.txt │ ├── topics_variables.rst.txt │ ├── topics_convenience.rst.txt │ ├── converters_replacing.rst.txt │ ├── topics_django.rst.txt │ ├── converters_adding.rst.txt │ ├── topics_class_views.rst.txt │ ├── topics_signals.rst.txt │ ├── converters_errors.rst.txt │ └── converters_decorators.rst.txt ├── .python-version ├── tests_project ├── errorsapp │ ├── __init__.py │ ├── views │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── apps.py │ └── templates │ │ └── syntax_error.html ├── homepage │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_filters.py │ │ ├── test_static_files.py │ │ ├── test_redirect.py │ │ ├── test_engine.py │ │ └── test_router.py │ ├── styles │ │ ├── base.css │ │ └── static_files.css │ ├── scripts │ │ ├── base.js │ │ └── static_files.js │ ├── templates │ │ ├── index.basic.html │ │ ├── static_files.html │ │ ├── index.html │ │ ├── filters.html │ │ └── base.htm │ ├── models.py │ ├── views │ │ ├── static_files.py │ │ ├── __init__.py │ │ ├── index.py │ │ ├── redirects.py │ │ └── converter.py │ └── fixtures │ │ └── ice_cream.json ├── tests_project │ ├── urls.py │ └── wsgi.py └── manage.py ├── django_mako_plus ├── app_template │ ├── __init__.py │ ├── views │ │ ├── __init__.py │ │ └── index.py │ ├── migrations │ │ └── __init__.py │ ├── admin.py │ ├── tests.py │ ├── models.py │ ├── styles │ │ ├── index.css │ │ └── base.css │ ├── media │ │ └── python.png │ ├── apps.py │ ├── scripts │ │ └── index.js │ └── templates │ │ ├── index.html │ │ ├── base_ajax.htm │ │ └── base.htm ├── management │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── dmp.py │ │ ├── dmp_startproject.py │ │ └── dmp_startapp.py │ └── mixins.py ├── project_template │ ├── project_name │ │ ├── __init__.py-tpl │ │ ├── wsgi.py-tpl │ │ └── urls.py-tpl │ └── manage.py-tpl ├── models.py ├── templatetags │ ├── __init__.py │ └── django_mako_plus.py ├── router │ ├── __init__.py │ └── urlparams.py ├── version.py ├── template │ └── __init__.py ├── util │ ├── __init__.py │ ├── reflect.py │ ├── datastruct.py │ └── base58.py ├── converter │ ├── __init__.py │ ├── decorators.py │ ├── parameter.py │ └── info.py ├── webroot │ ├── webpack.config.js │ └── package.json ├── context_processors.py ├── middleware.py ├── command.py ├── urls.py ├── __main__.py ├── convenience.py ├── provider │ ├── webpack.py │ ├── __init__.py │ └── context.py ├── http.py ├── __init__.py └── tags.py ├── setup.cfg ├── static └── tests │ ├── styles │ ├── base.css │ └── static_files.css │ └── scripts │ ├── base.js │ └── static_files.js ├── app_template.zip ├── project_template.zip ├── readme.txt ├── docs-rtd └── index.md ├── .gitignore ├── mkdocs.yml ├── readme.md ├── stats-download.txt ├── runtests.py ├── livereload-docs.py └── release_version.py /docs-src/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | dmp-3.4 2 | -------------------------------------------------------------------------------- /tests_project/errorsapp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests_project/homepage/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mako_plus/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [install] 2 | compile = 0 3 | -------------------------------------------------------------------------------- /tests_project/errorsapp/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests_project/homepage/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mako_plus/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/tests/styles/base.css: -------------------------------------------------------------------------------- 1 | /* This is +base.css+ */ -------------------------------------------------------------------------------- /django_mako_plus/project_template/project_name/__init__.py-tpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/tests/scripts/base.js: -------------------------------------------------------------------------------- 1 | console.log('This is +base.js+'); -------------------------------------------------------------------------------- /tests_project/homepage/styles/base.css: -------------------------------------------------------------------------------- 1 | /* This is +base.css+ */ -------------------------------------------------------------------------------- /static/tests/styles/static_files.css: -------------------------------------------------------------------------------- 1 | /* This is +providers.css+ */ -------------------------------------------------------------------------------- /tests_project/homepage/scripts/base.js: -------------------------------------------------------------------------------- 1 | console.log('This is +base.js+'); -------------------------------------------------------------------------------- /static/tests/scripts/static_files.js: -------------------------------------------------------------------------------- 1 | console.log('This is +providers.js+'); -------------------------------------------------------------------------------- /tests_project/homepage/styles/static_files.css: -------------------------------------------------------------------------------- 1 | /* This is +static_files.css+ */ 2 | -------------------------------------------------------------------------------- /app_template.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/app_template.zip -------------------------------------------------------------------------------- /django_mako_plus/models.py: -------------------------------------------------------------------------------- 1 | # this app has no models; file here just to conform to Django 2 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/objects.inv -------------------------------------------------------------------------------- /tests_project/homepage/scripts/static_files.js: -------------------------------------------------------------------------------- 1 | console.log('This is +static_files.js+'); 2 | -------------------------------------------------------------------------------- /docs/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/up.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/plus.png -------------------------------------------------------------------------------- /project_template.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/project_template.zip -------------------------------------------------------------------------------- /tests_project/errorsapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /docs/_images/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_images/webpack.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/webpack.png -------------------------------------------------------------------------------- /tests_project/errorsapp/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /docs/.doctrees/faq.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/faq.doctree -------------------------------------------------------------------------------- /docs/_static/webpack.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/webpack.pptx -------------------------------------------------------------------------------- /docs-src/_static/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs-src/_static/webpack.png -------------------------------------------------------------------------------- /docs-src/_static/webpack.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs-src/_static/webpack.pptx -------------------------------------------------------------------------------- /docs/.doctrees/deploy.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/deploy.doctree -------------------------------------------------------------------------------- /docs/.doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/index.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics.doctree -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/.doctrees/compare.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/compare.doctree -------------------------------------------------------------------------------- /docs/.doctrees/editors.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/editors.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial.doctree -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/.doctrees/converters.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters.doctree -------------------------------------------------------------------------------- /docs/.doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/.doctrees/install_new.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_new.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static_faq.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static_faq.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_csrf.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_csrf.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Inconsolata.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /django_mako_plus/app_template/models.py: -------------------------------------------------------------------------------- 1 | {{ unicode_literals }}from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /docs/.doctrees/deploy_static.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/deploy_static.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static_links.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static_links.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_django.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_django.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_paths.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_paths.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial_ajax.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial_ajax.doctree -------------------------------------------------------------------------------- /docs/.doctrees/upgrade_notes.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/upgrade_notes.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /docs/.doctrees/converters_raw.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_raw.doctree -------------------------------------------------------------------------------- /docs/.doctrees/converters_types.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_types.doctree -------------------------------------------------------------------------------- /docs/.doctrees/deploy_tutorials.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/deploy_tutorials.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_existing.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_existing.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static_compilers.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static_compilers.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static_overview.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static_overview.doctree -------------------------------------------------------------------------------- /docs/.doctrees/static_webpack.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/static_webpack.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_escaping.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_escaping.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_modules.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_modules.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_responses.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_responses.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_settings.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_settings.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_signals.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_signals.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_variables.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_variables.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial_css_js.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial_css_js.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial_views.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial_views.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /docs/.doctrees/converters_adding.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_adding.doctree -------------------------------------------------------------------------------- /docs/.doctrees/converters_errors.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_errors.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_as_router.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_as_router.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_class_views.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_class_views.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_convenience.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_convenience.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_redirecting.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_redirecting.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_third_party.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_third_party.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_translation.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_translation.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial_meet_dmp.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial_meet_dmp.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /django_mako_plus/app_template/styles/index.css: -------------------------------------------------------------------------------- 1 | main { 2 | text-align: center; 3 | } 4 | 5 | h4.utc-time { 6 | color: #3771A1; 7 | } 8 | -------------------------------------------------------------------------------- /docs/.doctrees/converters_decorators.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_decorators.doctree -------------------------------------------------------------------------------- /docs/.doctrees/converters_replacing.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/converters_replacing.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_app_specific.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_app_specific.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_as_renderer.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_as_renderer.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_custom_urls.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_custom_urls.doctree -------------------------------------------------------------------------------- /docs/.doctrees/install_subdirectory.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/install_subdirectory.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_other_syntax.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_other_syntax.doctree -------------------------------------------------------------------------------- /docs/.doctrees/topics_view_function.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_view_function.doctree -------------------------------------------------------------------------------- /docs/.doctrees/tutorial_parameters.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/tutorial_parameters.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /tests_project/errorsapp/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ErrorsappConfig(AppConfig): 5 | name = 'errorsapp' 6 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/media/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/django_mako_plus/app_template/media/python.png -------------------------------------------------------------------------------- /docs/.doctrees/deploy_recommendations.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/deploy_recommendations.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /django_mako_plus/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | # This package contains DJANGO tags in the normal Django style 2 | # Don't put regular DMP or Mako things in it. 3 | -------------------------------------------------------------------------------- /docs/.doctrees/topics_partial_templates.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/.doctrees/topics_partial_templates.doctree -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /tests_project/homepage/templates/index.basic.html: -------------------------------------------------------------------------------- 1 | <%inherit file="base.htm" /> 2 | 3 | <%block name="content"> 4 |

Hello world, this is DMP.

5 | -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doconix/django-mako-plus/HEAD/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Routing Django to Mako since 2013. 2 | 3 | **Please visit http://doconix.github.io/django-mako-plus/ for tutorials, examples, and other documentation topics.** 4 | -------------------------------------------------------------------------------- /django_mako_plus/router/__init__.py: -------------------------------------------------------------------------------- 1 | from .data import RoutingData 2 | from .decorators import view_function, RequestViewWrapper 3 | from .resolver import app_resolver, dmp_path 4 | -------------------------------------------------------------------------------- /docs-rtd/index.md: -------------------------------------------------------------------------------- 1 | Routing Django to Mako since 2013. 2 | 3 | **Please visit http://doconix.github.io/django-mako-plus/ for tutorials, examples, and other documentation topics.** 4 | -------------------------------------------------------------------------------- /django_mako_plus/app_template/apps.py: -------------------------------------------------------------------------------- 1 | {{ unicode_literals }}from django.apps import AppConfig 2 | 3 | 4 | class {{ camel_case_app_name }}Config(AppConfig): 5 | name = '{{ app_name }}' 6 | -------------------------------------------------------------------------------- /tests_project/tests_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | 3 | urlpatterns = [ 4 | # adds all DMP-enabled apps 5 | url('', include('django_mako_plus.urls')), 6 | ] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | .DS_Store 5 | __dmpcache__ 6 | django.mo 7 | *.tfile 8 | .emacs-project 9 | .idea/ 10 | .#* 11 | __pycache__ 12 | _build 13 | .vscode 14 | node_modules/ 15 | -------------------------------------------------------------------------------- /tests_project/homepage/templates/static_files.html: -------------------------------------------------------------------------------- 1 | <%inherit file="base.htm" /> 2 | 3 | <%block name="content"> 4 |

Hello world, testing the static files. See scripts/ and styles/

-------------------------------------------------------------------------------- /tests_project/homepage/templates/index.html: -------------------------------------------------------------------------------- 1 | <%inherit file="base.htm" /> 2 | 3 | <%block name="content"> 4 |

Hello world, this is DMP.

5 |

The current time is ${ current_time }.

6 | -------------------------------------------------------------------------------- /tests_project/homepage/templates/filters.html: -------------------------------------------------------------------------------- 1 | <%block filter="django_syntax(local)"> 2 | {{ django_var }} 3 | 4 | 5 | <%block filter="jinja2_syntax(local)"> 6 | {{ jinja2_var }} 7 | 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 | 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 | 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 | 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 | 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 | python 21 |
Welcome to
DMP!
22 |
23 | 24 |
25 | <%block name="content"> 26 | Site content goes here in sub-templates. 27 | 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 |
13 | ${ csrf_input } 14 | ${ form } 15 | 16 |
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('
') 41 | buf.write(csrf_input(request)) 42 | buf.write(str(form)) 43 | buf.write('') 44 | 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 |
13 | ${ csrf_input } 14 | ${ form } 15 | 16 |
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('
') 41 | buf.write(csrf_input(request)) 42 | buf.write(str(form)) 43 | buf.write('') 44 | 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
-> 19 | $('.sphinx-menu.menu .item').each(function() { 20 | var this_ = $(this); 21 | var a_this = $(''); 22 | 23 | a_this.html(this_.html()); 24 | $.each(this_.prop('attributes'), function() { 25 | a_this.attr(this.name, this.value); 26 | }); 27 | 28 | this_.replaceWith(a_this); 29 | }); 30 | 31 | // We store the data-tab values as sphinx-data- 32 | // Add data-tab attribute with the extracted value 33 | $('.sphinx-menu.menu .item, .sphinx-tab.tab').each(function() { 34 | var this_ = $(this); 35 | const prefix = 'sphinx-data-'; 36 | const classes = this_.attr('class').split(/\s+/); 37 | $.each(classes, function(idx, clazz) { 38 | if (clazz.startsWith(prefix)) { 39 | this_.attr('data-tab', 40 | clazz.substring(prefix.length)); 41 | } 42 | }); 43 | }); 44 | 45 | // Mimic the Semantic UI behaviour 46 | $('.sphinx-menu.menu .item').each(function() { 47 | var this1 = $(this); 48 | var data_tab = this1.attr('data-tab'); 49 | 50 | this1.on('click', function() { 51 | // Find offset in view 52 | const offset = (this1.offset().top - $(window).scrollTop()); 53 | 54 | // Enable all tabs with this id 55 | 56 | // For each tab group 57 | $('.sphinx-tabs').each(function() { 58 | var this2 = $(this); 59 | 60 | // Check if tab group has a tab matching the clicked tab 61 | var has_tab = false; 62 | this2.children().eq(0).children().each(function() { 63 | has_tab |= $(this).attr('data-tab') === data_tab; 64 | }); 65 | 66 | if (has_tab) { 67 | // Enable just the matching tab 68 | var toggle = function() { 69 | var this3 = $(this); 70 | if (this3.attr('data-tab') === data_tab) { 71 | this3.addClass('active'); 72 | } else { 73 | this3.removeClass('active'); 74 | } 75 | }; 76 | this2.children().eq(0).children('[data-tab]').each(toggle); 77 | this2.children('[data-tab]').each(toggle); 78 | } 79 | }); 80 | 81 | // Keep tab with the original view offset 82 | $(window).scrollTop(this1.offset().top - offset); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /django_mako_plus/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Author: Conan Albrecht 3 | # License: Apache Open Source License 4 | # 5 | 6 | 7 | # pointer to our app config 8 | # Django looks for this exact variable name 9 | default_app_config = 'django_mako_plus.apps.Config' 10 | 11 | 12 | # the version 13 | from .version import __version__ 14 | 15 | 16 | # the router, middleware, and view function decorator 17 | from .middleware import RequestInitMiddleware 18 | from .router import view_function, app_resolver, dmp_path 19 | 20 | 21 | # converter decorator 22 | from .converter import parameter_converter, ParameterConverter 23 | 24 | 25 | # the middleware and template 26 | # the template engine 27 | from .engine import MakoTemplates 28 | 29 | 30 | # the exceptions 31 | from .exceptions import BaseRedirectException 32 | from .exceptions import RedirectException 33 | from .exceptions import PermanentRedirectException 34 | from .exceptions import JavascriptRedirectException 35 | from .exceptions import InternalRedirectException 36 | from .exceptions import ConverterHttp404 37 | from .exceptions import ConverterException 38 | 39 | 40 | # filters and tags 41 | from .filters import django_syntax, jinja2_syntax, alternate_syntax 42 | from .templatetags.django_mako_plus import dmp_include 43 | 44 | # used internally in compiled templates for autoescaping 45 | # (needs to be exposed publicly so templates can see it) 46 | from .template import ExpressionPostProcessor 47 | 48 | # the http responses 49 | from .http import HttpResponseJavascriptRedirect 50 | 51 | 52 | # the convenience functions 53 | # 54 | # Instead of these functions, consider using request.dmp.render() and request.dmp.render_to_string(), 55 | # which are monkey-patched onto every DMP-enabled app at load time. See the documentation 56 | # for information on why we do this. 57 | # 58 | from .convenience import render_template 59 | from .convenience import render_template_for_path 60 | from .convenience import get_template 61 | from .convenience import get_template_for_path 62 | from .convenience import get_template_loader 63 | from .convenience import get_template_loader_for_path 64 | 65 | 66 | # the utilities 67 | from .util import merge_dicts 68 | 69 | 70 | # the urls 71 | # I'm specifically not including urls.py here because I want it imported 72 | # as late as possible (after all the apps are set up). Django will import it 73 | # when it processes the project's urls.py file. 74 | 75 | 76 | # html content shortcuts 77 | from .provider import links 78 | from .provider import template_links, template_obj_links 79 | # html content providers 80 | from .provider.base import BaseProvider 81 | from .provider.compile import CompileProvider, CompileScssProvider, CompileLessProvider 82 | from .provider.context import JsContextProvider, jscontext 83 | from .provider.link import LinkProvider, CssLinkProvider, JsLinkProvider 84 | from .provider.webpack import WebpackJsLinkProvider 85 | -------------------------------------------------------------------------------- /docs-src/topics_class_views.rst: -------------------------------------------------------------------------------- 1 | .. _topics_class_views: 2 | 3 | Class-Based Views 4 | ========================= 5 | 6 | Django-Mako-Plus supports Django's class-based view concept. You can read more about this pattern in the Django documentation. If you are using view classes, DMP automatically detects and adjusts accordingly. If you are using regular function-based views, skip this section for now. 7 | 8 | With DMP, your class-based view will be discovered via request url, so you have to name your class accordingly. In keeping with the rest of DMP, the default class name in a file should be named ``class process_request()``. Consider the following ``index.py`` file: 9 | 10 | .. code-block:: python 11 | 12 | from django.conf import settings 13 | from django.http import HttpResponse 14 | from django.views.generic import View 15 | from datetime import datetime 16 | 17 | class process_request(View): 18 | 19 | def get(self, request): 20 | context = { 21 | 'now': datetime.now().strftime(request.dmp.urlparams[0] if request.dmp.urlparams[0] else '%H:%M'), 22 | } 23 | return request.dmp.render('index.html', context) 24 | 25 | class discovery_section(View): 26 | 27 | def get(self, request): 28 | return HttpResponse('Get was called.') 29 | 30 | def post(self, request): 31 | return HttpResponse('Post was called.') 32 | 33 | In the above ``index.py`` file, two class-based views are defined. The first is called with the url ``/homepage/index/``. The second is called with the url ``/homepage/index.discovery_section/``. 34 | 35 | 36 | Hey! Where's @view_function? 37 | ----------------------------- 38 | 39 | In contrast with normal function-based routing, class-based views do not require the ``@view_function`` decorator, which provides security on which functions are web-accessible. Since class-based views must extend django.views.generic.View, the security provided by the decorator is already provided. DMP assumes that **any extension of View will be accessible**. 40 | 41 | One case where you might want to decorate your class-based endpoints is when using `keyword arguments `_. Here's an example of what that might look like: 42 | 43 | .. code-block:: python 44 | 45 | from django.views.generic import View 46 | 47 | class process_request(View): 48 | 49 | @view_function(vogon="Poetry", answer=42) 50 | def get(self, request): 51 | return request.dmp.render('index.html', {}) 52 | 53 | 54 | Naming Conventions 55 | ------------------------- 56 | Python programmers usually use TitleCaseClassName (capitalized words) for class names. In the above classes, I'm instead using all lowercase (which is the style for function and ariable names) so my URL doesn't have uppercase characters in it. If you'd rather use TitleCaseClassName, such as ``class DiscoverySection``, be sure your URL matches it, such as ``http://yourserver.com/homepage/index.DiscoverySection/``. 57 | -------------------------------------------------------------------------------- /docs/_sources/topics_class_views.rst.txt: -------------------------------------------------------------------------------- 1 | .. _topics_class_views: 2 | 3 | Class-Based Views 4 | ========================= 5 | 6 | Django-Mako-Plus supports Django's class-based view concept. You can read more about this pattern in the Django documentation. If you are using view classes, DMP automatically detects and adjusts accordingly. If you are using regular function-based views, skip this section for now. 7 | 8 | With DMP, your class-based view will be discovered via request url, so you have to name your class accordingly. In keeping with the rest of DMP, the default class name in a file should be named ``class process_request()``. Consider the following ``index.py`` file: 9 | 10 | .. code-block:: python 11 | 12 | from django.conf import settings 13 | from django.http import HttpResponse 14 | from django.views.generic import View 15 | from datetime import datetime 16 | 17 | class process_request(View): 18 | 19 | def get(self, request): 20 | context = { 21 | 'now': datetime.now().strftime(request.dmp.urlparams[0] if request.dmp.urlparams[0] else '%H:%M'), 22 | } 23 | return request.dmp.render('index.html', context) 24 | 25 | class discovery_section(View): 26 | 27 | def get(self, request): 28 | return HttpResponse('Get was called.') 29 | 30 | def post(self, request): 31 | return HttpResponse('Post was called.') 32 | 33 | In the above ``index.py`` file, two class-based views are defined. The first is called with the url ``/homepage/index/``. The second is called with the url ``/homepage/index.discovery_section/``. 34 | 35 | 36 | Hey! Where's @view_function? 37 | ----------------------------- 38 | 39 | In contrast with normal function-based routing, class-based views do not require the ``@view_function`` decorator, which provides security on which functions are web-accessible. Since class-based views must extend django.views.generic.View, the security provided by the decorator is already provided. DMP assumes that **any extension of View will be accessible**. 40 | 41 | One case where you might want to decorate your class-based endpoints is when using `keyword arguments `_. Here's an example of what that might look like: 42 | 43 | .. code-block:: python 44 | 45 | from django.views.generic import View 46 | 47 | class process_request(View): 48 | 49 | @view_function(vogon="Poetry", answer=42) 50 | def get(self, request): 51 | return request.dmp.render('index.html', {}) 52 | 53 | 54 | Naming Conventions 55 | ------------------------- 56 | Python programmers usually use TitleCaseClassName (capitalized words) for class names. In the above classes, I'm instead using all lowercase (which is the style for function and ariable names) so my URL doesn't have uppercase characters in it. If you'd rather use TitleCaseClassName, such as ``class DiscoverySection``, be sure your URL matches it, such as ``http://yourserver.com/homepage/index.DiscoverySection/``. 57 | -------------------------------------------------------------------------------- /tests_project/homepage/tests/test_router.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | 5 | 6 | class Tester(TestCase): 7 | 8 | def test_view_function_get(self): 9 | # GET method 10 | resp = self.client.get('/homepage/index.basic/1/2/3/') 11 | self.assertEqual(resp.status_code, 200) 12 | req = resp.wsgi_request 13 | self.assertEqual(req.dmp.urlparams[0], '1') 14 | self.assertEqual(req.dmp.urlparams[1], '2') 15 | self.assertEqual(req.dmp.urlparams[2], '3') 16 | 17 | 18 | def test_view_function_post(self): 19 | # POST method 20 | resp = self.client.post('/homepage/index.basic/1/2/3/') 21 | self.assertEqual(resp.status_code, 200) 22 | req = resp.wsgi_request 23 | self.assertEqual(req.dmp.urlparams[0], '1') 24 | self.assertEqual(req.dmp.urlparams[1], '2') 25 | self.assertEqual(req.dmp.urlparams[2], '3') 26 | 27 | 28 | def test_does_not_exist(self): 29 | resp = self.client.get('/homepage/index.does_not_exist/1/2/3/') 30 | self.assertEqual(resp.status_code, 404) 31 | 32 | 33 | def test_bad_response(self): 34 | resp = self.client.get('/homepage/index.bad_response/1/2/3/') 35 | self.assertEqual(resp.status_code, 500) 36 | 37 | 38 | def test_class_based_get(self): 39 | # GET method 40 | resp = self.client.get('/homepage/index.class_based/1/2/3/') 41 | self.assertEqual(resp.status_code, 200) 42 | req = resp.wsgi_request 43 | self.assertEqual(req.dmp.urlparams[0], '1') 44 | self.assertEqual(req.dmp.urlparams[1], '2') 45 | self.assertEqual(req.dmp.urlparams[2], '3') 46 | 47 | 48 | def test_class_based_post(self): 49 | # POST method 50 | resp = self.client.post('/homepage/index.class_based/1/2/3/') 51 | self.assertEqual(resp.status_code, 200) 52 | req = resp.wsgi_request 53 | self.assertEqual(req.dmp.urlparams[0], '1') 54 | self.assertEqual(req.dmp.urlparams[1], '2') 55 | self.assertEqual(req.dmp.urlparams[2], '3') 56 | 57 | 58 | def test_class_based_invalid(self): 59 | # PUT method (not defined in class) 60 | resp = self.client.put('/homepage/index.class_based/1/2/3/') 61 | self.assertEqual(resp.status_code, 405) # method not allowed 62 | 63 | 64 | def test_class_based_decorated(self): 65 | # GET method 66 | resp = self.client.get('/homepage/index.class_based_decorated/1/2/3/') 67 | self.assertEqual(resp.status_code, 200) 68 | req = resp.wsgi_request 69 | self.assertEqual(req.dmp.urlparams[0], '1') 70 | self.assertEqual(req.dmp.urlparams[1], '2') 71 | self.assertEqual(req.dmp.urlparams[2], '3') 72 | 73 | 74 | def test_class_based_argdecorated(self): 75 | # GET method 76 | resp = self.client.get('/homepage/index.class_based_argdecorated/1/2/3/') 77 | self.assertEqual(resp.status_code, 200) 78 | req = resp.wsgi_request 79 | self.assertEqual(req.dmp.urlparams[0], '1') 80 | self.assertEqual(req.dmp.urlparams[1], '2') 81 | self.assertEqual(req.dmp.urlparams[2], '3') 82 | -------------------------------------------------------------------------------- /django_mako_plus/tags.py: -------------------------------------------------------------------------------- 1 | from django.template import engines 2 | from django.template import TemplateDoesNotExist 3 | from mako.runtime import supports_caller 4 | 5 | ### 6 | ### Mako-style tags that DMP provides 7 | ### 8 | 9 | 10 | ############################################################### 11 | ### Include Django templates 12 | ### 13 | 14 | def django_include(context, template_name, **kwargs): 15 | ''' 16 | Mako tag to include a Django template withing the current DMP (Mako) template. 17 | Since this is a Django template, it is search for using the Django search 18 | algorithm (instead of the DMP app-based concept). 19 | See https://docs.djangoproject.com/en/2.1/topics/templates/. 20 | 21 | The current context is sent to the included template, which makes all context 22 | variables available to the Django template. Any additional kwargs are added 23 | to the context. 24 | ''' 25 | try: 26 | djengine = engines['django'] 27 | except KeyError as e: 28 | raise TemplateDoesNotExist("Django template engine not configured in settings, so template cannot be found: {}".format(template_name)) from e 29 | djtemplate = djengine.get_template(template_name) 30 | djcontext = {} 31 | djcontext.update(context) 32 | djcontext.update(kwargs) 33 | return djtemplate.render(djcontext, context['request']) 34 | 35 | 36 | 37 | ######################################################### 38 | ### Template autoescaping on/off 39 | 40 | # attaching to `caller_stack` because it's the same object 41 | # throughout rendering of a template inheritance 42 | AUTOESCAPE_KEY = '__dmp_autoescape' 43 | 44 | def is_autoescape(context): 45 | return bool(getattr(context.caller_stack, AUTOESCAPE_KEY, True)) 46 | 47 | 48 | def _toggle_autoescape(context, escape_on=True): 49 | ''' 50 | Internal method to toggle autoescaping on or off. This function 51 | needs access to the caller, so the calling method must be 52 | decorated with @supports_caller. 53 | ''' 54 | previous = is_autoescape(context) 55 | setattr(context.caller_stack, AUTOESCAPE_KEY, escape_on) 56 | try: 57 | context['caller'].body() 58 | finally: 59 | setattr(context.caller_stack, AUTOESCAPE_KEY, previous) 60 | 61 | 62 | @supports_caller 63 | def autoescape_on(context): 64 | ''' 65 | Mako tag to enable autoescaping for a given block within a template, 66 | (individual filters can still override with ${ somevar | n }). 67 | 68 | Example: 69 | <%namespace name="dmp" module="django_mako_plus.tags"/> 70 | <%dmp:autoescape_on> 71 | ${ somevar } will be autoescaped. 72 | 73 | ''' 74 | _toggle_autoescape(context, True) 75 | return '' 76 | 77 | 78 | @supports_caller 79 | def autoescape_off(context): 80 | ''' 81 | Mako tag to disable autoescaping for a given block within a template, 82 | (individual filters can still override with ${ somevar | h }). 83 | 84 | Example: 85 | <%namespace name="dmp" module="django_mako_plus.tags"/> 86 | <%dmp:autoescape> 87 | ${ somevar } will not be autoescaped. 88 | 89 | ''' 90 | _toggle_autoescape(context, False) 91 | return '' 92 | -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} 2 | -------------------------------------------------------------------------------- /docs-src/converters_errors.rst: -------------------------------------------------------------------------------- 1 | .. _converters_errors: 2 | 3 | Errors During Conversion 4 | =============================== 5 | 6 | When conversion fails, the default behavior of DMP is to raise an ``Http404`` exception, indicating to the browser that the given url does not exist. Most of the time, this makes sense because it is, in effect, an invalid URL. 7 | 8 | Resolution is as follows: 9 | 10 | * If a converter function raises ``ValueError`` on any parameter, an Http 404 response is returned to the browser, indicating that the conversion failed for "normal" reasons. 11 | * If a converter function raises any other type of exception, a ``ConverterException`` is raised, indicating that an unexpected error occurred during conversion. This generally returns an Http 500 server error to the browser. 12 | 13 | 14 | Customizing 15 | --------------------------------- 16 | 17 | Enough of this 404 rudeness! Suppose we want to be more forgiving of converter failures. When converter functions raise ``ValueError``, we'll use the view function defaults and let processing continue normally. 18 | 19 | For example, consider the following view function in ``index.py``: 20 | 21 | .. code-block:: python 22 | 23 | from django_mako_plus import view_function 24 | 25 | @view_function 26 | def process_request(request, age:int=0): 27 | ... 28 | 29 | With this view function: 30 | 31 | * ``/homepage/index/5/`` works perfectly: the function is called with ``age=5``. 32 | * ``/homepage/index/asdf/`` fails the conversion to integer: the Http 404 page is returned. 33 | 34 | Let's extend the ``ParameterConverter`` class to catch the 404 and return the default. Add the following to ``/homepage/apps.py``: 35 | 36 | .. code-block:: python 37 | 38 | from django.apps import AppConfig 39 | from django.http import Http404 40 | from django_mako_plus import ParameterConverter 41 | import inspect 42 | 43 | class HomepageConfig(AppConfig): 44 | name = 'homepage' 45 | 46 | 47 | class ForgivingConverter(ParameterConverter): 48 | '''Uses defaults for values that cannot be converted (instead of the usual 404)''' 49 | 50 | def convert_value(self, value, parameter, request): 51 | try: # try the normal conversion process 52 | return super().convert_value(value, parameter, request) 53 | 54 | except Http404: # converter function raised a ValueError 55 | if parameter.default is not inspect.Parameter.empty: 56 | return parameter.default # return the default specified in process_request() signature 57 | return None # return None if signature has no default 58 | 59 | Activate the custom converter in ``settings.py``: 60 | 61 | .. code-block:: python 62 | 63 | TEMPLATES = [ 64 | { 65 | 'NAME': 'django_mako_plus', 66 | 'BACKEND': 'django_mako_plus.MakoTemplates', 67 | 'OPTIONS': { 68 | 'PARAMETER_CONVERTER': 'homepage.apps.ForgivingConverter', 69 | ... 70 | } 71 | } 72 | ] 73 | 74 | With the above setup, converter failures will no longer trigger a 404. Instead, the default value is used anytime a conversion fails. 75 | -------------------------------------------------------------------------------- /docs-src/topics_signals.rst: -------------------------------------------------------------------------------- 1 | .. _topics_signals: 2 | 3 | Signals in DMP 4 | =================== 5 | 6 | DMP sends several custom signals through the Django signal dispatcher. The purpose is to allow you to hook into the router's processing and respond to events that occur in DMP. 7 | 8 | Before going further with this section's examples, be sure to read the standard Django signal documentation. DMP signals are simply additional signals in the same dispatching system, so the following examples should be easy to understand once you know how Django dispatches signals. 9 | 10 | Available Signals 11 | ------------------------------- 12 | 13 | ``dmp_signal_pre_process_request`` 14 | Triggered just before DMP calls a view's process_request() method. 15 | 16 | ``dmp_signal_post_process_request`` 17 | Triggered just after a view's process_request() method returns. 18 | 19 | ``dmp_signal_pre_render_template`` 20 | Triggered just before DMP renders a Mako template. 21 | 22 | ``dmp_signal_post_render_template`` 23 | Triggered just after DMP renders a Mako template. 24 | 25 | ``dmp_signal_redirect_exception`` 26 | Triggered when a RedirectException is encountered in the DMP controller. 27 | 28 | ``dmp_signal_internal_redirect_exception`` 29 | Triggered when an InternalRedirectException is encountered in the DMP controller. 30 | 31 | ``dmp_signal_register_app`` 32 | Triggered just after the DMP template engine registers an app as a DMP app (once per app at server start). 33 | 34 | 35 | See the `function documentation in signals.py `_ for more information about each signal and its kwargs. 36 | 37 | 38 | Enabling DMP Signals 39 | --------------------------------- 40 | 41 | Be sure your settings.py file has the following in it: 42 | 43 | .. code-block:: python 44 | 45 | TEMPLATES = [ 46 | { 47 | 'NAME': 'django_mako_plus', 48 | 'BACKEND': 'django_mako_plus.MakoTemplates', 49 | 'OPTIONS': { 50 | 'SIGNALS': True, 51 | ... 52 | } 53 | } 54 | ] 55 | 56 | 57 | Signal Receivers 58 | ------------------------------------- 59 | 60 | The following creates two receivers. The first is called just before the view's process\_request function is called. The second is called just before DMP renders .html templates. 61 | 62 | .. code-block:: python 63 | 64 | from django.dispatch import receiver 65 | from django_mako_plus import signals, get_template_loader 66 | 67 | @receiver(signals.dmp_signal_pre_process_request) 68 | def pre_process_request(sender, request, view_args, view_kwargs, **kwargs): 69 | print('>>> process_request signal received!') 70 | 71 | @receiver(signals.dmp_signal_pre_render_template) 72 | def pre_render_template(sender, request, context, template, **kwargs): 73 | print('>>> render_template signal received!') 74 | # let's return a different template to be used - DMP will use this instead of kwargs['template'] 75 | tlookup = get_template_loader('myapp') 76 | template = tlookup.get_template('different.html') 77 | 78 | The above code should be in a code file that is called during Django initialization, such as ``apps.py``. 79 | -------------------------------------------------------------------------------- /docs/_sources/topics_signals.rst.txt: -------------------------------------------------------------------------------- 1 | .. _topics_signals: 2 | 3 | Signals in DMP 4 | =================== 5 | 6 | DMP sends several custom signals through the Django signal dispatcher. The purpose is to allow you to hook into the router's processing and respond to events that occur in DMP. 7 | 8 | Before going further with this section's examples, be sure to read the standard Django signal documentation. DMP signals are simply additional signals in the same dispatching system, so the following examples should be easy to understand once you know how Django dispatches signals. 9 | 10 | Available Signals 11 | ------------------------------- 12 | 13 | ``dmp_signal_pre_process_request`` 14 | Triggered just before DMP calls a view's process_request() method. 15 | 16 | ``dmp_signal_post_process_request`` 17 | Triggered just after a view's process_request() method returns. 18 | 19 | ``dmp_signal_pre_render_template`` 20 | Triggered just before DMP renders a Mako template. 21 | 22 | ``dmp_signal_post_render_template`` 23 | Triggered just after DMP renders a Mako template. 24 | 25 | ``dmp_signal_redirect_exception`` 26 | Triggered when a RedirectException is encountered in the DMP controller. 27 | 28 | ``dmp_signal_internal_redirect_exception`` 29 | Triggered when an InternalRedirectException is encountered in the DMP controller. 30 | 31 | ``dmp_signal_register_app`` 32 | Triggered just after the DMP template engine registers an app as a DMP app (once per app at server start). 33 | 34 | 35 | See the `function documentation in signals.py `_ for more information about each signal and its kwargs. 36 | 37 | 38 | Enabling DMP Signals 39 | --------------------------------- 40 | 41 | Be sure your settings.py file has the following in it: 42 | 43 | .. code-block:: python 44 | 45 | TEMPLATES = [ 46 | { 47 | 'NAME': 'django_mako_plus', 48 | 'BACKEND': 'django_mako_plus.MakoTemplates', 49 | 'OPTIONS': { 50 | 'SIGNALS': True, 51 | ... 52 | } 53 | } 54 | ] 55 | 56 | 57 | Signal Receivers 58 | ------------------------------------- 59 | 60 | The following creates two receivers. The first is called just before the view's process\_request function is called. The second is called just before DMP renders .html templates. 61 | 62 | .. code-block:: python 63 | 64 | from django.dispatch import receiver 65 | from django_mako_plus import signals, get_template_loader 66 | 67 | @receiver(signals.dmp_signal_pre_process_request) 68 | def pre_process_request(sender, request, view_args, view_kwargs, **kwargs): 69 | print('>>> process_request signal received!') 70 | 71 | @receiver(signals.dmp_signal_pre_render_template) 72 | def pre_render_template(sender, request, context, template, **kwargs): 73 | print('>>> render_template signal received!') 74 | # let's return a different template to be used - DMP will use this instead of kwargs['template'] 75 | tlookup = get_template_loader('myapp') 76 | template = tlookup.get_template('different.html') 77 | 78 | The above code should be in a code file that is called during Django initialization, such as ``apps.py``. 79 | -------------------------------------------------------------------------------- /docs/_sources/converters_errors.rst.txt: -------------------------------------------------------------------------------- 1 | .. _converters_errors: 2 | 3 | Errors During Conversion 4 | =============================== 5 | 6 | When conversion fails, the default behavior of DMP is to raise an ``Http404`` exception, indicating to the browser that the given url does not exist. Most of the time, this makes sense because it is, in effect, an invalid URL. 7 | 8 | Resolution is as follows: 9 | 10 | * If a converter function raises ``ValueError`` on any parameter, an Http 404 response is returned to the browser, indicating that the conversion failed for "normal" reasons. 11 | * If a converter function raises any other type of exception, a ``ConverterException`` is raised, indicating that an unexpected error occurred during conversion. This generally returns an Http 500 server error to the browser. 12 | 13 | 14 | Customizing 15 | --------------------------------- 16 | 17 | Enough of this 404 rudeness! Suppose we want to be more forgiving of converter failures. When converter functions raise ``ValueError``, we'll use the view function defaults and let processing continue normally. 18 | 19 | For example, consider the following view function in ``index.py``: 20 | 21 | .. code-block:: python 22 | 23 | from django_mako_plus import view_function 24 | 25 | @view_function 26 | def process_request(request, age:int=0): 27 | ... 28 | 29 | With this view function: 30 | 31 | * ``/homepage/index/5/`` works perfectly: the function is called with ``age=5``. 32 | * ``/homepage/index/asdf/`` fails the conversion to integer: the Http 404 page is returned. 33 | 34 | Let's extend the ``ParameterConverter`` class to catch the 404 and return the default. Add the following to ``/homepage/apps.py``: 35 | 36 | .. code-block:: python 37 | 38 | from django.apps import AppConfig 39 | from django.http import Http404 40 | from django_mako_plus import ParameterConverter 41 | import inspect 42 | 43 | class HomepageConfig(AppConfig): 44 | name = 'homepage' 45 | 46 | 47 | class ForgivingConverter(ParameterConverter): 48 | '''Uses defaults for values that cannot be converted (instead of the usual 404)''' 49 | 50 | def convert_value(self, value, parameter, request): 51 | try: # try the normal conversion process 52 | return super().convert_value(value, parameter, request) 53 | 54 | except Http404: # converter function raised a ValueError 55 | if parameter.default is not inspect.Parameter.empty: 56 | return parameter.default # return the default specified in process_request() signature 57 | return None # return None if signature has no default 58 | 59 | Activate the custom converter in ``settings.py``: 60 | 61 | .. code-block:: python 62 | 63 | TEMPLATES = [ 64 | { 65 | 'NAME': 'django_mako_plus', 66 | 'BACKEND': 'django_mako_plus.MakoTemplates', 67 | 'OPTIONS': { 68 | 'PARAMETER_CONVERTER': 'homepage.apps.ForgivingConverter', 69 | ... 70 | } 71 | } 72 | ] 73 | 74 | With the above setup, converter failures will no longer trigger a 404. Instead, the default value is used anytime a conversion fails. 75 | -------------------------------------------------------------------------------- /docs-src/converters_decorators.rst: -------------------------------------------------------------------------------- 1 | .. _converters_decorators: 2 | 3 | Non-Wrapping Decorators 4 | ================================= 5 | 6 | This page is important only when you use additional decorators on your view functions. 7 | 8 | Automatic conversion is done using ``inspect.signature``, which comes standard with Python. This function reads your ``process_request`` source code signature and gives DMP the parameter hints. As we saw in the `tutorial `_, your code specifies these hints with something like the following: 9 | 10 | .. code-block:: python 11 | 12 | @view_function 13 | def process_request(request, hrs:int, mins:int, forward:bool=True): 14 | ... 15 | 16 | The trigger for DMP to read parameter hints is the ``@view_function`` decorator, which signals a callable endpoint to DMP. When it sees this decorator, DMP goes to the wrapped function, ``process_request``, and inspects the hints. 17 | 18 | Normally, this process works without issues. But it can fail when certain decorators are chained together. Consider the following code: 19 | 20 | .. code-block:: python 21 | 22 | @view_function 23 | @other_decorator # this might mess up the type hints! 24 | def process_request(request, hrs:int, mins:int, forward:bool=True): 25 | ... 26 | 27 | If the developer of ``@other_decorator`` didn't "wrap" it correctly, DMP will **read the signature from the wrong function**: ``def other_decorator(...)`` instead of ``def process_request(...)``! This issue is well known in the Python community -- Google "fix your python decorators" to read many blog posts about it. 28 | 29 | Debugging when this occurs can be fubar and hazardous to your health. Unwrapped decorators are essentially just function calls, and there is no way for DMP to differentiate them from your endpoints (without using hacks like reading your source code). You'll know something is wrong because DMP will ignore your parameters, sent them the wrong values, or throw unexpected exceptions during conversion. If you are using multiple decorators on your endpoints, check the wrapping before you debug too much (next paragraph). 30 | 31 | You can avoid/fix this issue by ensuring each decorator you are using is wrapped correctly, per the Python decorator pattern. When coding ``other_decorator``, be sure to include the ``@wraps(func)`` line. You can read more about this in the `Standard Python Documentation `_. The pattern looks something like the following: 32 | 33 | .. code-block:: python 34 | 35 | from functools import wraps 36 | 37 | def other_decorator(func): 38 | @wraps(func) 39 | def wrapper(request, *args, **kwargs): 40 | # decorator work here goes here 41 | # ... 42 | # call the endpoint 43 | return func(request, *args, **kwargs) 44 | # outer function return 45 | return wrapper 46 | 47 | When your inner function is decorated with ``@wraps``, DMP is able to "unwrap" the decorator chain to the real endpoint function. 48 | 49 | If your decorator comes from third-party code that you can't control, one solution is to create a new decorator (following the pattern above) that calls the third-party function as its "work". Then decorate functions with your own decorator rather than the third-party decorator. 50 | -------------------------------------------------------------------------------- /docs/_sources/converters_decorators.rst.txt: -------------------------------------------------------------------------------- 1 | .. _converters_decorators: 2 | 3 | Non-Wrapping Decorators 4 | ================================= 5 | 6 | This page is important only when you use additional decorators on your view functions. 7 | 8 | Automatic conversion is done using ``inspect.signature``, which comes standard with Python. This function reads your ``process_request`` source code signature and gives DMP the parameter hints. As we saw in the `tutorial `_, your code specifies these hints with something like the following: 9 | 10 | .. code-block:: python 11 | 12 | @view_function 13 | def process_request(request, hrs:int, mins:int, forward:bool=True): 14 | ... 15 | 16 | The trigger for DMP to read parameter hints is the ``@view_function`` decorator, which signals a callable endpoint to DMP. When it sees this decorator, DMP goes to the wrapped function, ``process_request``, and inspects the hints. 17 | 18 | Normally, this process works without issues. But it can fail when certain decorators are chained together. Consider the following code: 19 | 20 | .. code-block:: python 21 | 22 | @view_function 23 | @other_decorator # this might mess up the type hints! 24 | def process_request(request, hrs:int, mins:int, forward:bool=True): 25 | ... 26 | 27 | If the developer of ``@other_decorator`` didn't "wrap" it correctly, DMP will **read the signature from the wrong function**: ``def other_decorator(...)`` instead of ``def process_request(...)``! This issue is well known in the Python community -- Google "fix your python decorators" to read many blog posts about it. 28 | 29 | Debugging when this occurs can be fubar and hazardous to your health. Unwrapped decorators are essentially just function calls, and there is no way for DMP to differentiate them from your endpoints (without using hacks like reading your source code). You'll know something is wrong because DMP will ignore your parameters, sent them the wrong values, or throw unexpected exceptions during conversion. If you are using multiple decorators on your endpoints, check the wrapping before you debug too much (next paragraph). 30 | 31 | You can avoid/fix this issue by ensuring each decorator you are using is wrapped correctly, per the Python decorator pattern. When coding ``other_decorator``, be sure to include the ``@wraps(func)`` line. You can read more about this in the `Standard Python Documentation `_. The pattern looks something like the following: 32 | 33 | .. code-block:: python 34 | 35 | from functools import wraps 36 | 37 | def other_decorator(func): 38 | @wraps(func) 39 | def wrapper(request, *args, **kwargs): 40 | # decorator work here goes here 41 | # ... 42 | # call the endpoint 43 | return func(request, *args, **kwargs) 44 | # outer function return 45 | return wrapper 46 | 47 | When your inner function is decorated with ``@wraps``, DMP is able to "unwrap" the decorator chain to the real endpoint function. 48 | 49 | If your decorator comes from third-party code that you can't control, one solution is to create a new decorator (following the pattern above) that calls the third-party function as its "work". Then decorate functions with your own decorator rather than the third-party decorator. 50 | --------------------------------------------------------------------------------