Skip to content

Commit

Permalink
Remove base_url, allow specification of crossorigin and integrity in …
Browse files Browse the repository at this point in the history
…urls (fixes #443)
  • Loading branch information
dyve committed Aug 30, 2018
1 parent 18b3ec8 commit 14a57b5
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 62 deletions.
28 changes: 13 additions & 15 deletions bootstrap3/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@

# Default settings
BOOTSTRAP3_DEFAULTS = {
"jquery_url": "//code.jquery.com/jquery.min.js",
"base_url": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/",
"css_url": None,
"css_url": {
"url": "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",
"integrity": "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u",
"crossorigin": "anonymous",
},
"theme_url": None,
"javascript_url": None,
"javascript_url": {
"url": "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js",
"integrity": "sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa",
"crossorigin": "anonymous",
},
"jquery_url": "//code.jquery.com/jquery.min.js",
"javascript_in_head": False,
"include_jquery": False,
"horizontal_label_class": "col-md-3",
Expand Down Expand Up @@ -51,13 +58,6 @@ def get_bootstrap_setting(setting, default=None):
return BOOTSTRAP3.get(setting, default)


def bootstrap_url(postfix):
"""
Prefix a relative url with the bootstrap base url
"""
return get_bootstrap_setting("base_url") + postfix


def jquery_url():
"""
Return the full url to jQuery file to use
Expand All @@ -69,16 +69,14 @@ def javascript_url():
"""
Return the full url to the Bootstrap JavaScript file
"""
url = get_bootstrap_setting("javascript_url")
return url if url else bootstrap_url("js/bootstrap.min.js")
return get_bootstrap_setting("javascript_url")


def css_url():
"""
Return the full url to the Bootstrap CSS file
"""
url = get_bootstrap_setting("css_url")
return url if url else bootstrap_url("css/bootstrap.min.css")
return get_bootstrap_setting("css_url")


def theme_url():
Expand Down
12 changes: 8 additions & 4 deletions bootstrap3/templatetags/bootstrap3.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
render_formset_errors,
)
from ..text import force_text
from ..utils import handle_var, parse_token_contents, url_replace_param
from ..utils import (
handle_var,
parse_token_contents,
url_replace_param,
render_script_tag,
)
from ..utils import render_link_tag, render_tag, render_template_file

MESSAGE_LEVEL_CLASSES = {
Expand Down Expand Up @@ -243,11 +248,10 @@ def bootstrap_javascript(jquery=None):
if jquery:
url = bootstrap_jquery_url()
if url:
javascript += render_tag("script", attrs={"src": url})
javascript += render_script_tag(url)
url = bootstrap_javascript_url()
if url:
attrs = {"src": url}
javascript += render_tag("script", attrs=attrs)
javascript += render_script_tag(url)
return mark_safe(javascript)


Expand Down
44 changes: 33 additions & 11 deletions bootstrap3/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from django.contrib.messages import constants as DEFAULT_MESSAGE_LEVELS
from django.forms.formsets import formset_factory
from django.template import engines
from django.test import TestCase, override_settings
from django.test import TestCase

from .bootstrap import DBS3_SET_REQUIRED_SET_DISABLED
from .bootstrap import DBS3_SET_REQUIRED_SET_DISABLED, get_bootstrap_setting
from .exceptions import BootstrapError
from .text import text_value, text_concat
from .utils import add_css_class, render_tag
from .utils import add_css_class, render_tag, url_to_attrs_dict

try:
from html.parser import HTMLParser
Expand Down Expand Up @@ -215,18 +215,24 @@ def test_settings(self):

def test_bootstrap_javascript_tag(self):
res = render_template_with_form("{% bootstrap_javascript %}")
self.assertEqual(
res.strip(),
'<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>',
javascript_url = get_bootstrap_setting("javascript_url")
self.assertHTMLEqual(
res,
'<script src="{url}" crossorigin="{crossorigin}" integrity="{integrity}"></script>'.format(
**javascript_url
),
)

def test_bootstrap_css_tag(self):
res = render_template_with_form("{% bootstrap_css %}").strip()
self.assertIn(
'<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">',
res,
css_url = get_bootstrap_setting("css_url")
expected_html = (
'<link href="{url}" crossorigin="{crossorigin}" integrity="{integrity}" rel="stylesheet">'.format(
**css_url
)
+ '<link href="//example.com/theme.css" rel="stylesheet">'
)
self.assertIn('<link href="//example.com/theme.css" rel="stylesheet">', res)
self.assertHTMLEqual(expected_html, res)

def test_settings_filter(self):
res = render_template_with_form('{{ "required_css_class"|bootstrap_setting }}')
Expand Down Expand Up @@ -531,7 +537,8 @@ def test_input_group(self):

def test_input_group_addon_button(self):
res = render_template_with_form(
'{% bootstrap_field form.subject addon_before="$" addon_before_class="input-group-btn" addon_after=".00" addon_after_class="input-group-btn" %}' # noqa
'{% bootstrap_field form.subject addon_before="$" addon_before_class="input-group-btn" addon_after=".00" addon_after_class="input-group-btn" %}'
# noqa
)
self.assertIn('class="input-group"', res)
self.assertIn('class="input-group-btn">$', res)
Expand Down Expand Up @@ -751,6 +758,21 @@ def test_render_tag(self):
'<span bar="123">foo</span>',
)

def test_url_to_attrs_dict(self):
self.assertEqual(url_to_attrs_dict("my_link", "src"), {"src": "my_link"})
self.assertEqual(
url_to_attrs_dict({"url": "my_link"}, "src"), {"src": "my_link"}
)
self.assertEqual(
url_to_attrs_dict(
{"url": "my_link", "crossorigin": "anonymous", "integrity": "super"},
"src",
),
{"src": "my_link", "crossorigin": "anonymous", "integrity": "super"},
)
with self.assertRaises(BootstrapError):
url_to_attrs_dict(123, "src")


class ButtonTest(TestCase):
def test_button(self):
Expand Down
45 changes: 42 additions & 3 deletions bootstrap3/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import re
from collections import Mapping

from django.utils import six

from bootstrap3.exceptions import BootstrapError

try:
from urllib import urlencode
except ImportError:
Expand Down Expand Up @@ -105,14 +109,24 @@ def remove_css_class(css_classes, css_class):
return " ".join(classes_list)


def render_script_tag(url):
"""
Build a script tag
"""
url_dict = url_to_attrs_dict(url, url_attr="src")
return render_tag("script", url_dict)


def render_link_tag(url, rel="stylesheet", media=None):
"""
Build a link tag
"""
attrs = {"href": url, "rel": rel}
url_dict = url_to_attrs_dict(url, url_attr="href")
url_dict.setdefault("href", url_dict.pop("url", None))
url_dict["rel"] = rel
if media:
attrs["media"] = media
return render_tag("link", attrs=attrs, close=False)
url_dict["media"] = media
return render_tag("link", attrs=url_dict, close=False)


def render_tag(tag, attrs=None, content=None, close=True):
Expand Down Expand Up @@ -159,3 +173,28 @@ def url_replace_param(url, name, value):
]
)
)


def url_to_attrs_dict(url, url_attr):
"""
Sanitize url dict as used in django-bootstrap3 settings.
"""
result = dict()
# If url is not a string, it should be a dict
if isinstance(url, six.string_types):
url_value = url
else:
try:
url_value = url["url"]
except TypeError:
raise BootstrapError(
'Function "url_to_attrs_dict" expects a string or a dict with key "url".'
)
crossorigin = url.get("crossorigin", None)
integrity = url.get("integrity", None)
if crossorigin:
result["crossorigin"] = crossorigin
if integrity:
result["integrity"] = integrity
result[url_attr] = url_value
return result
66 changes: 37 additions & 29 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,61 +14,69 @@ The ``BOOTSTRAP3`` dict variable contains these settings and defaults:
# Default settings
BOOTSTRAP3 = {
# The URL to the jQuery JavaScript file
'jquery_url': '//code.jquery.com/jquery.min.js',
# The Bootstrap base URL
'base_url': '//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/',
# The complete URL to the Bootstrap CSS file (None means derive it from base_url)
'css_url': None,
# The complete URL to the Bootstrap CSS file
# Note that a URL can be either
# - a string, e.g. "//code.jquery.com/jquery.min.js"
# - a dict like the default value below (use key "url" for the actual link)
"css_url": {
"url": "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",
"integrity": "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u",
"crossorigin": "anonymous",
},
# The complete URL to the Bootstrap CSS file (None means no theme)
'theme_url': None,
"theme_url": None,
# The complete URL to the Bootstrap JavaScript file (None means derive it from base_url)
'javascript_url': None,
# The complete URL to the Bootstrap JavaScript file
"javascript_url": {
"url": "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js",
"integrity": "sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa",
"crossorigin": "anonymous",
},
# The URL to the jQuery JavaScript file
"jquery_url": "//code.jquery.com/jquery.min.js",
# Put JavaScript in the HEAD section of the HTML document (only relevant if you use bootstrap3.html)
'javascript_in_head': False,
"javascript_in_head": False,
# Include jQuery with Bootstrap JavaScript (affects django-bootstrap3 template tags)
'include_jquery': False,
"include_jquery": False,
# Label class to use in horizontal forms
'horizontal_label_class': 'col-md-3',
"horizontal_label_class": "col-md-3",
# Field class to use in horizontal forms
'horizontal_field_class': 'col-md-9',
"horizontal_field_class": "col-md-9",
# Set HTML required attribute on required fields, for Django <= 1.8 only
'set_required': True,
"set_required": True,
# Set HTML disabled attribute on disabled fields, for Django <= 1.8 only
'set_disabled': False,
"set_disabled": False,
# Set placeholder attributes to label if no placeholder is provided.
# This also considers the 'label' option of {% bootstrap_field %} tags.
'set_placeholder': True,
# This also considers the "label" option of {% bootstrap_field %} tags.
"set_placeholder": True,
# Class to indicate required (better to set this in your Django form)
'required_css_class': '',
"required_css_class": "",
# Class to indicate error (better to set this in your Django form)
'error_css_class': 'has-error',
"error_css_class": "has-error",
# Class to indicate success, meaning the field has valid input (better to set this in your Django form)
'success_css_class': 'has-success',
"success_css_class": "has-success",
# Renderers (only set these if you have studied the source and understand the inner workings)
'formset_renderers':{
'default': 'bootstrap3.renderers.FormsetRenderer',
"formset_renderers":{
"default": "bootstrap3.renderers.FormsetRenderer",
},
'form_renderers': {
'default': 'bootstrap3.renderers.FormRenderer',
"form_renderers": {
"default": "bootstrap3.renderers.FormRenderer",
},
'field_renderers': {
'default': 'bootstrap3.renderers.FieldRenderer',
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
"field_renderers": {
"default": "bootstrap3.renderers.FieldRenderer",
"inline": "bootstrap3.renderers.InlineFieldRenderer",
},
}

0 comments on commit 14a57b5

Please sign in to comment.