• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zostera / django-bootstrap4 / 10873049980

15 Sep 2024 05:41PM UTC coverage: 90.935%. First build
10873049980

Pull #745

github

web-flow
Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0

Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #745: Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0

311 of 384 branches covered (80.99%)

Branch coverage included in aggregate %.

1274 of 1359 relevant lines covered (93.75%)

13.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.45
/src/bootstrap4/renderers.py
1
from bs4 import BeautifulSoup
14✔
2
from django.forms import (
14✔
3
    BaseForm,
4
    BaseFormSet,
5
    BoundField,
6
    CheckboxInput,
7
    CheckboxSelectMultiple,
8
    DateInput,
9
    EmailInput,
10
    FileInput,
11
    MultiWidget,
12
    NumberInput,
13
    PasswordInput,
14
    RadioSelect,
15
    Select,
16
    SelectDateWidget,
17
    TextInput,
18
    URLInput,
19
)
20
from django.utils.html import conditional_escape, escape, strip_tags
14✔
21
from django.utils.safestring import mark_safe
14✔
22

23
from .bootstrap import get_bootstrap_setting
14✔
24
from .exceptions import BootstrapError
14✔
25
from .forms import (
14✔
26
    FORM_GROUP_CLASS,
27
    is_widget_with_placeholder,
28
    render_field,
29
    render_form,
30
    render_form_group,
31
    render_label,
32
)
33
from .text import text_value
14✔
34
from .utils import add_css_class, render_template_file
14✔
35

36
try:
14✔
37
    # If Django is set up without a database, importing this widget gives RuntimeError
38
    from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
14✔
39
except RuntimeError:
×
40
    ReadOnlyPasswordHashWidget = None
×
41

42

43
class BaseRenderer:
14✔
44
    """A content renderer."""
45

46
    def __init__(self, *args, **kwargs):
14✔
47
        self.layout = kwargs.get("layout", "")
14✔
48
        self.form_group_class = kwargs.get("form_group_class", FORM_GROUP_CLASS)
14✔
49
        self.field_class = kwargs.get("field_class", "")
14✔
50
        self.label_class = kwargs.get("label_class", "")
14✔
51
        self.show_help = kwargs.get("show_help", True)
14✔
52
        self.show_label = kwargs.get("show_label", True)
14✔
53
        self.exclude = kwargs.get("exclude", "")
14✔
54

55
        self.set_placeholder = kwargs.get("set_placeholder", True)
14✔
56
        self.size = self.parse_size(kwargs.get("size", ""))
14✔
57
        self.horizontal_label_class = kwargs.get(
14✔
58
            "horizontal_label_class", get_bootstrap_setting("horizontal_label_class")
59
        )
60
        self.horizontal_field_class = kwargs.get(
14✔
61
            "horizontal_field_class", get_bootstrap_setting("horizontal_field_class")
62
        )
63

64
    def parse_size(self, size):
14✔
65
        size = text_value(size).lower().strip()
14✔
66
        if size in ("sm", "small"):
14✔
67
            return "small"
14✔
68
        if size in ("lg", "large"):
14✔
69
            return "large"
14✔
70
        if size in ("md", "medium", ""):
14!
71
            return "medium"
14✔
72
        raise BootstrapError(f'Invalid value "{size}" for parameter "size" (expected "sm", "md", "lg" or "").')
×
73

74
    def get_size_class(self, prefix="form-control"):
14✔
75
        if self.size == "small":
14✔
76
            return prefix + "-sm"
14✔
77
        if self.size == "large":
14✔
78
            return prefix + "-lg"
14✔
79
        return ""
14✔
80

81
    def _render(self):
14✔
82
        return ""
×
83

84
    def render(self):
14✔
85
        return mark_safe(self._render())
14✔
86

87

88
class FormsetRenderer(BaseRenderer):
14✔
89
    """Default formset renderer."""
90

91
    def __init__(self, formset, *args, **kwargs):
14✔
92
        if not isinstance(formset, BaseFormSet):
14✔
93
            raise BootstrapError('Parameter "formset" should contain a valid Django Formset.')
14✔
94
        self.formset = formset
14✔
95
        super().__init__(*args, **kwargs)
