customtags.py 7.83 KB
Newer Older
1
2
import logging

3
from django import forms
4
from django import template
5
from django.contrib.messages.storage.base import Message
6
from django.db.models import QuerySet
7
from django.forms.utils import ErrorList, ErrorDict
8
from django.utils.safestring import mark_safe
9
from django.utils.translation import ugettext
10
11

register = template.Library()
12
13
logger = logging.getLogger(__name__)

14
15
16
17
18
19

@register.simple_tag
def url_replace(request, field, value):
    dict_ = request.GET.copy()
    dict_[field] = value
    return dict_.urlencode()
20
21


22
23
24
@register.simple_tag
def status_class(value):
    if value is True:
25
        return "table-success"
26
    elif value is False:
27
        return "table-danger"
28
    else:
29
        return "table-secondary"
30

31

32
33
34
@register.simple_tag
def rule_status_icon(value, title):
    if value is True:
35
36
        class_suffix = "success"
        icon_suffix = "check"
37
    elif value is False:
38
39
        class_suffix = "danger"
        icon_suffix = "times"
40
    else:
41
42
43
44
45
        class_suffix = "secondary"
        class_suffix = "question"
    return mark_safe(
        f'<i class="fas fa-{icon_suffix} text-{class_suffix}" title="{title}: failed"></i>'
    )
46

47

48
49
@register.filter
def bootstrap(object):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
50
    return mark_safe("".join(bootstrap_core(object)))
51

52

53
54
55
56
@register.filter
def get_item(d, key):
    return d[key]

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
57

58
def bootstrap_core(object):
59
    ret = []
60
    if isinstance(object, forms.BoundField):
61
        field = object
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
62
63
        print_label = True
        if isinstance(field.field.widget, forms.HiddenInput):
64
65
66
            ret.append(field.as_widget())
            if field.errors:
                for e in field.errors:
67
68
69
70
                    ret.append(
                        """<div class="alert alert-danger">%s: %s</div>"""
                        % (field.name, e)
                    )
71
            return ret
72
        attrs = field.field.widget.attrs
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
73
        # get the class specified in the code
74
        css_classes = set(attrs.get("class", "").split(" "))
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
75
        # if the field has errors, we add bootstrap classes
76
77
        if field.errors:
            css_classes.add("is-invalid")
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
78
        # we propagate the "required" attribute
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
79
        if field.field.required:
80
            attrs["required"] = "required"
81
82
        # we fetch the "help_text" attribute from wherever we can
        help_text = getattr(field, "help_text", None) or attrs.get("help_text", None)
83

84
        if isinstance(field.field.widget, forms.widgets.CheckboxInput):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
85
            # if it is a checkbox, we the classes are not the same
86
            wrapping_classes = "form-group form-check"
87
88
            label_classes = "form-check-label"
            css_classes.add("form-check-input")
89
90
91
92
93
        elif isinstance(field.field.widget, forms.widgets.SelectMultiple):
            # if it is a SelectMultiple, we assume that we will handle it on client side with boostrap-mutliselect
            wrapping_classes = "form-group"
            attrs["data-label"] = field.label
            print_label = False
94
        else:
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
95
            # usual classes
96
97
98
            wrapping_classes = "input_field form-group"
            label_classes = "form-control-placeholder"
            css_classes.add("form-control")
99
        if field.name == "DELETE":
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
100
            # HACK : If the field is the DELETE button of the formset
101
            attrs["onchange"] = "delete_button_clicked(this);"
102
            field.label = ugettext("DELETE_label")
103
            wrapping_classes += " formset-item-delete-host delete-btn col order-1"
104
            css_classes.add("formset-item-delete")
105
106
        # HACK: if the css_class is a bootstrap column layout, transfer it to container element
        for css_class in css_classes:
107
108
109
110
111
            if (
                css_class.startswith("col")
                or css_class.startswith("order-")
                or css_class.startswith("mb-")
            ):
112
                wrapping_classes += f" {css_class}"
