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

zostera / django-bootstrap4 / 14969570320

12 May 2025 10:11AM CUT coverage: 90.734%. Remained the same
14969570320

Pull #818

github

web-flow
Merge 4584b0cfb into f9a52afbd
Pull Request #818: Bump dependabot/fetch-metadata from 2.2.0 to 2.4.0

233 of 302 branches covered (77.15%)

Branch coverage included in aggregate %.

1275 of 1360 relevant lines covered (93.75%)

10.31 hits per line

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

79.85
/src/bootstrap4/templatetags/bootstrap4.py
1
from math import floor
11✔
2
from urllib.parse import parse_qs, urlparse, urlunparse
11✔
3

4
from django import template
11✔
5
from django.contrib.messages import constants as message_constants
11✔
6
from django.template import Context
11✔
7
from django.utils.http import urlencode
11✔
8
from django.utils.safestring import mark_safe
11✔
9

10
from ..bootstrap import css_url, get_bootstrap_setting, javascript_url, jquery_slim_url, jquery_url, theme_url
11✔
11
from ..components import render_alert
11✔
12
from ..forms import (
11✔
13
    render_button,
14
    render_field,
15
    render_field_and_label,
16
    render_form,
17
    render_form_errors,
18
    render_form_group,
19
    render_formset,
20
    render_formset_errors,
21
    render_label,
22
)
23
from ..utils import (
11✔
24
    handle_var,
25
    parse_token_contents,
26
    render_link_tag,
27
    render_script_tag,
28
    render_tag,
29
    render_template_file,
30
    url_replace_param,
31
)
32

33
MESSAGE_LEVEL_CLASSES = {
11✔
34
    message_constants.DEBUG: "alert alert-warning",
35
    message_constants.INFO: "alert alert-info",
36
    message_constants.SUCCESS: "alert alert-success",
37
    message_constants.WARNING: "alert alert-warning",
38
    message_constants.ERROR: "alert alert-danger",
39
}
40

41
register = template.Library()
11✔
42

43

44
@register.filter
11✔
45
def bootstrap_setting(value):
11✔
46
    """
47
    Get a setting.
48

49
    A simple way to read bootstrap settings in a template.
50
    Please consider this filter private for now, do not use it in your own templates.
51
    """
52
    return get_bootstrap_setting(value)
11✔
53

54

55
@register.filter
11✔
56
def bootstrap_message_classes(message):
11✔
57
    """Return the message classes for a message."""
58
    extra_tags = None
11✔
59
    try:
11✔
60
        extra_tags = message.extra_tags
11✔
61
    except AttributeError:
×
62
        pass
×
63
    if not extra_tags:
11!
64
        extra_tags = ""
11✔
65
    classes = [extra_tags]
11✔
66
    try:
11✔
67
        level = message.level
11✔
68
    except AttributeError:
×
69
        pass
×
70
    else:
71
        try:
11✔
72
            classes.append(MESSAGE_LEVEL_CLASSES[level])
11✔
73
        except KeyError:
11✔
74
            classes.append("alert alert-danger")
11✔
75
    return " ".join(classes).strip()
11✔
76

77

78
@register.simple_tag
11✔
79
def bootstrap_jquery_url():
11✔
80
    """
81
    Return url to full version of jQuery.
82

83
    **Tag name**::
84

85
        bootstrap_jquery_url
86

87
    Return the full url to jQuery plugin to use
88

89
    Default value: ``https://code.jquery.com/jquery-3.2.1.min.js``
90

91
    This value is configurable, see Settings section
92

93
    **Usage**::
94

95
        {% bootstrap_jquery_url %}
96

97
    **Example**::
98

99
        {% bootstrap_jquery_url %}
100
    """
101
    return jquery_url()
×
102

103

104
@register.simple_tag
11✔
105
def bootstrap_jquery_slim_url():
11✔
106
    """
107
    Return url to slim version of jQuery.
108

109
    **Tag name**::
110

111
        bootstrap_jquery_slim_url
112

113
    Return the full url to slim jQuery plugin to use
114

115
    Default value: ``https://code.jquery.com/jquery-3.2.1.slim.min.js``
116

117
    This value is configurable, see Settings section
118

119
    **Usage**::
120

121
        {% bootstrap_jquery_slim_url %}
122

123
    **Example**::
124

125
        {% bootstrap_jquery_slim_url %}
126
    """
