Browse Source

Merge pull request #1129 from rafalp/add-misago-admin-form-tags

Add misago_admin_form template tags
Rafał Pitoń 6 years ago
parent
commit
9a0c6dc560
40 changed files with 359 additions and 301 deletions
  1. 1 1
      .isort.cfg
  2. 0 7
      devproject/settings.py
  3. 73 0
      misago/admin/templatetags/misago_admin_form.py
  4. 0 78
      misago/admin/templatetags/misago_admin_forms.py
  5. 198 0
      misago/admin/tests/test_admin_form_templatetags.py
  6. 0 88
      misago/admin/tests/test_templatetags.py
  7. 0 48
      misago/templates/bootstrap3/field.html
  8. 0 14
      misago/templates/bootstrap3/layout/checkboxselectmultiple.html
  9. 0 7
      misago/templates/bootstrap3/layout/field_errors_block.html
  10. 0 16
      misago/templates/bootstrap3/layout/radioselect.html
  11. 1 1
      misago/templates/misago/admin/agreements/form.html
  12. 1 1
      misago/templates/misago/admin/agreements/list.html
  13. 1 1
      misago/templates/misago/admin/attachments/list.html
  14. 1 1
      misago/templates/misago/admin/attachmenttypes/form.html
  15. 1 1
      misago/templates/misago/admin/bans/form.html
  16. 1 1
      misago/templates/misago/admin/bans/list.html
  17. 4 4
      misago/templates/misago/admin/categories/delete.html
  18. 1 1
      misago/templates/misago/admin/categories/form.html
  19. 2 2
      misago/templates/misago/admin/categoryroles/categoryroles.html
  20. 1 1
      misago/templates/misago/admin/categoryroles/form.html
  21. 2 2
      misago/templates/misago/admin/categoryroles/rolecategories.html
  22. 1 1
      misago/templates/misago/admin/conf/group.html
  23. 1 1
      misago/templates/misago/admin/datadownloads/form.html
  24. 1 1
      misago/templates/misago/admin/datadownloads/list.html
  25. 12 0
      misago/templates/misago/admin/form/input.html
  26. 7 0
      misago/templates/misago/admin/form/multiple_choice.html
  27. 7 0
      misago/templates/misago/admin/form/radio_select.html
  28. 17 0
      misago/templates/misago/admin/form/row.html
  29. 8 0
      misago/templates/misago/admin/form/select.html
  30. 7 11
      misago/templates/misago/admin/permissions_table.html
  31. 1 1
      misago/templates/misago/admin/ranks/form.html
  32. 1 1
      misago/templates/misago/admin/roles/form.html
  33. 1 1
      misago/templates/misago/admin/users/ban.html
  34. 1 1
      misago/templates/misago/admin/users/delete.html
  35. 1 1
      misago/templates/misago/admin/users/edit.html
  36. 1 1
      misago/templates/misago/admin/users/list.html
  37. 3 3
      misago/templates/misago/admin/users/new.html
  38. 1 1
      misago/templates/misago/admin/warnings/form.html
  39. 0 1
      requirements.in
  40. 0 1
      requirements.txt

+ 1 - 1
.isort.cfg

@@ -9,6 +9,6 @@ knowm_requests=requests
 known_restframework=rest_framework
 known_unidecode=unidecode
 known_first_party=misago
-sections=FUTURE,STDLIB,THIRDPARTY,CRISPYFORMS,FAKER,MPTT,REQUESTS,RESTFRAMEWORK,UNIDECODE,DJANGO,FIRSTPARTY,LOCALFOLDER
+sections=FUTURE,STDLIB,THIRDPARTY,FAKER,MPTT,REQUESTS,RESTFRAMEWORK,UNIDECODE,DJANGO,FIRSTPARTY,LOCALFOLDER
 multi_line_output=4
 verbose=true

+ 0 - 7
devproject/settings.py

