Skip to content

Commit e5e0106

Browse files
sarahboycepauloxnet
authored andcommitted
Added scroll to text fragment to search.
1 parent 872f19d commit e5e0106

File tree

6 files changed

+127
-6
lines changed

6 files changed

+127
-6
lines changed

docs/models.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from .search import (
2929
DEFAULT_TEXT_SEARCH_CONFIG,
3030
DOCUMENT_SEARCH_VECTOR,
31+
START_SEL,
32+
STOP_SEL,
3133
TSEARCH_CONFIG_LANGUAGES,
3234
)
3335

@@ -265,15 +267,15 @@ def search(self, query_text, release):
265267
headline=SearchHeadline(
266268
"title",
267269
search_query,
268-
start_sel="<mark>",
269-
stop_sel="</mark>",
270+
start_sel=START_SEL,
271+
stop_sel=STOP_SEL,
270272
config=models.F("config"),
271273
),
272274
highlight=SearchHeadline(
273275
KeyTextTransform("body", "metadata"),
274276
search_query,
275-
start_sel="<mark>",
276-
stop_sel="</mark>",
277+
start_sel=START_SEL,
278+
stop_sel=STOP_SEL,
277279
config=models.F("config"),
278280
),
279281
breadcrumbs=models.F("metadata__breadcrumbs"),

docs/search.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@
4848
KeyTextTransform("parents", "metadata"), weight="D", config=F("config")
4949
)
5050
)
51+
52+
START_SEL = "<mark>"
53+
STOP_SEL = "</mark>"

docs/templates/docs/search_results.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ <h2>{% trans "No search query given" %}</h2>
3636
{% for result in page.object_list %}
3737
<dt>
3838
<h2 class="result-title">
39-
<a href="{% url 'document-detail' lang=result.release.lang version=result.release.version url=result.path host 'docs' %}">{{ result.headline|safe }}</a>
39+
<a href="{% url 'document-detail' lang=result.release.lang version=result.release.version url=result.path host 'docs' %}{% if not start_sel in result.headline %}{{ result.highlight|fragment }}{% endif %}">{{ result.headline|safe }}</a>
4040
</h2>
4141
<span class="meta breadcrumbs">
4242
{% for breadcrumb in result.breadcrumbs %}

docs/templatetags/docs.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import re
2+
from urllib.parse import quote
3+
14
from django import template
5+
from django.template.defaultfilters import stringfilter
26
from django.utils.safestring import mark_safe
37
from django.utils.version import get_version_tuple
48
from pygments import highlight
@@ -7,6 +11,7 @@
711

812
from ..forms import DocSearchForm
913
from ..models import DocumentRelease
14+
from ..search import START_SEL, STOP_SEL
1015
from ..utils import get_doc_path, get_doc_root
1116

1217
register = template.Library()
@@ -81,3 +86,38 @@ def view_article(request, pk):
8186
nodelist = parser.parse(("endpygment",))
8287
parser.delete_first_token()
8388
return PygmentsNode(parser.compile_filter(tokens[1]), nodelist)
89+
90+
91+
@register.filter(name="fragment")
92+
@stringfilter
93+
def generate_scroll_to_text_fragment(highlighted_text):
94+
"""
95+
Given the highlighted text generated from Document.objects.search()
96+
constructs a scroll to text fragment.
97+
98+
This will not work when:
99+
* the highlighted test starts from a partial word, e.g it starts from
100+
test_environment rather than DiscoverRunner.setup_test_environment().
101+
* it is trying to highlight a Python code snippet and the spacing logic
102+
has fallen down e.g. test = 5 not test=5 but test(a=5) not test(a = 5).
103+
"""
104+
first_non_empty_line = next(
105+
(
106+
stripped
107+
for line in highlighted_text.split("\n")
108+
if (stripped := line.strip())
109+
),
110+
"",
111+
)
112+
# Remove highlight tags and unwanted symbols.
113+
line_without_highlight = re.sub(
114+
rf"{START_SEL}|{STOP_SEL}|¶", "", first_non_empty_line
115+
)
116+
line_without_highlight = line_without_highlight.replace("&quot;", '"')
117+
# Remove excess spacing.
118+
single_spaced = re.sub(r"\s+", " ", line_without_highlight).strip()
119+
# Handle punctuation spacing.
120+
single_spaced = re.sub(r"\s([.,;:!?)(\]\[])", r"\1", single_spaced)
121+
# Due to Python code such as timezone.now(), remove the space after a bracket.
122+
single_spaced = re.sub(r"([(\[])\s", r"\1", single_spaced)
123+
return f"#:~:text={quote(single_spaced)}"

