Guides
Made by us, for you
-- cgit v1.2.3 From 82daedc766dc3986dc9ac17ea8a6b3da87a6b1ac Mon Sep 17 00:00:00 2001 From: kosayoda tag, since highlight.js does not style
- * backgrounds of tags but bulma does, resulting in a weird off-white
- * border.
- */
- background-color: #282c34;
+/*
+ * Move padding padding from tag to hljs tags so the padding
+ * space is colored the same as the background of hljs blocks.
+ */
+.content pre {
+ padding: 0;
+}
+
+code.hljs {
+ padding: 1.75em 2em;
+}
+
+/*
+ * Show header permalink on hover.
+ */
+.headerlink {
+ display: none;
+ padding-left: 0.5em;
+}
+
+:is(h1, h2, h3, h4, h5, h6):hover > .headerlink {
+ display: inline;
}
diff --git a/pydis_site/templates/content/base.html b/pydis_site/templates/content/base.html
index 1508dfb3..19eec5d4 100644
--- a/pydis_site/templates/content/base.html
+++ b/pydis_site/templates/content/base.html
@@ -3,7 +3,7 @@
{% block title %}{{ page_title }}{% endblock %}
{% block head %}
-
+
diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html
index 5e820c26..06d74208 100644
--- a/pydis_site/templates/content/page.html
+++ b/pydis_site/templates/content/page.html
@@ -12,7 +12,7 @@
{% if relevant_links|length > 0 %}
- {{ page.content|safe }}
+ {{ page|safe }}
@@ -26,6 +26,6 @@
{% else %}
- {{ page.content|safe }}
+ {{ page|safe }}
{% endif %}
{% endblock %}
--
cgit v1.2.3
From acf7880e233faeb91ce2eeb59855f40b6e7db3e0 Mon Sep 17 00:00:00 2001
From: kosayoda
Date: Thu, 25 Mar 2021 16:36:16 +0800
Subject: Refactor content app tests.
The tests uses pyfakefs to simulate a fake filesystem that is reused
over the content app tests.
Test coverage for the app is brought to 100%.
---
Pipfile | 1 +
Pipfile.lock | 10 +-
pydis_site/apps/content/tests/helpers.py | 84 +++++++
.../apps/content/tests/test_content/_info.yml | 2 -
.../content/tests/test_content/category/_info.yml | 2 -
.../test_content/category/subcategory/_info.yml | 2 -
.../test_content/category/subcategory/test4.md | 6 -
.../content/tests/test_content/category/test3.md | 6 -
pydis_site/apps/content/tests/test_content/test.md | 10 -
.../apps/content/tests/test_content/test2.md | 6 -
pydis_site/apps/content/tests/test_utils.py | 185 +++++---------
pydis_site/apps/content/tests/test_views.py | 267 +++++++++++----------
pydis_site/apps/content/utils.py | 2 +-
pydis_site/apps/content/views/page_category.py | 2 +-
14 files changed, 295 insertions(+), 290 deletions(-)
create mode 100644 pydis_site/apps/content/tests/helpers.py
delete mode 100644 pydis_site/apps/content/tests/test_content/_info.yml
delete mode 100644 pydis_site/apps/content/tests/test_content/category/_info.yml
delete mode 100644 pydis_site/apps/content/tests/test_content/category/subcategory/_info.yml
delete mode 100644 pydis_site/apps/content/tests/test_content/category/subcategory/test4.md
delete mode 100644 pydis_site/apps/content/tests/test_content/category/test3.md
delete mode 100644 pydis_site/apps/content/tests/test_content/test.md
delete mode 100644 pydis_site/apps/content/tests/test_content/test2.md
(limited to 'pydis_site/apps/content/utils.py')
diff --git a/Pipfile b/Pipfile
index c2b87ab4..c51215b6 100644
--- a/Pipfile
+++ b/Pipfile
@@ -36,6 +36,7 @@ mccabe = "~=0.6.1"
pep8-naming = "~=0.9"
pre-commit = "~=2.1"
unittest-xml-reporting = "~=3.0"
+pyfakefs = "~=4.4.0"
[requires]
python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
index 4f8bd0d3..6125e538 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "a338a377b64a5d25bf925646c97d932a2d1c783fc8f40d91b6a9ab8f30c2b14e"
+ "sha256": "dc85f4f1b6ef58c2832d79542d6e88729cd2464bdf9324fef4d0757561f07ca7"
},
"pipfile-spec": 6,
"requires": {
@@ -605,6 +605,14 @@
"markers": "python_version >= '3.6'",
"version": "==6.0.0"
},
+ "pyfakefs": {
+ "hashes": [
+ "sha256:082d863e0e2a74351f697da404e329a91e18e5055942e59d1b836e8459b2c94c",
+ "sha256:1ac3b2845dabe69af56c20691b9347914581195ccdde352535fb7d4ff0055c19"
+ ],
+ "index": "pypi",
+ "version": "==4.4.0"
+ },
"pyflakes": {
"hashes": [
"sha256:910208209dcea632721cb58363d0f72913d9e8cf64dc6f8ae2e02a3609aba40d",
diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py
new file mode 100644
index 00000000..4e0cca34
--- /dev/null
+++ b/pydis_site/apps/content/tests/helpers.py
@@ -0,0 +1,84 @@
+from pyfakefs.fake_filesystem_unittest import TestCase
+
+# Valid markdown content with YAML metadata
+MARKDOWN_WITH_METADATA = """
+---
+title: TestTitle
+description: TestDescription
+relevant_links:
+ Python Discord: https://pythondiscord.com
+ Discord: https://discord.com
+---
+# This is a header.
+"""
+
+MARKDOWN_WITHOUT_METADATA = """#This is a header."""
+
+# Valid YAML in a _info.yml file
+CATEGORY_INFO = """
+name: Category Name
+description: Description
+"""
+
+# The HTML generated from the above markdown data
+PARSED_HTML = (
+ 'This is a header.'
+ '¶
'
+)
+
+# The YAML metadata parsed from the above markdown data
+PARSED_METADATA = {
+ "title": "TestTitle", "description": "TestDescription",
+ "relevant_links": {
+ "Python Discord": "https://pythondiscord.com",
+ "Discord": "https://discord.com"
+ }
+}
+
+# The YAML data parsed from the above _info.yml file
+PARSED_CATEGORY_INFO = {"name": "Category Name", "description": "Description"}
+
+
+class MockPagesTestCase(TestCase):
+ """
+ TestCase with a fake filesystem for testing.
+
+ Structure:
+ ├── _info.yml
+ ├── root.md
+ ├── root_without_metadata.md
+ ├── not_a_page.md
+ ├── tmp
+ | ├── _info.yml
+ | └── category_without_info
+ └── category
+ ├── _info.yml
+ ├── with_metadata.md
+ └── subcategory
+ ├── with_metadata.md
+ └── without_metadata.md
+ """
+
+ def setUp(self):
+ """Create the fake filesystem."""
+ self.setUpPyfakefs()
+
+ self.fs.create_file("_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_file("root.md", contents=MARKDOWN_WITH_METADATA)
+ self.fs.create_file("root_without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA)
+ self.fs.create_file("not_a_page.md/_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_file("category/_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_file("category/with_metadata.md", contents=MARKDOWN_WITH_METADATA)
+ self.fs.create_file("category/subcategory/_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_file(
+ "category/subcategory/with_metadata.md", contents=MARKDOWN_WITH_METADATA
+ )
+ self.fs.create_file(
+ "category/subcategory/without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA
+ )
+
+ # There is always a `tmp` directory in the filesystem, so make it a category
+ # for testing purposes.
+ # See: https://jmcgeheeiv.github.io/pyfakefs/release/usage.html#os-temporary-directories
+ self.fs.create_file("tmp/_info.yml", contents=CATEGORY_INFO)
+ self.fs.create_dir("tmp/category_without_info")
diff --git a/pydis_site/apps/content/tests/test_content/_info.yml b/pydis_site/apps/content/tests/test_content/_info.yml
deleted file mode 100644
index ad5fc113..00000000
--- a/pydis_site/apps/content/tests/test_content/_info.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-name: Base Category
-description: Base Description
diff --git a/pydis_site/apps/content/tests/test_content/category/_info.yml b/pydis_site/apps/content/tests/test_content/category/_info.yml
deleted file mode 100644
index 8311509d..00000000
--- a/pydis_site/apps/content/tests/test_content/category/_info.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-name: My Category
-description: My Description
diff --git a/pydis_site/apps/content/tests/test_content/category/subcategory/_info.yml b/pydis_site/apps/content/tests/test_content/category/subcategory/_info.yml
deleted file mode 100644
index f1c40264..00000000
--- a/pydis_site/apps/content/tests/test_content/category/subcategory/_info.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-name: My Category 1
-description: My Description 1
diff --git a/pydis_site/apps/content/tests/test_content/category/subcategory/test4.md b/pydis_site/apps/content/tests/test_content/category/subcategory/test4.md
deleted file mode 100644
index 1763a869..00000000
--- a/pydis_site/apps/content/tests/test_content/category/subcategory/test4.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Test 4
-description: Testing 4
----
-
-This is also test content and in subcategory.
diff --git a/pydis_site/apps/content/tests/test_content/category/test3.md b/pydis_site/apps/content/tests/test_content/category/test3.md
deleted file mode 100644
index 9f294130..00000000
--- a/pydis_site/apps/content/tests/test_content/category/test3.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Test 3
-description: Testing 3
----
-
-This is too test content, but in category.
diff --git a/pydis_site/apps/content/tests/test_content/test.md b/pydis_site/apps/content/tests/test_content/test.md
deleted file mode 100644
index 709860d1..00000000
--- a/pydis_site/apps/content/tests/test_content/test.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: Test
-description: Testing
-relevant_links:
- Asking Good Questions: https://pythondiscord.com/pages/resources/guides/asking-good-questions/
- Help Channel Guide: https://pythondiscord.com/pages/resources/guides/help-channels/
- Code of Conduct: https://pythondiscord.com/pages/code-of-conduct/
----
-
-This is test content.
diff --git a/pydis_site/apps/content/tests/test_content/test2.md b/pydis_site/apps/content/tests/test_content/test2.md
deleted file mode 100644
index 0e57c3cd..00000000
--- a/pydis_site/apps/content/tests/test_content/test2.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Test 2
-description: Testing 2
----
-
-This is too test content.
diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py
index 3831ad33..58175d6f 100644
--- a/pydis_site/apps/content/tests/test_utils.py
+++ b/pydis_site/apps/content/tests/test_utils.py
@@ -1,150 +1,91 @@
from pathlib import Path
-from unittest.mock import patch
-from django.conf import settings
from django.http import Http404
-from django.test import TestCase, override_settings
-from markdown2 import markdown
from pydis_site.apps.content import utils
+from pydis_site.apps.content.tests.helpers import (
+ MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA
+)
-BASE_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content")
+class GetCategoryTests(MockPagesTestCase):
+ """Tests for the get_category function."""
-class TestGetCategory(TestCase):
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_category_successfully(self):
- """Check does this get right data from category data file."""
- path = BASE_PATH.joinpath("category")
- result = utils.get_category(path)
+ def test_get_valid_category(self):
+ result = utils.get_category(Path("category"))
- self.assertEqual(result, {"name": "My Category", "description": "My Description"})
+ self.assertEqual(result, {"name": "Category Name", "description": "Description"})
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_category_not_exists(self):
- """Check does this raise 404 error when category don't exists."""
+ def test_get_nonexistent_category(self):
with self.assertRaises(Http404):
- path = BASE_PATH.joinpath("invalid")
- utils.get_category(path)
+ utils.get_category(Path("invalid"))
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_category_not_directory(self):
- """Check does this raise 404 error when category isn't directory."""
+ def test_get_category_with_path_to_file(self):
+ # Valid categories are directories, not files
with self.assertRaises(Http404):
- path = BASE_PATH.joinpath("test.md")
- utils.get_category(path)
+ utils.get_category(Path("root.md"))
+ def test_get_category_without_info_yml(self):
+ # Categories should provide an _info.yml file
+ with self.assertRaises(FileNotFoundError):
+ utils.get_category(Path("tmp/category_without_info"))
-class TestGetCategories(TestCase):
- @override_settings(PAGES_PATH=BASE_PATH)
- @patch("pydis_site.apps.content.utils.get_category")
- def test_get_categories(self, get_category_mock):
- """Check does this return test content categories."""
- get_category_mock.return_value = {"name": "My Category", "description": "My Description"}
- path = BASE_PATH.joinpath("category")
- result = utils.get_categories(path)
+class GetCategoriesTests(MockPagesTestCase):
+ """Tests for the get_categories function."""
- self.assertEqual(
- result, {"subcategory": {"name": "My Category", "description": "My Description"}}
- )
+ def test_get_root_categories(self):
+ result = utils.get_categories(Path("."))
+
+ info = PARSED_CATEGORY_INFO
+ self.assertEqual(result, {"category": info, "tmp": info, "not_a_page.md": info})
+
+ def test_get_categories_with_subcategories(self):
+ result = utils.get_categories(Path("category"))
+
+ self.assertEqual(result, {"subcategory": PARSED_CATEGORY_INFO})
+
+ def test_get_categories_without_subcategories(self):
+ result = utils.get_categories(Path("category/subcategory"))
+
+ self.assertEqual(result, {})
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_categories_in_category(self):
- """Check does this call joinpath when getting subcategories."""
- path = BASE_PATH.joinpath("category")
- result = utils.get_categories(path)
+
+class GetCategoryPagesTests(MockPagesTestCase):
+ """Tests for the get_category_pages function."""
+
+ def test_get_pages_in_root_category_successfully(self):
+ """The method should successfully retrieve page metadata."""
+ root_category_pages = utils.get_category_pages(Path("."))
self.assertEqual(
- result, {"subcategory": {"name": "My Category 1", "description": "My Description 1"}}
+ root_category_pages, {"root": PARSED_METADATA, "root_without_metadata": {}}
)
+ def test_get_pages_in_subcategories_successfully(self):
+ """The method should successfully retrieve page metadata."""
+ category_pages = utils.get_category_pages(Path("category"))
-class TestGetPages(TestCase):
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_all_root_pages(self):
- """Check does this return all root level testing content."""
- path = BASE_PATH
- result = utils.get_pages(path)
-
- for case in ["test", "test2"]:
- with self.subTest(guide=case):
- md = markdown(BASE_PATH.joinpath(f"{case}.md").read_text(), extras=["metadata"])
-
- self.assertIn(case, result)
- self.assertEqual(md.metadata, result[case])
-
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_all_category_pages(self):
- """Check does this return all category testing content."""
- path = BASE_PATH.joinpath("category")
- result = utils.get_pages(path)
-
- md = markdown(BASE_PATH.joinpath("category", "test3.md").read_text(), extras=["metadata"])
-
- self.assertIn("test3", result)
- self.assertEqual(md.metadata, result["test3"])
-
-
-class TestGetPage(TestCase):
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_root_page_success(self):
- """Check does this return page HTML and metadata when root page exist."""
- path = BASE_PATH.joinpath("test.md")
- result = utils.get_page(path)
-
- md = markdown(
- BASE_PATH.joinpath("test.md").read_text(),
- extras=[
- "metadata",
- "fenced-code-blocks",
- "header-ids",
- "strike",
- "target-blank-links",
- "tables",
- "task_list"
- ]
- )
+ # Page metadata is properly retrieved
+ self.assertEqual(category_pages, {"with_metadata": PARSED_METADATA})
- self.assertEqual(result, {"page": str(md), "metadata": md.metadata})
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_root_page_dont_exist(self):
- """Check does this raise Http404 when root page don't exist."""
- with self.assertRaises(Http404):
- path = BASE_PATH.joinpath("invalid")
- utils.get_page(path)
-
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_category_page_success(self):
- """Check does this return page HTML and metadata when category guide exist."""
- path = BASE_PATH.joinpath("category", "test3.md")
- result = utils.get_page(path)
-
- md = markdown(
- BASE_PATH.joinpath("category", "test3.md").read_text(),
- extras=[
- "metadata",
- "fenced-code-blocks",
- "header-ids",
- "strike",
- "target-blank-links",
- "tables",
- "task_list"
- ]
- )
+class GetPageTests(MockPagesTestCase):
+ """Tests for the get_page function."""
- self.assertEqual(result, {"page": str(md), "metadata": md.metadata})
+ def test_get_page(self):
+ cases = [
+ ("Root page with metadata", "root.md", PARSED_HTML, PARSED_METADATA),
+ ("Root page without metadata", "root_without_metadata.md", PARSED_HTML, {}),
+ ("Page with metadata", "category/with_metadata.md", PARSED_HTML, PARSED_METADATA),
+ ("Page without metadata", "category/subcategory/without_metadata.md", PARSED_HTML, {}),
+ ]
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_get_category_page_dont_exist(self):
- """Check does this raise Http404 when category page don't exist."""
- with self.assertRaises(Http404):
- path = BASE_PATH.joinpath("category", "invalid")
- utils.get_page(path)
+ for msg, page_path, expected_html, expected_metadata in cases:
+ with self.subTest(msg=msg):
+ html, metadata = utils.get_page(Path(page_path))
+ self.assertEqual(html, expected_html)
+ self.assertEqual(metadata, expected_metadata)
- @patch("pydis_site.settings.PAGES_PATH", new=BASE_PATH)
- def test_get_category_page_category_dont_exist(self):
- """Check does this raise Http404 when category don't exist."""
+ def test_get_nonexistent_page_returns_404(self):
with self.assertRaises(Http404):
- path = BASE_PATH.joinpath("invalid", "some-guide")
- utils.get_page(path)
+ utils.get_page(Path("invalid"))
diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py
index c61671a2..560378bc 100644
--- a/pydis_site/apps/content/tests/test_views.py
+++ b/pydis_site/apps/content/tests/test_views.py
@@ -1,140 +1,145 @@
from pathlib import Path
-from unittest.mock import patch
+from unittest import TestCase
-from django.conf import settings
from django.http import Http404
-from django.test import RequestFactory, TestCase, override_settings
-from django_hosts.resolvers import reverse
+from django.test import RequestFactory, SimpleTestCase, override_settings
+from pyfakefs import fake_filesystem_unittest
+from pydis_site.apps.content.tests.helpers import (
+ MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA
+)
from pydis_site.apps.content.views import PageOrCategoryView
-BASE_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content")
-
-
-class TestPageOrCategoryView(TestCase):
- @override_settings(PAGES_PATH=BASE_PATH)
- @patch("pydis_site.apps.content.views.page_category.utils.get_page")
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- def test_page_return_code_200(self, get_category_mock, get_page_mock):
- get_page_mock.return_value = {"guide": "test", "metadata": {}}
-
- url = reverse("content:page_category", args=["test2"])
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- get_category_mock.assert_called_once()
- get_page_mock.assert_called_once()
-
- @patch("pydis_site.apps.content.views.page_category.utils.get_page")
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_page_return_404(self, get_category_mock, get_page_mock):
- """Check that return code is 404 when invalid page provided."""
- get_page_mock.side_effect = Http404("Page not found.")
-
- url = reverse("content:page_category", args=["invalid-guide"])
- response = self.client.get(url)
- self.assertEqual(response.status_code, 404)
- get_page_mock.assert_not_called()
- get_category_mock.assert_not_called()
-
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- @patch("pydis_site.apps.content.views.page_category.utils.get_pages")
- @patch("pydis_site.apps.content.views.page_category.utils.get_categories")
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_valid_category_code_200(
- self,
- get_categories_mock,
- get_pages_mock,
- get_category_mock
- ):
- """Check that return code is 200 when visiting valid category."""
- get_category_mock.return_value = {"name": "test", "description": "test"}
- get_pages_mock.return_value = {}
-
- url = reverse("content:page_category", args=["category"])
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- get_pages_mock.assert_called_once()
- self.assertEqual(get_category_mock.call_count, 2)
- get_categories_mock.assert_called_once()
-
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- @patch("pydis_site.apps.content.views.page_category.utils.get_pages")
- @patch("pydis_site.apps.content.views.page_category.utils.get_categories")
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_invalid_category_code_404(
- self,
- get_categories_mock,
- get_pages_mock,
- get_category_mock
- ):
- """Check that return code is 404 when trying to visit invalid category."""
- get_category_mock.side_effect = Http404("Category not found.")
-
- url = reverse("content:page_category", args=["invalid-category"])
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 404)
- get_category_mock.assert_not_called()
- get_pages_mock.assert_not_called()
- get_categories_mock.assert_not_called()
-
- @patch("pydis_site.apps.content.views.page_category.utils.get_page")
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_valid_category_page_code_200(
- self,
- get_category_mock,
- get_page_mock
- ):
- """Check that return code is 200 when visiting valid category page."""
- get_page_mock.return_value = {"guide": "test", "metadata": {}}
-
- url = reverse("content:page_category", args=["category/test3"])
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- get_page_mock.assert_called_once()
- self.assertEqual(get_category_mock.call_count, 2)
-
- @patch("pydis_site.apps.content.views.page_category.utils.get_page")
- @patch("pydis_site.apps.content.views.page_category.utils.get_category")
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_invalid_category_page_code_404(
- self,
- get_category_mock,
- get_page_mock
- ):
- """Check that return code is 200 when trying to visit invalid category page."""
- get_page_mock.side_effect = Http404("Page not found.")
-
- url = reverse("content:page_category", args=["category/invalid"])
- response = self.client.get(url)
- self.assertEqual(response.status_code, 404)
- get_page_mock.assert_not_called()
- get_category_mock.assert_not_called()
-
- @override_settings(PAGES_PATH=BASE_PATH)
- def test_page_category_template_names(self):
- """Check that this return category, page template or raise Http404."""
- factory = RequestFactory()
+
+# Set the module constant within Patcher to use the fake filesystem
+# https://jmcgeheeiv.github.io/pyfakefs/master/usage.html#modules-to-reload
+with fake_filesystem_unittest.Patcher() as _:
+ BASE_PATH = Path(".")
+
+
+@override_settings(PAGES_PATH=BASE_PATH)
+class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
+ """Tests for the PageOrCategoryView class."""
+
+ def setUp(self):
+ """Set test helpers, then set up fake filesystem."""
+ self.factory = RequestFactory()
+ self.view = PageOrCategoryView.as_view()
+ self.ViewClass = PageOrCategoryView()
+ super().setUp()
+
+ # Integration tests
+ def test_valid_page_or_category_returns_200(self):
+ cases = [
+ ("Page at root", "root"),
+ ("Category page", "category"),
+ ("Page in category", "category/with_metadata"),
+ ("Subcategory page", "category/subcategory"),
+ ("Page in subcategory", "category/subcategory/with_metadata"),
+ ]
+ for msg, path in cases:
+ with self.subTest(msg=msg, path=path):
+ request = self.factory.get(f"/{path}")
+ response = self.view(request, location=path)
+ self.assertEqual(response.status_code, 200)
+
+ def test_nonexistent_page_returns_404(self):
+ with self.assertRaises(Http404):
+ request = self.factory.get("/invalid")
+ self.view(request, location="invalid")
+
+ # Unit tests
+ def test_get_template_names_returns_correct_templates(self):
+ category_template = "content/listing.html"
+ page_template = "content/page.html"
+ cases = [
+ ("root", page_template),
+ ("root_without_metadata", page_template),
+ ("category/with_metadata", page_template),
+ ("category/subcategory/with_metadata", page_template),
+ ("category", category_template),
+ ("category/subcategory", category_template),
+ ]
+
+ for path, expected_template in cases:
+ with self.subTest(path=path, expected_template=expected_template):
+ self.ViewClass.full_location = Path(path)
+ self.assertEqual(self.ViewClass.get_template_names(), [expected_template])
+
+ def test_get_template_names_with_nonexistent_paths_returns_404(self):
+ for path in ("invalid", "another_invalid", "nonexistent"):
+ with self.subTest(path=path):
+ self.ViewClass.full_location = Path(path)
+ with self.assertRaises(Http404):
+ self.ViewClass.get_template_names()
+
+ def test_get_context_data_with_valid_page(self):
+ """The method should return required fields in the template context."""
+ request = self.factory.get("/root")
+ self.ViewClass.setup(request)
+ self.ViewClass.dispatch(request, location="root")
+
+ cases = [
+ ("Context includes HTML page content", "page", PARSED_HTML),
+ ("Context includes page title", "page_title", PARSED_METADATA["title"]),
+ (
+ "Context includes page description",
+ "page_description",
+ PARSED_METADATA["description"]
+ ),
+ (
+ "Context includes relevant link names and URLs",
+ "relevant_links",
+ PARSED_METADATA["relevant_links"]
+ ),
+ ]
+ context = self.ViewClass.get_context_data()
+ for msg, key, expected_value in cases:
+ with self.subTest(msg=msg):
+ self.assertEqual(context[key], expected_value)
+
+ def test_get_context_data_with_valid_category(self):
+ """The method should return required fields in the template context."""
+ request = self.factory.get("/category")
+ self.ViewClass.setup(request)
+ self.ViewClass.dispatch(request, location="category")
+
cases = [
- {"location": "category", "output": ["content/listing.html"]},
- {"location": "test", "output": ["content/page.html"]},
- {"location": "invalid", "output": None, "raises": Http404}
+ (
+ "Context includes subcategory names and their information",
+ "categories",
+ {"subcategory": PARSED_CATEGORY_INFO}
+ ),
+ (
+ "Context includes page names and their metadata",
+ "pages",
+ {"with_metadata": PARSED_METADATA}
+ ),
+ (
+ "Context includes page description",
+ "page_description",
+ PARSED_CATEGORY_INFO["description"]
+ ),
+ ("Context includes page title", "page_title", PARSED_CATEGORY_INFO["name"]),
]
- for case in cases:
- with self.subTest(location=case["location"], output=case["output"]):
- request = factory.get(f"/pages/{case['location']}")
- instance = PageOrCategoryView()
- instance.request = request
- location = Path(case["location"])
- instance.location = location
- instance.full_location = BASE_PATH / location
-
- if "raises" in case:
- with self.assertRaises(case["raises"]):
- instance.get_template_names()
- else:
- self.assertEqual(case["output"], instance.get_template_names())
+ context = self.ViewClass.get_context_data()
+ for msg, key, expected_value in cases:
+ with self.subTest(msg=msg):
+ self.assertEqual(context[key], expected_value)
+
+ def test_get_context_data_breadcrumbs(self):
+ """The method should return correct breadcrumbs."""
+ request = self.factory.get("/category/subcategory/with_metadata")
+ self.ViewClass.setup(request)
+ self.ViewClass.dispatch(request, location="category/subcategory/with_metadata")
+
+ context = self.ViewClass.get_context_data()
+ self.assertEquals(
+ context["breadcrumb_items"],
+ [
+ {"name": PARSED_CATEGORY_INFO["name"], "path": "."},
+ {"name": PARSED_CATEGORY_INFO["name"], "path": "category"},
+ {"name": PARSED_CATEGORY_INFO["name"], "path": "category/subcategory"},
+ ]
+ )
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index 11d2b792..726c991f 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -33,7 +33,7 @@ def get_category_pages(path: Path) -> Dict[str, Dict]:
for item in path.glob("*.md"):
if item.is_file():
- pages[item.stem] = frontmatter.load(item)
+ pages[item.stem] = frontmatter.load(item).metadata
return pages
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index 4a2ed2d6..eec4e7e5 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -13,7 +13,7 @@ class PageOrCategoryView(TemplateView):
def dispatch(self, request: t.Any, *args, **kwargs) -> t.Any:
"""Conform URL path location to the filesystem path."""
- self.location = Path(self.kwargs.get("location", ""))
+ self.location = Path(kwargs.get("location", ""))
self.full_location = settings.PAGES_PATH / self.location
return super().dispatch(request, *args, **kwargs)
--
cgit v1.2.3
From 69f33290b8bd12b95bb6c620a9a1422a5d11b798 Mon Sep 17 00:00:00 2001
From: kosayoda
Date: Mon, 29 Mar 2021 17:51:10 +0800
Subject: Allow adding a table of contents to a page.
---
.../pydis-guides/how-to-contribute-a-page.md | 2 ++
pydis_site/apps/content/utils.py | 12 +++++++---
pydis_site/apps/content/views/page_category.py | 1 +
pydis_site/static/css/content/page.css | 19 +++++++++++++++
pydis_site/templates/content/page.html | 28 +++++++++++++++-------
5 files changed, 50 insertions(+), 12 deletions(-)
(limited to 'pydis_site/apps/content/utils.py')
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
index 67666428..c2d9d975 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
@@ -5,6 +5,7 @@ icon: fas fa-info
relevant_links:
Contributing to Site: https://pythondiscord.com/pages/contributing/site/
Using Git: https://pythondiscord.com/pages/contributing/working-with-git/
+toc: 4
---
Pages, which include guides, articles, and other static content, are stored in markdown files in the `site` repository on Github.
@@ -80,6 +81,7 @@ Pages, which include guides, articles, and other static content,...
### Optional Fields
- **icon:** Icon for the category entry for the page. Default: `fab fa-python`
- **relevant_links:** A YAML dictionary containing `text:link` pairs. See the example above.
+- **toc:** A number representing the smallest heading tag to show in the table of contents.
## Extended Markdown
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index 726c991f..d6886ce2 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -44,14 +44,20 @@ def get_page(path: Path) -> Tuple[str, Dict]:
raise Http404("Page not found.")
metadata, content = frontmatter.parse(path.read_text(encoding="utf-8"))
- html = markdown.markdown(
- content,
+ toc_depth = metadata.get("toc", 1)
+
+ md = markdown.Markdown(
extensions=[
"extra",
# Empty string for marker to disable text searching for [TOC]
# By using a metadata key instead, we save time on long markdown documents
- TocExtension(title="Table of Contents:", permalink=True, marker="")
+ TocExtension(permalink=True, marker="", toc_depth=toc_depth)
]
)
+ html = md.convert(content)
+
+ # Don't set the TOC if the metadata does not specify one
+ if "toc" in metadata:
+ metadata["toc"] = md.toc
return str(html), metadata
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index 711d6a56..a995d2a1 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -67,6 +67,7 @@ class PageOrCategoryView(TemplateView):
"page_title": metadata["title"],
"page_description": metadata["description"],
"relevant_links": metadata.get("relevant_links", {}),
+ "toc": metadata.get("toc")
}
@staticmethod
diff --git a/pydis_site/static/css/content/page.css b/pydis_site/static/css/content/page.css
index 97b297b2..3ac41d1b 100644
--- a/pydis_site/static/css/content/page.css
+++ b/pydis_site/static/css/content/page.css
@@ -49,3 +49,22 @@ img + em {
/* Style */
font-size: .875em;
}
+
+/*
+ * Remove extra padding on the left of TOC items
+ */
+ul.menu-list.toc {
+ margin-left: 0;
+}
+
+/*
+ * Remove bullets set by the markdown extension, since bulma adds vertical
+ * lines to represent nesting
+ */
+.toc li {
+ list-style-type: none;
+}
+/* ..but we still want bullets on the top items */
+.toc > ul > li {
+ list-style-type: disc;
+}
diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html
index 06d74208..45aa8221 100644
--- a/pydis_site/templates/content/page.html
+++ b/pydis_site/templates/content/page.html
@@ -9,20 +9,30 @@
{% endblock %}
{% block page_content %}
- {% if relevant_links|length > 0 %}
+ {% if relevant_links or toc %}
{{ page|safe }}
-
-
+ {% if toc %}
+
-
-
+
+ {% endif %}
+ {% if relevant_links %}
+
+
+
+
+ {% endif %}
+
+
{% else %}
--
cgit v1.2.3
From 4208626262d6cf790da1c0e00765b375fa427aa7 Mon Sep 17 00:00:00 2001
From: kosayoda
Date: Thu, 1 Apr 2021 16:29:29 +0800
Subject: Place category pages in the same directory as categories.
---
.../resources/guides/pydis-guides/how-to-contribute-a-page.md | 6 +++---
pydis_site/apps/content/tests/helpers.py | 4 ++--
pydis_site/apps/content/tests/test_views.py | 1 -
pydis_site/apps/content/utils.py | 3 ++-
pydis_site/apps/content/views/page_category.py | 11 ++++-------
5 files changed, 11 insertions(+), 14 deletions(-)
(limited to 'pydis_site/apps/content/utils.py')
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
index c2d9d975..51f1097d 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md
@@ -36,14 +36,14 @@ icon: fas fa-folder # Optional
All the markdown files in this folder will then be under this category.
-#### Having the Category also be a Page
-In order to make categories a page, place a page inside the category folder **with the same name as the category folder**.
+#### Having the Category Also Be a Page
+In order to make categories a page, just create a page **with the same name as the category folder** in the category's parent directory.
```plaintext
guides
+├── contributing.md
├── contributing
│ ├── _info.yml
-│ ├── contributing.md
│ └── bot.md
└── _info.yml
```
diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py
index 59cd3bd6..202dee42 100644
--- a/pydis_site/apps/content/tests/helpers.py
+++ b/pydis_site/apps/content/tests/helpers.py
@@ -50,9 +50,9 @@ class MockPagesTestCase(TestCase):
├── root.md
├── root_without_metadata.md
├── not_a_page.md
+ ├── tmp.md
├── tmp
| ├── _info.yml
- | ├── tmp.md
| └── category
| ├── _info.yml
| └── subcategory_without_info
@@ -86,6 +86,6 @@ class MockPagesTestCase(TestCase):
# for testing purposes.
# See: https://jmcgeheeiv.github.io/pyfakefs/release/usage.html#os-temporary-directories
self.fs.create_file("tmp/_info.yml", contents=CATEGORY_INFO)
- self.fs.create_file("tmp/tmp.md", contents=MARKDOWN_WITH_METADATA)
+ self.fs.create_file("tmp.md", contents=MARKDOWN_WITH_METADATA)
self.fs.create_file("tmp/category/_info.yml", contents=MARKDOWN_WITH_METADATA)
self.fs.create_dir("tmp/category/subcategory_without_info")
diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py
index 81c4012b..ab266b29 100644
--- a/pydis_site/apps/content/tests/test_views.py
+++ b/pydis_site/apps/content/tests/test_views.py
@@ -29,7 +29,6 @@ def patch_dispatch_attributes(view: PageOrCategoryView, location: str) -> None:
view.full_location = view.location
# Possible places to find page content information
- view.category_page_path = view.full_location.joinpath(view.location.stem).with_suffix(".md")
view.category_path = view.full_location
view.page_path = view.full_location.with_suffix(".md")
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index d6886ce2..d3f270ff 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -32,7 +32,8 @@ def get_category_pages(path: Path) -> Dict[str, Dict]:
pages = {}
for item in path.glob("*.md"):
- if item.is_file():
+ # Only list page if there is no category with the same name
+ if item.is_file() and not item.with_suffix("").is_dir():
pages[item.stem] = frontmatter.load(item).metadata
return pages
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index a995d2a1..8783e33f 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -19,7 +19,6 @@ class PageOrCategoryView(TemplateView):
self.full_location = settings.PAGES_PATH / self.location
# Possible places to find page content information
- self.category_page_path = self.full_location.joinpath(self.location.stem).with_suffix(".md")
self.category_path = self.full_location
self.page_path = self.full_location.with_suffix(".md")
@@ -27,7 +26,7 @@ class PageOrCategoryView(TemplateView):
def get_template_names(self) -> t.List[str]:
"""Checks if the view uses the page template or listing template."""
- if self.category_page_path.is_file() or self.page_path.is_file():
+ if self.page_path.is_file():
template_name = "content/page.html"
elif self.category_path.is_dir():
template_name = "content/listing.html"
@@ -40,13 +39,11 @@ class PageOrCategoryView(TemplateView):
"""Assign proper context variables based on what resource user requests."""
context = super().get_context_data(**kwargs)
- if self.category_page_path.is_file():
- context.update(self._get_page_context(self.category_page_path))
+ if self.page_path.is_file():
+ context.update(self._get_page_context(self.page_path))
elif self.category_path.is_dir():
context.update(self._get_category_context(self.category_path))
context["path"] = f"{self.location}/" # Add trailing slash to simplify template
- elif self.page_path.is_file():
- context.update(self._get_page_context(self.page_path))
else:
raise Http404
@@ -71,7 +68,7 @@ class PageOrCategoryView(TemplateView):
}
@staticmethod
- def _get_category_context(path) -> t.Dict[str, t.Any]:
+ def _get_category_context(path: Path) -> t.Dict[str, t.Any]:
category = utils.get_category(path)
return {
"categories": utils.get_categories(path),
--
cgit v1.2.3