.. include:: globals.txt

.. _middleware:

ConcurrencyMiddleware
=====================

You can globally intercept :class:`RecordModifiedError <concurrency.exceptions.RecordModifiedError>`
adding :class:`ConcurrencyMiddleware <concurrency.middleware.ConcurrencyMiddleware>` to your :setting:`MIDDLEWARE_CLASSES`.
Each time a :class:`RecordModifiedError <concurrency.exceptions.RecordModifiedError>` is raised it goes up to the ConcurrencyMiddleware and the handler defined in
:setting:`CONCURRENCY_HANDLER409` is invoked.

**Example**

``settings.py``

.. code-block:: python

        MIDDLEWARE_CLASSES=('django.middleware.common.CommonMiddleware',
                            'concurrency.middleware.ConcurrencyMiddleware',
                            'django.contrib.sessions.middleware.SessionMiddleware',
                            'django.middleware.csrf.CsrfViewMiddleware',
                            'django.contrib.auth.middleware.AuthenticationMiddleware',
                            'django.contrib.messages.middleware.MessageMiddleware')

        CONCURRENCY_HANDLER409 = 'demoproject.demoapp.views.conflict'
        CONCURRENCY_POLICY = 2  # CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL

:file:`views.py`

.. code-block:: python

    from diff_match_patch import diff_match_patch
    from concurrency.views import ConflictResponse
    from django.template import loader
    from django.utils.safestring import mark_safe
    from django.template.context import RequestContext

    def get_diff(current, stored):
        data = []
        dmp = diff_match_patch()
        fields = current._meta.fields
        for field in fields:
            v1 = getattr(current, field.name, "")
            v2 = getattr(stored, field.name, "")
            diff = dmp.diff_main(unicode(v1), unicode(v2))
            dmp.diff_cleanupSemantic(diff)
            html = dmp.diff_prettyHtml(diff)
            html = mark_safe(html)
            data.append((field, v1, v2, html))
        return data

    def conflict(request, target=None, template_name='409.html'):
        template = loader.get_template(template_name)
        try:
            saved = target.__class__._default_manager.get(pk=target.pk)
            diff = get_diff(target, saved)
        except target.__class__.DoesNotExists:
            saved = None
            diff = None

        ctx = RequestContext(request, {'target': target,
                                       'diff': diff,
                                       'saved': saved,
                                       'request_path': request.path})
        return ConflictResponse(template.render(ctx))



:file:`409.html`

.. code-block:: html

    {% load concurrency %}
    <table>
        <tr>
            <th>
                Field
            </th>
            <th>
                Current
            </th>
            <th>
                Stored
            </th>
            <th>
                Diff
            </th>

        </tr>
        <tr>
            {% for field, current, stored, entry in diff %}
                {% if not field.primary_key and not field|is_version %}
                    <tr>
                        <td>
                            {{ field.verbose_name }}
                        </td>
                        <td>
                            {{ current }}
                        </td>
                        <td>
                            {{ stored }}
                        </td>
                        <td>
                            {{ entry }}
                        </td>
                    </tr>
                {% endif %}
            {% endfor %}
        </tr>
    </table>

If you want to use ConcurrentMiddleware in the admin and you are using
:class:`concurrency.admin.ConcurrentModelAdmin` remember to set your ModelAdmin to NOT
use :class:`concurrency.forms.ConcurrentForm`

.. code-block:: python

    from django import forms

    class MyModelAdmin(ConcurrentModelAdmin):
        form = forms.ModelForm  # overrides default ConcurrentForm