Skip to content

Commit 7e511ca

Browse files
committed
Updated to have unique HTML ids for q field in header.
1 parent c37f9f9 commit 7e511ca

File tree

9 files changed

+83
-50
lines changed

9 files changed

+83
-50
lines changed

djangoproject/static/js/djangoproject.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,9 @@ document.querySelector('.menu-button').addEventListener('click', function () {
6767

6868
// Update search input placeholder text based on the user's operating system
6969
(function () {
70-
const elements = document.querySelectorAll('[id="id_q"]');
71-
72-
if (!elements.length) {
73-
return;
74-
}
70+
const mobile_search_input = document.getElementById('id_mobile-q');
71+
const desktop_search_input = document.getElementById('id_desktop-q');
72+
const elements = [mobile_search_input, desktop_search_input];
7573

7674
const is_mac = navigator.userAgent.indexOf('Mac') !== -1;
7775

@@ -96,13 +94,17 @@ window.addEventListener('keydown', function (e) {
9694

9795
e.preventDefault();
9896

99-
function querySelectorVisible(selector) {
100-
return [...document.querySelectorAll(selector)].find(
101-
(el) => el.offsetParent !== null,
102-
);
97+
function isVisible(el) {
98+
return el && el.offsetParent !== null;
10399
}
104100

105-
const el = querySelectorVisible('#id_q');
101+
const inputs = [
102+
document.getElementById('id_desktop-q'),
103+
document.getElementById('id_mobile-q'),
104+
];
105+
106+
const el = inputs.find(isVisible);
107+
if (!el) return;
106108

107109
el.select();
108110
el.focus();

djangoproject/templates/includes/header.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% load docs %}
2+
{% search_context as cached_search_context %}
23
{% if 'preview.djangoproject.com' in request.get_host %}
34
<div class="copy-banner" style="background: #fff78e; padding: 10px;"></div>
45
{% endif %}
@@ -7,7 +8,7 @@
78
<a class="logo" href="{% url 'homepage' %}">Django</a>
89
<p class="meta">The web framework for perfectionists with deadlines.</p>
910
<div class="header-mobile-only">
10-
{% search_form %}
11+
{% search_form prefix="mobile" cached_search_context=cached_search_context %}
1112
<div class="light-dark-mode-toggle">
1213
{% include "includes/toggle_theme.html" %}
1314
</div>
@@ -47,7 +48,7 @@
4748
<a href="{% url 'fundraising:index' %}">&#9829; Donate</a>
4849
</li>
4950
<li>
50-
{% search_form %}
51+
{% search_form prefix="desktop" cached_search_context=cached_search_context %}
5152
</li>
5253
<li>
5354
{% include "includes/toggle_theme.html" %}

djangoproject/templates/search_form.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
{% load i18n %}
1+
{% load i18n docs %}
22
<search class="search form-input" aria-labelledby="docs-search-label">
3-
<form action="{% url 'document-search' version=version lang=lang host 'docs' %}">
3+
<form action="{% url 'document-search' version=cached_search_context.version lang=cached_search_context.lang host 'docs' %}">
4+
{% build_search_form cached_search_context.release prefix as form %}
45
<label id="docs-search-label" class="visuallyhidden" for="{{ form.q.id_for_label }}">{{ form.q.field.widget.attrs.placeholder }}</label>
56
{{ form.q }}
67

djangoproject/tests.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -237,45 +237,39 @@ def test_search_ctrl_k_hotkey_desktop(self):
237237
page = self.browser.new_page()
238238
page.goto(self.live_server_url)
239239

240-
mobile_search_bar, desktop_search_bar = page.query_selector_all("#id_q")
240+
mobile_search_bar = page.locator("#id_mobile-q")
241+
desktop_search_bar = page.locator("#id_desktop-q")
241242
self.assertFalse(mobile_search_bar.is_visible())
242243
self.assertTrue(desktop_search_bar.is_visible())
243-
is_focused = page.evaluate(
244-
"el => document.activeElement === el", desktop_search_bar
245-
)
244+
is_focused = page.evaluate("document.activeElement.id === 'id_desktop-q'")
246245
self.assertFalse(is_focused)
247246

248247
page.keyboard.press("Control+KeyK")
249-
is_focused = page.evaluate(
250-
"el => document.activeElement === el", desktop_search_bar
251-
)
248+
is_focused = page.evaluate("document.activeElement.id === 'id_desktop-q'")
252249
self.assertTrue(is_focused)
253250
page.close()
254251

255252
def test_search_ctrl_k_hotkey_mobile(self):
256253
page = self.browser.new_page(viewport={"width": 375, "height": 812})
257254
page.goto(self.live_server_url)
258255

259-
mobile_search_bar, desktop_search_bar = page.query_selector_all("#id_q")
256+
mobile_search_bar = page.locator("#id_mobile-q")
257+
desktop_search_bar = page.locator("#id_desktop-q")
260258
self.assertTrue(mobile_search_bar.is_visible())
261259
self.assertFalse(desktop_search_bar.is_visible())
262-
is_focused = page.evaluate(
263-
"el => document.activeElement === el", mobile_search_bar
264-
)
260+
is_focused = page.evaluate("document.activeElement.id === 'id_mobile-q'")
265261
self.assertFalse(is_focused)
266262

267263
page.keyboard.press("Control+KeyK")
268-
is_focused = page.evaluate(
269-
"el => document.activeElement === el", mobile_search_bar
270-
)
264+
is_focused = page.evaluate("document.activeElement.id === 'id_mobile-q'")
271265
self.assertTrue(is_focused)
272266
page.close()
273267

274268
def test_search_placeholder_mac_mode(self):
275269
page = self.browser.new_page(user_agent="Mozilla/5.0 (Macintosh) AppleWebKit")
276270
page.goto(self.live_server_url)
277271

278-
inputs = page.query_selector_all("#id_q")
272+
inputs = page.query_selector_all('[name="q"]')
279273
self.assertEqual(len(inputs), 2)
280274

281275
for el in inputs:

djangoproject/utils.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,24 @@
22

33

44
class CachedLibrary(template.Library):
5-
def cached_context_inclusion_tag(self, template_name, *, name=None):
5+
def cached_context_simple_tag(self, name=None):
66
"""
7-
Wraps @register.inclusion_tag(template_name, takes_context=True) to
8-
automatically cache the returned context dictionary inside the
9-
template's render_context for the duration of a single render pass.
7+
Wraps @register.simple_tag(takes_context=True) to cache the returned
8+
value inside the template's render_context during a single template
9+
render pass.
1010
1111
This is useful when a tag may be rendered multiple times within the
12-
same template and computing its context is expensive (e.g. due to
13-
database queries).
12+
same template and with an expensive computation (e.g. due to database
13+
queries).
1414
"""
1515

1616
def decorator(func):
1717
tag_name = name or func.__name__
1818

19-
@self.inclusion_tag(template_name, takes_context=True, name=tag_name)
19+
@self.simple_tag(takes_context=True, name=tag_name)
2020
def wrapper(context, *args, **kwargs):
2121
render_context = getattr(context, "render_context", None)
22-
cache_key = f"{tag_name}_cached_context"
22+
cache_key = f"{tag_name}_cached_value"
2323

2424
if render_context is not None and cache_key in render_context:
2525
return render_context[cache_key]

docs/forms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ def __init__(self, data=None, **kwargs):
1616
"placeholder": search_label_placeholder,
1717
}
1818
)
19+
q_with_prefix = super().add_prefix("q")
20+
self.fields["q"].widget.attrs["id"] = f"id_{q_with_prefix}"
21+
22+
def add_prefix(self, field_name):
23+
return field_name

docs/templatetags/docs.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,35 @@
2020
register = CachedLibrary()
2121

2222

23-
@register.cached_context_inclusion_tag("search_form.html")
24-
def search_form(context):
25-
if "request" not in context:
23+
@register.inclusion_tag("search_form.html", takes_context=True)
24+
def search_form(context, *, prefix, cached_search_context=None):
25+
return {
26+
"prefix": prefix,
27+
"request": context.get("request"),
28+
"cached_search_context": cached_search_context,
29+
}
30+
31+
32+
@register.simple_tag(takes_context=True)
33+
def build_search_form(context, release, prefix=None):
34+
request = context.get("request")
35+
return DocSearchForm(
36+
request.GET if request else None, release=release, prefix=prefix
37+
)
38+
39+
40+
@register.cached_context_simple_tag()
41+
def search_context(context):
42+
if context.get("request") is None:
2643
# Django's built-in error views (like django.views.defaults.server_error)
2744
# render templates without attaching a RequestContext — meaning the 'request'
2845
# variable is not present in the template context.
2946
return {
30-
"form": DocSearchForm(release=None),
3147
"version": "dev",
3248
"lang": settings.DEFAULT_LANGUAGE_CODE,
49+
"release": None,
3350
}
3451

35-
request = context["request"]
3652
lang = context.get("lang", settings.DEFAULT_LANGUAGE_CODE)
3753

3854
if "version" in context:
@@ -43,9 +59,9 @@ def search_form(context):
4359
release = DocumentRelease.objects.select_related("release").current()
4460

4561
return {
46-
"form": DocSearchForm(request.GET, release=release),
4762
"version": release.version,
4863
"lang": lang,
64+
"release": release,
4965
}
5066

5167

docs/tests/test_templates.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,11 @@ def test_search_form_renders_without_request_in_template(self):
235235
Ensures the tag doesn't crash when rendered inside a template that
236236
lacks a 'request' variable e.g. during Django's built-in error views.
237237
"""
238-
template = Template("{% load docs %}{% search_form %}")
238+
template = Template(
239+
"{% load docs %}"
240+
"{% search_context as cached %}"
241+
'{% search_form prefix="desktop" cached_search_context=cached %}'
242+
)
239243
rendered = template.render(Context({}))
240244
self.assertIn(
241245
'<search class="search form-input" aria-labelledby="docs-search-label">',
@@ -253,7 +257,12 @@ def test_search_form_queries_multiple_renders(self):
253257
DocumentRelease.objects.create(
254258
lang=settings.DEFAULT_LANGUAGE_CODE, release=r2, is_default=True
255259
)
256-
template = Template("{% load docs %}{% search_form %}{% search_form %}")
260+
template = Template(
261+
"{% load docs %}"
262+
"{% search_context as cached %}"
263+
'{% search_form prefix="mobile" cached_search_context=cached %}'
264+
'{% search_form prefix="desktop" cached_search_context=cached %}'
265+
)
257266
with self.assertNumQueries(1):
258267
rendered = template.render(Context({"request": Mock()}))
259268

docs/views.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,24 +144,29 @@ def search_results(request, lang, version, per_page=10, orphans=3):
144144

145145
activate(lang)
146146

147-
form = DocSearchForm(request.GET or None, release=release)
148-
149147
# Get available languages for the language switcher
150148
available_languages = DocumentRelease.objects.get_available_languages_by_version(
151149
version
152150
)
153151

154152
context = {
155-
"form": form,
156153
"lang": release.lang,
157154
"version": release.version,
158155
"release": release,
159156
"available_languages": available_languages,
160157
"searchparams": request.GET.urlencode(),
161158
}
162159

163-
if form.is_valid():
164-
q = form.cleaned_data.get("q")
160+
mobile_form = DocSearchForm(request.GET or None, release=release, prefix="mobile")
161+
desktop_form = DocSearchForm(request.GET or None, release=release, prefix="desktop")
162+
submitted_form = None
163+
if desktop_form.is_valid():
164+
submitted_form = desktop_form
165+
elif mobile_form.is_valid():
166+
submitted_form = mobile_form
167+
168+
if submitted_form:
169+
q = submitted_form.cleaned_data.get("q")
165170

166171
if q:
167172
# catch queries that are coming from browser search bars

0 commit comments

Comments
 (0)