=============
Installation
=============

* `django-jinja-knockout` 0.4.3 supports Python 3.4 / 3.5 / 3.6, Django 1.8 / 1.9 / 1.10 / 1.11.
* Django template language is supported via including Jinja2 templates from DTL templates. Pure Jinja2 projects are
  supported as well.

.. _app.js: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/static/js/front/app.js
.. _apps.DjkAppConfig: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/apps.py
.. _club_app/templates: https://github.com/Dmitri-Sintsov/djk-sample/tree/master/club_app/templates
.. _content types framework: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
.. _context_processors.py: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/context_processors.py
.. _ContextMiddleware: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/middleware.py
.. _django-allauth: https://github.com/pennersr/django-allauth
.. _djk_sample.ContextMiddleware: https://github.com/Dmitri-Sintsov/djk-sample/blob/master/djk_sample/middleware.py
.. _djk_sample.TemplateContextProcessor: https://github.com/Dmitri-Sintsov/djk-sample/blob/master/djk_sample/context_processors.py
.. _.get_context_middleware(): https://github.com/Dmitri-Sintsov/django-jinja-knockout/search?utf8=%E2%9C%93&q=get_context_middleware
.. _grids: https://django-jinja-knockout.readthedocs.io/en/latest/grids.html
.. _jinja2/base_min.htm: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/jinja2/base_min.htm
.. _jinja2/base_head.htm: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/jinja2/base_head.htm
.. _jinja2/base_bottom_scripts.htm: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/jinja2/base_bottom_scripts.htm
.. _bs_form(): https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/jinja2/bs_form.htm
.. _bs_inline_formsets(): https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/jinja2/bs_inline_formsets.htm
.. _ko_grid.js: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/static/js/front/ko_grid.js
.. _settings.py: https://github.com/Dmitri-Sintsov/djk-sample/blob/master/djk_sample/settings.py
.. _settings.ADMINS: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-ADMINS
.. _templates/base_min.html: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/templates/base_min.html
.. _TemplateContextProcessor: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/context_processors.py
.. _viewmodels: https://django-jinja-knockout.readthedocs.io/en/latest/viewmodels.html
.. _views: https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/views/
.. _urls.py: https://github.com/Dmitri-Sintsov/djk-sample/blob/master/djk_sample/urls.py

Virtual environment
-------------------

.. highlight:: bash

Inside virtualenv of your Django 1.8 / 1.9 / 1.10 project, install `django-jinja-knockout`::

    python3 -m pip install django-jinja-knockout

To install latest master from repository::

    python3 -m pip install --upgrade git+https://github.com/Dmitri-Sintsov/django-jinja-knockout.git

To install specific commit::

    python3 -m pip install --upgrade git+https://github.com/Dmitri-Sintsov/django-jinja-knockout.git@b1a6a90334beef6c5988f71464d2356e8dee3b26


settings.py
-----------

One may use existing example of `settings.py`_ as the base to develop your own ``settings.py``.

DJK_APPS
~~~~~~~~

``DJK_APPS`` list is the subset of ``INSTALLED_APPS`` list that defines project applications which views will be
processed by built-in `ContextMiddleware`_ class ``process_view()`` method via checking the result of
``is_our_module()`` method.

.. highlight:: python

To apply `django-jinja-knockout` `ContextMiddleware`_ to the views of project apps, define ``DJK_APPS`` list with the
list of Django project's own applications like that::

    DJK_APPS = (
        'djk_sample',
        'club_app',
        'event_app',
    )

It increases the compatibility with external apps which views do not require to be processed by `django-jinja-knockout`
`ContextMiddleware`_.

Add ``DJK_APPS`` (if there is any) and ``django_jinja_knockout`` to ``INSTALLED_APPS`` in ``settings.py``::

    # Order of installed apps is important for Django Template loader to find 'djk_sample/templates/base.html'
    # before original allauth 'base.html' is found, when allauth DTL templates are used instead of built-in
    # 'django_jinja_knockout._allauth' Jinja2 templates, thus DJK_APPS are included before 'allauth'.
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # 'sites' is required by allauth
        'django.contrib.sites',
        'django_jinja',
        'django_jinja.contrib._humanize',
        'django_jinja_knockout',
        'django_jinja_knockout._allauth',
    ) + DJK_APPS + \
    (
        'allauth',
        'allauth.account',
        # Required for socialaccount template tag library despite we do not use social login
        'allauth.socialaccount',
    )