127
    return jquery_slim_url()
×
128

129

130
@register.simple_tag
11✔
131
def bootstrap_javascript_url():
11✔
132
    """
133
    Return the full url to the Bootstrap JavaScript library.
134

135
    Default value: ``None``
136

137
    This value is configurable, see Settings section
138

139
    **Tag name**::
140

141
        bootstrap_javascript_url
142

143
    **Usage**::
144

145
        {% bootstrap_javascript_url %}
146

147
    **Example**::
148

149
        {% bootstrap_javascript_url %}
150
    """
151
    return javascript_url()
11✔
152

153

154
@register.simple_tag
11✔
155
def bootstrap_css_url():
11✔
156
    """
157
    Return the full url to the Bootstrap CSS library.
158

159
    Default value: ``None``
160

161
    This value is configurable, see Settings section
162

163
    **Tag name**::
164

165
        bootstrap_css_url
166

167
    **Usage**::
168

169
        {% bootstrap_css_url %}
170

171
    **Example**::
172

173
        {% bootstrap_css_url %}
174
    """
175
    return css_url()
11✔
176

177

178
@register.simple_tag
11✔
179
def bootstrap_theme_url():
11✔
180
    """
181
    Return the full url to a Bootstrap theme CSS library.
182

183
    Default value: ``None``
184

185
    This value is configurable, see Settings section
186

187
    **Tag name**::
188

189
        bootstrap_theme_url
190

191
    **Usage**::
192

193
        {% bootstrap_theme_url %}
194

195
    **Example**::
196

197
        {% bootstrap_theme_url %}
198
    """
199
    return theme_url()
11✔
200

201

202
@register.simple_tag
11✔
203
def bootstrap_css():
11✔
204
    """
205
    Return HTML for Bootstrap CSS. If no CSS url is available, return empty string.
206

207
    Default value: ``None``
208

209
    This value is configurable, see Settings section
210

211
    **Tag name**::
212

213
        bootstrap_css
214

215
    **Usage**::
216

217
        {% bootstrap_css %}
218

219
    **Example**::
220

221
        {% bootstrap_css %}
222
    """
223
    rendered_urls = []
11✔
224
    if bootstrap_css_url():
11!
225
        rendered_urls.append(render_link_tag(bootstrap_css_url()))
11✔
226
    if bootstrap_theme_url():
11!
227
        rendered_urls.append(render_link_tag(bootstrap_theme_url()))
11✔
228
    return mark_safe("".join([url for url in rendered_urls]))
11✔
229

230

231
@register.simple_tag
11✔
232
def bootstrap_jquery(jquery=True):
11✔
233
    """
234
    Return HTML for jQuery tag.
235

236
    Adjust the url dict in settings.
237
    If no url is returned, we don't want this statement to return any HTML. This is intended behavior.
238

239
    This value is configurable, see Settings section. Note that any value that evaluates to True and is
240
    not "slim" will be interpreted as True.
241

242
    **Tag name**::
243

244
        bootstrap_jquery
245

246
    **Parameters**::
247

248
        :jquery: False|"slim"|True (default=True)
249

250
    **Usage**::
251

252
        {% bootstrap_jquery %}
253

254
    **Example**::
255

256
        {% bootstrap_jquery jquery='slim' %}
257
    """
258
    if not jquery:
11✔
259
        return ""
11✔
260
    elif jquery == "slim":
11✔
261
        jquery = get_bootstrap_setting("jquery_slim_url")
11✔
262
    else:
263
        jquery = get_bootstrap_setting("jquery_url")
11✔
264

265
    if isinstance(jquery, str):
11✔
266
        jquery = dict(src=jquery)
11✔
267
    else:
268
        jquery = jquery.copy()
11✔
269
        jquery.setdefault("src", jquery.pop("url", None))