@@ -181,7 +181,6 @@ INSTALLED_APPS = [
 
     # 3rd party apps used by Misago
     'debug_toolbar',
-    'crispy_forms',
     'mptt',
     'rest_framework',
     'social_django',
@@ -309,12 +308,6 @@ TEMPLATES = [
 WSGI_APPLICATION = 'devproject.wsgi.application'
 
 
-# Django Crispy Forms
-#http://django-crispy-forms.readthedocs.io/en/latest/install.html
-
-CRISPY_TEMPLATE_PACK = 'bootstrap3'
-
-
 # Django Debug Toolbar
 # http://django-debug-toolbar.readthedocs.io/en/stable/configuration.html
 

+ 73 - 0
misago/admin/templatetags/misago_admin_form.py

@@ -0,0 +1,73 @@
+from django import forms, template
+from django.utils.html import format_html_join
+
+register = template.Library()
+
+
+@register.inclusion_tag("misago/admin/form/row.html")
+def form_row(field, label_class=None, field_class=None):
+    return {
+        "field": field,
+        "label_class": label_class,
+        "field_class": field_class,
+    }
+
+
+@register.inclusion_tag("misago/admin/form/input.html")
+def form_input(field):
+    attrs = field.field.widget.attrs
+    context = field.field.widget.get_context(field.html_name, field.value(), attrs)
+    context["attrs"] = attrs
+    context["field"] = field
+    return context
+
+
+@register.simple_tag
+def render_attrs(widget, class_name=None):
+    rendered_attrs = []
+    for attr, value in widget['attrs'].items():
+        if value not in (True, False, None):
+            rendered_attrs.append((attr, value))
+    if not widget["attrs"].get("class") and class_name:
+        rendered_attrs.append(("class", class_name))
+    return format_html_join(" ", '{}="{}"', rendered_attrs)
+
+
+@register.simple_tag
+def render_bool_attrs(widget):
+    attrs_html = []
+    for attr, value in widget.items():
+        if value is True:
+            attrs_html.append(attr)
+    return " ".join(attrs_html)
+
+
+@register.filter
+def is_radio_select_field(field):
+    return isinstance(field.field.widget, forms.RadioSelect)
+
+
+@register.filter
+def is_select_field(field):
+    return isinstance(field.field.widget, forms.Select)
+
+
+@register.filter
+def is_multiple_choice_field(field):
+    multichoice_widgets = (forms.CheckboxSelectMultiple, forms.SelectMultiple)
+    return isinstance(field.field.widget, multichoice_widgets)
+
+
+@register.filter
+def is_textarea_field(field):
+    return isinstance(field.field.widget, forms.Textarea)
+
+
+@register.filter
+def get_options(field):
+    """Filter that extracts field choices into an easily iterable list"""
+    widget = field.field.widget
+    attrs = dict(id=field.auto_id, **widget.attrs)
+    context = widget.get_context(field.html_name, field.value(), attrs)
+    widget_context = context["widget"]
+    return widget.options(widget_context["name"], widget_context["value"], attrs)

+ 0 - 78
misago/admin/templatetags/misago_admin_forms.py

@@ -1,78 +0,0 @@
-from crispy_forms.templatetags import crispy_forms_field, crispy_forms_filters
-
-from django import template
-from django.template.loader import render_to_string
-
-
-register = template.Library()
-
-
-@register.tag
-def form_row(parser, token):
-    """
-    Form row: renders single row in form
-
-    Syntax:
-    {% form_row form.field %} # renders vertical field
-    {% form_row form.field "col-md-3" "col-md-9" %} # renders horizontal field
-    """
-    args = token.split_contents()
-
-    if len(args) < 2:
-        raise template.TemplateSyntaxError("form_row tag requires at least one argument")
-
-    if len(args) == 3 or len(args) > 4:
-        raise template.TemplateSyntaxError(
-            "form_row tag supports either one argument (form field) or "
-            "four arguments (form field, label class, field class)"
-        )
-
-    form_field = args[1]
-
-    if len(args) == 4:
-        label_class = args[2]
-        field_class = args[3]
-    else:
-        label_class = None
-        field_class = None
-
-    return FormRowNode(form_field, label_class, field_class)
-
-
-class FormRowNode(template.Node):
-    def __init__(self, form_field, label_class, field_class):
-        self.form_field = template.Variable(form_field)
-
-        if label_class and field_class:
-            self.label_class = template.Variable(label_class)
-            self.field_class = template.Variable(field_class)
-        else:
-            self.label_class = None
-            self.field_class = None
-
-    def render(self, context):
-        field = self.form_field.resolve(context)
-
-        if self.label_class and self.field_class:
-            label_class = self.label_class.resolve(context)
-            field_class = self.field_class.resolve(context)
-        else:
-            label_class = None
-            field_class = None
-
-        template_pack = crispy_forms_filters.TEMPLATE_PACK
-        return render_to_string(
-            '%s/field.html' % template_pack, {
-                'field': field,
-                'form_show_errors': True,
-                'form_show_labels': True,
-                'label_class': label_class or '',
-                'field_class': field_class or '',
-            }
-        )
-
-
-@register.tag
-def form_input(parser, token):
-    """form input: renders given field input"""
-    return crispy_forms_field.crispy_field(parser, token)

+ 198 - 0
misago/admin/tests/test_admin_form_templatetags.py

@@ -0,0 +1,198 @@
+from django import forms
+from django.template import Context, Template, TemplateSyntaxError
+from django.test import TestCase
+
+from misago.admin.templatetags.misago_admin_form import (
+    is_radio_select_field, is_select_field, is_multiple_choice_field, is_textarea_field,
+    render_attrs, render_bool_attrs
+)
+from misago.admin.forms import YesNoSwitch
+
+
+class TestForm(forms.Form):
+    text_field = forms.CharField(label="Hello!", max_length=255, help_text="I am a help text.")
+    textarea_field = forms.CharField(label="Message", max_length=255, widget=forms.Textarea())
+    select_field = forms.ChoiceField(label="Choice", choices=(("y", "Yes"), ("n", "No")))
+    checkbox_select_field = forms.MultipleChoiceField(
+        label="Color",
+        choices=(("r", "Red"), ("g", "Green"), ("b", "Blue")),
+        widget=forms.CheckboxSelectMultiple,
+    )
+    multiple_select_field = forms.MultipleChoiceField(
+        label="Rank",
+        choices=(("r", "Red"), ("g", "Green"), ("b", "Blue")),
+    )
+    yesno_field = YesNoSwitch(label="Switch")
+
+
+def render(template_str):
+    base_template = "{%% load misago_admin_form %%} %s"
+    context = Context({'form': TestForm()})
+    template = Template(base_template % template_str)
+    return template.render(context).strip()
+
+
+class FormRowTagTests(TestCase):
+    def test_row_with_field_input_is_rendered(self):
+        html = render("{% form_row form.text_field %}")
+        self.assertIn('id_text_field', html)
+
+    def test_row_with_field_input_and_label_css_class_is_rendered(self):
+        html = render('{% form_row form.text_field label_class="col-md-3" %}')
+        self.assertIn('id_text_field', html)
+        self.assertIn('col-md-3', html)
+
+    def test_row_with_field_input_and_field_css_class_is_rendered(self):
+        html = render('{% form_row form.text_field field_class="col-md-9" %}')
+        self.assertIn('id_text_field', html)
+        self.assertIn('col-md-9', html)
+
+    def test_row_with_field_input_and_label_andfield_css_classes_is_rendered(self):
+        html = render('{% form_row form.text_field "col-md-3" "col-md-9" %}')
+        self.assertIn('id_text_field', html)
+        self.assertIn('col-md-3', html)
+        self.assertIn('col-md-9', html)
+
+    def test_tag_without_field_raises_exception(self):
+        with self.assertRaises(TemplateSyntaxError):
+            render('{% form_row %}')
+
+    def test_field_label_is_rendered(self):
+        html = render("{% form_row form.text_field %}")
+        self.assertIn("Hello!", html)
+
+    def test_field_help_text_is_rendered(self):
+        html = render("{% form_row form.text_field %}")
+        self.assertIn("I am a help text.", html)
+
+
+class IsRadioSelectFieldFilterTests(TestCase):
+    def test_for_field_with_radio_select_widget_filter_returns_true(self):
+        form = TestForm()
+        self.assertTrue(is_radio_select_field(form['yesno_field']))
+
+    def test_for_field_without_radio_select_widget_filter_returns_false(self):
+        form = TestForm()
+        self.assertFalse(is_radio_select_field(form['text_field']))
+
+
+class IsSelectFieldFilerTests(TestCase):
+    def test_for_field_with_select_widget_filter_returns_true(self):
+        form = TestForm()
+        self.assertTrue(is_select_field(form['select_field']))
+
+    def teste_for_field_without_select_widget_filter_returns_false(self):
+        form = TestForm()
+        self.assertFalse(is_select_field(form['text_field']))
+
+
+class IsMultipleChoiceFieldFilerTests(TestCase):
+    def test_for_field_with_checkbox_select_widget_filter_returns_true(self):
+        form = TestForm()
+        self.assertTrue(is_multiple_choice_field(form['checkbox_select_field']))
+
+    def test_for_field_without_checkbox_select_widget_filter_returns_false(self):
+        form = TestForm()
+        self.assertFalse(is_multiple_choice_field(form['text_field']))
+
+    def test_for_field_with_multiple_select_widget_filter_returns_true(self):
+        form = TestForm()
+        self.assertTrue(is_multiple_choice_field(form['multiple_select_field']))
+
+    def test_for_field_without_multiple_select_widget_filter_returns_false(self):
+        form = TestForm()
+        self.assertFalse(is_multiple_choice_field(form['text_field']))
+
+
+class IsTextareaFieldFilterTests(TestCase):
+    def test_for_field_with_textarea_widget_filter_returns_true(self):
+        form = TestForm()
+        self.assertTrue(is_textarea_field(form['textarea_field']))
+
+    def test_for_field_without_textarea_widget_filter_returns_false(self):
+        form = TestForm()
+        self.assertFalse(is_textarea_field(form['text_field']))
+
+
+class RenderAttrsTagTests(TestCase):
+    def test_specified_class_name_is_rendered(self):
+        result = render_attrs({"attrs": {}}, class_name="form-control")
+        self.assertEqual(result, 'class="form-control"')
+
+    def test_specified_class_name_overrided_by_class_attr(self):
+        result = render_attrs({"attrs": {"class": "custom"}}, class_name="form-control")
+        self.assertEqual(result, 'class="custom"')
+
+    def test_attr_with_string_value_is_rendered(self):
+        result = render_attrs({"attrs": {"name": "lorem"}})
+        self.assertEqual(result, 'name="lorem"')
+
+    def test_attr_with_int_value_is_rendered(self):
+        result = render_attrs({"attrs": {"cols": 5}})
+        self.assertEqual(result, 'cols="5"')
+
+    def test_attr_with_boolean_true_value_is_not_rendered(self):
+        result = render_attrs({"attrs": {"selected": True}})
+        self.assertEqual(result, "")
+
+    def test_attr_with_boolean_false_value_is_not_rendered(self):
+        result = render_attrs({"attrs": {"selected": False}})
+        self.assertEqual(result, "")
+
+    def test_attr_with_none_value_is_not_rendered(self):
+        result = render_attrs({"attrs": {"selected": None}})
+        self.assertEqual(result, "")
+
+    def test_attr_name_is_escaped(self):
+        result = render_attrs({"attrs": {'"': 'test'}})
+        self.assertEqual(result, '&quot;="test"')
+
+    def test_attr_value_is_escaped(self):
+        result = render_attrs({"attrs": {"name": '"'}})
+        self.assertEqual(result, 'name="&quot;"')
+
+    def test_multiple_valid_attrs_are_rendered(self):
+        result = render_attrs({"attrs": {"name": "lorem", "cols": 5}})
+        self.assertEqual(result, 'name="lorem" cols="5"')
+
+    def test_empty_attr_dict_is_not_rendered(self):
+        result = render_attrs({"attrs": {}})
+        self.assertEqual(result, "")
+
+
+class RenderBoolAttrsTagTests(TestCase):
+    def test_attr_with_boolean_true_value_is_rendered(self):
+        result = render_bool_attrs({"bool": True})
+        self.assertEqual(result, "bool")
+
+    def test_attr_with_string_value_is_not_rendered(self):
+        result = render_bool_attrs({"name": "hello"})
+        self.assertEqual(result, "")
+
+    def test_attr_with_int_value_is_not_rendered(self):
+        result = render_bool_attrs({"col": 13})
+        self.assertEqual(result, "")
+
+    def test_attr_with_boolean_false_value_is_not_rendered(self):
+        result = render_bool_attrs({"selected": False})
+        self.assertEqual(result, "")
+
+    def test_attr_with_none_value_is_not_rendered(self):
+        result = render_bool_attrs({"selected": None})
+        self.assertEqual(result, "")
+
+    def test_attr_with_false_int_value_is_not_rendered(self):
+        result = render_bool_attrs({"selected": 0})
+        self.assertEqual(result, "")
+
+    def test_multiple_attrs_with_boolean_true_value_are_rendered(self):
+        result = render_bool_attrs({"selected": True, "required": True})
+        self.assertEqual(result, "selected required")
+
+    def test_only_attrs_with_boolean_true_value_are_rendered(self):
+        result = render_bool_attrs({"bool": True, "string": "hello", "int": 123})
+        self.assertEqual(result, "bool")
+
+    def test_empty_attr_dict_is_not_rendered(self):
+        result = render_bool_attrs({})
+        self.assertEqual(result, "")

+ 0 - 88
misago/admin/tests/test_templatetags.py

@@ -1,88 +0,0 @@
-from django import forms
-from django.template import Context, Template, TemplateSyntaxError
-from django.test import TestCase
-
-
-class TestForm(forms.Form):
-    somefield = forms.CharField(label="Hello!", max_length=255)
-
-
-class FormRowTests(TestCase):
-    def setUp(self):
-        self.context = Context({'form': TestForm()})
-
-    def test_form_row_no_args(self):
-        """form_row with no args renders form row"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% form_row form.somefield %}
-"""
-
-        tpl = Template(tpl_content)
-        render = tpl.render(self.context).strip()
-        self.assertIn('id_somefield', render)
-
-    def test_form_row_with_args(self):
-        """form_row with args renders form row"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% form_row form.somefield "col-md-3" "col-md-9" %}
-"""
-
-        tpl = Template(tpl_content)
-        render = tpl.render(self.context).strip()
-
-        self.assertIn('id_somefield', render)
-        self.assertIn('col-md-3', render)
-        self.assertIn('col-md-9', render)
-
-    def test_form_row_with_value_args(self):
-        """form_row with values args renders form row"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% with label="col-md-3" field="col-md-9" %}
-    {% form_row form.somefield label field %}
-{% endwith %}
-"""
-
-        tpl = Template(tpl_content)
-        render = tpl.render(self.context).strip()
-        self.assertIn('id_somefield', render)
-        self.assertIn('col-md-3', render)
-        self.assertIn('col-md-9', render)
-
-    def test_form_row_with_no_args(self):
-        """form_row with no args raises exception"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% form_row %}
-"""
-
-        with self.assertRaises(TemplateSyntaxError):
-            Template(tpl_content)
-
-    def test_form_row_with_two_args(self):
-        """form_row with two args raises exception"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% form_row form.somefield "col-md-9" %}
-"""
-
-        with self.assertRaises(TemplateSyntaxError):
-            Template(tpl_content)
-
-    def test_form_row_with_four_args(self):
-        """form_row with four args raises exception"""
-        tpl_content = """
-{% load misago_admin_forms %}
-
-{% form_row form.somefield "col-md-9" "col-md-9" "col-md-9" %}
-"""
-
-        with self.assertRaises(TemplateSyntaxError):
-            Template(tpl_content)

+ 0 - 48
misago/templates/bootstrap3/field.html

@@ -1,48 +0,0 @@
-{% load crispy_forms_field %}
-
-{% if field.is_hidden %}
-	{{ field }}
-{% else %}
-    {% if field|is_checkbox %}
-        <div class="form-group">
-    {% endif %}
-	<{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" {% if not field|is_checkbox %}class="form-group{% else %}class="checkbox{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if form_show_errors%}{% if field.errors %} has-error{% endif %}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
-		{% if field.label and not field|is_checkbox and form_show_labels %}
-			<label for="{{ field.id_for_label }}" class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
-				{{ field.label|safe }}:
-			</label>
-		{% endif %}
-
-        {% if field|is_checkboxselectmultiple %}
-            {% include 'bootstrap3/layout/checkboxselectmultiple.html' %}
-        {% endif %}
-
-        {% if field|is_radioselect %}
-            {% include 'bootstrap3/layout/radioselect.html' %}
-        {% endif %}
-
-        {% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
-            {% if field|is_checkbox and form_show_labels %}
-                {% if label_class %}
-                    <div class="controls {{ label_class }} {{ field_class }}">
-                {% endif %}
-                <label for="{{ field.id_for_label }}" class="{% if field.field.required %} requiredField{% endif %}">
-                    {% crispy_field field 'class' 'checkbox' %}
-                    {{ field.label|safe }}
-                    {% include 'bootstrap3/layout/help_text_and_errors.html' %}
-                </label>
-                {% if label_class %}
-                    </div>
-                {% endif %}
-            {% else %}
-                <div class="controls {{ field_class }}"{% if field.field.input_formats %} data-input-format="{{ field.field.input_formats.0 }}"{% endif %}>
-                    {% crispy_field field %}
-                    {% include 'bootstrap3/layout/help_text_and_errors.html' %}
-                </div>
-            {% endif %}
-        {% endif %}
-	</{% if tag %}{{ tag }}{% else %}div{% endif %}>
-    {% if field|is_checkbox %}
-        </div>
-    {% endif %}
-{% endif %}

+ 0 - 14
misago/templates/bootstrap3/layout/checkboxselectmultiple.html

@@ -1,14 +0,0 @@
-{% load crispy_forms_filters %}
-{% load l10n %}
-
-<div class="controls control-checkboxselect {{ field_class }}"{% if flat_attrs %} {{ flat_attrs|safe }}{% endif %}>
-    {% include 'bootstrap3/layout/field_errors_block.html' %}
-
-    {% for choice in field.field.choices %}
-        <label class="checkbox{% if inline_class %}-{{ inline_class }}{% endif %}">
-            <input type="checkbox"{% if choice.0 in field.value or choice.0|stringformat:"s" in field.value or choice.0|stringformat:"s" == field.value|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>{{ choice.1|unlocalize }}
-        </label>
-    {% endfor %}
-
-    {% include 'bootstrap3/layout/help_text.html' %}
-</div>

+ 0 - 7
misago/templates/bootstrap3/layout/field_errors_block.html

@@ -1,7 +0,0 @@
-{% if form_show_errors and field.errors %}
-  <div class="control-errors">
-  {% for error in field.errors %}
-    <p id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="help-block"><strong>{{ error }}</strong></p>
-  {% endfor %}
-  </div>
-{% endif %}

+ 0 - 16
misago/templates/bootstrap3/layout/radioselect.html

@@ -1,16 +0,0 @@
-{% load crispy_forms_filters %}
-{% load l10n %}
-
-<div class="controls control-radioselect {{ field_class }}"{% if flat_attrs %} {{ flat_attrs|safe }}{% endif %}>
-    {% include 'bootstrap3/layout/field_errors_block.html' %}
-
-    {% for choice in field.field.choices %}
-        <label class="radio{% if inline_class %}-{{ inline_class }}{% endif %}">
-            <input type="radio"{% if choice.0|stringformat:"s" == field.value|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>{{ choice.1|unlocalize }}
-        </label>
-    {% endfor %}
-
-    {% if not hide_help_text %}
-    {% include 'bootstrap3/layout/help_text.html' %}
-    {% endif %}
-</div>

+ 1 - 1
misago/templates/misago/admin/agreements/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/agreements/list.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load i18n misago_capture misago_admin_forms %}
+{% load i18n misago_capture misago_admin_form %}
 
 
 {% block page-actions %}

+ 1 - 1
misago/templates/misago/admin/attachments/list.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load i18n misago_capture misago_admin_forms %}
+{% load i18n misago_capture misago_admin_form %}
 
 
 {% block table-header %}

+ 1 - 1
misago/templates/misago/admin/attachmenttypes/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/bans/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/bans/list.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block page-actions %}

+ 4 - 4
misago/templates/misago/admin/categories/delete.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load crispy_forms_filters i18n %}
+{% load misago_admin_form i18n %}
 
 
 {% block title %}
@@ -19,7 +19,7 @@ Delete category: {{ category }}
 {% block form-header %}
 <h1>
   {% blocktrans with category=target.name %}
-  Delete category: {{ category }}
+    Delete category: {{ category }}
   {% endblocktrans %}
 </h1>
 {% endblock %}
@@ -31,9 +31,9 @@ Delete category: {{ category }}
     <legend>{% trans "Category contents" %}</legend>
 
     {% if not form.instance.is_leaf_node %}
-    {{ form.move_children_to|as_crispy_field }}
+      {% form_row form.move_children_to %}
     {% endif %}
-    {{ form.move_threads_to|as_crispy_field }}
+    {% form_row form.move_threads_to %}
 
   </fieldset>
 </div>

+ 1 - 1
misago/templates/misago/admin/categories/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 2 - 2
misago/templates/misago/admin/categoryroles/categoryroles.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load crispy_forms_field i18n %}
+{% load misago_admin_form i18n %}
 
 
 {% block title %}
@@ -36,7 +36,7 @@
           {{ form.role }}
         </td>
         <td>
-          {% crispy_field form.category_role %}
+          {% form_input form.category_role %}
         </td>
       </tr>
       {% endfor %}

+ 1 - 1
misago/templates/misago/admin/categoryroles/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 2 - 2
misago/templates/misago/admin/categoryroles/rolecategories.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load crispy_forms_field i18n %}
+{% load misago_admin_form i18n %}
 
 
 {% block title %}
