Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hyphenate attributes with certain prefixes #737

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ The ``BOOTSTRAP5`` dict variable contains these settings and defaults:
# Field class used for horizontal fields withut a label.
'horizontal_field_offset_class': 'offset-sm-2',

# HTML attributes with any of these prefixes will have underscores converted to hyphens.
"hyphenate_attribute_prefixes": ["data"],

# Set placeholder attributes to label if no placeholder is provided.
'set_placeholder': True,

Expand Down
2 changes: 1 addition & 1 deletion src/django_bootstrap5/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from django.conf import settings

BOOTSTRAP5 = {"foo": "bar"}
BOOTSTRAP5_DEFAULTS = {
"css_url": {
"url": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
Expand Down Expand Up @@ -34,6 +33,7 @@
"field_renderers": {
"default": "django_bootstrap5.renderers.FieldRenderer",
},
"hyphenate_attribute_prefixes": ["data"],
}


Expand Down
20 changes: 20 additions & 0 deletions src/django_bootstrap5/html.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from copy import copy

from django.forms.utils import flatatt
from django.utils.html import format_html

from django_bootstrap5.text import text_value
from django_bootstrap5.utils import get_url_attrs

from .core import get_bootstrap_setting
dyve marked this conversation as resolved.
Show resolved Hide resolved


def render_script_tag(url):
"""Build a script tag."""
Expand All @@ -17,8 +21,24 @@ def render_link_tag(url):
return render_tag("link", attrs=attrs, close=False)


def has_prefix(name, prefixes):
"""Return whether the name has one of the given prefixes."""
return any(name.startswith(f"{prefix}_") for prefix in prefixes)
dyve marked this conversation as resolved.
Show resolved Hide resolved


def hyphenate(attr_name):
"""Return the hyphenated version of the attribute name."""
return attr_name.replace("_", "-")


def render_tag(tag, attrs=None, content=None, close=True):
"""Render an HTML tag."""
prefixes = get_bootstrap_setting("hyphenate_attribute_prefixes") or []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specifying should result in the default value being used.

Suggested change
prefixes = get_bootstrap_setting("hyphenate_attribute_prefixes") or []
prefixes = get_bootstrap_setting("hyphenate_attribute_prefixes") or ["data"]

if attrs:
for attr_name, attr_value in copy(attrs).items():
if has_prefix(attr_name, prefixes):
attrs[hyphenate(attr_name)] = attr_value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe error out or warn if the hyphenate key already exists?

del attrs[attr_name]
attrs_string = flatatt(attrs) if attrs else ""
builder = "<{tag}{attrs}>{content}"
content_string = text_value(content)
Expand Down
35 changes: 35 additions & 0 deletions tests/test_bootstrap_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,38 @@ def test_button_type_link(self):

with self.assertRaises(ValueError):
self.render("{% bootstrap_button 'button' button_type='button' href='#' %}")

def test_button_hyphenate_attributes(self):
# No attributes to hyphenate
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' %}"),
'<a class="btn btn-primary" href="#" role="button">button</a>',
)
# Default: data prefix
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' data_foo='bar' %}"),
'<a class="btn btn-primary" href="#" role="button" data-foo="bar">button</a>',
)
# Default: no hx prefix
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' data_foo='bar' hx_xyz='abc' %}"),
'<a class="btn btn-primary" href="#" role="button" data-foo="bar" hx_xyz="abc">button</a>',
)
# Custom: data and hx
with self.settings(BOOTSTRAP5={"hyphenate_attribute_prefixes": ["data", "hx"]}):
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' data_foo='bar' hx_xyz='abc' %}"),
'<a class="btn btn-primary" href="#" role="button" data-foo="bar" hx-xyz="abc">button</a>',
)
# Custom: hx only
with self.settings(BOOTSTRAP5={"hyphenate_attribute_prefixes": ["hx"]}):
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' data_foo='bar' hx_xyz='abc' %}"),
'<a class="btn btn-primary" href="#" role="button" data_foo="bar" hx-xyz="abc">button</a>',
)
# Custom: empty list
with self.settings(BOOTSTRAP5={"hyphenate_attribute_prefixes": []}):
self.assertHTMLEqual(
self.render("{% bootstrap_button 'button' href='#' data_foo='bar' hx_xyz='abc' %}"),
'<a class="btn btn-primary" href="#" role="button" data_foo="bar" hx_xyz="abc">button</a>',
)
2 changes: 0 additions & 2 deletions tests/test_bootstrap_field_radio_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class DisabledSelectTestForm(forms.Form):


class RadioSelectWithDisabledOptions(forms.RadioSelect):

def __init__(self, attrs=None, choices=(), *, disabled_values=()):
super().__init__(attrs)
self.choices = choices
Expand All @@ -48,7 +47,6 @@ class DisabledOptionSelectTestForm(forms.Form):
)



class BootstrapFieldSelectTestCase(BootstrapTestCase):
def test_select(self):
"""Test field with select widget."""
Expand Down
3 changes: 0 additions & 3 deletions tests/test_bootstrap_field_radio_select_button_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class DisabledSelectTestForm(forms.Form):


class RadioSelectButtonGroupWithDisabledOptions(RadioSelectButtonGroup):

def __init__(self, attrs=None, choices=(), *, disabled_values=()):
super().__init__(attrs)
self.choices = choices
Expand All @@ -50,8 +49,6 @@ class DisabledOptionSelectTestForm(forms.Form):
)




class BootstrapFieldSelectTestCase(BootstrapTestCase):
def test_select(self):
"""Test field with select widget."""
Expand Down
Loading
Loading