11✔
270

271
    return render_tag("script", attrs=jquery)
11✔
272

273

274
@register.simple_tag
11✔
275
def bootstrap_javascript(jquery=False):
11✔
276
    """
277
    Return HTML for Bootstrap JavaScript.
278

279
    Adjust url in settings.
280
    If no url is returned, we don't want this statement to return any HTML. This is intended behavior.
281

282
    Default value: False
283

284
    This value is configurable, see Settings section. Note that any value that evaluates to True and is
285
    not "slim" will be interpreted as True.
286

287
    **Tag name**::
288

289
        bootstrap_javascript
290

291
    **Parameters**::
292

293
        :jquery: False|"slim"|True (default=False)
294

295
    **Usage**::
296

297
        {% bootstrap_javascript %}
298

299
    **Example**::
300

301
        {% bootstrap_javascript jquery="slim" %}
302
    """
303
    # List of JS tags to include
304
    javascript_tags = []
11✔
305

306
    # Get jquery value from setting or leave default.
307
    jquery = jquery or get_bootstrap_setting("include_jquery", False)
11✔
308

309
    # Include jQuery if the option is passed
310
    if jquery:
11✔
311
        javascript_tags.append(bootstrap_jquery(jquery=jquery))
11✔
312

313
    # Bootstrap 4 JavaScript
314
    bootstrap_js_url = bootstrap_javascript_url()
11✔
315
    if bootstrap_js_url:
11!
316
        javascript_tags.append(render_script_tag(bootstrap_js_url))
11✔
317

318
    # Join and return
319
    return mark_safe("\n".join(javascript_tags))
11✔
320

321

322
@register.simple_tag
11✔
323
def bootstrap_formset(*args, **kwargs):
11✔
324
    """
325
    Render a formset.
326

327
    **Tag name**::
328

329
        bootstrap_formset
330

331
    **Parameters**::
332

333
        formset
334
            The formset that is being rendered
335

336

337
        See bootstrap_field_ for other arguments
338

339
    **Usage**::
340

341
        {% bootstrap_formset formset %}
342

343
    **Example**::
344

345
        {% bootstrap_formset formset layout='horizontal' %}
346
    """
347
    return render_formset(*args, **kwargs)
11✔
348

349

350
@register.simple_tag
11✔
351
def bootstrap_formset_errors(*args, **kwargs):
11✔
352
    """
353
    Render formset errors.
354

355
    **Tag name**::
356

357
        bootstrap_formset_errors
358

359
    **Parameters**::
360

361
        formset
362
            The formset that is being rendered
363

364
        layout
365
            Context value that is available in the template ``bootstrap4/form_errors.html`` as ``layout``.
366

367
    **Usage**::
368

369
        {% bootstrap_formset_errors formset %}
370

371
    **Example**::
372

373
        {% bootstrap_formset_errors formset layout='inline' %}
374
    """
375
    return render_formset_errors(*args, **kwargs)
×
376

377

378
@register.simple_tag
11✔
379
def bootstrap_form(*args, **kwargs):
11✔
380
    """
381
    Render a form.
382

383
    **Tag name**::
384

385
        bootstrap_form
386

387
    **Parameters**::
388

389
        form
390
            The form that is to be rendered
391

392
        exclude
393
            A list of field names (comma separated) that should not be rendered
394
            E.g. exclude=subject,bcc
395

396
        alert_error_type
397
            Control which type of errors should be rendered in global form alert.
398

399
                One of the following values:
400

401
                    * ``'all'``
402
                    * ``'fields'``
403
                    * ``'non_fields'``
404

405
                :default: ``'non_fields'``
406

407
        See bootstrap_field_ for other arguments
408

409
    **Usage**::
410

411
        {% bootstrap_form form %}
412

413
    **Example**::
414

415
        {% bootstrap_form form layout='inline' %}
416
    """
417
    return render_form(*args, **kwargs)
11✔
418

419