@@ -39,7 +39,7 @@
           {{ form.category }}
         </td>
         <td>
-          {% crispy_field form.role %}
+          {% form_input form.role %}
         </td>
       </tr>
       {% endfor %}

+ 1 - 1
misago/templates/misago/admin/conf/group.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/conf/index.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}{% trans active_group.name %} | {{ block.super }}{% endblock %}

+ 1 - 1
misago/templates/misago/admin/datadownloads/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/datadownloads/list.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load i18n misago_avatars misago_admin_forms %}
+{% load i18n misago_avatars misago_admin_form %}
 
 
 {% block page-actions %}

+ 12 - 0
misago/templates/misago/admin/form/input.html

@@ -0,0 +1,12 @@
+{% load misago_admin_form %}
+{% if field|is_radio_select_field %}
+  {% include "misago/admin/form/radio_select.html" %}
+{% elif field|is_select_field %}
+  {% include "misago/admin/form/select.html" %}
+{% elif field|is_multiple_choice_field %}
+  {% include "misago/admin/form/multiple_choice.html" %}
+{% elif field|is_textarea_field %}
+  <textarea {% render_attrs widget class_name="form-control" %} id="{{ field.id_for_label }}" name="{{ widget.name }}" {% render_bool_attrs widget %}>{% if widget.value is not None %}{{ widget.value }}{% endif %}</textarea>
+{% else %}
+  <input {% render_attrs widget class_name="form-control" %} id="{{ field.id_for_label }}" name="{{ widget.name }}" type="{{ widget.type }}"{% if widget.value is not None %} value="{{ widget.value }}"{% endif %} {% render_bool_attrs widget %}>
+{% endif %}