14✔
96

97
    def render_management_form(self):
14✔
98
        return text_value(self.formset.management_form)
14✔
99

100
    def render_form(self, form, **kwargs):
14✔
101
        return render_form(form, **kwargs)
14✔
102

103
    def render_forms(self):
14✔
104
        rendered_forms = []
14✔
105
        for form in self.formset.forms:
14✔
106
            rendered_forms.append(
14✔
107
                self.render_form(
108
                    form,
109
                    layout=self.layout,
110
                    form_group_class=self.form_group_class,
111
                    field_class=self.field_class,
112
                    label_class=self.label_class,
113
                    show_label=self.show_label,
114
                    show_help=self.show_help,
115
                    exclude=self.exclude,
116
                    set_placeholder=self.set_placeholder,
117
                    size=self.size,
118
                    horizontal_label_class=self.horizontal_label_class,
119
                    horizontal_field_class=self.horizontal_field_class,
120
                )
121
            )
122
        return "\n".join(rendered_forms)
14✔
123

124
    def get_formset_errors(self):
14✔
125
        return self.formset.non_form_errors()
14✔
126

127
    def render_errors(self):
14✔
128
        formset_errors = self.get_formset_errors()
14✔
129
        if formset_errors:
14!
130
            return render_template_file(
×
131
                "bootstrap4/form_errors.html",
132
                context={"errors": formset_errors, "form": self.formset, "layout": self.layout},
133
            )
134
        return ""
14✔
135

136
    def _render(self):
14✔
137
        return "".join([self.render_errors(), self.render_management_form(), self.render_forms()])
14✔
138

139

140
class FormRenderer(BaseRenderer):
14✔
141
    """Default form renderer."""
142

143
    def __init__(self, form, *args, **kwargs):
14✔
144
        if not isinstance(form, BaseForm):
14✔
145
            raise BootstrapError('Parameter "form" should contain a valid Django Form.')
14✔
146
        self.form = form
14✔
147
        super().__init__(*args, **kwargs)
14✔
148
        self.error_css_class = kwargs.get("error_css_class", None)
14✔
149
        self.required_css_class = kwargs.get("required_css_class", None)
14✔
150
        self.bound_css_class = kwargs.get("bound_css_class", None)
14✔
151
        self.alert_error_type = kwargs.get("alert_error_type", "non_fields")
14✔
152
        self.form_check_class = kwargs.get("form_check_class", "form-check")
14✔
153

154
    def render_fields(self):
14✔
155
        rendered_fields = []
14✔
156
        for field in self.form:
14✔
157
            rendered_fields.append(
14✔
158
                render_field(
159
                    field,
160
                    layout=self.layout,
161
                    form_group_class=self.form_group_class,
162
                    field_class=self.field_class,
163
                    label_class=self.label_class,
164
                    form_check_class=self.form_check_class,
165
                    show_label=self.show_label,
166
                    show_help=self.show_help,
167
                    exclude=self.exclude,
168
                    set_placeholder=self.set_placeholder,
169
                    size=self.size,
170
                    horizontal_label_class=self.horizontal_label_class,
171
                    horizontal_field_class=self.horizontal_field_class,
172
                    error_css_class=self.error_css_class,
173
                    required_css_class=self.required_css_class,
174
                    bound_css_class=self.bound_css_class,
175
                )
176
            )
177
        return "\n".join(rendered_fields)
14✔
178

179
    def get_fields_errors(self):
14✔
180
        form_errors = []
14✔
181
        for field in self.form:
14✔
182
            if not field.is_hidden and field.errors:
14✔
183
                form_errors += field.errors
14✔
184
        return form_errors
14✔
185

186
    def render_errors(self, type="all"):
14✔
187
        form_errors = None
14✔
188
        if type == "all":
14✔
189
            form_errors = self.get_fields_errors() + self.form.non_field_errors()
14✔
190
        elif type == "fields":
14✔
191
            form_errors = self.get_fields_errors()
14✔
192
        elif type == "non_fields":
14✔
193
            form_errors = self.form.non_field_errors()
14✔
194

195
        if form_errors:
14✔
196
            return render_template_file(
14✔
197
                "bootstrap4/form_errors.html",
198
                context={"errors": form_errors, "form": self.form, "layout": self.layout, "type": type},
199
            )
200

201
        return ""
14✔
202

203
    def _render(self):
14✔
204
        return self.render_errors(self.alert_error_type) + self.render_fields()
14✔
205

206

207
class FieldRenderer(BaseRenderer):
14✔
208
    """Default field renderer."""
209

210
    # These widgets will not be wrapped in a form-control class
211
    WIDGETS_NO_FORM_CONTROL = (CheckboxInput, RadioSelect, CheckboxSelectMultiple, FileInput)
14✔
212

213
    def __init__(self, field, *args, **kwargs):
14✔
214
        if not isinstance(field, BoundField):
14✔
215
            raise BootstrapError('Parameter "field" should contain a valid Django BoundField.')
14✔
216
        self.field = field
14✔
217
        super().__init__(*args, **kwargs)
14✔
218

219
        self.widget = field.field.widget
14✔
220
        self.is_multi_widget = isinstance(field.field.widget, MultiWidget)
14✔
221
        self.initial_attrs = self.widget.attrs.copy()
14✔
222
        self.field_help = text_value(mark_safe(field.help_text)) if self.show_help and field.help_text else ""
14✔
223
        self.field_errors = [conditional_escape(text_value(error)) for error in field.errors]
14✔
224
        self.form_check_class = kwargs.get("form_check_class", "form-check")
14✔
225

226
        if "placeholder" in kwargs:
14!
227
            # Find the placeholder in kwargs, even if it's empty
228
            self.placeholder = kwargs["placeholder"]
×
229
        elif get_bootstrap_setting("set_placeholder"):
14!
230
            # If not found, see if we set the label
231
            self.placeholder = field.label
14✔
232
        else:
233
            # Or just set it to empty
234
            self.placeholder = ""
×
235
        if self.placeholder:
14!
236
            self.placeholder = text_value(self.placeholder)
14✔
237

238
        self.addon_before = kwargs.get("addon_before", self.widget.attrs.pop("addon_before", ""))
14✔
239
        self.addon_after = kwargs.get("addon_after", self.widget.attrs.pop("addon_after", ""))
14✔
240
        self.addon_before_class = kwargs.get(
14✔
241
            "addon_before_class", self.widget.attrs.pop("addon_before_class", "input-group-text")
242
        )
243
        self.addon_after_class = kwargs.get(
14✔
244
            "addon_after_class", self.widget.attrs.pop("addon_after_class", "input-group-text")
245
        )
246

247
        # These are set in Django or in the global BOOTSTRAP4 settings, and
248
        # they can be overwritten in the template
249
        error_css_class = kwargs.get("error_css_class", None)
14✔
250
        required_css_class = kwargs.get("required_css_class", None)
14✔
251
        bound_css_class = kwargs.get("bound_css_class", None)
14✔
252
        if error_css_class is not None:
14✔
253
            self.error_css_class = error_css_class
14✔
254
        else:
255
            self.error_css_class = getattr(field.form, "error_css_class", get_bootstrap_setting("error_css_class"))
14✔
256
        if required_css_class is not None:
14✔
257
            self.required_css_class = required_css_class
14✔
258
        else:
259
            self.required_css_class = getattr(
14✔
260
                field.form, "required_css_class", get_bootstrap_setting("required_css_class")
261
            )
262
        if bound_css_class is not None:
14✔
263
            self.success_css_class = bound_css_class
14✔
264
        else:
265
            self.success_css_class = getattr(field.form, "bound_css_class", get_bootstrap_setting("success_css_class"))
14✔
266

267
        # If the form is marked as form.empty_permitted, do not set required class
268
        if self.field.form.empty_permitted:
14✔
269
            self.required_css_class = ""
14✔
270

271
    def restore_widget_attrs(self):
14✔
272
        self.widget.attrs = self.initial_attrs.copy()
14✔
273

274
    def add_class_attrs(self, widget=None):
14✔
275
        if widget is None:
14!
276
            widget = self.widget
×
277
        classes = widget.attrs.get("class", "")