`django-allauth`_ support is not mandatory but optional; just remove the following apps from ``INSTALLED_APPS`` in case
you do not need it::

    # The Django sites framework is required for 'allauth'
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'django_jinja_knockout._allauth',

Built-in allauth DTL templates are supported without any modification. In such case next module is not required::

    'django_jinja_knockout._allauth',

* It is possible to extend `django-jinja-knockout` `ContextMiddleware`_ to add new functionality. See
  `djk_sample.ContextMiddleware`_ code for example.

.. _installation_djk_middleware:

DJK_MIDDLEWARE
~~~~~~~~~~~~~~

Since version 0.4.0 there is `apps.DjkAppConfig`_ class which has `.get_context_middleware()`_ method that should
be invoked to get extended middleware class to be used by django-jinja-knockout code and across the project. In case
one's project has a middleware extended from django-jinja-knockout middleware, one should specify it import string
as ``DJK_MIDDLEWARE`` variable value in ``settings.py`` like that::

    DJK_MIDDLEWARE = 'djk_sample.middleware.ContextMiddleware'

FILE_MAX_SIZE
~~~~~~~~~~~~~

This optional setting allows to specify maximal allowed file size to upload with `app.js`_ ``App.AjaxForm()`` class::

    FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.TemporaryFileUploadHandler",)
    FILE_MAX_SIZE = 100 * 1024 * 1024

LAYOUT_CLASSES
~~~~~~~~~~~~~~

This optional setting allows to override default Bootstrap 3 grid layout classes for `bs_form()`_ and
`bs_inline_formsets()`_ Jinja2 macros used to display ``ModelForm`` and inline formsets in the `django-jinja-knockout`
code. The default value is specified in `context_processors.py`_ but can be overriden in `settings.py`_::

    LAYOUT_CLASSES = {'label': 'col-md-3', 'field': 'col-md-7'}

.. _installation_objects_per_page:

OBJECTS_PER_PAGE
~~~~~~~~~~~~~~~~
Allows to specify default limit for Django paginated querysets for ``ListSortingView`` / ``KoGridView`` (see `views`_
submodule)::

    # Pagination settings.
    OBJECTS_PER_PAGE = 3 if DEBUG else 10

USE_JS_TIMEZONE
~~~~~~~~~~~~~~~
Optional boolean value (by default is ``False``). When ``True``, `ContextMiddleware`_ class ``process_request()`` method
will autodetect Django timezone from current browser session timezone.

Javascript errors logger
~~~~~~~~~~~~~~~~~~~~~~~~
Since version 0.7.0 it's possible to setup Javascript logger which would either display Javascript errors in Bootstrap
dialog, or will report these via email to site admins whose emails are specified by `settings.ADMINS`_::

    ADMINS = [('John Smith', 'user@host.com'),]
    if DEBUG:
        # Javascript error will display Bootstrap dialog.
        JS_ERRORS_ALERT = True
    else:
        # Javascript error will be reported via ADMINS emails.
        JS_ERRORS_LOGGING = True

Context processors
~~~~~~~~~~~~~~~~~~

Add `django_jinja_knockout` `TemplateContextProcessor`_ to `settings.py`_::

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    TEMPLATES = [
        {
            "BACKEND": "django_jinja.backend.Jinja2",
            "APP_DIRS": True,
            "OPTIONS": {
                "match_extension": ".htm",
                "app_dirname": "jinja2",
                'context_processors': [
                    'django.template.context_processors.i18n',
                    'django_jinja_knockout.context_processors.template_context_processor'
                ]
            },
        },
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    # Next line is required only if project uses Django templates (DTL).
                    'django_jinja_knockout.context_processors.template_context_processor'
                ],
            },
        },
    ]

If you want to use built-in server-side to client-side global route mapping, create your own project
``context_processors.py`` (see `Extending context processor`_).

.. _installation_context-processor:

Context processor
-----------------

Since version 0.2.0, it is possible to specify client-side routes per view::

    def feed_view(request):
        request.client_routes.extend([
            'blog_feed'
        ])

and per class-based view::

    class MyGrid(KoGridView):

        client_routes = [
            'my_grid_url_name'
        ]