420
@register.simple_tag
11✔
421
def bootstrap_form_errors(*args, **kwargs):
11✔
422
    """
423
    Render form errors.
424

425
    **Tag name**::
426

427
        bootstrap_form_errors
428

429
    **Parameters**::
430

431
        form
432
            The form that is to be rendered
433

434
        type
435
            Control which type of errors should be rendered.
436

437
            One of the following values:
438

439
                * ``'all'``
440
                * ``'fields'``
441
                * ``'non_fields'``
442

443
            :default: ``'all'``
444

445
        layout
446
            Context value that is available in the template ``bootstrap4/form_errors.html`` as ``layout``.
447

448
    **Usage**::
449

450
        {% bootstrap_form_errors form %}
451

452
    **Example**::
453

454
        {% bootstrap_form_errors form layout='inline' %}
455
    """
456
    return render_form_errors(*args, **kwargs)
×
457

458

459
@register.simple_tag
11✔
460
def bootstrap_field(*args, **kwargs):
11✔
461
    """
462
    Render a field.
463

464
    **Tag name**::
465

466
        bootstrap_field
467

468
    **Parameters**::
469

470

471
        field
472
            The form field to be rendered
473

474
        layout
475
            If set to ``'horizontal'`` then the field and label will be rendered side-by-side, as long as there
476
            is no ``field_class`` set as well.
477

478
        form_group_class
479
            CSS class of the ``div`` that wraps the field and label.
480

481
            :default: ``'form-group'``
482

483
        field_class
484
            CSS class of the ``div`` that wraps the field.
485

486
        label_class
487
            CSS class of the ``label`` element. Will always have ``control-label`` as the last CSS class.
488

489
        form_check_class
490
            CSS class of the ``div`` element wrapping the label and input when rendering checkboxes and radio buttons.
491

492
        show_help
493
            Show the field's help text, if the field has help text.
494

495
            :default: ``True``
496

497
        show_label
498
            Whether the show the label of the field.
499

500
                * ``True``
501
                * ``False``/``'sr-only'``
502
                * ``'skip'``
503

504
            :default: ``True``
505

506
        exclude
507
            A list of field names that should not be rendered
508

509
        size
510
            Controls the size of the rendered ``div.form-group`` through the use of CSS classes.
511

512
            One of the following values:
513

514
                * ``'small'``
515
                * ``'medium'``
516
                * ``'large'``
517

518
        placeholder
519
            Sets the placeholder text of a textbox
520

521
        horizontal_label_class
522
            Class used on the label when the ``layout`` is set to ``horizontal``.
523

524
            :default: ``'col-md-3'``. Can be changed in :doc:`settings`
525

526
        horizontal_field_class
527
            Class used on the field when the ``layout`` is set to ``horizontal``.
528

529
            :default: ``'col-md-9'``. Can be changed in :doc:`settings`
530

531
        addon_before
532
            Text that should be prepended to the form field. Can also be an icon, e.g.
533
            ``'<span class="glyphicon glyphicon-calendar"></span>'``
534

535
            See the `Bootstrap docs <http://getbootstrap.com/components/#input-groups-basic>` for more examples.
536

537
        addon_after
538
            Text that should be appended to the form field. Can also be an icon, e.g.
539
            ``'<span class="glyphicon glyphicon-calendar"></span>'``
540

541
            See the `Bootstrap docs <http://getbootstrap.com/components/#input-groups-basic>` for more examples.
542

543
        addon_before_class
544
            Class used on the span when ``addon_before`` is used.
545

546
            One of the following values:
547

548
                * ``'input-group-text'``
549
                * ``None``
550

551
            Set to None to disable the span inside the addon. (for use with buttons)
552

553
            :default: ``input-group-text``
554

555
        addon_after_class
556
            Class used on the span when ``addon_after`` is used.
557

558
            One of the following values:
559

560
                * ``'input-group-text'``
561
                * ``None``
562

563
            Set to None to disable the span inside the addon. (for use with buttons)
564

565
            :default: ``input-group-text``
566

567
        error_css_class
568
            CSS class used when the field has an error
569

570
            :default: ``'has-error'``. Can be changed :doc:`settings`
571

572
        required_css_class
573
            CSS class used on the ``div.form-group`` to indicate a field is required
574

575
            :default: ``''``. Can be changed :doc:`settings`
576

577
        bound_css_class
578
            CSS class used when the field is bound
579

580
            :default: ``'has-success'``. Can be changed :doc:`settings`
581

582
    **Usage**::
583

584
        {% bootstrap_field field %}
585

586
    **Example**::
587

588
        {% bootstrap_field field show_label=False %}
589
    """