14✔
278
        if ReadOnlyPasswordHashWidget is not None and isinstance(widget, ReadOnlyPasswordHashWidget):
14!
279
            # Render this is a static control
280
            classes = add_css_class(classes, "form-control-static", prepend=True)
×
281
        elif not isinstance(widget, self.WIDGETS_NO_FORM_CONTROL):
14✔
282
            classes = add_css_class(classes, "form-control", prepend=True)
14✔
283
            # For these widget types, add the size class here
284
            classes = add_css_class(classes, self.get_size_class())
14✔
285
        elif isinstance(widget, CheckboxInput):
14✔
286
            classes = add_css_class(classes, "form-check-input", prepend=True)
14✔
287
        elif isinstance(widget, FileInput):
14!
288
            classes = add_css_class(classes, "form-control-file", prepend=True)
×
289

290
        if self.field.errors:
14✔
291
            if self.error_css_class:
14✔
292
                classes = add_css_class(classes, self.error_css_class)
14✔
293
        else:
294
            if self.field.form.is_bound:
14✔
295
                classes = add_css_class(classes, self.success_css_class)
14✔
296

297
        widget.attrs["class"] = classes
14✔
298

299
    def add_placeholder_attrs(self, widget=None):
14✔
300
        if widget is None:
14!
301
            widget = self.widget
×
302
        placeholder = widget.attrs.get("placeholder", self.placeholder)
14✔
303
        if placeholder and self.set_placeholder and is_widget_with_placeholder(widget):
14✔
304
            # TODO: Should this be stripped and/or escaped?
305
            widget.attrs["placeholder"] = placeholder
14✔
306

307
    def add_help_attrs(self, widget=None):
14✔
308
        if widget is None:
14!
309
            widget = self.widget
×
310
        if not isinstance(widget, CheckboxInput):
14✔
311
            widget.attrs["title"] = widget.attrs.get("title", escape(strip_tags(self.field_help)))
14✔
312

313
    def add_widget_attrs(self):
14✔
314
        if self.is_multi_widget:
14✔
315
            widgets = self.widget.widgets
14✔
316
        else:
317
            widgets = [self.widget]
14✔
318
        for widget in widgets:
14✔
319
            self.add_class_attrs(widget)
14✔
320
            self.add_placeholder_attrs(widget)
14✔
321
            self.add_help_attrs(widget)
14✔
322

323
    def list_to_class(self, html, klass):
14✔
324
        classes = add_css_class(klass, self.get_size_class())
14✔
325
        soup = BeautifulSoup(html, features="html.parser")
14✔
326
        enclosing_div = soup.find("div")
14✔
327
        enclosing_div.attrs["class"] = classes
14✔
328
        for inner_div in enclosing_div.find_all("div"):
14✔
329
            inner_div.attrs["class"] = inner_div.attrs.get("class", []) + [self.form_check_class]
14✔
330
        # Apply bootstrap4 classes to labels and inputs.
331
        # A simple 'replace' isn't enough as we don't want to have several 'class' attr definition, which would happen
332
        # if we tried to 'html.replace("input", "input class=...")'
333
        enclosing_div = soup.find("div", {"class": classes})
14✔
334
        if enclosing_div:
14!
335
            for label in enclosing_div.find_all("label"):
14✔
336
                label.attrs["class"] = label.attrs.get("class", []) + ["form-check-label"]
14✔
337
                try:
14✔
338
                    label.input.attrs["class"] = label.input.attrs.get("class", []) + ["form-check-input"]
14✔
339
                except AttributeError:
14✔
340
                    pass
14✔
341
        return str(soup)
14✔
342

343
    def add_checkbox_label(self, html):
14✔
344
        return html + render_label(
14✔
345
            content=self.field.label,
346
            label_for=self.field.id_for_label,
347
            label_title=escape(strip_tags(self.field_help)),
348
            label_class="form-check-label",
349
        )
350

351
    def fix_date_select_input(self, html):
14✔
352
        div1 = '<div class="col-4">'
×
353
        div2 = "</div>"
×
354
        html = html.replace("<select", div1 + "<select")
×
355
        html = html.replace("</select>", "</select>" + div2)
×
356
        return f'<div class="row bootstrap4-multi-input">{html}</div>'
