Skip to content

Commit b12b828

Browse files
Merge branch 'main' into fix-js-and-linting
2 parents 38ed54f + 6d0b621 commit b12b828

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+429
-294
lines changed

src/django_unicorn/call_method_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def parse_kwarg(kwarg: str, *, raise_if_unparseable=False) -> dict[str, Any]:
119119
# context when the templatetag is rendered, so just return the expr
120120
# as a string.
121121
value = _get_expr_string(assign.value)
122-
return {target.id: value} # type: ignore
122+
return {key: value} # type: ignore
123123
else:
124124
raise InvalidKwargError(f"'{kwarg}' is invalid")
125125
except SyntaxError as e:
@@ -157,7 +157,7 @@ def parse_call_method_name(
157157

158158
if tree.body and isinstance(statement, ast.Call):
159159
call = tree.body[0].value # type: ignore
160-
method_name = call.func.id
160+
method_name = _get_expr_string(call.func)
161161
args = [eval_value(arg) for arg in call.args]
162162
kwargs = {kw.arg: eval_value(kw.value) for kw in call.keywords}
163163

src/django_unicorn/components/unicorn_template_response.py

Lines changed: 10 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
MultipleRootComponentElementError,
1616
NoRootComponentElementError,
1717
)
18-
from django_unicorn.settings import get_minify_html_enabled, get_script_location
19-
from django_unicorn.utils import generate_checksum, html_element_to_string, sanitize_html
18+
from django_unicorn.settings import get_minify_html_enabled
19+
from django_unicorn.utils import generate_checksum, html_element_to_string
2020

2121
logger = logging.getLogger(__name__)
2222

