import os
import shutil
import tempfile
from pathlib import Path
from django.conf import settings
from django.template import Context, Template
from django.template.loader import render_to_string
from django.test import RequestFactory, TestCase
from django_hosts.resolvers import reverse as reverse_with_host
from releases.models import Release
from ..models import Document, DocumentRelease
from ..templatetags.docs import (
code_links,
generate_scroll_to_text_fragment,
get_all_doc_versions,
)
class TemplateTagTests(TestCase):
fixtures = ["doc_test_fixtures"]
def test_get_all_doc_versions_empty(self):
with self.assertNumQueries(1):
self.assertEqual(get_all_doc_versions({}), ["dev"])
def test_get_all_doc_versions(self):
tmp_docs_build_root = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, tmp_docs_build_root)
os.makedirs(
tmp_docs_build_root
/ settings.DEFAULT_LANGUAGE_CODE
/ "1.8"
/ "_built"
/ "json"
)
os.makedirs(
tmp_docs_build_root
/ settings.DEFAULT_LANGUAGE_CODE
/ "1.11"
/ "_built"
/ "json"
)
with self.settings(DOCS_BUILD_ROOT=tmp_docs_build_root):
self.assertEqual(get_all_doc_versions({}), ["1.8", "1.11", "dev"])
def test_pygments_template_tag(self):
template = Template(
'''
{% load docs %}
{% pygment 'python' %}
def band_listing(request):
"""A view of all bands."""
bands = models.Band.objects.all()
return render(request, 'bands/band_listing.html', {'bands': bands})
{% endpygment %}
'''
)
self.assertHTMLEqual(
template.render(Context()),
"""
def
band_listing
(request
):
"""A view of all bands."""
bands =
models.
Band.
objects.
all()
return render
(request
,
'bands/band_listing.html'
, {
'bands':
bands})
""",
)
def test_fragment_template_tag(self):
highlighted_text = (
"testing frameworks section of Advanced testing"
" topics .\nWriting and running tests\n"
"Testing tools\nAdvanced testing\ntests ¶"
)
template = Template(
"{% load docs %}"
"https://docs.djangoproject.com/en/5.1/topics/testing/"
"{{ highlighted_text|fragment }}"
)
self.assertHTMLEqual(
template.render(Context({"highlighted_text": highlighted_text})),
"https://docs.djangoproject.com/en/5.1/topics/testing/"
"#:~:text=testing%20frameworks%20section%20of%20Advanced%20testing"
"%20topics.",
)
def test_generate_scroll_to_text_fragment(self):
stacked_inline = """"StackedInline [source] ¶
The admin interface has the ability to edit models"""
text_choices = """TextChoices ):
FRESHMAN = "FR" , _ ( "Freshman" )
SOPHOMORE = "SO" , _ ( "Sophomore" )
JUNIOR"""
nested_text_choices = (
"TextChoices ( "Medal" , "GOLD "
"SILVER BRONZE" )\n\n SPORT_CHOICES = [\n( ""
"Martial Arts" , [( "judo\n"
)
cases = [
(
stacked_inline,
"#:~:text=%22StackedInline%5Bsource%5D",
),
(
text_choices,
"#:~:text=TextChoices%29%3A",
),
(
nested_text_choices,
"#:~:text=TextChoices%28%22Medal%22%2C%20%22GOLD%20SILVER%20"
"BRONZE%22%29",
),
(
"""TextChoices , IntegerChoices , and Choices
are now available as a way to define Field.choices .
TextChoices and IntegerChoices""",
"#:~:text=TextChoices%2C%20IntegerChoices%2C%20and%20Choices",
),
(
"""TextChoices or IntegerChoices ) instances.
Any Django field
Any function or method reference (e.g. datetime.datetime.today )
(must""",
"#:~:text=TextChoices%20or%20IntegerChoices%29%20instances.",
),
(
"""database configuration in DATABASES :
settings.py ¶
DATABASES = {
"default" : {
"ENGINE" : "django.db.backends.postgresql" ,
"OPTIONS""",
"#:~:text=database%20configuration%20in%20DATABASES%3A",
),
(
"""
Generic views ¶
See Built-in class-based views API . """,
"#:~:text=Generic%20views",
),
]
for text, url_text_fragment in cases:
with self.subTest(url_text_fragment=url_text_fragment):
self.assertEqual(
generate_scroll_to_text_fragment(text),
url_text_fragment,
)
def test_code_links(self):
python_objects = {
"Layer": "django.contrib.gis.gdal.Layer",
"Migration.initial": "django.db.migrations.Migration.initial",
"db_for_write": "db_for_write",
}
for searched_python_objects, expected in [
(None, {}),
("", {}),
("Layer initial db_for_write", {}),
(
"Layer initial db_for_write",
{"db_for_write": {"full_path": "db_for_write", "module_path": None}},
),
(
"Layer initial db_for_write",
{
"db_for_write": {"full_path": "db_for_write", "module_path": None},
"Layer": {
"full_path": "django.contrib.gis.gdal.Layer",
"module_path": "django.contrib.gis.gdal",
},
"Migration.initial": {
"full_path": "django.db.migrations.Migration.initial",
"module_path": "django.db.migrations",
},
},
),
]:
with self.subTest(searched_python_objects=searched_python_objects):
self.assertEqual(
code_links(searched_python_objects, python_objects),
expected,
)
class TemplateTestCase(TestCase):
def _assertOGTitleEqual(self, doc, expected):
output = render_to_string(
"docs/doc.html",
{"doc": doc, "lang": settings.DEFAULT_LANGUAGE_CODE, "version": "5.0"},
request=RequestFactory().get("/"),
)
self.assertInHTML(f'', output)
def test_opengraph_title(self):
doc = Document.objects.create(
metadata={"body": "test body"},
release=DocumentRelease.objects.create(
lang=settings.DEFAULT_LANGUAGE_CODE,
release=Release.objects.create(version="5.0"),
),
)
for title, expected in [
("test title", "test title"),
("test & title", "test & title"),
('test "title"', "test "title""),
("test title", "test title"),
]:
doc.title = title
with self.subTest(title=title):
self._assertOGTitleEqual(doc, f"{expected} | Django documentation")
class TemplateTagTestCase(TestCase):
def test_search_form_renders_without_request_in_template(self):
"""
Ensures the tag doesn't crash when rendered inside a template that
lacks a 'request' variable e.g. during Django's built-in error views.
"""
template = Template("{% load docs %}{% search_form %}")
rendered = template.render(Context({}))
self.assertIn(
'',
rendered,
)
docs_search_url = reverse_with_host(
"document-search",
host="docs",
kwargs={"lang": settings.DEFAULT_LANGUAGE_CODE, "version": "dev"},
)
self.assertIn(f'