×
357

358
    def fix_file_input_label(self, html):
14✔
359
        if self.layout != "horizontal":
×
360
            html = "<br>" + html
×
361
        return html
×
362

363
    def post_widget_render(self, html):
14✔
364
        if isinstance(self.widget, CheckboxSelectMultiple):
14✔
365
            html = self.list_to_class(html, "checkbox")
14✔
366
        elif isinstance(self.widget, RadioSelect):
14✔
367
            html = self.list_to_class(html, "radio radio-success")
14✔
368
        elif isinstance(self.widget, SelectDateWidget):
14!
369
            html = self.fix_date_select_input(html)
×
370
        elif isinstance(self.widget, CheckboxInput):
14✔
371
            html = self.add_checkbox_label(html)
14✔
372
        elif isinstance(self.widget, FileInput):
14!
373
            html = self.fix_file_input_label(html)
×
374
        return html
14✔
375

376
    def wrap_widget(self, html):
14✔
377
        if isinstance(self.widget, CheckboxInput):
14✔
378
            # Wrap checkboxes
379
            # Note checkboxes do not get size classes, see #318
380
            html = f'<div class="form-check">{html}</div>'
14✔
381
        return html
14✔
382

383
    def make_input_group_addon(self, inner_class, outer_class, content):
14✔
384
        if not content:
14✔
385
            return ""
14✔
386
        if inner_class:
14✔
387
            content = f'<span class="{inner_class}">{content}</span>'
14✔
388
        return f'<div class="{outer_class}">{content}</div>'
14✔
389

390
    @property
14✔
391
    def is_input_group(self):
14✔
392
        allowed_widget_types = (TextInput, PasswordInput, DateInput, NumberInput, Select, EmailInput, URLInput)
14✔
393
        return (self.addon_before or self.addon_after) and isinstance(self.widget, allowed_widget_types)
14✔
394

395
    def make_input_group(self, html):
14✔
396
        if self.is_input_group:
14✔
397
            before = self.make_input_group_addon(self.addon_before_class, "input-group-prepend", self.addon_before)
14✔
398
            after = self.make_input_group_addon(self.addon_after_class, "input-group-append", self.addon_after)
14✔
399
            html = self.append_errors(f"{before}{html}{after}")
14✔
400
            html = f'<div class="input-group">{html}</div>'
14✔
401
        return html
14✔
402

403
    def append_help(self, html):
14✔
404
        field_help = self.field_help or None
14✔
405
        if field_help:
14✔
406
            help_html = render_template_file(
14✔
407
                "bootstrap4/field_help_text.html",
408
                context={
409
                    "field": self.field,
410
                    "field_help": field_help,
411
                    "layout": self.layout,
412
                    "show_help": self.show_help,
413
                },
414
            )
415
            html += help_html
14✔
416
        return html
14✔
417

418
    def append_errors(self, html):
14✔
419
        field_errors = self.field_errors
14✔
420
        if field_errors:
14✔
421
            errors_html = render_template_file(
14✔
422
                "bootstrap4/field_errors.html",
423
                context={
424
                    "field": self.field,
425
                    "field_errors": field_errors,
426
                    "layout": self.layout,
427
                    "show_help": self.show_help,
428
                },
429
            )
430
            html += errors_html
14✔
431
        return html
14✔
432

433
    def append_to_field(self, html):
14✔
434
        if isinstance(self.widget, CheckboxInput):
14✔
435
            # we have already appended errors and help to checkboxes
436
            # in append_to_checkbox_field
437
            return html
14✔
438

439
        if not self.is_input_group:
14✔
440
            # we already appended errors for input groups in make_input_group
441
            html = self.append_errors(html)
14✔
442

443
        return self.append_help(html)
14✔
444

445
    def append_to_checkbox_field(self, html):
14✔
446
        if not isinstance(self.widget, CheckboxInput):
14✔
447
            # we will append errors and help to normal fields later in append_to_field
448
            return html
14✔
449

450
        html = self.append_errors(html)
14✔
451
        return self.append_help(html)
14✔
452

453
    def get_field_class(self):
14✔
454
        field_class = self.field_class
14✔
455
        if not field_class and self.layout == "horizontal":