for ``urls.py`` like this::

    from my_blog.views import feed_view
    # ...
    url(r'^blog-(?P<blog_id>\d+)/$', feed_view, name='blog_feed',
        kwargs={'ajax': True, 'permission_required': 'my_blog.add_feed'}),
    url(r'^my-grid(?P<action>/?\w*)/$', MyGrid.as_view(), name='my_grid_url_name',
        kwargs={'view_title': 'My Sample Grid'}),

to make the resolved url available in client-side scripts.

In such case extending built-in `TemplateContextProcessor`_ is not necessary, but one has to specity required
client-side url names in every view which includes Javascript template that accesses these url names (for example
foreign key widgets of `grids`_ require resolved url names of their view classes).

Extending context processor
~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you want to use `global` client-side url name mapping available in `app.js`_, which dispatches AJAX requests
according to Django ``urls.py`` urls, create ``context_processors.py`` in your main project application with the
following code::

    from django_jinja_knockout.context_processors import TemplateContextProcessor as BaseContextProcessor


    class TemplateContextProcessor(BaseContextProcessor):

        CLIENT_ROUTES = (
            # This route is injected into every page globally (not per view).
            # This is a good idea if some client-side route is frequently used.
            # Alternatively one can specify client route url names per view.
            # Second element of each tuple defines whether client-side route should be available to anonymous users.
            ('blog_feed', True),
        )


    def template_context_processor(HttpRequest=None):
        return TemplateContextProcessor(HttpRequest).get_context_data()

while ``urls.py`` should have url name defined as::

    from my_blog.views import feed_view
    # ...
    url(r'^blog-(?P<blog_id>\d+)/$', feed_view, name='blog_feed',
        kwargs={'ajax': True, 'permission_required': 'my_blog.add_feed'}),

and register your context processor in ``settings.py`` as the value of ``TEMPLATES`` list item of nested dictionary keys
``['OPTIONS']`` ``['context_processors']``::

    'my_project.context_processors.template_context_processor'

instead of default one::

    'django_jinja_knockout.context_processors.template_context_processor'

.. highlight:: javascript

Then current url generated for ``'blog_feed'`` url name will be available at client-side Javascript as::

    App.routeUrl('blog_feed', {'blog_id': 1});

You will be able to call Django view via AJAX request in your Javascript code like this::

    App.post('blog_feed', {'postvar1': 1, 'postvar2': 2}, {
        kwargs: {'blog_id': 1}
    });
    App.get('blog_feed', {'getvar1': 1}, {
        kwargs: {'blog_id': 1}
    });

where AJAX response will be treated as the list of `viewmodels`_ and will be automatically routed by `app.js`_ to
appropriate viewmodel handler. Django exceptions and AJAX errors are handled gracefully, displayed in
``BootstrapDialog`` window by default.

.. highlight:: python

Extending context processor is also useful when templates should receive additional arguments by default::

    from django_jinja_knockout.context_processors import TemplateContextProcessor as BaseContextProcessor
    from my_project.tpl import format_currency, static_hash

    class TemplateContextProcessor(BaseContextProcessor):

        def get_context_data(self):
            context_data = super().get_context_data()
            # Add two custom function to template context.
            context_data.update({
                'format_currency': format_currency,
                'static_hash': static_hash,
            })
            return context_data

* See `djk_sample.TemplateContextProcessor`_ source code for the example of extending `django-jinja-knockout`
  `TemplateContextProcessor`_ to define Django ``url_name`` ``equipment_grid`` as a project-wide client-side route, to
  make it accessible in client-side Javascript from any project's view.

Middleware
----------

Key functionality of ``django-jinja-knockout`` middleware is:

.. highlight:: jinja

* AJAX file upload via iframe emulation support for jQuery.ajaxForm() plugin in IE9 (minimally supported version of IE).
* Setting current Django timezone via browser current timezone.
* Getting current request in non-view functions and methods where Django provides no instance of request available.
* Checking ``DJK_APPS`` applications views for the permissions defined as values of kwargs argument keys in `urls.py`_
  ``url()`` calls:

 * ``'ajax' key`` - ``True`` when view is required to be processed in AJAX request, ``False`` - required to be non-AJAX;

   Missing key means that the view is allowed to be processed both as AJAX and non-AJAX. Such feature is used by
   `grids`_ ``KoGridView`` to share traditional and AJAX requests by single CBV class. HTTP GET is processed as non-AJAX
   Jinja2 template view, while HTTP POST is routed to AJAX methods of the same view.
 * ``'allow_anonymous' key`` - ``True`` when view is allowed to anonymous user (``False`` by default).
 * ``'allow_inactive' key`` - ``True`` when view is allowed to inactive user (``False`` by default).
 * ``'permission_required' key`` - value is the name of Django app / model permission string required for this view to
   be called.
 * ``'view_title' key`` - string value of view verbose name, that is displayed by default in `jinja2/base_head.htm`_ as::

    {% if request.view_title %}
        <title>{{ request.view_title }}</title>
    {% endif %}