590
    return render_field(*args, **kwargs)
11✔
591

592

593
@register.simple_tag()
11✔
594
def bootstrap_label(*args, **kwargs):
11✔
595
    """
596
    Render a label.
597

598
    **Tag name**::
599

600
        bootstrap_label
601

602
    **Parameters**::
603

604
        content
605
            The label's text
606

607
        label_for
608
            The value that will be in the ``for`` attribute of the rendered ``<label>``
609

610
        label_class
611
            The CSS class for the rendered ``<label>``
612

613
        label_title
614
            The value that will be in the ``title`` attribute of the rendered ``<label>``
615

616
    **Usage**::
617

618
        {% bootstrap_label content %}
619

620
    **Example**::
621

622
        {% bootstrap_label "Email address" label_for="exampleInputEmail1" %}
623
    """
624
    return render_label(*args, **kwargs)
11✔
625

626

627
@register.simple_tag
11✔
628
def bootstrap_button(*args, **kwargs):
11✔
629
    """
630
    Render a button.
631

632
    **Tag name**::
633

634
        bootstrap_button
635

636
    **Parameters**::
637

638
        content
639
            The text to be displayed in the button
640

641
        button_type
642
            Optional field defining what type of button this is.
643

644
            Accepts one of the following values:
645

646
                * ``'submit'``
647
                * ``'reset'``
648
                * ``'button'``
649
                * ``'link'``
650

651
        button_class
652
            The class of button to use. If none is given, btn-primary will be used.
653

654
        extra_classes
655
            Any extra CSS classes that should be added to the button.
656

657
        size
658
            Optional field to control the size of the button.
659

660
            Accepts one of the following values:
661

662
                * ``'xs'``
663
                * ``'sm'``
664
                * ``'small'``
665
                * ``'md'``
666
                * ``'medium'``
667
                * ``'lg'``
668
                * ``'large'``
669

670

671
        href
672
            Render the button as an ``<a>`` element. The ``href`` attribute is set with this value.
673
            If a ``button_type`` other than ``link`` is defined, specifying a ``href`` will throw a
674
            ``ValueError`` exception.
675

676
        name
677
            Value of the ``name`` attribute of the rendered element.
678

679
        value
680
            Value of the ``value`` attribute of the rendered element.
681

682
    **Usage**::
683

684
        {% bootstrap_button content %}
685

686
    **Example**::
687

688
        {% bootstrap_button "Save" button_type="submit" button_class="btn-primary" %}
689
    """
690
    return render_button(*args, **kwargs)
11✔
691

692

693
@register.simple_tag
11✔
694
def bootstrap_alert(content, alert_type="info", dismissible=True):
11✔
695
    """
696
    Render an alert.
697

698
    **Tag name**::
699

700
        bootstrap_alert
701

702
    **Parameters**::
703

704
        content
705
            HTML content of alert
706

707
        alert_type
708
            * ``'info'``
709
            * ``'warning'``
710
            * ``'danger'``
711
            * ``'success'``
712

713
            :default: ``'info'``
714

715
        dismissible
716
            boolean, is alert dismissible
717

718
            :default: ``True``
719

720
    **Usage**::
721

722
        {% bootstrap_alert content %}
723

724
    **Example**::
725

726
        {% bootstrap_alert "Something went wrong" alert_type='danger' %}
727
    """
728
    return render_alert(content, alert_type, dismissible)
11✔
729

730