+ 7 - 0
misago/templates/misago/admin/form/multiple_choice.html

@@ -0,0 +1,7 @@
+{% load misago_admin_form %}
+{% for option in field|get_options %}
+  <label class="checkbox">
+    <input id="{{ option.attrs.id }}" name="{{ widget.name }}" type="checkbox"{% if option.value is not None %} value="{{ option.value }}"{% endif %}{% if option.selected %} checked{% endif %}>
+    {{ option.label }}
+  </label>
+{% endfor %}

+ 7 - 0
misago/templates/misago/admin/form/radio_select.html

@@ -0,0 +1,7 @@
+{% load misago_admin_form %}
+{% for option in field|get_options %}
+  <label class="radio">
+    <input {% render_attrs widget class_name="form-control" %} id="{{ option.attrs.id }}" name="{{ widget.name }}" type="radio"{% if option.value is not None %} value="{{ option.value }}"{% endif %}{% if option.selected %} checked{% endif %}>
+    {{ option.label }}
+  </label>
+{% endfor %}

+ 17 - 0
misago/templates/misago/admin/form/row.html

@@ -0,0 +1,17 @@
+{% load misago_admin_form %}
+<div class="form-group{% if field.errors %} has-error{% endif %}">
+  <label for="{{ field.id_for_label }}" class="control-label{% if label_class %} {{ label_class }}{% endif %}">
+    {{ field.label }}:
+  </label>
+  <div class="{% if field|is_radio_select_field %}controls control-radioselect {% endif %}{% if field|is_multiple_choice_field %}controls control-checkboxselect {% endif %}{% if field_class %}{{ field_class }}{% endif %}">
+    {% form_input field %}
+    {% for error in field.errors %}
+      <div class="text-danger">
+        <strong>{{ error }}</strong>
+      </div>
+    {% endfor %}
+    {% if field.help_text %}
+      <div class="help-block">{{ field.help_text }}</div>
+    {% endif %}
+  </div>
+</div>

