Skip to content

Commit 30fc8df

Browse files
committed
template tag security, errors, and tests
1 parent 5664dac commit 30fc8df

28 files changed

+2691
-52
lines changed
-19 Bytes
Binary file not shown.
1.07 KB
Binary file not shown.
3.76 KB
Binary file not shown.
118 Bytes
Binary file not shown.
148 Bytes
Binary file not shown.

BaseApp/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
LOG_FORMAT = "{time} -- {level} -- {function} -- line {line}\n\t {message}"
1+
LOG_FORMAT = "{time}{level}\n{function}{line}{message}"
22
# colors! upon changing these, run manage.py tailwind_dummy
33
COLORS = [
44
"red",

BaseApp/exceptions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.template.exceptions import TemplateSyntaxError
2+
3+
4+
class TemplateTagInitError(TemplateSyntaxError):
5+
"""
6+
Custom exception for init errors in template tags.
7+
8+
This exception automatically appends a message about not including a specific suffix
9+
10+
example usage:
11+
raise TemplateTagInitError(f"Invalid group ID '{group_id}'.", "-toggled-button-group")
12+
"""
13+
14+
def __init__(self, msg: str, reserved_suffix: str = ""):
15+
if reserved_suffix:
16+
super().__init__(
17+
f"{msg}\n\nDo not include the '{reserved_suffix}' suffix in this tag's argument.\nIf your group's ID is 'example-abc{reserved_suffix}', use 'example-abc' as the argument."
18+
)

BaseApp/logs/views.log

Lines changed: 932 additions & 0 deletions
Large diffs are not rendered by default.

BaseApp/static/BaseApp/modules/ToggledButtonGroup.mjs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const DEBUG = false;
66
* and optionally trigger HTMX requests.
77
*
88
* Usage:
9-
* 1. Create a button group container element in your HTML with an ID ending in "-button-group".
9+
* 1. Create a button group container element in your HTML with an ID ending in "-toggled-button-group".
1010
* 2. Add "button" elements inside the container.
1111
* 3. (Optional) Set `data-active-class` on the container to customize the active class (default is "active").
1212
* 4. (Optional) Set `data-initial-active` on the container to define the initially active button (options: "first", "last", "none", "random" or a number for the button index).
@@ -16,7 +16,9 @@ const DEBUG = false;
1616
export class ToggledButtonGroup {
1717
constructor(config) {
1818
this.groupId = config.groupId;
19-
this.container = document.getElementById(`${this.groupId}-button-group`);
19+
this.container = document.getElementById(
20+
`${this.groupId}-toggled-button-group`
21+
);
2022
this.buttons = this.container
2123
? this.container.querySelectorAll("button")
2224
: [];
@@ -26,7 +28,7 @@ export class ToggledButtonGroup {
2628
// ERROR CHECKING
2729
if (!this.container) {
2830
console.error(
29-
`[ToggledButtonGroup Error] Button group container with ID "${this.groupId}-button-group" not found. Please ensure the container element exists and has the correct ID.`
31+
`[ToggledButtonGroup Error] Button group container with ID "${this.groupId}-toggled-button-group" not found. Please ensure the container element exists and has the correct ID.`
3032
);
3133
return;
3234
}
@@ -157,16 +159,18 @@ export class ToggledButtonGroup {
157159
}
158160
ToggledButtonGroup.initAll = function (groupFilter = "") {
159161
// Use this to Initialize all button groups
160-
// with an empty string, it will check for all elements with the ID ending in "-button-group"
161-
// given a string as argument, it will only check for elements with the ID starting with the string and ending in "-button-group"
162+
// with an empty string, it will check for all elements with the ID ending in "-toggled-button-group"
163+
// given a string as argument, it will only check for elements with the ID starting with the string and ending in "-toggled-button-group"
162164
// if the string includes spaces, it will split the string and call itself recursively
163165
const initializedGroups = new Set();
164166
const initializeGroup = (groupId) => {
165167
if (initializedGroups.has(groupId)) return; // Skip if already initialized
166-
const groupElement = document.getElementById(`${groupId}-button-group`);
168+
const groupElement = document.getElementById(
169+
`${groupId}-toggled-button-group`
170+
);
167171
if (!groupElement) {
168172
console.error(
169-
`[ToggledButtonGroup Error] Button group container with ID "${groupId}-button-group" not found.`
173+
`[ToggledButtonGroup Error] Button group container with ID "${groupId}-toggled-button-group" not found.`
170174
);
171175
return;
172176
}
@@ -189,9 +193,9 @@ ToggledButtonGroup.initAll = function (groupFilter = "") {
189193
} else {
190194
// Initialize all groups if no filter is provided
191195
document
192-
.querySelectorAll('[id$="-button-group"]')
196+
.querySelectorAll('[id$="-toggled-button-group"]')
193197
.forEach((groupElement) => {
194-
initializeGroup(groupElement.id.replace("-button-group", ""));
198+
initializeGroup(groupElement.id.replace("-toggled-button-group", ""));
195199
});
196200
}
197201
};

BaseApp/templates/BaseApp/home/sections/tech_info.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% load static %}
22
<div class="bg-black/10 text-center bg-gradient-to-b from-black/20 from-0% via-blue-300/5 via-5% to-black/20 to-50%">
33
<span class="text-sm text-white/30">Click to learn more about technologies used on this website</span>
4-
<div id="tech-info-button-group"
4+
<div id="tech-info-toggled-button-group"
55
data-active-class="bg-black/50 hover:bg-black/50 font-bold text-white border-b-4 border-white/20"
66
data-initial-active="random"
77
class="flex gap-4 p-2 px-6 w-full justify-evenly">

BaseApp/templates/BaseApp/ui_elements/partials/button_example_1.html renamed to BaseApp/templates/BaseApp/ui_elements/partials/buttons/button_example_1.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
{% load button_group_tags %}
12
<div class="flex flex-col space-y-2 border border-y-4 border-white/10 rounded-lg p-4 bg-slate-900/50">
23
<h4 class="text-lg font-bold text-center">
34
Responsive Row/Column of Buttons, toggled active, uniform size, initial first, dark theme
45
</h4>
5-
<form id="example-button-group"
6+
<form id="example-toggled-button-group"
67
data-active-class="bg-black/50 hover:bg-black/50 font-bold text-white border-b-4 border-white/20"
7-
data-initial-active="2"
8+
data-initial-active="first"
89
class="flex flex-col md:flex-row gap-2 justify-center">
910
{% csrf_token %}
1011
<button hx-post="{% url 'BaseApp:display_number' %}"
@@ -55,3 +56,4 @@ <h4 class="text-lg font-bold text-center">
5556
</form>
5657
<div id="result-container"></div>
5758
</div>
59+
{% init_toggled_button_groups "example-toggled-button-group" %}

BaseApp/templates/BaseApp/ui_elements/partials/button_example_2.html renamed to BaseApp/templates/BaseApp/ui_elements/partials/buttons/button_example_2.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
{% load button_group_tags %}
12
<div class="flex flex-col space-y-2 border border-y-4 border-black/10 rounded-lg p-4 bg-slate-200/100">
23
<h4 class="text-lg font-bold text-center text-black/70">
34
Responsive Buttons, toggled active, various sizes, initial random, light theme
45
</h4>
5-
<form id="example2-button-group"
6+
<form id="example2-toggled-button-group"
67
data-active-class="bg-white hover:bg-white font-bold text-black border-b-4 border-black/60"
78
data-initial-active="random"
89
class="flex flex-wrap gap-2 justify-center transition-all duration-300 ease-linear">
@@ -55,3 +56,4 @@ <h4 class="text-lg font-bold text-center text-black/70">
5556
</form>
5657
<div id="result-container2" class="text-black/80"></div>
5758
</div>
59+
{% init_toggled_button_groups "example2" %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% load button_group_tags %}
2+
<div class="flex flex-col space-y-2 border border-y-4 border-white/10 rounded-lg p-4">
3+
<h4 class="text-lg font-bold text-center">Toggled Button Group with minimal styling</h4>
4+
<div id="minimal-toggled-button-group"
5+
data-active-class="p-4 bg-white text-black/50 font-bold border-b-4 border-black/60"
6+
data-initial-active="2"
7+
class="flex gap-2 justify-center">
8+
<button class="bg-black text-white p-2">Button 1</button>
9+
<button class="bg-black text-white p-2">Button 2</button>
10+
<button class="bg-black text-white p-2">Button 3</button>
11+
<button class="bg-black text-white p-2">Button 4</button>
12+
<button class="bg-black text-white p-2">Button 5</button>
13+
</div>
14+
</div>
15+
{% init_toggled_button_groups 'minimal' %}
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
{% load button_group_tags %}
2-
<div class="flex flex-col space-y-4">
1+
<div class="flex flex-col space-y-8">
32
<h3 class="text-2xl font-bold text-center mt-2">Buttons</h3>
4-
{% include 'BaseApp/ui_elements/partials/button_example_1.html' %}
5-
{% include 'BaseApp/ui_elements/partials/button_example_2.html' %}
3+
{% include 'BaseApp/ui_elements/partials/buttons/button_example_minimal.html' %}
4+
{% include 'BaseApp/ui_elements/partials/buttons/button_example_1.html' %}
5+
{% include 'BaseApp/ui_elements/partials/buttons/button_example_2.html' %}
66
</div>
7-
// TODO: add MultiButtonGroup example with multiple buttons active at once
8-
{% init_button_groups "example" "example2" %}

BaseApp/templates/BaseApp/ui_elements/ui-elements.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<h2 class="base-font text-2xl w-full text-center font-bold py-2 -mt-2 rounded-t-lg bg-gradient-to-r from-black/20 from-10% via-blue-300/10 via-50% to-black/20 to-90% border-b-4 border-white/10">
55
User Interface Elements
66
</h2>
7-
<div id="ui-elements-button-group"
7+
<div id="ui-elements-toggled-button-group"
88
data-active-class="bg-blue-800 hover:bg-blue-800 font-bold text-white border-b-4 flex-grow text-white/80 bg-blue-700 border-x border-white/20"
9-
data-initial-active="random"
9+
data-initial-active="first"
1010
class="flex gap-4 p-2 w-full justify-evenly border-b-4 border-white/10 rounded-b-lg">
1111
<button hx-get="{% url 'BaseApp:buttons-examples' %}"
1212
hx-target="#ui-elements-content-target"
Binary file not shown.
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,51 @@
1+
import re
2+
13
from django import template
24
from django.templatetags.static import static
5+
from django.utils.html import escapejs
36
from django.utils.safestring import mark_safe
47

8+
from BaseApp.exceptions import TemplateTagInitError
9+
from BaseApp.utils import get_module_logger
10+
11+
module_logger = get_module_logger("templatetags", __file__)
12+
513
register = template.Library()
614

15+
VALID_GROUP_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\-]+$')
16+
717

818
@register.simple_tag
9-
def init_button_groups(*group_ids):
19+
def init_toggled_button_groups(*group_ids: str):
1020
"""
1121
Generates the JavaScript code to initialize ToggledButtonGroup instances.
12-
1322
Args:
1423
*group_ids: Variable number of button group IDs.
15-
1624
Returns:
1725
Safe JavaScript code string.
1826
"""
19-
27+
group_ids = [group_id.strip() for group_id in group_ids]
28+
reserved_suffix = "-toggled-button-group"
2029
if not group_ids:
21-
return ""
22-
30+
raise TemplateTagInitError(
31+
"init_toggled_button_groups tag requires at least one button group ID.", reserved_suffix
32+
)
33+
for group_id in group_ids:
34+
if reserved_suffix in group_id:
35+
raise TemplateTagInitError(
36+
f"Invalid group ID '{group_id}'.", reserved_suffix
37+
)
38+
if not VALID_GROUP_ID_PATTERN.match(group_id):
39+
raise TemplateTagInitError(
40+
f"Group ID '{group_id}' contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.", reserved_suffix
41+
)
42+
# Escape group_ids to ensure no XSS injection can happen
43+
escaped_group_ids = [escapejs(group_id) for group_id in group_ids]
2344
module_path = static("BaseApp/modules/ToggledButtonGroup.mjs")
24-
2545
js_code = f"""
2646
<script type="module">
2747
import {{ ToggledButtonGroup }} from "{module_path}";
2848
ToggledButtonGroup.initAll('{ " ".join(group_ids) }');
2949
</script>
3050
"""
31-
3251
return mark_safe(js_code)

0 commit comments

Comments
 (0)