113
114
115
116
117
118
119
        css_classes = set(
            [
                css_class
                for css_class in css_classes
                if not (
                    css_class.startswith("col")
                    or css_class.startswith("order-")
120
                    or css_class.startswith("mb-")
121
122
123
                )
            ]
        )
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
124
125

        # overwrite the css classes
126
        attrs["class"] = " ".join(css_classes)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
127

128
        if "datalist" in attrs:
129
130
131
132
133
            datalist = attrs.pop("datalist")
            attrs["list"] = "list__%s" % field.id_for_label
        else:
            datalist = None

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
134
        # render the field
135
        ret.append('<div class="%s">' % wrapping_classes)
136
        ret.append(field.as_widget(attrs=attrs))
137

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
138
139
        if print_label:
            # most of the use case want to print the label
140
141
142
143
144
145
146
147
148
            ret.append(
                """<label class="%s" for="%s">%s%s</label>"""
                % (
                    label_classes,
                    field.id_for_label,
                    field.label,
                    " *" if field.field.required else "",
                )
            )
149
150
151
152
153
154

        # If the field has a datalist attribut the input field will suggest from it, printing the element
        if datalist:
            ret.append('<datalist id="%s">' % attrs["list"])
            for option in datalist:
                ret.append('<option value="%s">' % option)
155
            ret.append("</datalist>")
156

157
        if help_text:
158
159
160
            ret.append(
                """<small class ="form-text text-muted">%s</small>""" % help_text
            )
161
        if field.errors:
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
162
            # render its errors
163
164
            for e in field.errors:
                ret.append("""<div class="invalid-feedback">%s</div>""" % e)
165
        ret.append("</div>")
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
166
        # return it as safe html code
167
    elif isinstance(object, forms.BaseFormSet):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
168
        # print formset's error which are not related to a form the formset itself
169
170
        formset = object
        for error in formset.non_form_errors():
171
172
173
174
            ret.append(
                """<div class="alert alert-danger">%s</div>"""
                % error.replace("\n", "<br/>")
            )
175
    elif isinstance(object, forms.BaseForm):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
176
        # print form's error which are not related to a field but the form itself
177
178
        form = object
        for field in form:
Fabien  MAREUIL's avatar
Fabien MAREUIL committed
179
180
            for el in bootstrap_core(field):
                ret.append(el)
181
    elif isinstance(object, ErrorList):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
182
        # print errors in a bootstrap way
183
        for error in object:
184
185
186
187
            ret.append(
                """<div class="alert alert-danger">%s</div>"""
                % error.replace("\n", "<br/>")
            )
188
189
190
191
192
193
    elif isinstance(object, ErrorDict):
        # print errors in a bootstrap way
        for key, error in object.items():
            ret.append('<div class="alert alert-danger"><b>%s:</b>' % key)
            for line in bootstrap_core(error):
                ret.append(line)
194
            ret.append("</div>")
195
    elif isinstance(object, Message):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
196
        # print messages in a bootstrap way
197
198
199
200
201
202
        message = object
        level_tag = message.level_tag
        if level_tag == "error":
            level_tag = "danger"
        elif level_tag == "debug":
            level_tag = "default"
203
204
205
206
        ret.append(
            """<div class="alert alert-%s">%s</div>"""
            % (level_tag, message.message.replace("\n", "<br/>"))
        )
207
    elif len(str(object)) > 0:
208
209
210
211
        ret.append(
            """<div class="alert alert-danger">Can't bootstrapize object of class %s</div>"""
            % str(type(object).__name__)
        )
212
    return ret
213
214


215
@register.filter("startswith")
216
def startswith(text, starts):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
217
    return text.startswith(starts)
218

219
220

@register.filter("endswith")
221
222
223
def endswith(text, starts):
    return text.endswith(starts)

224
225
226

@register.filter
def verbose_name(obj, field_name=None):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
227
228
    if isinstance(obj, str):
        return ""
229
230
231
232
233
    if isinstance(obj, QuerySet):
        return obj.model._meta.verbose_name_plural
    if field_name is None:
        return obj._meta.verbose_name
    return obj._meta.get_field(field_name).verbose_name.title()