~netlandish/django-wiki

d13700be146cdb9cc207a7adb6cb48d486a01801 — Benjamin Bach 6 years ago da2eb17 + 4d9ba1c
Merge pull request #825 from rsalmaso/renderer

Use template based rendering for custom form widgets
M src/wiki/compat.py => src/wiki/compat.py +0 -17
@@ 9,28 9,11 @@ except ImportError:


__all__ = [
    'BuildAttrsCompat',
    'get_default_engine',
    'include', 'url'
]


# Django 1.11 Widget.build_attrs has a different signature, designed for the new
# template based rendering. The previous version was more useful for our needs,
# so we restore that version.
# When support for Django < 1.11 is dropped, we should look at using the
# new template based rendering, at which point this probably won't be needed at all.
class BuildAttrsCompat:
    def build_attrs_compat(self, extra_attrs=None, **kwargs):
        "Helper function for building an attribute dictionary."
        attrs = self.attrs.copy()
        if extra_attrs is not None:
            attrs.update(extra_attrs)
        if kwargs is not None:
            attrs.update(kwargs)
        return attrs


def get_default_engine():
    """
    Django >= 2.1 Engine.get_default() behaviour

M src/wiki/editors/markitup.py => src/wiki/editors/markitup.py +10 -38
@@ 1,54 1,26 @@
from django import forms
from django.forms.utils import flatatt
from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from wiki.compat import BuildAttrsCompat
from wiki.editors.base import BaseEditor


class MarkItUpAdminWidget(BuildAttrsCompat, forms.Widget):

    """A simplified more fail-safe widget for the backend"""
class MarkItUpWidget(forms.Widget):
    template_name = "wiki/forms/markitup.html"

    def __init__(self, attrs=None):
        # The 'rows' and 'cols' attributes are required for HTML correctness.
        default_attrs = {'class': 'markItUp',
                         'rows': '10', 'cols': '40', }
        default_attrs = {
            'class': 'markItUp',
            'rows': '10',
            'cols': '40',
        }
        if attrs:
            default_attrs.update(attrs)
        super().__init__(default_attrs)

    def render(self, name, value, attrs=None, renderer=None):
        if value is None:
            value = ''
        final_attrs = self.build_attrs_compat(attrs, name=name)
        return mark_safe(
            '<textarea%s>%s</textarea>' %
            (flatatt(final_attrs),
             conditional_escape(
                force_text(value))))

class MarkItUpAdminWidget(MarkItUpWidget):
    """A simplified more fail-safe widget for the backend"""

class MarkItUpWidget(BuildAttrsCompat, forms.Widget):

    def __init__(self, attrs=None):
        # The 'rows' and 'cols' attributes are required for HTML correctness.
        default_attrs = {'class': 'markItUp',
                         'rows': '10', 'cols': '40', }
        if attrs:
            default_attrs.update(attrs)
        super().__init__(default_attrs)

    def render(self, name, value, attrs=None, renderer=None):
        if value is None:
            value = ''
        final_attrs = self.build_attrs_compat(attrs, name=name)
        return mark_safe(
            '<div><textarea%s>%s</textarea></div>' %
            (flatatt(final_attrs),
             conditional_escape(
                force_text(value))))
    template_name = "wiki/forms/markitup-admin.html"


class MarkItUp(BaseEditor):

M src/wiki/forms.py => src/wiki/forms.py +15 -56
@@ 1,7 1,6 @@
import random
import string
from datetime import timedelta
from itertools import chain

from django import forms
from django.apps import apps


@@ 9,17 8,13 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.core import validators
from django.core.validators import RegexValidator
from django.forms.utils import flatatt
from django.forms.widgets import HiddenInput
from django.shortcuts import get_object_or_404
from django.urls import Resolver404, resolve
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.html import conditional_escape, escape
from django.utils.safestring import mark_safe
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
from wiki import models
from wiki.compat import BuildAttrsCompat
from wiki.conf import settings
from wiki.core import permissions
from wiki.core.diff import simple_merge


@@ 285,12 280,15 @@ class EditForm(forms.Form, SpamProtectionMixin):
        return cd


class SelectWidgetBootstrap(BuildAttrsCompat, forms.Select):
class SelectWidgetBootstrap(forms.Select):
    """
    http://twitter.github.com/bootstrap/components.html#buttonDropdowns
    Needs bootstrap and jquery
    """

    template_name = "wiki/forms/select.html"
    option_template_name = "wiki/forms/select_option.html"

    def __init__(self, attrs={}, choices=(), disabled=False):
        attrs['class'] = 'btn-group pull-left btn-group-form'
        self.disabled = disabled


@@ 302,51 300,12 @@ class SelectWidgetBootstrap(BuildAttrsCompat, forms.Select):
        if k not in ('attrs', 'disabled'):
            self.noscript_widget.__setattr__(k, value)

    def render(self, name, value, attrs=None, choices=()):
        if value is None:
            value = ''
        final_attrs = self.build_attrs_compat(attrs, name=name)
        output = [
            """<div%(attrs)s>"""
            """    <button class="btn btn-group-label%(disabled)s" type="button">%(label)s</button>"""
            """    <button class="btn btn-default dropdown-toggle%(disabled)s" type="button" data-toggle="dropdown">"""
            """        <span class="caret"></span>"""
            """    </button>"""
            """    <ul class="dropdown-menu">"""
            """        %(options)s"""
            """    </ul>"""
            """    <input type="hidden" name="%(name)s" value="" class="btn-group-value" />"""
            """</div>"""
            """<noscript>%(noscript)s</noscript>""" %
            {'attrs': flatatt(final_attrs),
             'options': self.render_options(choices, [value]),
             'label': _('Select an option'),
             'name': name, 'disabled': ' disabled' if self.disabled else '',
             'noscript': self.noscript_widget.render(name, value, {})}]
        return mark_safe('\n'.join(output))

    def render_option(self, selected_choices, option_value, option_label):
        option_value = force_text(option_value)
        selected_html = (
            option_value in selected_choices) and ' selected="selected"' or ''
        return '<li><a href="javascript:void(0)" data-value="%s"%s>%s</a></li>' % (
            escape(option_value), selected_html,
            conditional_escape(force_text(option_label)))

    def render_options(self, choices, selected_choices):
        # Normalize to strings.
        selected_choices = set([force_text(v) for v in selected_choices])
        output = []
        for option_value, option_label in chain(self.choices, choices):
            if isinstance(option_label, (list, tuple)):
                output.append(
                    '<li class="divider" label="%s"></li>' %
                    escape(force_text(option_value)))
                for option in option_label:
                    output.append(self.render_option(selected_choices, *option))
            else:
                output.append(self.render_option(selected_choices, option_value, option_label))
        return '\n'.join(output)
    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context['label'] = _('Select an option')
        context['noscript'] = self.noscript_widget.render(name, value, {})
        context['disabled'] = ' disabled' if self.disabled else ''
        return context

    class Media(forms.Media):



@@ 354,16 313,16 @@ class SelectWidgetBootstrap(BuildAttrsCompat, forms.Select):


class TextInputPrepend(forms.TextInput):
    template_name = "wiki/forms/text.html"

    def __init__(self, *args, **kwargs):
        self.prepend = kwargs.pop('prepend', "")
        super().__init__(*args, **kwargs)

    def render(self, *args, **kwargs):
        html = super().render(*args, **kwargs)
        return mark_safe(
            '<div class="input-group"><span class="input-group-addon">%s</span>%s</div>' %
            (self.prepend, html))
    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context['prepend'] = mark_safe(self.prepend)
        return context


class CreateForm(forms.Form, SpamProtectionMixin):

A src/wiki/templates/wiki/forms/markitup-admin.html => src/wiki/templates/wiki/forms/markitup-admin.html +1 -0
@@ 0,0 1,1 @@
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

A src/wiki/templates/wiki/forms/markitup.html => src/wiki/templates/wiki/forms/markitup.html +1 -0
@@ 0,0 1,1 @@
<div><textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea></div>

A src/wiki/templates/wiki/forms/select.html => src/wiki/templates/wiki/forms/select.html +15 -0
@@ 0,0 1,15 @@
<div{% include "django/forms/widgets/attrs.html" %}>
  <button class="btn btn-group-label{{ disabled }}" type="button">{{ label }}</button>
  <button class="btn btn-default dropdown-toggle{{ disabled }}" type="button" data-toggle="dropdown">
        <span class="caret"></span>
    </button>
    <ul class="dropdown-menu">{% for group_name, group_choices, group_index in widget.optgroups %}
      {% if group_name %}<li class="divider" label="{{ group_name }}"></li>{% endif %}
      {% for option in group_choices %}
      {% include option.template_name with widget=option %}
      {% endfor %}
      {% endfor %}
    </ul>
    <input type="hidden" name="{{ widget.name }}" value="" class="btn-group-value" />
</div>
<noscript>{{ noscript }}</noscript>

A src/wiki/templates/wiki/forms/select_option.html => src/wiki/templates/wiki/forms/select_option.html +1 -0
@@ 0,0 1,1 @@
<li><a href="javascript:void(0)" data-value="{{ widget.value|stringformat:'s' }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</a></li>

A src/wiki/templates/wiki/forms/text.html => src/wiki/templates/wiki/forms/text.html +1 -0
@@ 0,0 1,1 @@
<div class="input-group"><span class="input-group-addon">{{ prepend }}</span>{% include "django/forms/widgets/input.html" %}</div>