@@ -159,6 +159,7 @@ def __init__(
159159
using=None,
160160
component=None,
161161
init_js=False,
162+
epoch="",
162163
**kwargs, # noqa: ARG002
163164
):
164165
super().__init__(
@@ -173,6 +174,7 @@ def __init__(
173174

174175
self.component = component
175176
self.init_js = init_js
177+
self.epoch = epoch or ""
176178

177179
def resolve_template(self, template):
178180
"""Override the TemplateResponseMixin to resolve a list of Templates.
@@ -213,82 +215,25 @@ def render(self):
213215
# Prepare Data
214216
frontend_context_variables = self.component.get_frontend_context_variables()
215217
frontend_context_variables_dict = orjson.loads(frontend_context_variables)
216-
checksum = generate_checksum(frontend_context_variables_dict)
218+
data_checksum = generate_checksum(frontend_context_variables_dict)
217219

218220
# Modify Attributes
219221
root_element.set("unicorn:id", self.component.component_id)
220222
if hasattr(self.component, "component_name"):
221223
root_element.set("unicorn:name", self.component.component_name)
222224
root_element.set("unicorn:key", str(self.component.component_key or ""))
223-
root_element.set("unicorn:checksum", checksum)
224225
root_element.set("unicorn:data", frontend_context_variables)
225226
root_element.set("unicorn:calls", orjson.dumps(self.component.calls).decode("utf-8"))
226227

227-
# Calculate content hash (without script)
228+
# Calculate content hash (without script and without checksum)
228229
rendered_template_no_script = html_element_to_string(root_element)
229230
content_hash = generate_checksum(rendered_template_no_script)
231+
self.component._content_hash = content_hash
230232

231-
rendered_template = rendered_template_no_script
232-
233-
# Inject Scripts
234-
if self.init_js:
235-
init = {
236-
"id": self.component.component_id,
237-
"name": self.component.component_name,
238-
"key": self.component.component_key,
239-
"data": orjson.loads(frontend_context_variables),
240-
"calls": self.component.calls,
241-
"hash": content_hash,
242-
}
243-
init_json = orjson.dumps(init).decode("utf-8")
244-
init_json_safe = sanitize_html(init_json)
245-
json_element_id = f"unicorn:data:{self.component.component_id}"
246-
init_script = (
247-
f"Unicorn.componentInit(JSON.parse(document.getElementById('{json_element_id}').textContent));"
248-
)
233+
# Set the data checksum on the root element
234+
root_element.set("unicorn:meta", data_checksum)
249235

250-
# Create JSON script tag
251-
json_tag = html.Element("script", type="application/json", id=json_element_id)
252-
json_tag.text = init_json_safe
253-
254-
if self.component.parent:
255-
self.component._init_script = init_script
256-
self.component._json_tag = json_tag
257-
else:
258-
json_tags = [json_tag]
259-
260-
descendants = []
261-
descendants.append(self.component)
262-
while descendants:
263-
descendant = descendants.pop()
264-
for child in descendant.children:
265-
init_script = f"{init_script} {child._init_script}"
266-
267-
if hasattr(child, "_json_tag"):
268-
json_tags.append(child._json_tag)
269-
del child._json_tag
270-
271-
descendants.append(child)
272-
273-
script_tag = html.Element("script", type="module")
274-
script_content = (
275-
"if (typeof Unicorn === 'undefined') { "
276-
"console.error('Unicorn is missing. Do you need {% load unicorn %} or {% unicorn_scripts %}?') "
277-
"} else { " + init_script + " }"
278-
)
279-
script_tag.text = script_content
280-
281-
if get_script_location() == "append":
282-
root_element.append(script_tag)
283-
for t in json_tags:
284-
root_element.append(t)
285-
rendered_template = html_element_to_string(root_element)
286-
else:
287-
root_html = html_element_to_string(root_element)
288-
script_html = html_element_to_string(script_tag)
289-
for t in json_tags:
290-
script_html += html_element_to_string(t)
291-
rendered_template = root_html + script_html
236+
rendered_template = html_element_to_string(root_element)
292237

293238
self.component.rendered(rendered_template)
294239
response.content = rendered_template

src/django_unicorn/components/unicorn_view.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ def post_parse(self):
411411
pass
412412

413413
@timed
414-
def render(self, *, init_js=False, extra_context=None, request=None) -> str:
414+
def render(self, *, init_js=False, extra_context=None, request=None, epoch=None) -> str:
415415
"""
416416
Renders a UnicornView component with the public properties available. Delegates to a
417417
UnicornTemplateResponse to actually render a response.
@@ -422,6 +422,7 @@ def render(self, *, init_js=False, extra_context=None, request=None) -> str:
422422
param request: Set the `request` for rendering. Usually it will be in the context,
423423
but it is missing when the component is re-rendered as a direct view, so it needs
424424
to be set explicitly.
425+
param epoch: Set the `epoch` for rendering.
425426
"""
426427

427428
if extra_context is not None:
@@ -438,6 +439,7 @@ def render(self, *, init_js=False, extra_context=None, request=None) -> str:
438439
context=self.get_context_data(),
439440
component=self,
440441
init_js=init_js,
442+
epoch=epoch,
441443
)
442444

443445
# render_to_response() could only return a HttpResponse, so check for render()

src/django_unicorn/static/unicorn/js/attribute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class Attribute {
6060
if (
6161
actionEventType !== "id" &&
6262
actionEventType !== "name" &&
63-
actionEventType !== "checksum"
63+
actionEventType !== "meta"
6464
) {
6565
this.eventType = actionEventType;
6666
}

src/django_unicorn/static/unicorn/js/component.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class Component {
7575
throw Error("No id found");
7676
}
7777

78-
this.refreshChecksum();
78+
this.refreshMeta();
7979
}
8080

8181
/**
@@ -430,10 +430,25 @@ export class Component {
430430
}
431431

432432
/**
433-
* Refresh the checksum.
433+
* Refresh the meta.
434434
*/
435-
refreshChecksum() {
436-
this.checksum = this.root.getAttribute("unicorn:checksum");
435+
refreshMeta() {
436+
const meta = this.root.getAttribute("unicorn:meta");
437+
438+
if (meta && meta.indexOf(":") > -1) {
439+
const parts = meta.split(":");
440+
this.checksum = parts[0];
441+
442+
if (parts.length > 1) {
443+
this.hash = parts[1];
444+
}
445+
446+
if (parts.length > 2) {
447+
this.epoch = parts[2];
448+
}
449+
} else {
450+
this.checksum = meta;
451+
}
437452
}
438453

439454
/**

src/django_unicorn/static/unicorn/js/element.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ export class Element {
375375
* Whether the element is a root node or not.
376376
*/
377377
isRoot() {
378-
// A litte hacky, but assume that an element with `unicorn:checksum` is a component root
379-
return hasValue(this.el.getAttribute("unicorn:checksum"));
378+
// A litte hacky, but assume that an element with `unicorn:meta` is a component root
379+
return hasValue(this.el.getAttribute("unicorn:meta"));
380380
}
381381
}

src/django_unicorn/static/unicorn/js/messageSender.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ export function send(component, callback) {
2424
component.currentActionQueue = component.actionQueue;
2525
component.actionQueue = [];
2626

27+
const epoch = Date.now();
2728
const body = {
2829
id: component.id,
2930
data: component.data,
30-
checksum: component.checksum,
31+
meta: `${component.checksum}:${component.hash}:${epoch}`,
3132
actionQueue: component.currentActionQueue,
32-
epoch: Date.now(),
33-
hash: component.hash,
3433
};
3534

3635
const headers = {
@@ -87,9 +86,20 @@ export function send(component, callback) {
8786
return;
8887
}
8988

90-
if (responseJson.epoch) {
91-
if (responseJson.epoch > component.latestActionEpoch) {
92-
component.latestActionEpoch = responseJson.epoch;
89+
let { meta } = responseJson;
90+
let { epoch } = responseJson;
91+
92+
if (meta && meta.indexOf(":") > -1) {
93+
const parts = meta.split(":");
94+
95+
if (parts.length > 2) {
96+
epoch = parts[2];
97+
}
98+
}
99+
100+
if (epoch) {
101+
if (epoch > component.latestActionEpoch) {
102+
component.latestActionEpoch = epoch;
93103
} else {
94104
return;
95105
}
@@ -151,12 +161,23 @@ export function send(component, callback) {
151161

152162
component.errors = responseJson.errors || {};
153163
component.return = responseJson.return || {};
154-
component.hash = responseJson.hash;
155164

156165
let parent = responseJson.parent || {};
157166
const rerenderedComponent = responseJson.dom || "";
158167
const partials = responseJson.partials || [];
159-
const { checksum } = responseJson;
168+
169+
if (meta && meta.indexOf(":") > -1) {
170+
const parts = meta.split(":");
171+
component.checksum = parts[0];
172+
component.hash = parts[1];
173+
174+
if (parts.length > 2) {
175+
component.epoch = parts[2];
176+
}
177+
} else {
178+
component.checksum = meta;
179+
component.hash = responseJson.hash;
180+
}
160181

161182
// Handle poll
162183
const poll = responseJson.poll || {};
@@ -203,13 +224,25 @@ export function send(component, callback) {
203224
});
204225
}
205226

206-
if (parent.checksum) {
227+
if (parent.meta) {
228+
const parentMeta = parent.meta;
229+
230+
if (parentMeta.indexOf(":") > -1) {
231+
const parts = parentMeta.split(":");
232+
parentComponent.checksum = parts[0];
233+
parentComponent.hash = parts[1];
234+
235+
if (parts.length > 2) {
236+
parentComponent.epoch = parts[2];
237+
}
238+
} else {
239+
parentComponent.checksum = parentMeta;
240+
}
241+
207242
parentComponent.root.setAttribute(
208-
"unicorn:checksum",
209-
parent.checksum
243+
"unicorn:meta",
244+
parentComponent.checksum
210245
);
211-
212-
parentComponent.refreshChecksum();
213246
}
214247

215248
// Set parent component hash
@@ -249,9 +282,8 @@ export function send(component, callback) {
249282
}
250283
}
251284

252-
if (checksum) {
253-
component.root.setAttribute("unicorn:checksum", checksum);
254-
component.refreshChecksum();
285+
if (meta) {
286+
component.root.setAttribute("unicorn:meta", component.checksum);
255287
}
256288
} else if (rerenderedComponent) {
257289
component.morphRoot(rerenderedComponent);

src/django_unicorn/static/unicorn/js/unicorn.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export function init(
4949
});
5050
}
5151

52+
scan();
53+
5254
return {
5355
messageUrl,
5456
csrfTokenHeaderName,
@@ -83,11 +85,31 @@ export function insertComponentFromDom(node) {
8385
const nodeId = node.getAttribute("unicorn:id");
8486

8587
if (!components[nodeId]) {
88+
const fullMeta = node.getAttribute("unicorn:meta");
89+
let meta = fullMeta;
90+
let hash = "";
91+
let epoch = "";
92+
93+
if (fullMeta && fullMeta.indexOf(":") > -1) {
94+
const parts = fullMeta.split(":");
95+
meta = parts[0];
96+
97+
if (parts.length > 1) {
98+
hash = parts[1];
99+
}
100+
101+
if (parts.length > 2) {
102+
epoch = parts[2];
103+
}
104+
}
105+
86106
const args = {
87107
id: nodeId,
88108
name: node.getAttribute("unicorn:name"),
89109
key: node.getAttribute("unicorn:key"),
90-
checksum: node.getAttribute("unicorn:checksum"),
110+
checksum: meta,
111+
hash,
112+
epoch,
91113
data: JSON.parse(node.getAttribute("unicorn:data")),
92114
calls: JSON.parse(node.getAttribute("unicorn:calls")),
93115
};

src/django_unicorn/static/unicorn/js/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export const FilterAny = {
104104
*/
105105
export const FilterSkipNested = {
106106
acceptNode: (node) => {
107-
if (node.getAttribute("unicorn:checksum")) {
107+
if (node.getAttribute("unicorn:meta")) {
108108
// with a tree walker, child nodes are also rejected
109109
return NodeFilter.FILTER_REJECT;
110110
}

src/django_unicorn/views/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def _process_request(self, component_request: ComponentRequest) -> dict:
211211
self._handle_queued_messages(component, return_data)
212212

213213
# Render
214-
rendered_component = component.render(request=self.request)
214+
rendered_component = component.render(request=self.request, epoch=component_request.epoch)
215215
component.rendered(rendered_component)
216216

217217
# Restore queued messages

0 commit comments

Comments
 (0)