731
@register.tag("buttons")
11✔
732
def bootstrap_buttons(parser, token):
11✔
733
    """
734
    Render buttons for form.
735

736
    **Tag name**::
737

738
        buttons
739

740
    **Parameters**::
741

742
        submit
743
            Text for a submit button
744

745
        reset
746
            Text for a reset button
747

748
    **Usage**::
749

750
        {% buttons %}{% endbuttons %}
751

752
    **Example**::
753

754
        {% buttons submit='OK' reset="Cancel" %}{% endbuttons %}
755
    """
756
    kwargs = parse_token_contents(parser, token)
11✔
757
    kwargs["nodelist"] = parser.parse(("endbuttons",))
11✔
758
    parser.delete_first_token()
11✔
759
    return ButtonsNode(**kwargs)
11✔
760

761

762
class ButtonsNode(template.Node):
11✔
763
    def __init__(self, nodelist, args, kwargs, asvar, **kwargs2):
11✔
764
        self.nodelist = nodelist
11✔
765
        self.args = args
11✔
766
        self.kwargs = kwargs
11✔
767
        self.asvar = asvar
11✔
768

769
    def render(self, context):
11✔
770
        output_kwargs = {}
11✔
771
        for key in self.kwargs:
11✔
772
            output_kwargs[key] = handle_var(self.kwargs[key], context)
11✔
773
        buttons = []
11✔
774
        submit = output_kwargs.get("submit", None)
11✔
775
        reset = output_kwargs.get("reset", None)
11✔
776
        if submit:
11!
777
            buttons.append(bootstrap_button(submit, "submit"))
×
778
        if reset:
11!
779
            buttons.append(bootstrap_button(reset, "reset"))
×
780
        buttons = " ".join(buttons) + self.nodelist.render(context)
11✔
781
        output_kwargs.update({"label": None, "field": buttons})
11✔
782
        css_class = output_kwargs.pop("form_group_class", "form-group")
11✔
783
        output = render_form_group(render_field_and_label(**output_kwargs), css_class=css_class)
11✔
784
        if self.asvar:
11!
785
            context[self.asvar] = output
×
786
            return ""
×
787
        else:
788
            return output
11✔
789

790

791
@register.simple_tag(takes_context=True)
11✔
792
def bootstrap_messages(context, *args, **kwargs):
11✔
793
    """
794
    Show django.contrib.messages Messages in Bootstrap alert containers.
795

796
    In order to make the alerts dismissible (with the close button),
797
    we have to set the jquery parameter too when using the
798
    bootstrap_javascript tag.
799

800
    Uses the template ``bootstrap4/messages.html``.
801

802
    **Tag name**::
803

804
        bootstrap_messages
805

806
    **Parameters**::
807

808
        None.
809

810
    **Usage**::
811

812
        {% bootstrap_messages %}
813

814
    **Example**::
815

816
        {% bootstrap_javascript jquery=True %}
817
        {% bootstrap_messages %}
818
    """
819
    # Force Context to dict
820
    if isinstance(context, Context):
11!
821
        context = context.flatten()
11✔
822
    context.update({"message_constants": message_constants})
11✔
823
    return render_template_file("bootstrap4/messages.html", context=context)
11✔
824

825

826
@register.inclusion_tag("bootstrap4/pagination.html")
11✔
827
def bootstrap_pagination(page, **kwargs):
11✔
828
    """
829
    Render pagination for a page.
830

831
    **Tag name**::
832

833
        bootstrap_pagination
834

835
    **Parameters**::
836

837
        page
838
            The page of results to show.
839

840
        pages_to_show
841
            Number of pages in total
842

843
            :default: ``11``
844

845
        url
846
            URL to navigate to for pagination forward and pagination back.
847

848
            :default: ``None``
849

850
        size
851
            Controls the size of the pagination through CSS. Defaults to being normal sized.
852

853
            One of the following:
854

855
                * ``'small'``
856
                * ``'large'``
857

858
            :default: ``None``
859

860
        extra
861
            Any extra page parameters.
862

863
            :default: ``None``
864

865
        parameter_name
866
            Name of the paging URL parameter.
867

868
            :default: ``'page'``
869

870
    **Usage**::
871

872
        {% bootstrap_pagination page %}
873

874
    **Example**::
875

876
        {% bootstrap_pagination lines url="/pagination?page=1" size="large" %}
877

878
    **Tip**::
879

880
      If you want to repeat the query string arguments in subsequent pagination links,
881
      use the "extra" parameter with "request.GET.urlencode":
882

883
        {% bootstrap_pagination page_obj extra=request.GET.urlencode %}
884
    """