14✔
456
            field_class = self.horizontal_field_class
14✔
457
        return field_class
14✔
458

459
    def wrap_field(self, html):
14✔
460
        field_class = self.get_field_class()
14✔
461
        if field_class:
14✔
462
            html = f'<div class="{field_class}">{html}</div>'
14✔
463
        return html
14✔
464

465
    def get_label_class(self):
14✔
466
        label_class = self.label_class
14✔
467
        if not label_class and self.layout == "horizontal":
14✔
468
            label_class = self.horizontal_label_class
14✔
469
            label_class = add_css_class(label_class, "col-form-label")
14✔
470
        label_class = text_value(label_class)
14✔
471
        if not self.show_label or self.show_label == "sr-only":
14✔
472
            label_class = add_css_class(label_class, "sr-only")
14✔
473
        return label_class
14✔
474

475
    def get_label(self):
14✔
476
        if self.show_label == "skip":
14✔
477
            return None
14✔
478
        elif isinstance(self.widget, CheckboxInput):
14✔
479
            label = None
14✔
480
        else:
481
            label = self.field.label
14✔
482
        if self.layout == "horizontal" and not label:
14✔
483
            return mark_safe("&#160;")
14✔
484
        return label
14✔
485

486
    def add_label(self, html):
14✔
487
        label = self.get_label()
14✔
488
        if label:
14✔
489
            html = render_label(label, label_for=self.field.id_for_label, label_class=self.get_label_class()) + html
14✔
490
        return html
14✔
491

492
    def get_form_group_class(self):
14✔
493
        form_group_class = self.form_group_class
14✔
494
        if self.field.errors:
14✔
495
            if self.error_css_class:
14✔
496
                form_group_class = add_css_class(form_group_class, self.error_css_class)
14✔
497
        else:
498
            if self.field.form.is_bound:
14✔
499
                form_group_class = add_css_class(form_group_class, self.success_css_class)
14✔
500
        if self.field.field.required and self.required_css_class:
14✔
501
            form_group_class = add_css_class(form_group_class, self.required_css_class)
14✔
502
        if self.layout == "horizontal":
14✔
503
            form_group_class = add_css_class(form_group_class, "row")
14✔
504
        return form_group_class
14✔
505

506
    def wrap_label_and_field(self, html):
14✔
507
        return render_form_group(html, self.get_form_group_class())
14✔
508

509
    def _render(self):
14✔
510
        # See if we're not excluded
511
        if self.field.name in self.exclude.replace(" ", "").split(","):
14✔
512
            return ""
14✔
513
        # Hidden input requires no special treatment
514
        if self.field.is_hidden:
14✔
515
            return text_value(self.field)
14✔
516
        # Render the widget
517
        self.add_widget_attrs()
14✔
518
        html = self.field.as_widget(attrs=self.widget.attrs)
14✔
519
        self.restore_widget_attrs()
14✔
520
        # Start post render
521
        html = self.post_widget_render(html)
14✔
522
        html = self.append_to_checkbox_field(html)
14✔
523
        html = self.wrap_widget(html)
14✔
524
        html = self.make_input_group(html)
14✔
525
        html = self.append_to_field(html)
14✔
526
        html = self.wrap_field(html)
14✔
527
        html = self.add_label(html)
14✔
528
        html = self.wrap_label_and_field(html)
14✔
529
        return html
14✔
530

531

532
class InlineFieldRenderer(FieldRenderer):
14✔
533
    """Inline field renderer."""
534

535
    def add_error_attrs(self):
14✔
536
        field_title = self.widget.attrs.get("title", "")
×
537
        field_title += " " + " ".join([strip_tags(e) for e in self.field_errors])
×
538
        self.widget.attrs["title"] = field_title.strip()
×
539

540
    def add_widget_attrs(self):
14✔
541
        super().add_widget_attrs()
×
542
        self.add_error_attrs()
×
543

544
    def append_to_field(self, html):
14✔
545
        return html
×
546

547
    def get_field_class(self):
14✔
548
        return self.field_class
×
549

550
    def get_label_class(self):
14✔
551
        return add_css_class(self.label_class, "sr-only")
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc