• 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.09
/src/bootstrap4/utils.py
1
import re
11✔
2
from collections.abc import Mapping
11✔
3
from urllib.parse import parse_qs, urlparse, urlunparse
11✔
4

5
from django.forms.utils import flatatt
11✔
6
from django.template.base import FilterExpression, TemplateSyntaxError, Variable, VariableDoesNotExist, kwarg_re
11✔
7
from django.template.loader import get_template
11✔
8
from django.utils.encoding import force_str
11✔
9
from django.utils.html import format_html
11✔
10
from django.utils.http import urlencode
11✔
11
from django.utils.safestring import mark_safe
11✔
12

13
from .text import text_value
11✔
14

15
# RegEx for quoted string
16
QUOTED_STRING = re.compile(r'^["\'](?P<noquotes>.+)["\']$')
11✔
17

18

19
def handle_var(value, context):
11✔
20
    """Handle template tag variable."""
21
    # Resolve FilterExpression and Variable immediately
22
    if isinstance(value, FilterExpression) or isinstance(value, Variable):
11!
23
        return value.resolve(context)
11✔
24
    # Return quoted strings unquoted
25
    # http://djangosnippets.org/snippets/886
26
    stringval = QUOTED_STRING.search(value)
×
27
    if stringval:
×
28
        return stringval.group("noquotes")
×
29
    # Resolve variable or return string value
30
    try:
×
31
        return Variable(value).resolve(context)
×
32
    except VariableDoesNotExist:
×
33
        return value
×
34

35

36
def parse_token_contents(parser, token):
11✔
37
    """Parse template tag contents."""
38
    bits = token.split_contents()
11✔
39
    tag = bits.pop(0)
11✔
40
    args = []
11✔
41
    kwargs = {}
11✔
42
    asvar = None
11✔
43
    if len(bits) >= 2 and bits[-2] == "as":
11!
44
        asvar = bits[-1]
×
45
        bits = bits[:-2]
×
46
    if len(bits):
11!
47
        for bit in bits:
11✔
48
            match = kwarg_re.match(bit)
11✔
49
            if not match:
11!
50
                raise TemplateSyntaxError(f'Malformed arguments to tag "{tag}"')
×
51
            name, value = match.groups()
11✔
52
            if name:
11!
53
                kwargs[name] = parser.compile_filter(value)
11✔
54
            else:
55
                args.append(parser.compile_filter(value))
×
56
    return {"tag": tag, "args": args, "kwargs": kwargs, "asvar": asvar}
11✔
57

58

59
def split_css_classes(css_classes):
11✔
60
    """Turn string into a list of CSS classes."""
61
    classes_list = text_value(css_classes).split(" ")
11✔
62
    return [c for c in classes_list if c]
11✔
63

64

65
def add_css_class(css_classes, css_class, prepend=False):
11✔
66
    """Add a CSS class to a string of CSS classes."""
67
    classes_list = split_css_classes(css_classes)
11✔
68
    classes_to_add = [c for c in split_css_classes(css_class) if c not in classes_list]
11✔
69
    if prepend:
11✔
70
        classes_list = classes_to_add + classes_list
11✔
71
    else:
72
        classes_list += classes_to_add
11✔
73
    return " ".join(classes_list)
11✔
74

75

76
def remove_css_class(css_classes, css_class):
11✔
77
    """Remove a CSS class from a string of CSS classes."""
78
    remove = set(split_css_classes(css_class))
×
79
    classes_list = [c for c in split_css_classes(css_classes) if c not in remove]
×
80
    return " ".join(classes_list)
×
81

82

83
def render_script_tag(url):
11✔
84
    """Build a script tag."""
85
    url_dict = sanitize_url_dict(url)
11✔
86
    url_dict.setdefault("src", url_dict.pop("url", None))
11✔
87
    return render_tag("script", url_dict)
11✔
88

89

90
def render_link_tag(url, rel="stylesheet", media=None):
11✔
91
    """Build a link tag."""
92
    url_dict = sanitize_url_dict(url, url_attr="href")
11✔
93
    url_dict.setdefault("href", url_dict.pop("url", None))
11✔
94
    url_dict["rel"] = rel
11✔
95
    if media:
11!
96
        url_dict["media"] = media
×
97
    return render_tag("link", attrs=url_dict, close=False)
11✔
98

99

100
def render_tag(tag, attrs=None, content=None, close=True):
11✔
101
    """Render a HTML tag."""
102
    builder = "<{tag}{attrs}>{content}"
11✔
103
    if content or close:
11✔
104
        builder += "</{tag}>"
11✔
105
    return format_html(builder, tag=tag, attrs=mark_safe(flatatt(attrs)) if attrs else "", content=text_value(content))
11✔
106

107

108
def render_template_file(template, context=None):
11✔
109
    """Render a Template to unicode."""
110
    assert isinstance(context, Mapping)
11✔
111
    template = get_template(template)
11✔
112
    return template.render(context)
11✔
113

114

115
def url_replace_param(url, name, value):
11✔
116
    """Replace a GET parameter in an URL."""
117
    url_components = urlparse(force_str(url))
11✔
118

119
    params = parse_qs(url_components.query)
11✔
120

121
    if value is None:
11✔
122
        del params[name]
11✔
123
    else:
124
        params[name] = value
11✔
125

126
    return mark_safe(
11✔
127
        urlunparse(
128
            [
129
                url_components.scheme,
130
                url_components.netloc,
131
                url_components.path,
132
                url_components.params,
133
                urlencode(params, doseq=True),
134
                url_components.fragment,
135
            ]
136
        )
137
    )
138

139

140
def sanitize_url_dict(url, url_attr="src"):
11✔
141
    """Sanitize url dict as used in django-bootstrap4 settings."""
142
    if isinstance(url, str):
11✔
143
        return {url_attr: url}
11✔
144
    return url.copy()
11✔
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