885
    pagination_kwargs = kwargs.copy()
11✔
886
    pagination_kwargs["page"] = page
11✔
887
    return get_pagination_context(**pagination_kwargs)
11✔
888

889

890
@register.simple_tag
11✔
891
def bootstrap_url_replace_param(url, name, value):
11✔
892
    return url_replace_param(url, name, value)
11✔
893

894

895
def get_pagination_context(
11✔
896
    page, pages_to_show=11, url=None, size=None, justify_content=None, extra=None, parameter_name="page"
897
):
898
    """Generate Bootstrap pagination context from a page object."""
899
    pages_to_show = int(pages_to_show)
11✔
900
    if pages_to_show < 1:
11!
901
        raise ValueError(f"Pagination pages_to_show should be a positive integer, you specified {pages_to_show}.")
×
902
    num_pages = page.paginator.num_pages
11✔
903
    current_page = page.number
11✔
904
    half_page_num = int(floor(pages_to_show / 2))
11✔
905
    if half_page_num < 0:
11!
906
        half_page_num = 0
×
907
    first_page = current_page - half_page_num
11✔
908
    if first_page <= 1:
11!
909
        first_page = 1
11✔
910
    if first_page > 1:
11!
911
        pages_back = first_page - half_page_num
×
912
        if pages_back < 1:
×
913
            pages_back = 1
×
914
    else:
915
        pages_back = None
11✔
916
    last_page = first_page + pages_to_show - 1
11✔
917
    if pages_back is None:
11!
918
        last_page += 1
11✔
919
    if last_page > num_pages:
11!
920
        last_page = num_pages
11✔
921
    if last_page < num_pages:
11!
922
        pages_forward = last_page + half_page_num
×
923
        if pages_forward > num_pages:
×
924
            pages_forward = num_pages
×
925
    else:
926
        pages_forward = None
11✔
927
        if first_page > 1:
11!
928
            first_page -= 1
×
929
        if pages_back is not None and pages_back > 1:
11!
930
            pages_back -= 1
×
931
        else:
932
            pages_back = None
11✔
933
    pages_shown = []
11✔
934
    for i in range(first_page, last_page + 1):
11✔
935
        pages_shown.append(i)
11✔
936

937
    # parse the url
938
    parts = urlparse(url or "")
11✔
939
    params = parse_qs(parts.query)
11✔
940

941
    # append extra querystring parameters to the url.
942
    if extra:
11✔
943
        params.update(parse_qs(extra))
11✔
944

945
    # build url again.
946
    url = urlunparse(
11✔
947
        [parts.scheme, parts.netloc, parts.path, parts.params, urlencode(params, doseq=True), parts.fragment]
948
    )
949

950
    # Set CSS classes, see http://getbootstrap.com/components/#pagination
951
    pagination_css_classes = ["pagination"]
11✔
952
    if size == "small":
11!
953
        pagination_css_classes.append("pagination-sm")
×
954
    elif size == "large":
11!
955
        pagination_css_classes.append("pagination-lg")
×
956

957
    if justify_content == "start":
11!
958
        pagination_css_classes.append("justify-content-start")
×
959
    elif justify_content == "center":
11!
960
        pagination_css_classes.append("justify-content-center")
×
961
    elif justify_content == "end":
11!
962
        pagination_css_classes.append("justify-content-end")
×
963

964
    return {
11✔
965
        "bootstrap_pagination_url": url,
966
        "num_pages": num_pages,
967
        "current_page": current_page,
968
        "first_page": first_page,
969
        "last_page": last_page,
970
        "pages_shown": pages_shown,
971
        "pages_back": pages_back,
972
        "pages_forward": pages_forward,
973
        "pagination_css_classes": " ".join(pagination_css_classes),
974
        "parameter_name": parameter_name,
975
    }
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