+ 8 - 0
misago/templates/misago/admin/form/select.html

@@ -0,0 +1,8 @@
+{% load misago_admin_form %}
+<select {% render_attrs widget class_name="form-control" %} id="{{ field.id_for_label }}" name="{{ widget.name }}" {% render_bool_attrs widget %}>
+  {% for option in field|get_options %}
+    <option{% if option.value is not None %} value="{{ option.value }}"{% endif %} {% render_bool_attrs option.attrs %}>
+      {{ option.label }}
+    </option>
+  {% endfor %}
+</select>

+ 7 - 11
misago/templates/misago/admin/permissions_table.html

@@ -1,4 +1,4 @@
-{% load crispy_forms_field crispy_forms_tags i18n %}
+{% load misago_admin_form i18n %}
 <fieldset>
   <legend>{{ form.legend }}</legend>
 
@@ -16,16 +16,12 @@
       </td>
       <td>
         <div class="form-group{% if field.errors %} has-error{% endif %}">
-          {% if field|is_checkboxselectmultiple %}
-            {% include 'bootstrap3/layout/checkboxselectmultiple.html' %}
-          {% endif %}
-
-          {% if field|is_radioselect %}
-            {% include 'bootstrap3/layout/radioselect.html' with hide_help_text=1 %}
-          {% endif %}
-
-          {% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
-          {% crispy_field field %}
+          {% if field|is_radio_select_field %}
+            <div class="controls control-radioselect">
+              {% form_input field %}
+            </div>
+          {% else %}
+            {% form_input field %}
           {% endif %}
         </div>
       </td>

+ 1 - 1
misago/templates/misago/admin/ranks/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/roles/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/users/ban.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_avatars misago_capture misago_admin_forms %}
+{% load i18n misago_avatars misago_capture misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/users/delete.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_avatars misago_capture misago_admin_forms %}
+{% load i18n misago_avatars misago_capture misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/users/edit.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 1 - 1
misago/templates/misago/admin/users/list.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/list.html" %}
-{% load i18n misago_avatars misago_admin_forms %}
+{% load i18n misago_avatars misago_admin_form %}
 
 
 {% block page-actions %}

+ 3 - 3
misago/templates/misago/admin/users/new.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}
@@ -33,14 +33,14 @@ class="form-horizontal"
     {% form_row form.username label_class field_class %}
 
     {% if 'rank' in form.fields %}