All of the keys are optional but some have restricted default values.

.. highlight:: python

Install ``django_jinja_knockout.middleware`` into `settings.py`_::

    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'django.middleware.security.SecurityMiddleware',
        'django_jinja_knockout.middleware.ContextMiddleware',
    )

Then use it in a project::

    from django_jinja_knockout.middleware import ContextMiddleware

For example to get current request in non-view functions and methods, one may use::

    ContextMiddleware.get_request()

and to get current request user::

    ContextMiddleware.get_request().user

* Do not forget that request is mocked when running in console, for example in management jobs. It is possible to
  override the middleware class for custom mocking.

Extending middleware
~~~~~~~~~~~~~~~~~~~~

It's possible to extend built-in `ContextMiddleware`_. In such case ``MIDDLEWARE_CLASSES`` in `settings.py`_ should
contain full name of the extended class. See `djk_sample.ContextMiddleware`_ for the example of extending middleware to
enable logging of Django models performed actions via `content types framework`_.

urls.py
-------

The example of `urls.py`_ for Jinja2 ``_allauth`` templates::

    # More pretty-looking but possibly not compatible with arbitrary allauth version:
    url(r'^accounts/', include('django_jinja_knockout._allauth.urls')),

The example of `urls.py`_ for DTL ``allauth`` templates::

    # Standard allauth DTL templates working together with Jinja2 templates via {% load jinja %}
    url(r'^accounts/', include('allauth.urls')),

Note that ``accounts`` urls are not processed by ``DJK_MIDDLEWARE`` thus do not require ``is_anonymous`` or
``permission_required`` kwargs keys to be defined.

The example of ``DJK_MIDDLEWARE`` view `urls.py`_ with AJAX requirement, view title and permission checking (anonymous /
inactive users are not allowed by default)::

    url(r'^equipment-grid(?P<action>/?\w*)/$', EquipmentGrid.as_view(), name='equipment_grid', kwargs={
        'view_title': 'Grid with the available equipment',
        'ajax': True,
        'permission_required': 'club_app.change_manufacturer'
    }),

Templates
---------

.. highlight:: jinja

Integration of django-jinja-knockout into existing Django / Bootstrap 3 project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your project base template uses ``Jinja2`` templating language, there are the following possibilities:

* Extend your ``base.htm`` template from `jinja2/base_min.htm`_ template.
* Include styles from `jinja2/base_head.htm`_ and scripts from `jinja2/base_bottom_scripts.htm`_. These are required to
  run client-side scripts like `app.js`_ and `ko_grid.js`_.

If your project base template uses Djanto Template Language (``DTL``), there are the following possibilities:

* Extend your ``base.html`` template from `templates/base_min.html`_ template.
* Include styles from `jinja2/base_head.htm`_ and scripts from `jinja2/base_bottom_scripts.htm`_ via
  ``{% load jinja %}`` template tag library to your ``DTL`` template::

    {% load jinja %}
    {% jinja 'base_head.htm' %}
    {% if messages %}
        {% jinja 'base_messages.htm' %}
    {% endif %}
    {% jinja 'base_bottom_scripts.htm' %}

Do not forget that Jinja2 does not support extending included templates.

Template engines can be mixed with inclusion of Jinja2 templates from DTL templates like this::

    {% jinja 'bs_navs.htm' with _render_=1 navs=main_navs %}
    {% jinja 'bs_inline_formsets.htm' with _render_=1 related_form=form formsets=formsets action=view.get_form_action_url html=view.get_bs_form_opts %}
    {% jinja 'bs_list.htm' with _render_=1 view=view object_list=object_list is_paginated=is_paginated page_obj=page_obj %}
    {% jinja 'ko_grid.htm' with _render_=1 grid_options=club_grid_options %}
    {% jinja 'ko_grid_body.htm' with _render_=1 %}

See `club_app/templates`_ for full-size examples of including Jinja2 templates from DTL templates.