docs/tests.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from .models import DOCUMENT_SEARCH_VECTOR, Document, DocumentRelease
2121
from .sitemaps import DocsSitemap
22-
from .templatetags.docs import get_all_doc_versions
22+
from .templatetags.docs import generate_scroll_to_text_fragment, get_all_doc_versions
2323
from .utils import get_doc_path, sanitize_for_trigram
2424

2525

@@ -252,6 +252,80 @@ def band_listing(request):
252252
""",
253253
)
254254

255+
def test_fragment_template_tag(self):
256+
highlighted_text = """<mark>testing</mark> frameworks section of Advanced <mark>testing</mark> topics .
257+
Writing and running <mark>tests</mark>
258+
<mark>Testing</mark> tools
259+
Advanced <mark>testing</mark>
260+
<mark>tests</mark> ¶"""
261+
template = Template(
262+
"{% load docs %}"
263+
"https://docs.djangoproject.com/en/5.1/topics/testing/"
264+
"{{ highlighted_text|fragment }}"
265+
)
266+
self.assertHTMLEqual(
267+
template.render(Context({"highlighted_text": highlighted_text})),
268+
"https://docs.djangoproject.com/en/5.1/topics/testing/"
269+
"#:~:text=testing%20frameworks%20section%20of%20Advanced%20testing%20topics.",
270+
)
271+
272+
def test_generate_scroll_to_text_fragment(self):
273+
cases = [
274+
(
275+
""""<mark>StackedInline</mark> [source] ¶
276+
The admin interface has the ability to edit models""",
277+
"#:~:text=%22StackedInline%5Bsource%5D",
278+
),
279+
(
280+
"""<mark>TextChoices</mark> ):
281+
FRESHMAN = &quot;FR&quot; , _ ( &quot;Freshman&quot; )
282+
SOPHOMORE = &quot;SO&quot; , _ ( &quot;Sophomore&quot; )
283+
JUNIOR""",
284+
"#:~:text=TextChoices%29%3A",
285+
),
286+
(
287+
"""<mark>TextChoices</mark> ( &quot;Medal&quot; , &quot;GOLD SILVER BRONZE&quot; )
288+
289+
SPORT_CHOICES = [
290+
( &quot;Martial Arts&quot; , [( &quot;judo""",
291+
"#:~:text=TextChoices%28%22Medal%22%2C%20%22GOLD%20SILVER%20BRONZE%22%29",
292+
),
293+
(
294+
"""<mark>TextChoices</mark> , IntegerChoices , and Choices
295+
are now available as a way to define Field.choices . <mark>TextChoices</mark>
296+
and IntegerChoices""",
297+
"#:~:text=TextChoices%2C%20IntegerChoices%2C%20and%20Choices",
298+
),
299+
(
300+
"""<mark>TextChoices</mark> or IntegerChoices ) instances.
301+
Any Django field
302+
Any function or method reference (e.g. datetime.datetime.today ) (must""",
303+
"#:~:text=TextChoices%20or%20IntegerChoices%29%20instances.",
304+
),
305+
(
306+
"""<mark>database</mark> configuration in <mark>DATABASES</mark> :
307+
308+
settings.py ¶
309+
<mark>DATABASES</mark> = {
310+
&quot;default&quot; : {
311+
&quot;ENGINE&quot; : &quot;django.db.backends.postgresql&quot; ,
312+
&quot;OPTIONS""",
313+
"#:~:text=database%20configuration%20in%20DATABASES%3A",
314+
),
315+
(
316+
"""
317+
<mark>Generic</mark> <mark>views</mark> ¶
318+
See Built-in class-based <mark>views</mark> API . """,
319+
"#:~:text=Generic%20views",
320+
),
321+
]
322+
for text, url_text_fragment in cases:
323+
with self.subTest(url_text_fragment=url_text_fragment):
324+
self.assertEqual(
325+
generate_scroll_to_text_fragment(text),
326+
url_text_fragment,
327+
)
328+
255329

256330
class TestUtils(TestCase):
257331
def test_get_doc_path(self):

docs/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from .forms import DocSearchForm
1818
from .models import Document, DocumentRelease
19+
from .search import START_SEL
1920
from .utils import get_doc_path_or_404, get_doc_root_or_404
2021

2122
SIMPLE_SEARCH_OPERATORS = ["+", "|", "-", '"', "*", "(", ")", "~"]
@@ -215,6 +216,7 @@ def search_results(request, lang, version, per_page=10, orphans=3):
215216
"query": q,
216217
"page": page,
217218
"paginator": paginator,
219+
"start_sel": START_SEL,
218220
}
219221
)
220222

0 commit comments

Comments
 (0)