-    {% form_row form.rank label_class field_class %}
+      {% form_row form.rank label_class field_class %}
     {% endif %}
 
     {% form_row form.title label_class field_class %}
     {% form_row form.roles label_class field_class %}
 
     {% if 'staff_level' in form.fields %}
-    {% form_row form.staff_level label_class field_class %}
+      {% form_row form.staff_level label_class field_class %}
     {% endif %}
 
   </fieldset>

+ 1 - 1
misago/templates/misago/admin/warnings/form.html

@@ -1,5 +1,5 @@
 {% extends "misago/admin/generic/form.html" %}
-{% load i18n misago_admin_forms %}
+{% load i18n misago_admin_form %}
 
 
 {% block title %}

+ 0 - 1
requirements.in

@@ -3,7 +3,6 @@ bleach<2.2
 django<2
 djangorestframework<3.7
 django-debug-toolbar<1.9
-django-crispy-forms<1.7
 django-htmlmin<0.11
 django-mptt<0.9
 Faker<0.9

+ 0 - 1
requirements.txt

@@ -8,7 +8,6 @@ beautifulsoup4==4.6.3
 bleach==2.1.4
 certifi==2018.10.15       # via requests
 chardet==3.0.4            # via requests
-django-crispy-forms==1.6.1
 django-debug-toolbar==1.8
 django-htmlmin==0.10.0
 django-mptt==0.8.7