From aff3a89c3cec04eda096e8f27115e36108ee6286 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 18:55:33 +0300 Subject: Change guides system to content system As this system will be used for more than just guides, I had to do some refactoring to match this system with plans. Basically now there isn't guides, but articles instead. --- pydis_site/apps/content/utils.py | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 pydis_site/apps/content/utils.py (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py new file mode 100644 index 00000000..57905a69 --- /dev/null +++ b/pydis_site/apps/content/utils.py @@ -0,0 +1,77 @@ +import os +from typing import Dict, Optional, Union + +import yaml +from django.conf import settings +from django.http import Http404 +from markdown import Markdown + + +def _get_base_path() -> str: + """Have extra function for base path getting for testability.""" + return os.path.join(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") + + +def get_category(category: str) -> Dict[str, str]: + """Load category information by name from _info.yml.""" + path = os.path.join(_get_base_path(), category) + if not os.path.exists(path) or not os.path.isdir(path): + raise Http404("Category not found.") + + with open(os.path.join(path, "_info.yml")) as f: + return yaml.safe_load(f.read()) + + +def get_categories() -> Dict[str, Dict]: + """Get all categories information.""" + base_path = _get_base_path() + categories = {} + + for name in os.listdir(base_path): + if os.path.isdir(os.path.join(base_path, name)): + categories[name] = get_category(name) + + return categories + + +def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: + """Get all root content when category is not specified. Otherwise get all this category content.""" + if category is None: + base_dir = _get_base_path() + else: + base_dir = os.path.join(_get_base_path(), category) + + articles = {} + + for filename in os.listdir(base_dir): + full_path = os.path.join(base_dir, filename) + if os.path.isfile(full_path) and filename.endswith(".md"): + md = Markdown(extensions=['meta']) + with open(full_path) as f: + md.convert(f.read()) + + articles[os.path.splitext(filename)[0]] = md.Meta + + return articles + + +def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, Dict]]: + """Get one specific article. When category is specified, get it from there.""" + if category is None: + base_path = _get_base_path() + else: + base_path = os.path.join(_get_base_path(), category) + + if not os.path.exists(base_path) or not os.path.isdir(base_path): + raise Http404("Category not found.") + + article_path = os.path.join(base_path, f"{article}.md") + if not os.path.exists(article_path) or not os.path.isfile(article_path): + raise Http404("Article not found.") + + md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) + + with open(article_path) as f: + html = md.convert(f.read()) + + return {"article": html, "metadata": md.Meta} -- cgit v1.2.3 From ae1878167304e4cd4004fc942f83da8ed480d9ac Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 20:10:14 +0300 Subject: Move from standard OS path actions to Pathlib --- pydis_site/apps/content/tests/test_utils.py | 19 +++++-------- pydis_site/apps/content/utils.py | 42 +++++++++++++---------------- pydis_site/apps/content/views/article.py | 5 ++-- 3 files changed, 29 insertions(+), 37 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index 82e1ac5f..84007b27 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from unittest.mock import patch from django.conf import settings @@ -8,7 +9,7 @@ from markdown import Markdown from pydis_site.apps.content import utils -BASE_PATH = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content") +BASE_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content") class TestGetBasePath(TestCase): @@ -16,7 +17,7 @@ class TestGetBasePath(TestCase): """Test does function return content base path.""" self.assertEqual( utils._get_base_path(), - os.path.join(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") + Path(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") ) @@ -59,8 +60,7 @@ class TestGetArticles(TestCase): for case in ["test", "test2"]: with self.subTest(guide=case): md = Markdown(extensions=['meta']) - with open(os.path.join(BASE_PATH, f"{case}.md")) as f: - md.convert(f.read()) + md.convert(BASE_PATH.joinpath(f"{case}.md").read_text()) self.assertIn(case, result) self.assertEqual(md.Meta, result[case]) @@ -71,8 +71,7 @@ class TestGetArticles(TestCase): result = utils.get_articles("category") md = Markdown(extensions=['meta']) - with open(os.path.join(BASE_PATH, "category", "test3.md")) as f: - md.convert(f.read()) + md.convert(BASE_PATH.joinpath("category", "test3.md").read_text()) self.assertIn("test3", result) self.assertEqual(md.Meta, result["test3"]) @@ -85,9 +84,7 @@ class TestGetArticle(TestCase): result = utils.get_article("test", None) md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - - with open(os.path.join(BASE_PATH, "test.md")) as f: - html = md.convert(f.read()) + html = md.convert(BASE_PATH.joinpath("test.md").read_text()) self.assertEqual(result, {"article": html, "metadata": md.Meta}) @@ -103,9 +100,7 @@ class TestGetArticle(TestCase): result = utils.get_article("test3", "category") md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - - with open(os.path.join(BASE_PATH, "category", "test3.md")) as f: - html = md.convert(f.read()) + html = md.convert(BASE_PATH.joinpath("category", "test3.md").read_text()) self.assertEqual(result, {"article": html, "metadata": md.Meta}) diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 57905a69..32c750c3 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from typing import Dict, Optional, Union import yaml @@ -7,19 +8,18 @@ from django.http import Http404 from markdown import Markdown -def _get_base_path() -> str: +def _get_base_path() -> Path: """Have extra function for base path getting for testability.""" - return os.path.join(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") + return Path(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") def get_category(category: str) -> Dict[str, str]: """Load category information by name from _info.yml.""" - path = os.path.join(_get_base_path(), category) - if not os.path.exists(path) or not os.path.isdir(path): + path = _get_base_path().joinpath(category) + if not path.exists() or not path.is_dir(): raise Http404("Category not found.") - with open(os.path.join(path, "_info.yml")) as f: - return yaml.safe_load(f.read()) + return yaml.safe_load(path.joinpath("_info.yml").read_text()) def get_categories() -> Dict[str, Dict]: @@ -27,9 +27,9 @@ def get_categories() -> Dict[str, Dict]: base_path = _get_base_path() categories = {} - for name in os.listdir(base_path): - if os.path.isdir(os.path.join(base_path, name)): - categories[name] = get_category(name) + for name in base_path.iterdir(): + if name.is_dir(): + categories[name.name] = get_category(name.name) return categories @@ -39,18 +39,16 @@ def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: if category is None: base_dir = _get_base_path() else: - base_dir = os.path.join(_get_base_path(), category) + base_dir = _get_base_path().joinpath(category) articles = {} - for filename in os.listdir(base_dir): - full_path = os.path.join(base_dir, filename) - if os.path.isfile(full_path) and filename.endswith(".md"): + for item in base_dir.iterdir(): + if item.is_file() and item.name.endswith(".md"): md = Markdown(extensions=['meta']) - with open(full_path) as f: - md.convert(f.read()) + md.convert(item.read_text()) - articles[os.path.splitext(filename)[0]] = md.Meta + articles[os.path.splitext(item.name)[0]] = md.Meta return articles @@ -60,18 +58,16 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D if category is None: base_path = _get_base_path() else: - base_path = os.path.join(_get_base_path(), category) + base_path = _get_base_path().joinpath(category) - if not os.path.exists(base_path) or not os.path.isdir(base_path): + if not base_path.exists() or not base_path.is_dir(): raise Http404("Category not found.") - article_path = os.path.join(base_path, f"{article}.md") - if not os.path.exists(article_path) or not os.path.isfile(article_path): + article_path = base_path.joinpath(f"{article}.md") + if not article_path.exists() or not article_path.is_file(): raise Http404("Article not found.") md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - - with open(article_path) as f: - html = md.convert(f.read()) + html = md.convert(article_path.read_text()) return {"article": html, "metadata": md.Meta} diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index b34ca3ee..34404719 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -1,5 +1,6 @@ import os from datetime import datetime +from pathlib import Path from typing import Optional from django.conf import settings @@ -19,11 +20,11 @@ class ArticleView(View): article_result = get_article(article, category) if category is not None: - path = os.path.join( + path = Path( settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content", category, f"{article}.md" ) else: - path = os.path.join( + path = Path( settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content", f"{article}.md" ) -- cgit v1.2.3 From 6517bb9078db8b9bb9ca7bbc11c6838309355dcb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 16:41:22 +0300 Subject: Migrate content system from Python-Markdown to markdown2 --- Pipfile | 2 +- Pipfile.lock | 85 ++++++++++++---------- .../content/resources/content/guides/_info.yml | 2 +- .../content/guides/how-to-write-a-guide.md | 17 +++-- .../content/tests/test_content/category/test3.md | 7 +- pydis_site/apps/content/tests/test_content/test.md | 15 ++-- .../apps/content/tests/test_content/test2.md | 9 ++- pydis_site/apps/content/tests/test_utils.py | 45 ++++++++---- pydis_site/apps/content/utils.py | 16 ++-- pydis_site/apps/content/views/article.py | 17 +++-- pydis_site/templates/content/article.html | 9 +-- pydis_site/templates/content/articles.html | 4 +- pydis_site/templates/content/category.html | 4 +- 13 files changed, 130 insertions(+), 102 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/Pipfile b/Pipfile index 99c2cb30..a9fcf0ff 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,7 @@ pyuwsgi = {version = "~=2.0",sys_platform = "!='win32'"} django-allauth = "~=0.41" sentry-sdk = "~=0.14" gitpython = "~=3.1.7" -markdown = "~=3.2.2" +markdown2 = "~=2.3.9" [dev-packages] coverage = "~=5.0" diff --git a/Pipfile.lock b/Pipfile.lock index 87e743c8..c120cc63 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3cd339ce2e945d1728b52bdc00c615c154797e0fbcda6e75ee78ab069545e3bc" + "sha256": "4f47bb9faf104653e2cb19990b5d8fbcde54d888447da13ff3375b03035d40a1" }, "pipfile-spec": 6, "requires": { @@ -135,11 +135,11 @@ }, "djangorestframework": { "hashes": [ - "sha256:6dd02d5a4bd2516fb93f80360673bf540c3b6641fec8766b1da2870a5aa00b32", - "sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b" + "sha256:5cc724dc4b076463497837269107e1995b1fbc917468d1b92d188fd1af9ea789", + "sha256:a5967b68a04e0d97d10f4df228e30f5a2d82ba63b9d03e1759f84993b7bf1b53" ], "index": "pypi", - "version": "==3.11.1" + "version": "==3.11.2" }, "djangorestframework-bulk": { "hashes": [ @@ -157,11 +157,11 @@ }, "gitpython": { "hashes": [ - "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912", - "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910" + "sha256:138016d519bf4dd55b22c682c904ed2fd0235c3612b2f8f65ce218ff358deed8", + "sha256:a03f728b49ce9597a6655793207c6ab0da55519368ff5961e4a74ae475b9fa8e" ], "index": "pypi", - "version": "==3.1.8" + "version": "==3.1.9" }, "idna": { "hashes": [ @@ -193,9 +193,16 @@ "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17", "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59" ], - "index": "pypi", "version": "==3.2.2" }, + "markdown2": { + "hashes": [ + "sha256:89526090907ae5ece66d783c434b35c29ee500c1986309e306ce2346273ada6a", + "sha256:e6b401ec80b75e76a6b3dbb2c8ade513156fa55fa6c30b9640a1abf6184a07c8" + ], + "index": "pypi", + "version": "==2.3.9" + }, "oauthlib": { "hashes": [ "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", @@ -312,22 +319,22 @@ }, "pyuwsgi": { "hashes": [ - "sha256:1a4dd8d99b8497f109755e09484b0bd2aeaa533f7621e7c7e2a120a72111219d", - "sha256:206937deaebbac5c87692657c3151a5a9d40ecbc9b051b94154205c50a48e963", - "sha256:2cf35d9145208cc7c96464d688caa3de745bfc969e1a1ae23cb046fc10b0ac7e", - "sha256:3ab84a168633eeb55847d59475d86e9078d913d190c2a1aed804c562a10301a3", - "sha256:430406d1bcf288a87f14fde51c66877eaf5e98516838a1c6f761af5d814936fc", - "sha256:72be25ce7aa86c5616c59d12c2961b938e7bde47b7ff6a996ff83b89f7c5cd27", - "sha256:aa4d615de430e2066a1c76d9cc2a70abf2dfc703a82c21aee625b445866f2c3b", - "sha256:aadd231256a672cf4342ef9fb976051949e4d5b616195e696bcb7b8a9c07789e", - "sha256:b15ee6a7759b0465786d856334b8231d882deda5291cf243be6a343a8f3ef910", - "sha256:bd1d0a8d4cb87eb63417a72e6b1bac47053f9b0be550adc6d2a375f4cbaa22f0", - "sha256:d5787779ec24b67ac8898be9dc2b2b4e35f17d79f14361f6cf303d6283a848f2", - "sha256:ecfae85d6504e0ecbba100a795032a88ce8f110b62b93243f2df1bd116eca67f" + "sha256:0bd14517398f494d828d77a9bf72b5a6cbef0112e1cc05e9a0080fa8828ccfa0", + "sha256:285e263a9094389f13cfdefd033a4e99fbed3ad120dba9ac5093846cc03ac5ab", + "sha256:297d1d0b8c472374b12eda7f17a9f5de67cf516612e42b71a7636afb9d1e3974", + "sha256:5439f0f3ef5d6bf1622f341662d04c1d92b88889db40b295419e5fe75a7c7d45", + "sha256:56ecda11e873b2eb937b33d2999766322eebfa82ee5b26a2196a335c4e786186", + "sha256:66a9751f28abf348e0ddccadc4ded47623f2d35cf9609c87b57909d55a4cdc15", + "sha256:890e7e863cb61c8369b6bcfa5d6f323753aaeec2cfaba16741f119c79b964aa7", + "sha256:90e4235020048456ad867aefc383cdf5528b7f6e327555ceec579c428a828759", + "sha256:94d4287b155aa789ce4b6f671c981f7d6c58fc3113330e2f29ac7926cb854645", + "sha256:a425f562f382a097ca49df26b70d47d12f0cf0abf233610f3f58b1f7f780355e", + "sha256:bddeb8df77010d0f842068765a0b3155fdcfd847f14bc1ba89fc7e37914a13d2", + "sha256:dac4a04dc0f69d641dba984e83214d2c2cc098496c5d5585e7d3f4e7a9190f84" ], "index": "pypi", "markers": "sys_platform != 'win32'", - "version": "==2.0.19.1" + "version": "==2.0.19.1.post0" }, "pyyaml": { "hashes": [ @@ -363,11 +370,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:1a086486ff9da15791f294f6e9915eb3747d161ef64dee2d038a4d0b4a369b24", - "sha256:45486deb031cea6bbb25a540d7adb4dd48cd8a1cc31e6a5ce9fb4f792a572e9a" + "sha256:1d91a0059d2d8bb980bec169578035c2f2d4b93cd8a4fb5b85c81904d33e221a", + "sha256:6222cf623e404c3e62b8e0e81c6db866ac2d12a663b7c1f7963350e3f397522a" ], "index": "pypi", - "version": "==0.17.6" + "version": "==0.18.0" }, "six": { "hashes": [ @@ -513,19 +520,19 @@ }, "flake8": { "hashes": [ - "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", - "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" + "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", + "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" ], "index": "pypi", - "version": "==3.8.3" + "version": "==3.8.4" }, "flake8-annotations": { "hashes": [ - "sha256:09fe1aa3f40cb8fef632a0ab3614050a7584bb884b6134e70cf1fc9eeee642fa", - "sha256:5bda552f074fd6e34276c7761756fa07d824ffac91ce9c0a8555eb2bc5b92d7a" + "sha256:0bcebb0792f1f96d617ded674dca7bf64181870bfe5dace353a1483551f8e5f1", + "sha256:bebd11a850f6987a943ce8cdff4159767e0f5f89b3c88aca64680c2175ee02df" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.4.1" }, "flake8-bandit": { "hashes": [ @@ -597,18 +604,18 @@ }, "gitpython": { "hashes": [ - "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912", - "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910" + "sha256:138016d519bf4dd55b22c682c904ed2fd0235c3612b2f8f65ce218ff358deed8", + "sha256:a03f728b49ce9597a6655793207c6ab0da55519368ff5961e4a74ae475b9fa8e" ], "index": "pypi", - "version": "==3.1.8" + "version": "==3.1.9" }, "identify": { "hashes": [ - "sha256:c770074ae1f19e08aadbda1c886bc6d0cb55ffdc503a8c0fe8699af2fc9664ae", - "sha256:d02d004568c5a01261839a05e91705e3e9f5c57a3551648f9b3fb2b9c62c0f62" + "sha256:7c22c384a2c9b32c5cc891d13f923f6b2653aa83e2d75d8f79be240d6c86c4f4", + "sha256:da683bfb7669fa749fc7731f378229e2dbf29a1d1337cbde04106f02236eb29d" ], - "version": "==1.5.3" + "version": "==1.5.5" }, "mccabe": { "hashes": [ @@ -731,10 +738,10 @@ }, "virtualenv": { "hashes": [ - "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc", - "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b" + "sha256:3d427459dfe5ec3241a6bad046b1d10c0e445940e013c81946458987c7c7e255", + "sha256:9160a8f6196afcb8bb91405b5362651f302ee8e810fc471f5f9ce9a06b070298" ], - "version": "==20.0.31" + "version": "==20.0.32" } } } diff --git a/pydis_site/apps/content/resources/content/guides/_info.yml b/pydis_site/apps/content/resources/content/guides/_info.yml index 8a38271d..369f05d4 100644 --- a/pydis_site/apps/content/resources/content/guides/_info.yml +++ b/pydis_site/apps/content/resources/content/guides/_info.yml @@ -1,2 +1,2 @@ name: Guides -description: Python and PyDis guides. \ No newline at end of file +description: Python and PyDis guides. diff --git a/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md index 072c2538..8ea438a2 100644 --- a/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md +++ b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md @@ -1,11 +1,12 @@ -Title: How to Write a Guide -ShortDescription: Learn how to write a guide for this website -Contributors: ks129 +--- +title: How to Write a Guide +short_description: Learn how to write a guide for this website +--- When you are interested about how to write guide for this site (like this), then you can learn about it here. PyDis use Markdown files for guides, but these files have some small differences from standard Markdown (like defining HTML IDs and classes). -## [Getting Started](#getting-started){: id="getting-started" } +## Getting Started First, you have to have a good idea, that match with PyDis theme. We can't accept guides like *How to bake a cake*, *How to lose weigth*. These doesn't match with PyDis theme and will be declined. Most of guides theme should be server and Python, but there can be some exceptions, when they are connected with PyDis. Best way to find out is your idea good is to discuss about it in #dev-core channel. There can other peoples give their opinion about your idea. Even better, open issue in site repository first, then PyDis staff can see it and approve/decline this idea. @@ -14,12 +15,12 @@ It's good idea to wait for staff decision before starting to write guide to avoi To start with contributing, you should read [how to contribute to site](https://pythondiscord.com/pages/contributing/site/). You should also read our [Git workflow](https://pythondiscord.com/pages/contributing/working-with-git/), because you need to push your guide to GitHub. -## [Creating a File](#creating-a-file){: id="creating-a-file" } +## Creating a File All guides is located at `site` repository, in `pydis_site/apps/guides/resources/guides`. Under this is root level guides (.md files) and categories (directories). Learn more about categories in [categories section](#categories). At this point, you will need your guide name for filename. Replace all your guide name spaces with `-` and make all lowercase. Save this as `.md` (Markdown) file. This name (without Markdown extension) is path of guide in URL. -## [Markdown Metadata](#markdown-metadata){: id="markdown-metadata" } +## Markdown Metadata Guide files have some required metadata, like title, contributors, description, relevant pages. Metadata is first thing in file, YAML-like key-value pairs: ```md @@ -46,13 +47,13 @@ You can read more about Markdown metadata [here](https://python-markdown.github. - **Contributors:** All who have contributed to this guide. One person per-line, and they **have to be at same level**. When you edit guide, add your name to there. - **Relevant Links and Values:** Links that will be shown at right side. Both key's values have to be at same level, just like for contributors field. -## [Content](#content){: id="content" } +## Content For content, mostly you can use standard markdown, but there is a few addition that is available. ### HTML classes and IDs To provide HTML classes and/or IDs, this use `{: id="myid" class="class1 class2" }`. When using it at header, place this **right after** title, no space between them. For mutliline items, place them next line after end of block. You can read more about it [here](https://python-markdown.github.io/extensions/attr_list/). -## [Categories](#categories){: id="categories" } +## Categories To have some systematic sorting of guides, site support guides categories. Currently this system support only 1 level of categories. Categories live at `site` repo in `pydis_site/apps/guides/resources/guides` subdirectories. Directory name is path of category in URL. Inside category directory, there is 1 file required: `_info.yml`. This file need 2 key-value pairs defined: ```yml diff --git a/pydis_site/apps/content/tests/test_content/category/test3.md b/pydis_site/apps/content/tests/test_content/category/test3.md index bdde6188..03ddd67b 100644 --- a/pydis_site/apps/content/tests/test_content/category/test3.md +++ b/pydis_site/apps/content/tests/test_content/category/test3.md @@ -1,5 +1,6 @@ -Title: Test 3 -ShortDescription: Testing 3 -Contributors: user3 +--- +title: Test 3 +short_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 index 7a917899..175c1fdb 100644 --- a/pydis_site/apps/content/tests/test_content/test.md +++ b/pydis_site/apps/content/tests/test_content/test.md @@ -1,11 +1,8 @@ -Title: Test -ShortDescription: Testing -Contributors: user -RelevantLinks: https://pythondiscord.com/pages/resources/guides/asking-good-questions/ - https://pythondiscord.com/pages/resources/guides/help-channels/ - https://pythondiscord.com/pages/code-of-conduct/ -RelevantLinkValues: Asking Good Questions - Help Channel Guide - Code of Conduct +--- +title: Test +short_description: Testing +relevant_links: https://pythondiscord.com/pages/resources/guides/asking-good-questions/,https://pythondiscord.com/pages/resources/guides/help-channels/,https://pythondiscord.com/pages/code-of-conduct/ +relevant_link_values: Asking Good Questions,Help Channel Guide,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 index f0852356..14d8a54b 100644 --- a/pydis_site/apps/content/tests/test_content/test2.md +++ b/pydis_site/apps/content/tests/test_content/test2.md @@ -1,5 +1,6 @@ -Title: Test 2 -ShortDescription: Testing 2 -Contributors: user2 +--- +title: Test 2 +short_description: Testing 2 +--- -This is too test content. \ No newline at end of file +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 84007b27..bba998fe 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -1,11 +1,10 @@ -import os from pathlib import Path from unittest.mock import patch from django.conf import settings from django.http import Http404 from django.test import TestCase -from markdown import Markdown +from markdown2 import markdown from pydis_site.apps.content import utils @@ -59,22 +58,20 @@ class TestGetArticles(TestCase): for case in ["test", "test2"]: with self.subTest(guide=case): - md = Markdown(extensions=['meta']) - md.convert(BASE_PATH.joinpath(f"{case}.md").read_text()) + md = markdown(BASE_PATH.joinpath(f"{case}.md").read_text(), extras=["metadata"]) self.assertIn(case, result) - self.assertEqual(md.Meta, result[case]) + self.assertEqual(md.metadata, result[case]) def test_get_all_category_articles(self): """Check does this return all category testing content.""" with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): result = utils.get_articles("category") - md = Markdown(extensions=['meta']) - md.convert(BASE_PATH.joinpath("category", "test3.md").read_text()) + md = markdown(BASE_PATH.joinpath("category", "test3.md").read_text(), extras=["metadata"]) self.assertIn("test3", result) - self.assertEqual(md.Meta, result["test3"]) + self.assertEqual(md.metadata, result["test3"]) class TestGetArticle(TestCase): @@ -83,10 +80,20 @@ class TestGetArticle(TestCase): with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): result = utils.get_article("test", None) - md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - html = md.convert(BASE_PATH.joinpath("test.md").read_text()) + md = markdown( + BASE_PATH.joinpath("test.md").read_text(), + extras=[ + "metadata", + "fenced-code-blocks", + "header-ids", + "strike", + "target-blank-links", + "tables", + "task_list" + ] + ) - self.assertEqual(result, {"article": html, "metadata": md.Meta}) + self.assertEqual(result, {"article": str(md), "metadata": md.metadata}) def test_get_root_article_dont_exist(self): """Check does this raise Http404 when root article don't exist.""" @@ -99,10 +106,20 @@ class TestGetArticle(TestCase): with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): result = utils.get_article("test3", "category") - md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - html = md.convert(BASE_PATH.joinpath("category", "test3.md").read_text()) + md = markdown( + BASE_PATH.joinpath("category", "test3.md").read_text(), + extras=[ + "metadata", + "fenced-code-blocks", + "header-ids", + "strike", + "target-blank-links", + "tables", + "task_list" + ] + ) - self.assertEqual(result, {"article": html, "metadata": md.Meta}) + self.assertEqual(result, {"article": str(md), "metadata": md.metadata}) def test_get_category_article_dont_exist(self): """Check does this raise Http404 when category article don't exist.""" diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 32c750c3..b2451745 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -5,7 +5,7 @@ from typing import Dict, Optional, Union import yaml from django.conf import settings from django.http import Http404 -from markdown import Markdown +from markdown2 import markdown def _get_base_path() -> Path: @@ -45,10 +45,8 @@ def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: for item in base_dir.iterdir(): if item.is_file() and item.name.endswith(".md"): - md = Markdown(extensions=['meta']) - md.convert(item.read_text()) - - articles[os.path.splitext(item.name)[0]] = md.Meta + md = markdown(item.read_text(), extras=["metadata"]) + articles[os.path.splitext(item.name)[0]] = md.metadata return articles @@ -67,7 +65,9 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D if not article_path.exists() or not article_path.is_file(): raise Http404("Article not found.") - md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - html = md.convert(article_path.read_text()) + html = markdown( + article_path.read_text(), + extras=["metadata", "fenced-code-blocks", "header-ids", "strike", "target-blank-links", "tables", "task_list"] + ) - return {"article": html, "metadata": md.Meta} + return {"article": str(html), "metadata": html.metadata} diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index 34404719..ede3ba43 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -34,6 +34,16 @@ class ArticleView(View): else: category_data = {"name": None, "raw_name": None} + relevant_links = { + link: value for link, value in zip( + article_result["metadata"].get("relevant_links", "").split(","), + article_result["metadata"].get("relevant_link_values", "").split(",") + ) + } + + if relevant_links == {"": ""}: + relevant_links = {} + return render( request, "content/article.html", @@ -41,11 +51,6 @@ class ArticleView(View): "article": article_result, "last_modified": datetime.fromtimestamp(os.path.getmtime(path)).strftime("%dth %B %Y"), "category_data": category_data, - "relevant_links": { - link: value for link, value in zip( - article_result["metadata"].get("relevantlinks", []), - article_result["metadata"].get("relevantlinkvalues", []) - ) - } + "relevant_links": relevant_links } ) diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html index de6cd28d..f4282df2 100644 --- a/pydis_site/templates/content/article.html +++ b/pydis_site/templates/content/article.html @@ -1,11 +1,11 @@ {% extends 'base/base.html' %} {% load static %} -{% block title %}{{ metadata.title|first }}{% endblock %} +{% block title %}{{ article.metadata.title }}{% endblock %} {% block head %} - + @@ -23,7 +23,7 @@ {% if category_data.raw_name is not None %}
  • {{ category_data.name }}
  • {% endif %} -
  • {{ article.metadata.title|first }}
  • +
  • {{ article.metadata.title }}
  • @@ -32,13 +32,12 @@
    -

    {{ article.metadata.title|first }}

    +

    {{ article.metadata.title }}

    {{ article.article|safe }}

    Last modified: {{ last_modified }}
    - Contributors: {{ article.metadata.contributors|join:", " }}

    diff --git a/pydis_site/templates/content/articles.html b/pydis_site/templates/content/articles.html index 6fea66e5..363bbb4f 100644 --- a/pydis_site/templates/content/articles.html +++ b/pydis_site/templates/content/articles.html @@ -29,9 +29,9 @@ - {{ data.title.0 }} + {{ data.title }} -

    {{ data.shortdescription.0 }}

    +

    {{ data.short_description }}

    {% endfor %} {% for category, data in categories.items %} diff --git a/pydis_site/templates/content/category.html b/pydis_site/templates/content/category.html index 61e20c43..c2201745 100644 --- a/pydis_site/templates/content/category.html +++ b/pydis_site/templates/content/category.html @@ -33,9 +33,9 @@ - {{ data.title.0 }} + {{ data.title }} -

    {{ data.shortdescription.0 }}

    +

    {{ data.short_description }}

    {% endfor %}
    -- cgit v1.2.3 From 2e3eec0998a735a7b9512889c3a7491e4a73ea68 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 19:11:50 +0300 Subject: Fix content app linting --- pydis_site/apps/content/tests/test_utils.py | 4 +++- pydis_site/apps/content/tests/test_views.py | 12 ++++-------- pydis_site/apps/content/urls.py | 6 +++++- pydis_site/apps/content/utils.py | 12 ++++++++++-- pydis_site/apps/content/views/__init__.py | 2 +- pydis_site/apps/content/views/article.py | 9 +++++++-- pydis_site/apps/content/views/articles.py | 8 ++++++-- pydis_site/apps/content/views/category.py | 8 ++++++-- 8 files changed, 42 insertions(+), 19 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index bba998fe..9c7c4f31 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -47,7 +47,9 @@ class TestGetCategories(TestCase): with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): result = utils.get_categories() - self.assertEqual(result, {"category": {"name": "My Category", "description": "My Description"}}) + self.assertEqual( + result, {"category": {"name": "My Category", "description": "My Description"}} + ) class TestGetArticles(TestCase): diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 98054534..0901c67f 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -21,10 +21,9 @@ class TestGuidesIndexView(TestCase): class TestGuideView(TestCase): - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_guide_return_code_200(self, get_category_mock, get_article_mock, get_time_mock): + def test_guide_return_code_200(self, get_category_mock, get_article_mock): get_article_mock.return_value = {"guide": "test", "metadata": {}} url = reverse("content:article", args=["test-guide"]) @@ -33,10 +32,9 @@ class TestGuideView(TestCase): get_category_mock.assert_not_called() get_article_mock.assert_called_once_with("test-guide", None) - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_guide_return_404(self, get_category_mock, get_article_mock, get_time_mock): + def test_guide_return_404(self, get_category_mock, get_article_mock): """Check that return code is 404 when invalid article provided.""" get_article_mock.side_effect = Http404("Article not found.") @@ -77,10 +75,9 @@ class TestCategoryView(TestCase): class TestCategoryGuidesView(TestCase): - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_valid_category_article_code_200(self, get_category_mock, get_article_mock, get_time_mock): + def test_valid_category_article_code_200(self, get_category_mock, get_article_mock): """Check that return code is 200 when visiting valid category article.""" get_article_mock.return_value = {"guide": "test", "metadata": {}} @@ -90,10 +87,9 @@ class TestCategoryGuidesView(TestCase): get_article_mock.assert_called_once_with("test3", "category") get_category_mock.assert_called_once_with("category") - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_invalid_category_article_code_404(self, get_category_mock, get_article_mock, get_time_mock): + def test_invalid_category_article_code_404(self, get_category_mock, get_article_mock): """Check that return code is 200 when trying to visit invalid category article.""" get_article_mock.side_effect = Http404("Article not found.") diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index ae9b1e57..5a4ee37a 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -6,6 +6,10 @@ app_name = "content" urlpatterns = [ path("", views.ArticlesView.as_view(), name='content'), path("category//", views.CategoryView.as_view(), name='category'), - path("category///", views.ArticleView.as_view(), name='category_article'), + path( + "category///", + views.ArticleView.as_view(), + name='category_article' + ), path("/", views.ArticleView.as_view(), name='article') ] diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index b2451745..e164c39f 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -35,7 +35,7 @@ def get_categories() -> Dict[str, Dict]: def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: - """Get all root content when category is not specified. Otherwise get all this category content.""" + """Get all root or category articles.""" if category is None: base_dir = _get_base_path() else: @@ -67,7 +67,15 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D html = markdown( article_path.read_text(), - extras=["metadata", "fenced-code-blocks", "header-ids", "strike", "target-blank-links", "tables", "task_list"] + extras=[ + "metadata", + "fenced-code-blocks", + "header-ids", + "strike", + "target-blank-links", + "tables", + "task_list" + ] ) return {"article": str(html), "metadata": html.metadata} diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py index b50d487b..616bc850 100644 --- a/pydis_site/apps/content/views/__init__.py +++ b/pydis_site/apps/content/views/__init__.py @@ -1,5 +1,5 @@ -from .category import CategoryView from .article import ArticleView from .articles import ArticlesView +from .category import CategoryView __all__ = ["ArticleView", "ArticlesView", "CategoryView"] diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index 51c9a199..f4c834db 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -5,13 +5,18 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_category, get_article +from pydis_site.apps.content.utils import get_article, get_category class ArticleView(View): """Shows specific guide page.""" - def get(self, request: WSGIRequest, article: str, category: Optional[str] = None) -> HttpResponse: + def get( + self, + request: WSGIRequest, + article: str, + category: Optional[str] = None + ) -> HttpResponse: """Collect guide content and display it. When guide don't exist, return 404.""" article_result = get_article(article, category) diff --git a/pydis_site/apps/content/views/articles.py b/pydis_site/apps/content/views/articles.py index ff945a19..cce601e1 100644 --- a/pydis_site/apps/content/views/articles.py +++ b/pydis_site/apps/content/views/articles.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_categories, get_articles +from pydis_site.apps.content.utils import get_articles, get_categories class ArticlesView(View): @@ -11,4 +11,8 @@ class ArticlesView(View): def get(self, request: WSGIRequest) -> HttpResponse: """Shows all content and categories.""" - return render(request, "content/articles.html", {"content": get_articles(), "categories": get_categories()}) + return render( + request, + "content/articles.html", + {"content": get_articles(), "categories": get_categories()} + ) diff --git a/pydis_site/apps/content/views/category.py b/pydis_site/apps/content/views/category.py index 62e80a47..9d2a978e 100644 --- a/pydis_site/apps/content/views/category.py +++ b/pydis_site/apps/content/views/category.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_category, get_articles +from pydis_site.apps.content.utils import get_articles, get_category class CategoryView(View): @@ -14,5 +14,9 @@ class CategoryView(View): return render( request, "content/category.html", - {"category_info": get_category(category), "content": get_articles(category), "category_name": category} + { + "category_info": get_category(category), + "content": get_articles(category), + "category_name": category + } ) -- cgit v1.2.3 From fc11a23f6561407883aa2f6107f14f9089ac85af Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:37:34 +0300 Subject: Implement fetching contributors and last modification date from GitHub --- pydis_site/apps/content/utils.py | 38 ++++++++++++++++++++++++++++++- pydis_site/apps/content/views/article.py | 3 ++- pydis_site/templates/content/article.html | 18 +++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index e164c39f..0c2a027d 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,12 +1,17 @@ import os from pathlib import Path -from typing import Dict, Optional, Union +from typing import Dict, List, Optional, Union +import requests import yaml +from dateutil import parser from django.conf import settings from django.http import Http404 from markdown2 import markdown +COMMITS_URL = "https://api.github.com/repos/{owner}/{name}/commits?path={path}&sha={branch}" +BASE_ARTICLES_LOCATION = "pydis_site/apps/content/resources/content/" + def _get_base_path() -> Path: """Have extra function for base path getting for testability.""" @@ -79,3 +84,34 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D ) return {"article": str(html), "metadata": html.metadata} + + +def get_github_information( + article: str, + category: Optional[str] = None +) -> Dict[str, Union[List[str], str]]: + """Get article last modified date and contributors from GitHub.""" + result = requests.get( + COMMITS_URL.format( + owner=settings.SITE_REPOSITORY_OWNER, + name=settings.SITE_REPOSITORY_NAME, + branch=settings.SITE_REPOSITORY_BRANCH, + path=f"{BASE_ARTICLES_LOCATION}{f'{category}/' if category else ''}{article}.md" + ) + ) + + if result.status_code == 200: + data = result.json() + return { + "last_modified": parser.isoparse( + data[0]["commit"]["committer"]["date"] + ).strftime("%dth %B %Y"), + "contributors": { + c["commit"]["committer"]["name"]: c["committer"]["html_url"] for c in data + } + } + else: + return { + "last_modified": "N/A", + "contributors": [] + } diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index 02e8103f..42b92cee 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -5,7 +5,7 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_article, get_category +from pydis_site.apps.content.utils import get_article, get_category, get_github_information class ArticleView(View): @@ -38,5 +38,6 @@ class ArticleView(View): article_result["metadata"].get("relevant_link_values", "").split(",") ) if link != "" and value != "" }, + "github_data": get_github_information(article, category), } ) diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html index 5e090050..151d6cf5 100644 --- a/pydis_site/templates/content/article.html +++ b/pydis_site/templates/content/article.html @@ -33,11 +33,29 @@

    {{ article.metadata.title }}

    +

    Last modified: {{ github_data.last_modified }}

    {{ article.article|safe }}
    +
    +
    +

    Contributors

    +
    +
    + {% if github_data.contributors|length %} +
    + {% for user, profile_url in github_data.contributors.items %} + {{ user }} + {% endfor %} +
    + {% else %} +

    N/A

    + {% endif %} +
    +
    + {% if relevant_links|length > 0 %}
    -- cgit v1.2.3 From 968f3504f49c5695ef88686c89be1fbe0974484c Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 6 Oct 2020 21:48:12 +0300 Subject: Cover fetching article GitHub information with tests --- pydis_site/apps/content/tests/test_utils.py | 44 ++++++++++++++++++++++++++++- pydis_site/apps/content/tests/test_views.py | 4 ++- pydis_site/apps/content/utils.py | 2 +- 3 files changed, 47 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index 9c7c4f31..a00c293f 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -1,6 +1,8 @@ +from datetime import datetime from pathlib import Path -from unittest.mock import patch +from unittest.mock import patch, MagicMock +from dateutil.parser import isoparse from django.conf import settings from django.http import Http404 from django.test import TestCase @@ -134,3 +136,43 @@ class TestGetArticle(TestCase): with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): with self.assertRaises(Http404): utils.get_article("some-guide", "invalid") + + +class GetGitHubInformationTests(TestCase): + @patch("pydis_site.apps.content.utils.requests.get") + @patch("pydis_site.apps.content.utils.COMMITS_URL", "foobar") + def test_call_get_github_information_requests_get(self, requests_get_mock): + """Check does this call requests.get function with proper URL.""" + utils.get_github_information("foo", None) + requests_get_mock.assert_called_once_with("foobar") + + @patch("pydis_site.apps.content.utils.requests.get") + def test_github_status_code_200_response(self, requests_get_mock): + """Check does this return provided modified date and contributors.""" + requests_get_mock.return_value = MagicMock(status_code=200) + requests_get_mock.return_value.json.return_value = [{ + "commit": { + "committer": { + "date": datetime(2020, 10, 1).isoformat(), + "name": "foobar", + } + }, + "committer": { + "html_url": "abc1234" + } + }] + result = utils.get_github_information("foo", None) + self.assertEqual(result, { + "last_modified": datetime(2020, 10, 1).strftime("%dth %B %Y"), + "contributors": {"foobar": "abc1234"} + }) + + @patch("pydis_site.apps.content.utils.requests.get") + def test_github_other_status_code_response(self, requests_get_mock): + """Check does this return provided modified date and contributors.""" + requests_get_mock.return_value = MagicMock(status_code=404) + result = utils.get_github_information("foo", None) + self.assertEqual(result, { + "last_modified": "N/A", + "contributors": {} + }) diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index c5da066f..67177c93 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -23,7 +23,8 @@ class TestArticlesIndexView(TestCase): class TestArticleView(TestCase): @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_article_return_code_200(self, get_category_mock, get_article_mock): + @patch("pydis_site.apps.content.views.article.get_github_information") + def test_article_return_code_200(self, gh_info_mock, get_category_mock, get_article_mock): get_article_mock.return_value = {"guide": "test", "metadata": {}} url = reverse("articles:article", args=["test-guide"]) @@ -31,6 +32,7 @@ class TestArticleView(TestCase): self.assertEqual(response.status_code, 200) get_category_mock.assert_not_called() get_article_mock.assert_called_once_with("test-guide", None) + gh_info_mock.assert_called_once() @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 0c2a027d..e8f1af70 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -113,5 +113,5 @@ def get_github_information( else: return { "last_modified": "N/A", - "contributors": [] + "contributors": {} } -- cgit v1.2.3 From a867c697b2ef56fb8d8bf81f9e33c9516007944a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 28 Nov 2020 10:02:50 +0200 Subject: Update utils to match with new unlimited categories system --- pydis_site/apps/content/utils.py | 53 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index e8f1af70..a89db83c 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,5 +1,4 @@ import os -from pathlib import Path from typing import Dict, List, Optional, Union import requests @@ -13,38 +12,37 @@ COMMITS_URL = "https://api.github.com/repos/{owner}/{name}/commits?path={path}&s BASE_ARTICLES_LOCATION = "pydis_site/apps/content/resources/content/" -def _get_base_path() -> Path: - """Have extra function for base path getting for testability.""" - return Path(settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content") - - -def get_category(category: str) -> Dict[str, str]: +def get_category(path: List[str]) -> Dict[str, str]: """Load category information by name from _info.yml.""" - path = _get_base_path().joinpath(category) + path = settings.ARTICLES_PATH.joinpath(*path) if not path.exists() or not path.is_dir(): raise Http404("Category not found.") return yaml.safe_load(path.joinpath("_info.yml").read_text()) -def get_categories() -> Dict[str, Dict]: +def get_categories(path: Optional[List[str]] = None) -> Dict[str, Dict]: """Get all categories information.""" - base_path = _get_base_path() categories = {} + if path is None: + categories_path = settings.ARTICLES_PATH + path = [] + else: + categories_path = settings.ARTICLES_PATH.joinpath(*path) - for name in base_path.iterdir(): + for name in categories_path.iterdir(): if name.is_dir(): - categories[name.name] = get_category(name.name) + categories[name.name] = get_category([*path, name.name]) return categories -def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: +def get_articles(path: Optional[List[str]] = None) -> Dict[str, Dict]: """Get all root or category articles.""" - if category is None: - base_dir = _get_base_path() + if path is None: + base_dir = settings.ARTICLES_PATH else: - base_dir = _get_base_path().joinpath(category) + base_dir = settings.ARTICLES_PATH.joinpath(*path) articles = {} @@ -56,17 +54,12 @@ def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: return articles -def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, Dict]]: +def get_article(path: List[str]) -> Dict[str, Union[str, Dict]]: """Get one specific article. When category is specified, get it from there.""" - if category is None: - base_path = _get_base_path() - else: - base_path = _get_base_path().joinpath(category) - - if not base_path.exists() or not base_path.is_dir(): - raise Http404("Category not found.") + article_path = settings.ARTICLES_PATH.joinpath(*path[:-1]) - article_path = base_path.joinpath(f"{article}.md") + # We need to include extension MD + article_path = article_path.joinpath(f"{path[-1]}.md") if not article_path.exists() or not article_path.is_file(): raise Http404("Article not found.") @@ -87,8 +80,7 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D def get_github_information( - article: str, - category: Optional[str] = None + path: List[str] ) -> Dict[str, Union[List[str], str]]: """Get article last modified date and contributors from GitHub.""" result = requests.get( @@ -96,11 +88,14 @@ def get_github_information( owner=settings.SITE_REPOSITORY_OWNER, name=settings.SITE_REPOSITORY_NAME, branch=settings.SITE_REPOSITORY_BRANCH, - path=f"{BASE_ARTICLES_LOCATION}{f'{category}/' if category else ''}{article}.md" + path=( + f"{BASE_ARTICLES_LOCATION}{'/'.join(path[:-1])}" + f"{'/' if len(path) > 1 else ''}{path[-1]}.md" + ) ) ) - if result.status_code == 200: + if result.status_code == 200 and len(result.json()): data = result.json() return { "last_modified": parser.isoparse( -- cgit v1.2.3 From 7de2959402a048652bf6d4ec8e3bec1172bb11e8 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 23 Mar 2021 17:17:22 +0800 Subject: Remove Github metadata feature. This feature is still under implementation debate, so it will be further discussed in another issue and implemeneted in a future PR. --- Pipfile | 1 - Pipfile.lock | 503 ++++++++++++---------- pydis_site/apps/content/tests/test_utils.py | 40 -- pydis_site/apps/content/tests/test_views.py | 10 +- pydis_site/apps/content/utils.py | 38 -- pydis_site/apps/content/views/article_category.py | 1 - pydis_site/templates/content/article.html | 18 - 7 files changed, 283 insertions(+), 328 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/Pipfile b/Pipfile index dae75ea0..8af6e3ce 100644 --- a/Pipfile +++ b/Pipfile @@ -19,7 +19,6 @@ pyuwsgi = {version = "~=2.0", sys_platform = "!='win32'"} sentry-sdk = "~=0.14" gitpython = "~=3.1.7" markdown2 = "~=2.3.9" -python-dateutil = "~=2.8.1" [dev-packages] coverage = "~=5.0" diff --git a/Pipfile.lock b/Pipfile.lock index 18b0d582..3dfb7ec6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c0e53fd7b7c3d2fc62331078d4ba9301a7e848cd39ec8b41f0f7bc6d911a4d88" + "sha256": "893ab5cc0b64f7bb87c0304c9fb6eb24b1856c40286fbbdb1a2bf4a0d7a6d39f" }, "pipfile-spec": 6, "requires": { @@ -18,33 +18,34 @@ "default": { "asgiref": { "hashes": [ - "sha256:a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e", - "sha256:cd88907ecaec59d78e4ac00ea665b03e571cb37e3a0e37b3702af1a9e86c365a" + "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", + "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" ], "markers": "python_version >= '3.5'", - "version": "==3.3.0" + "version": "==3.3.1" }, "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], - "version": "==2020.6.20" + "version": "==2020.12.5" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "version": "==3.0.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" }, "django": { "hashes": [ - "sha256:2d14be521c3ae24960e5e83d4575e156a8c479a75c935224b671b1c6e66eddaf", - "sha256:313d0b8f96685e99327785cc600a5178ca855f8e6f4ed162e671e8c3cf749739" + "sha256:2afe4900667bcceac792fa34b4fb25448c4fd950d8b32c5508b3442c4b10442a", + "sha256:6f13c3e8109236129c49d65a42fbf30c928e66b05ca6862246061b9343ecbaf2" ], "index": "pypi", - "version": "==3.0.10" + "version": "==3.0.13" }, "django-environ": { "hashes": [ @@ -103,11 +104,11 @@ }, "gitpython": { "hashes": [ - "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", - "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" + "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", + "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" ], "index": "pypi", - "version": "==3.1.11" + "version": "==3.1.14" }, "idna": { "hashes": [ @@ -145,61 +146,56 @@ }, "psycopg2-binary": { "hashes": [ - "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", + "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", - "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", - "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", + "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", + "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", - "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", - "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", - "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", - "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", + "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", + "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", + "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", + "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", + "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", - "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", + "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", + "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", + "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", + "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", - "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", + "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", + "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", + "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", + "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", + "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", + "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", + "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", - "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", - "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5", "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", - "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", - "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", - "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", - "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", - "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", - "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", - "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", - "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", - "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", - "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", - "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", - "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", + "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", - "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679" + "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", + "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", + "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", + "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" ], "index": "pypi", "version": "==2.8.6" }, - "python-dateutil": { - "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" - ], - "index": "pypi", - "version": "==2.8.1" - }, "pytz": { "hashes": [ - "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", - "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" ], - "version": "==2020.1" + "version": "==2021.1" }, "pyuwsgi": { "hashes": [ "sha256:0bd14517398f494d828d77a9bf72b5a6cbef0112e1cc05e9a0080fa8828ccfa0", + "sha256:149675b2e020b0e833e8b871a545751ca346cbfed85c8fd2b320a01d40dc3d8f", "sha256:285e263a9094389f13cfdefd033a4e99fbed3ad120dba9ac5093846cc03ac5ab", "sha256:297d1d0b8c472374b12eda7f17a9f5de67cf516612e42b71a7636afb9d1e3974", "sha256:5439f0f3ef5d6bf1622f341662d04c1d92b88889db40b295419e5fe75a7c7d45", @@ -209,6 +205,7 @@ "sha256:90e4235020048456ad867aefc383cdf5528b7f6e327555ceec579c428a828759", "sha256:94d4287b155aa789ce4b6f671c981f7d6c58fc3113330e2f29ac7926cb854645", "sha256:a425f562f382a097ca49df26b70d47d12f0cf0abf233610f3f58b1f7f780355e", + "sha256:ac79dead0685beab5ecfe0926426849a44c5572528f89bb17f6ecf5eb561024e", "sha256:bddeb8df77010d0f842068765a0b3155fdcfd847f14bc1ba89fc7e37914a13d2", "sha256:dac4a04dc0f69d641dba984e83214d2c2cc098496c5d5585e7d3f4e7a9190f84" ], @@ -218,36 +215,54 @@ }, "pyyaml": { "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "index": "pypi", - "version": "==5.3.1" + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "index": "pypi", + "version": "==5.4.1" }, "requests": { "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" ], "index": "pypi", - "version": "==2.24.0" + "version": "==2.25.1" }, "sentry-sdk": { "hashes": [ - "sha256:0eea248408d36e8e7037c7b73827bea20b13a4375bf1719c406cae6fcbc094e3", - "sha256:5cf36eb6b1dc62d55f3c64289792cbaebc8ffa5a9da14474f49b46d20caa7fc8" + "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237", + "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b" ], "index": "pypi", - "version": "==0.19.1" + "version": "==0.20.3" }, "six": { "hashes": [ @@ -259,11 +274,11 @@ }, "smmap": { "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + "sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714", + "sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.0.4" + "version": "==3.0.5" }, "sqlparse": { "hashes": [ @@ -275,11 +290,11 @@ }, "urllib3": { "hashes": [ - "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", - "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", + "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.25.11" + "version": "==1.26.4" }, "whitenoise": { "hashes": [ @@ -300,18 +315,18 @@ }, "attrs": { "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.0" + "version": "==20.3.0" }, "bandit": { "hashes": [ - "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952", - "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065" + "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07", + "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608" ], - "version": "==1.6.2" + "version": "==1.7.0" }, "cfgv": { "hashes": [ @@ -323,43 +338,61 @@ }, "coverage": { "hashes": [ - "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", - "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", - "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", - "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", - "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", - "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", - "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", - "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", - "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", - "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", - "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", - "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", - "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", - "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", - "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", - "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", - "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", - "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", - "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", - "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", - "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", - "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", - "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", - "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", - "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", - "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", - "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", - "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", - "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", - "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", - "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", - "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", - "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", - "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" - ], - "index": "pypi", - "version": "==5.3" + "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", + "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", + "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", + "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", + "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", + "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", + "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", + "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", + "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", + "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", + "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", + "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", + "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", + "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", + "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", + "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", + "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", + "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", + "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", + "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", + "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", + "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", + "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", + "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", + "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", + "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", + "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", + "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", + "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", + "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", + "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", + "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", + "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", + "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", + "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", + "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", + "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", + "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", + "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", + "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", + "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", + "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", + "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", + "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", + "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", + "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", + "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", + "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", + "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", + "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", + "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", + "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" + ], + "index": "pypi", + "version": "==5.5" }, "distlib": { "hashes": [ @@ -377,19 +410,19 @@ }, "flake8": { "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff", + "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0" ], "index": "pypi", - "version": "==3.8.4" + "version": "==3.9.0" }, "flake8-annotations": { "hashes": [ - "sha256:0bcebb0792f1f96d617ded674dca7bf64181870bfe5dace353a1483551f8e5f1", - "sha256:bebd11a850f6987a943ce8cdff4159767e0f5f89b3c88aca64680c2175ee02df" + "sha256:40a4d504cdf64126ea0bdca39edab1608bc6d515e96569b7e7c3c59c84f66c36", + "sha256:eabbfb2dd59ae0e9835f509f930e79cd99fa4ff1026fe6ca073503a57407037c" ], "index": "pypi", - "version": "==2.4.1" + "version": "==2.6.1" }, "flake8-bandit": { "hashes": [ @@ -400,19 +433,19 @@ }, "flake8-bugbear": { "hashes": [ - "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63", - "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162" + "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538", + "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703" ], "index": "pypi", - "version": "==20.1.4" + "version": "==20.11.1" }, "flake8-docstrings": { "hashes": [ - "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717", - "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc" + "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde", + "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b" ], "index": "pypi", - "version": "==1.5.0" + "version": "==1.6.0" }, "flake8-import-order": { "hashes": [ @@ -439,11 +472,11 @@ }, "flake8-tidy-imports": { "hashes": [ - "sha256:62059ca07d8a4926b561d392cbab7f09ee042350214a25cf12823384a45d27dd", - "sha256:c30b40337a2e6802ba3bb611c26611154a27e94c53fc45639e3e282169574fd3" + "sha256:52e5f2f987d3d5597538d5941153409ebcab571635835b78f522c7bf03ca23bc", + "sha256:76e36fbbfdc8e3c5017f9a216c2855a298be85bc0631e66777f4e6a07a859dc4" ], "index": "pypi", - "version": "==4.1.0" + "version": "==4.2.1" }, "flake8-todo": { "hashes": [ @@ -462,27 +495,27 @@ }, "gitpython": { "hashes": [ - "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", - "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" + "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", + "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" ], "index": "pypi", - "version": "==3.1.11" + "version": "==3.1.14" }, "identify": { "hashes": [ - "sha256:3139bf72d81dfd785b0a464e2776bd59bdc725b4cc10e6cf46b56a0db931c82e", - "sha256:969d844b7a85d32a5f9ac4e163df6e846d73c87c8b75847494ee8f4bd2186421" + "sha256:39c0b110c9d0cd2391b6c38cd0ff679ee4b4e98f8db8b06c5d9d9e502711a1e1", + "sha256:efbf090a619255bc31c4fbba709e2805f7d30913fd4854ad84ace52bd276e2f6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.6" + "markers": "python_full_version >= '3.6.1'", + "version": "==2.2.0" }, "importlib-metadata": { "hashes": [ - "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da", - "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3" + "sha256:742add720a20d0467df2f444ae41704000f50e1234f46174b51f9c6031a1bd71", + "sha256:b74159469b464a99cb8cc3e21973e4d96e05d3024d337313fedb618a6e86e6f4" ], "markers": "python_version < '3.8'", - "version": "==2.0.0" + "version": "==3.7.3" }, "mccabe": { "hashes": [ @@ -517,52 +550,70 @@ }, "pre-commit": { "hashes": [ - "sha256:7eadaa7f4547a8a19b83230ce430ba81bbe4797bd41c8d7fb54b246164628d1f", - "sha256:8fb2037c404ef8c87125e72564f316cf2bc94fc9c1cb184b8352117de747e164" + "sha256:94c82f1bf5899d56edb1d926732f4e75a7df29a0c8c092559c77420c9d62428b", + "sha256:de55c5c72ce80d79106e48beb1b54104d16495ce7f95b0c7b13d4784193a00af" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.11.1" }, "pycodestyle": { "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" + "version": "==2.7.0" }, "pydocstyle": { "hashes": [ - "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", - "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" + "sha256:164befb520d851dbcf0e029681b91f4f599c62c5cd8933fd54b1bfbd50e89e1f", + "sha256:d4449cf16d7e6709f63192146706933c7a334af7c0f083904799ccb851c50f6d" ], - "markers": "python_version >= '3.5'", - "version": "==5.1.1" + "markers": "python_version >= '3.6'", + "version": "==6.0.0" }, "pyflakes": { "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + "sha256:910208209dcea632721cb58363d0f72913d9e8cf64dc6f8ae2e02a3609aba40d", + "sha256:e59fd8e750e588358f1b8885e5a4751203a0516e0ee6d34811089ac294c8806f" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" + "version": "==2.3.0" }, "pyyaml": { "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "index": "pypi", - "version": "==5.3.1" + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "index": "pypi", + "version": "==5.4.1" }, "six": { "hashes": [ @@ -574,69 +625,79 @@ }, "smmap": { "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + "sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714", + "sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.0.4" + "version": "==3.0.5" }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "stevedore": { "hashes": [ - "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62", - "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0" + "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", + "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" ], "markers": "python_version >= '3.6'", - "version": "==3.2.2" + "version": "==3.3.0" }, "toml": { "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==0.10.1" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" }, "typed-ast": { "hashes": [ - "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", - "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", - "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d", - "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", - "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", - "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", - "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c", - "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", - "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", - "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", - "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", - "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", - "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", - "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d", - "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", - "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", - "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c", - "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", - "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395", - "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", - "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", - "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", - "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", - "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", - "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072", - "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298", - "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91", - "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", - "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f", - "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", + "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", + "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", + "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", + "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", + "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", + "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", + "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", + "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", + "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", + "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", + "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", + "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", + "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", + "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", + "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", + "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", + "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", + "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", + "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", + "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", + "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", + "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", + "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", + "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", + "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", + "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", + "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", + "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", + "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" + ], + "markers": "python_version < '3.8'", + "version": "==1.4.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" ], "markers": "python_version < '3.8'", - "version": "==1.4.1" + "version": "==3.7.4.3" }, "unittest-xml-reporting": { "hashes": [ @@ -648,19 +709,19 @@ }, "virtualenv": { "hashes": [ - "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2", - "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380" + "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", + "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.1.0" + "version": "==20.4.3" }, "zipp": { "hashes": [ - "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108", - "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb" + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" ], "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "version": "==3.4.1" } } } diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index 85f1139a..26b2bba9 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -146,43 +146,3 @@ class TestGetArticle(TestCase): """Check does this raise Http404 when category don't exist.""" with self.assertRaises(Http404): utils.get_article(["invalid", "some-guide"]) - - -class GetGitHubInformationTests(TestCase): - @patch("pydis_site.apps.content.utils.requests.get") - @patch("pydis_site.apps.content.utils.COMMITS_URL", "foobar") - def test_call_get_github_information_requests_get(self, requests_get_mock): - """Check does this call requests.get function with proper URL.""" - utils.get_github_information(["foo"]) - requests_get_mock.assert_called_once_with("foobar") - - @patch("pydis_site.apps.content.utils.requests.get") - def test_github_status_code_200_response(self, requests_get_mock): - """Check does this return provided modified date and contributors.""" - requests_get_mock.return_value = MagicMock(status_code=200) - requests_get_mock.return_value.json.return_value = [{ - "commit": { - "committer": { - "date": datetime(2020, 10, 1).isoformat(), - "name": "foobar", - } - }, - "committer": { - "html_url": "abc1234" - } - }] - result = utils.get_github_information(["foo"]) - self.assertEqual(result, { - "last_modified": datetime(2020, 10, 1).strftime("%dth %B %Y"), - "contributors": {"foobar": "abc1234"} - }) - - @patch("pydis_site.apps.content.utils.requests.get") - def test_github_other_status_code_response(self, requests_get_mock): - """Check does this return provided modified date and contributors.""" - requests_get_mock.return_value = MagicMock(status_code=404) - result = utils.get_github_information(["foo"]) - self.assertEqual(result, { - "last_modified": "N/A", - "contributors": {} - }) diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 98b99b83..55bfb8ea 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -30,8 +30,7 @@ class TestArticleOrCategoryView(TestCase): @override_settings(ARTICLES_PATH=BASE_PATH) @patch("pydis_site.apps.content.views.article_category.utils.get_article") @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @patch("pydis_site.apps.content.views.article_category.utils.get_github_information") - def test_article_return_code_200(self, gh_info_mock, get_category_mock, get_article_mock): + def test_article_return_code_200(self, get_category_mock, get_article_mock): get_article_mock.return_value = {"guide": "test", "metadata": {}} url = reverse("content:article_category", args=["test2"]) @@ -39,7 +38,6 @@ class TestArticleOrCategoryView(TestCase): self.assertEqual(response.status_code, 200) get_category_mock.assert_not_called() get_article_mock.assert_called_once() - gh_info_mock.assert_called_once() @patch("pydis_site.apps.content.views.article_category.utils.get_article") @patch("pydis_site.apps.content.views.article_category.utils.get_category") @@ -99,11 +97,9 @@ class TestArticleOrCategoryView(TestCase): @patch("pydis_site.apps.content.views.article_category.utils.get_article") @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @patch("pydis_site.apps.content.views.article_category.utils.get_github_information") @override_settings(ARTICLES_PATH=BASE_PATH) def test_valid_category_article_code_200( self, - gh_info_mock, get_category_mock, get_article_mock ): @@ -115,15 +111,12 @@ class TestArticleOrCategoryView(TestCase): self.assertEqual(response.status_code, 200) get_article_mock.assert_called_once() self.assertEqual(get_category_mock.call_count, 2) - gh_info_mock.assert_called_once() @patch("pydis_site.apps.content.views.article_category.utils.get_article") @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @patch("pydis_site.apps.content.views.article_category.utils.get_github_information") @override_settings(ARTICLES_PATH=BASE_PATH) def test_invalid_category_article_code_404( self, - gh_info_mock, get_category_mock, get_article_mock ): @@ -135,7 +128,6 @@ class TestArticleOrCategoryView(TestCase): self.assertEqual(response.status_code, 404) get_article_mock.assert_not_called() get_category_mock.assert_not_called() - gh_info_mock.assert_not_called() @override_settings(ARTICLES_PATH=BASE_PATH) def test_article_category_template_names(self): diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index a89db83c..305d26b5 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,16 +1,11 @@ import os from typing import Dict, List, Optional, Union -import requests import yaml -from dateutil import parser from django.conf import settings from django.http import Http404 from markdown2 import markdown -COMMITS_URL = "https://api.github.com/repos/{owner}/{name}/commits?path={path}&sha={branch}" -BASE_ARTICLES_LOCATION = "pydis_site/apps/content/resources/content/" - def get_category(path: List[str]) -> Dict[str, str]: """Load category information by name from _info.yml.""" @@ -77,36 +72,3 @@ def get_article(path: List[str]) -> Dict[str, Union[str, Dict]]: ) return {"article": str(html), "metadata": html.metadata} - - -def get_github_information( - path: List[str] -) -> Dict[str, Union[List[str], str]]: - """Get article last modified date and contributors from GitHub.""" - result = requests.get( - COMMITS_URL.format( - owner=settings.SITE_REPOSITORY_OWNER, - name=settings.SITE_REPOSITORY_NAME, - branch=settings.SITE_REPOSITORY_BRANCH, - path=( - f"{BASE_ARTICLES_LOCATION}{'/'.join(path[:-1])}" - f"{'/' if len(path) > 1 else ''}{path[-1]}.md" - ) - ) - ) - - if result.status_code == 200 and len(result.json()): - data = result.json() - return { - "last_modified": parser.isoparse( - data[0]["commit"]["committer"]["date"] - ).strftime("%dth %B %Y"), - "contributors": { - c["commit"]["committer"]["name"]: c["committer"]["html_url"] for c in data - } - } - else: - return { - "last_modified": "N/A", - "contributors": {} - } diff --git a/pydis_site/apps/content/views/article_category.py b/pydis_site/apps/content/views/article_category.py index 0c22b5e8..51b1def2 100644 --- a/pydis_site/apps/content/views/article_category.py +++ b/pydis_site/apps/content/views/article_category.py @@ -54,7 +54,6 @@ class ArticleOrCategoryView(TemplateView): article_result["metadata"].get("relevant_link_values", "").split(",") ) if link != "" and value != "" } - context["github_data"] = utils.get_github_information(location) else: raise Http404 diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html index fcb29f32..bcdee95a 100644 --- a/pydis_site/templates/content/article.html +++ b/pydis_site/templates/content/article.html @@ -34,29 +34,11 @@

    {{ article.metadata.title }}

    -

    Last modified: {{ github_data.last_modified }}

    {{ article.article|safe }}
    -
    -
    -

    Contributors

    -
    -
    - {% if github_data.contributors|length %} -
    - {% for user, profile_url in github_data.contributors.items %} - {{ user }} - {% endfor %} -
    - {% else %} -

    N/A

    - {% endif %} -
    -
    - {% if relevant_links|length > 0 %}
    -- cgit v1.2.3 From 367ba267c4dbc6d406922f80bc4337ee9a0139a0 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 23 Mar 2021 18:24:56 +0800 Subject: Rename `articles` to `pages`. Articles was a good name, but we want an `articles` category in the future. `/pages/guides/` and `/pages/articles/` are clearer in name than `/articles/guides/` and `/articles/articles/`. --- pydis_site/apps/content/tests/test_utils.py | 70 ++++++------ pydis_site/apps/content/tests/test_views.py | 127 +++++++++++----------- pydis_site/apps/content/urls.py | 4 +- pydis_site/apps/content/utils.py | 36 +++--- pydis_site/apps/content/views/__init__.py | 6 +- pydis_site/apps/content/views/article_category.py | 69 ------------ pydis_site/apps/content/views/articles.py | 16 --- pydis_site/apps/content/views/page_category.py | 69 ++++++++++++ pydis_site/apps/content/views/pages.py | 16 +++ pydis_site/apps/events/urls.py | 4 +- pydis_site/apps/events/views/__init__.py | 4 +- pydis_site/apps/events/views/page.py | 2 +- pydis_site/apps/home/urls.py | 2 +- pydis_site/settings.py | 2 +- pydis_site/static/css/content/articles.css | 16 --- pydis_site/static/css/content/page.css | 16 +++ pydis_site/templates/content/article.html | 58 ---------- pydis_site/templates/content/listing.html | 20 ++-- pydis_site/templates/content/page.html | 58 ++++++++++ pydis_site/templates/resources/resources.html | 2 +- 20 files changed, 299 insertions(+), 298 deletions(-) delete mode 100644 pydis_site/apps/content/views/article_category.py delete mode 100644 pydis_site/apps/content/views/articles.py create mode 100644 pydis_site/apps/content/views/page_category.py create mode 100644 pydis_site/apps/content/views/pages.py delete mode 100644 pydis_site/static/css/content/articles.css create mode 100644 pydis_site/static/css/content/page.css delete mode 100644 pydis_site/templates/content/article.html create mode 100644 pydis_site/templates/content/page.html (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index 26b2bba9..e1f1b92a 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -13,20 +13,20 @@ BASE_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "t class TestGetCategory(TestCase): - @override_settings(ARTICLES_PATH=BASE_PATH) + @override_settings(PAGES_PATH=BASE_PATH) def test_get_category_successfully(self): """Check does this get right data from category data file.""" result = utils.get_category(["category"]) self.assertEqual(result, {"name": "My Category", "description": "My Description"}) - @override_settings(ARTICLES_PATH=BASE_PATH) + @override_settings(PAGES_PATH=BASE_PATH) def test_get_category_not_exists(self): """Check does this raise 404 error when category don't exists.""" with self.assertRaises(Http404): utils.get_category(["invalid"]) - @override_settings(ARTICLES_PATH=BASE_PATH) + @override_settings(PAGES_PATH=BASE_PATH) def test_get_category_not_directory(self): """Check does this raise 404 error when category isn't directory.""" with self.assertRaises(Http404): @@ -34,7 +34,7 @@ class TestGetCategory(TestCase): class TestGetCategories(TestCase): - @override_settings(ARTICLES_PATH=BASE_PATH) + @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.""" @@ -47,7 +47,7 @@ class TestGetCategories(TestCase): result, {"category": {"name": "My Category", "description": "My Description"}} ) - @override_settings(ARTICLES_PATH=BASE_PATH) + @override_settings(PAGES_PATH=BASE_PATH) def test_get_categories_root_path(self): """Check does this doesn't call joinpath when getting root categories.""" result = utils.get_categories() @@ -55,7 +55,7 @@ class TestGetCategories(TestCase): result, {"category": {"name": "My Category", "description": "My Description"}} ) - @override_settings(ARTICLES_PATH=BASE_PATH) + @override_settings(PAGES_PATH=BASE_PATH) def test_get_categories_in_category(self): """Check does this call joinpath when getting subcategories.""" result = utils.get_categories(["category"]) @@ -64,11 +64,11 @@ class TestGetCategories(TestCase): ) -class TestGetArticles(TestCase): - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_all_root_articles(self): +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.""" - result = utils.get_articles() + result = utils.get_pages() for case in ["test", "test2"]: with self.subTest(guide=case): @@ -77,10 +77,10 @@ class TestGetArticles(TestCase): self.assertIn(case, result) self.assertEqual(md.metadata, result[case]) - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_all_category_articles(self): + @override_settings(PAGES_PATH=BASE_PATH) + def test_get_all_category_pages(self): """Check does this return all category testing content.""" - result = utils.get_articles(["category"]) + result = utils.get_pages(["category"]) md = markdown(BASE_PATH.joinpath("category", "test3.md").read_text(), extras=["metadata"]) @@ -88,11 +88,11 @@ class TestGetArticles(TestCase): self.assertEqual(md.metadata, result["test3"]) -class TestGetArticle(TestCase): - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_root_article_success(self): - """Check does this return article HTML and metadata when root article exist.""" - result = utils.get_article(["test"]) +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.""" + result = utils.get_page(["test"]) md = markdown( BASE_PATH.joinpath("test.md").read_text(), @@ -107,18 +107,18 @@ class TestGetArticle(TestCase): ] ) - self.assertEqual(result, {"article": str(md), "metadata": md.metadata}) + self.assertEqual(result, {"page": str(md), "metadata": md.metadata}) - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_root_article_dont_exist(self): - """Check does this raise Http404 when root article don't exist.""" + @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): - utils.get_article(["invalid"]) + utils.get_page(["invalid"]) - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_category_article_success(self): - """Check does this return article HTML and metadata when category guide exist.""" - result = utils.get_article(["category", "test3"]) + @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.""" + result = utils.get_page(["category", "test3"]) md = markdown( BASE_PATH.joinpath("category", "test3.md").read_text(), @@ -133,16 +133,16 @@ class TestGetArticle(TestCase): ] ) - self.assertEqual(result, {"article": str(md), "metadata": md.metadata}) + self.assertEqual(result, {"page": str(md), "metadata": md.metadata}) - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_get_category_article_dont_exist(self): - """Check does this raise Http404 when category article don't exist.""" + @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): - utils.get_article(["category", "invalid"]) + utils.get_page(["category", "invalid"]) - @patch("pydis_site.settings.ARTICLES_PATH", new=BASE_PATH) - def test_get_category_article_category_dont_exist(self): + @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.""" with self.assertRaises(Http404): - utils.get_article(["invalid", "some-guide"]) + utils.get_page(["invalid", "some-guide"]) diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 55bfb8ea..5ce18afb 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -6,143 +6,144 @@ from django.http import Http404 from django.test import RequestFactory, TestCase, override_settings from django_hosts.resolvers import reverse -from pydis_site.apps.content.views import ArticleOrCategoryView +from pydis_site.apps.content.views import PageOrCategoryView BASE_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content") -class TestArticlesIndexView(TestCase): - @patch("pydis_site.apps.content.views.articles.get_articles") - @patch("pydis_site.apps.content.views.articles.get_categories") - def test_articles_index_return_200(self, get_categories_mock, get_articles_mock): +class TestPagesIndexView(TestCase): + @patch("pydis_site.apps.content.views.pages.get_pages") + @patch("pydis_site.apps.content.views.pages.get_categories") + def test_pages_index_return_200(self, get_categories_mock, get_page_mock): """Check that content index return HTTP code 200.""" get_categories_mock.return_value = {} - get_articles_mock.return_value = {} + get_page_mock.return_value = {} - url = reverse('content:articles') + url = reverse('content:pages') response = self.client.get(url) self.assertEqual(response.status_code, 200) - get_articles_mock.assert_called_once() + get_page_mock.assert_called_once() get_categories_mock.assert_called_once() -class TestArticleOrCategoryView(TestCase): - @override_settings(ARTICLES_PATH=BASE_PATH) - @patch("pydis_site.apps.content.views.article_category.utils.get_article") - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - def test_article_return_code_200(self, get_category_mock, get_article_mock): - get_article_mock.return_value = {"guide": "test", "metadata": {}} +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") + @patch("pydis_site.apps.content.views.page_category.utils.get_github_information") + def test_page_return_code_200(self, get_category_mock, get_page_mock): + get_page_mock.return_value = {"guide": "test", "metadata": {}} - url = reverse("content:article_category", args=["test2"]) + url = reverse("content:page_category", args=["test2"]) response = self.client.get(url) self.assertEqual(response.status_code, 200) get_category_mock.assert_not_called() - get_article_mock.assert_called_once() + get_page_mock.assert_called_once() - @patch("pydis_site.apps.content.views.article_category.utils.get_article") - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_article_return_404(self, get_category_mock, get_article_mock): - """Check that return code is 404 when invalid article provided.""" - get_article_mock.side_effect = Http404("Article not found.") + @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:article_category", args=["invalid-guide"]) + url = reverse("content:page_category", args=["invalid-guide"]) response = self.client.get(url) self.assertEqual(response.status_code, 404) - get_article_mock.assert_not_called() + get_page_mock.assert_not_called() get_category_mock.assert_not_called() - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @patch("pydis_site.apps.content.views.article_category.utils.get_articles") - @patch("pydis_site.apps.content.views.article_category.utils.get_categories") - @override_settings(ARTICLES_PATH=BASE_PATH) + @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_articles_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_articles_mock.return_value = {} + get_pages_mock.return_value = {} - url = reverse("content:article_category", args=["category"]) + url = reverse("content:page_category", args=["category"]) response = self.client.get(url) self.assertEqual(response.status_code, 200) - get_articles_mock.assert_called_once() + get_pages_mock.assert_called_once() get_category_mock.assert_called_once() get_categories_mock.assert_called_once() - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @patch("pydis_site.apps.content.views.article_category.utils.get_articles") - @patch("pydis_site.apps.content.views.article_category.utils.get_categories") - @override_settings(ARTICLES_PATH=BASE_PATH) + @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_articles_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:article_category", args=["invalid-category"]) + 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_articles_mock.assert_not_called() + get_pages_mock.assert_not_called() get_categories_mock.assert_not_called() - @patch("pydis_site.apps.content.views.article_category.utils.get_article") - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_valid_category_article_code_200( + @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_article_mock + get_page_mock ): - """Check that return code is 200 when visiting valid category article.""" - get_article_mock.return_value = {"guide": "test", "metadata": {}} + """Check that return code is 200 when visiting valid category page.""" + get_page_mock.return_value = {"guide": "test", "metadata": {}} - url = reverse("content:article_category", args=["category/test3"]) + url = reverse("content:page_category", args=["category/test3"]) response = self.client.get(url) self.assertEqual(response.status_code, 200) - get_article_mock.assert_called_once() + get_page_mock.assert_called_once() self.assertEqual(get_category_mock.call_count, 2) - @patch("pydis_site.apps.content.views.article_category.utils.get_article") - @patch("pydis_site.apps.content.views.article_category.utils.get_category") - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_invalid_category_article_code_404( + @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_article_mock + get_page_mock ): - """Check that return code is 200 when trying to visit invalid category article.""" - get_article_mock.side_effect = Http404("Article not found.") + """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:article_category", args=["category/invalid"]) + url = reverse("content:page_category", args=["category/invalid"]) response = self.client.get(url) self.assertEqual(response.status_code, 404) - get_article_mock.assert_not_called() + get_page_mock.assert_not_called() get_category_mock.assert_not_called() - @override_settings(ARTICLES_PATH=BASE_PATH) - def test_article_category_template_names(self): - """Check that this return category, article template or raise Http404.""" + @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() cases = [ {"location": "category", "output": ["content/listing.html"]}, - {"location": "test", "output": ["content/article.html"]}, + {"location": "test", "output": ["content/page.html"]}, {"location": "invalid", "output": None, "raises": Http404} ] for case in cases: with self.subTest(location=case["location"], output=case["output"]): - request = factory.get(f"/articles/{case['location']}") - instance = ArticleOrCategoryView() + request = factory.get(f"/pages/{case['location']}") + instance = PageOrCategoryView() instance.request = request instance.kwargs = {"location": case["location"]} diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index 49a5a2ef..1406f672 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -4,6 +4,6 @@ from . import views app_name = "content" urlpatterns = [ - path("", views.ArticlesView.as_view(), name='articles'), - path("/", views.ArticleOrCategoryView.as_view(), name='article_category'), + path("", views.PagesView.as_view(), name='pages'), + path("/", views.PageOrCategoryView.as_view(), name='page_category'), ] diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 305d26b5..db502a71 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -9,7 +9,7 @@ from markdown2 import markdown def get_category(path: List[str]) -> Dict[str, str]: """Load category information by name from _info.yml.""" - path = settings.ARTICLES_PATH.joinpath(*path) + path = settings.PAGES_PATH.joinpath(*path) if not path.exists() or not path.is_dir(): raise Http404("Category not found.") @@ -20,10 +20,10 @@ def get_categories(path: Optional[List[str]] = None) -> Dict[str, Dict]: """Get all categories information.""" categories = {} if path is None: - categories_path = settings.ARTICLES_PATH + categories_path = settings.PAGES_PATH path = [] else: - categories_path = settings.ARTICLES_PATH.joinpath(*path) + categories_path = settings.PAGES_PATH.joinpath(*path) for name in categories_path.iterdir(): if name.is_dir(): @@ -32,34 +32,34 @@ def get_categories(path: Optional[List[str]] = None) -> Dict[str, Dict]: return categories -def get_articles(path: Optional[List[str]] = None) -> Dict[str, Dict]: - """Get all root or category articles.""" +def get_pages(path: Optional[List[str]] = None) -> Dict[str, Dict]: + """Get all root or category pages.""" if path is None: - base_dir = settings.ARTICLES_PATH + base_dir = settings.PAGES_PATH else: - base_dir = settings.ARTICLES_PATH.joinpath(*path) + base_dir = settings.PAGES_PATH.joinpath(*path) - articles = {} + pages = {} for item in base_dir.iterdir(): if item.is_file() and item.name.endswith(".md"): md = markdown(item.read_text(), extras=["metadata"]) - articles[os.path.splitext(item.name)[0]] = md.metadata + pages[os.path.splitext(item.name)[0]] = md.metadata - return articles + return pages -def get_article(path: List[str]) -> Dict[str, Union[str, Dict]]: - """Get one specific article. When category is specified, get it from there.""" - article_path = settings.ARTICLES_PATH.joinpath(*path[:-1]) +def get_page(path: List[str]) -> Dict[str, Union[str, Dict]]: + """Get one specific page. When category is specified, get it from there.""" + page_path = settings.PAGES_PATH.joinpath(*path[:-1]) # We need to include extension MD - article_path = article_path.joinpath(f"{path[-1]}.md") - if not article_path.exists() or not article_path.is_file(): - raise Http404("Article not found.") + page_path = page_path.joinpath(f"{path[-1]}.md") + if not page_path.exists() or not page_path.is_file(): + raise Http404("Page not found.") html = markdown( - article_path.read_text(), + page_path.read_text(), extras=[ "metadata", "fenced-code-blocks", @@ -71,4 +71,4 @@ def get_article(path: List[str]) -> Dict[str, Union[str, Dict]]: ] ) - return {"article": str(html), "metadata": html.metadata} + return {"page": str(html), "metadata": html.metadata} diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py index f92660d6..740d98e9 100644 --- a/pydis_site/apps/content/views/__init__.py +++ b/pydis_site/apps/content/views/__init__.py @@ -1,4 +1,4 @@ -from .article_category import ArticleOrCategoryView -from .articles import ArticlesView +from .page_category import PageOrCategoryView +from .pages import PagesView -__all__ = ["ArticleOrCategoryView", "ArticlesView"] +__all__ = ["PageOrCategoryView", "PagesView"] diff --git a/pydis_site/apps/content/views/article_category.py b/pydis_site/apps/content/views/article_category.py deleted file mode 100644 index 2a407b99..00000000 --- a/pydis_site/apps/content/views/article_category.py +++ /dev/null @@ -1,69 +0,0 @@ -import typing as t - -from django.conf import settings -from django.http import Http404 -from django.views.generic import TemplateView - -from pydis_site.apps.content import utils - - -class ArticleOrCategoryView(TemplateView): - """Handles article and category pages.""" - - def get_template_names(self) -> t.List[str]: - """Checks does this use article template or listing template.""" - location = self.kwargs["location"].split("/") - full_location = settings.ARTICLES_PATH.joinpath(*location) - - if full_location.is_dir(): - template_name = "content/listing.html" - elif full_location.with_suffix(".md").is_file(): - template_name = "content/article.html" - else: - raise Http404 - - return [template_name] - - def get_context_data(self, **kwargs) -> t.Dict[str, t.Any]: - """Assign proper context variables based on what resource user requests.""" - context = super().get_context_data(**kwargs) - - location: list = self.kwargs["location"].split("/") - full_location = settings.ARTICLES_PATH.joinpath(*location) - - if full_location.is_dir(): - context["category_info"] = utils.get_category(location) - context["content"] = utils.get_articles(location) - context["categories"] = utils.get_categories(location) - # Add trailing slash here to simplify template - context["path"] = "/".join(location) + "/" - context["in_category"] = True - elif full_location.with_suffix(".md").is_file(): - article_result = utils.get_article(location) - - if len(location) > 1: - context["category_data"] = utils.get_category(location[:-1]) - context["category_data"]["raw_name"] = location[:-1][-1] - else: - context["category_data"] = {"name": None, "raw_name": None} - - context["article"] = article_result - context["relevant_links"] = article_result["metadata"].get("relevant_links", {}) - else: - raise Http404 - - location.pop() - breadcrumb_items = [] - while len(location): - breadcrumb_items.insert( - 0, - { - "name": utils.get_category(location)["name"], - "path": "/".join(location) - } - ) - location.pop() - - context["breadcrumb_items"] = breadcrumb_items - - return context diff --git a/pydis_site/apps/content/views/articles.py b/pydis_site/apps/content/views/articles.py deleted file mode 100644 index 999002d0..00000000 --- a/pydis_site/apps/content/views/articles.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.views.generic import TemplateView - -from pydis_site.apps.content.utils import get_articles, get_categories - - -class ArticlesView(TemplateView): - """Shows all content and categories.""" - - template_name = "content/listing.html" - - def get_context_data(self, **kwargs) -> dict: - """Add articles and categories data to template context.""" - context = super().get_context_data(**kwargs) - context["content"] = get_articles() - context["categories"] = get_categories() - return context diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py new file mode 100644 index 00000000..f00a79ee --- /dev/null +++ b/pydis_site/apps/content/views/page_category.py @@ -0,0 +1,69 @@ +import typing as t + +from django.conf import settings +from django.http import Http404 +from django.views.generic import TemplateView + +from pydis_site.apps.content import utils + + +class PageOrCategoryView(TemplateView): + """Handles pages and page categories.""" + + def get_template_names(self) -> t.List[str]: + """Checks does this use page template or listing template.""" + location = self.kwargs["location"].split("/") + full_location = settings.PAGES_PATH.joinpath(*location) + + if full_location.is_dir(): + template_name = "content/listing.html" + elif full_location.with_suffix(".md").is_file(): + template_name = "content/page.html" + else: + raise Http404 + + return [template_name] + + def get_context_data(self, **kwargs) -> t.Dict[str, t.Any]: + """Assign proper context variables based on what resource user requests.""" + context = super().get_context_data(**kwargs) + + location: list = self.kwargs["location"].split("/") + full_location = settings.PAGES_PATH.joinpath(*location) + + if full_location.is_dir(): + context["category_info"] = utils.get_category(location) + context["content"] = utils.get_pages(location) + context["categories"] = utils.get_categories(location) + # Add trailing slash here to simplify template + context["path"] = "/".join(location) + "/" + context["in_category"] = True + elif full_location.with_suffix(".md").is_file(): + page_result = utils.get_page(location) + + if len(location) > 1: + context["category_data"] = utils.get_category(location[:-1]) + context["category_data"]["raw_name"] = location[:-1][-1] + else: + context["category_data"] = {"name": None, "raw_name": None} + + context["page"] = page_result + context["relevant_links"] = page_result["metadata"].get("relevant_links", {}) + else: + raise Http404 + + location.pop() + breadcrumb_items = [] + while len(location): + breadcrumb_items.insert( + 0, + { + "name": utils.get_category(location)["name"], + "path": "/".join(location) + } + ) + location.pop() + + context["breadcrumb_items"] = breadcrumb_items + + return context diff --git a/pydis_site/apps/content/views/pages.py b/pydis_site/apps/content/views/pages.py new file mode 100644 index 00000000..11ac0eeb --- /dev/null +++ b/pydis_site/apps/content/views/pages.py @@ -0,0 +1,16 @@ +from django.views.generic import TemplateView + +from pydis_site.apps.content.utils import get_pages, get_categories + + +class PagesView(TemplateView): + """Shows all pages and categories.""" + + template_name = "content/listing.html" + + def get_context_data(self, **kwargs) -> dict: + """Add page and category data to template context.""" + context = super().get_context_data(**kwargs) + context["content"] = get_pages() + context["categories"] = get_categories() + return context diff --git a/pydis_site/apps/events/urls.py b/pydis_site/apps/events/urls.py index 9a65cf1f..601451da 100644 --- a/pydis_site/apps/events/urls.py +++ b/pydis_site/apps/events/urls.py @@ -1,9 +1,9 @@ from django.urls import path -from pydis_site.apps.events.views import IndexView, PageView +from pydis_site.apps.events.views import IndexView, PagesView app_name = "events" urlpatterns = [ path("", IndexView.as_view(), name="index"), - path("/", PageView.as_view(), name="page"), + path("/", PagesView.as_view(), name="page"), ] diff --git a/pydis_site/apps/events/views/__init__.py b/pydis_site/apps/events/views/__init__.py index 8a107e2f..6188f723 100644 --- a/pydis_site/apps/events/views/__init__.py +++ b/pydis_site/apps/events/views/__init__.py @@ -1,4 +1,4 @@ from .index import IndexView -from .page import PageView +from .page import PagesView -__all__ = ["IndexView", "PageView"] +__all__ = ["IndexView", "PagesView"] diff --git a/pydis_site/apps/events/views/page.py b/pydis_site/apps/events/views/page.py index f4c37aeb..fbedd4e8 100644 --- a/pydis_site/apps/events/views/page.py +++ b/pydis_site/apps/events/views/page.py @@ -5,7 +5,7 @@ from django.http import Http404 from django.views.generic import TemplateView -class PageView(TemplateView): +class PagesView(TemplateView): """Handles event pages showing.""" def get_template_names(self) -> List[str]: diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index bd7c0625..3c716875 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -8,6 +8,6 @@ urlpatterns = [ path('', HomeView.as_view(), name='home'), path('admin/', admin.site.urls), path('resources/', include('pydis_site.apps.resources.urls')), - path('articles/', include('pydis_site.apps.content.urls')), + path('pages/', include('pydis_site.apps.content.urls')), path('events/', include('pydis_site.apps.events.urls', namespace='events')), ] diff --git a/pydis_site/settings.py b/pydis_site/settings.py index d509d980..0b4d9fd1 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -287,4 +287,4 @@ SITE_REPOSITORY_OWNER = "python-discord" SITE_REPOSITORY_NAME = "site" SITE_REPOSITORY_BRANCH = "master" -ARTICLES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources", "content") +PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources", "content") diff --git a/pydis_site/static/css/content/articles.css b/pydis_site/static/css/content/articles.css deleted file mode 100644 index f46d6b15..00000000 --- a/pydis_site/static/css/content/articles.css +++ /dev/null @@ -1,16 +0,0 @@ -.breadcrumb-section { - padding: 1rem; -} - -i.has-icon-padding { - padding: 0 10px 25px 0; -} - -pre { - /* - * Style it the same as the tag, since highlight.js does not style - * backgrounds of
     tags but bulma does, resulting in a weird off-white
    -     * border.
    -     */
    -    background-color: #282c34;
    -}
    diff --git a/pydis_site/static/css/content/page.css b/pydis_site/static/css/content/page.css
    new file mode 100644
    index 00000000..f46d6b15
    --- /dev/null
    +++ b/pydis_site/static/css/content/page.css
    @@ -0,0 +1,16 @@
    +.breadcrumb-section {
    +    padding: 1rem;
    +}
    +
    +i.has-icon-padding {
    +    padding: 0 10px 25px 0;
    +}
    +
    +pre {
    +    /*
    +     * Style it the same as the  tag, since highlight.js does not style
    +     * backgrounds of 
     tags but bulma does, resulting in a weird off-white
    +     * border.
    +     */
    +    background-color: #282c34;
    +}
    diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html
    deleted file mode 100644
    index 69d01a8d..00000000
    --- a/pydis_site/templates/content/article.html
    +++ /dev/null
    @@ -1,58 +0,0 @@
    -{% extends 'base/base.html' %}
    -{% load static %}
    -
    -{% block title %}{{ article.metadata.title }}{% endblock %}
    -{% block head %}
    -  
    -  
    -  
    -  
    -  
    -  
    -  
    -{% endblock %}
    -
    -{% block content %}
    -    {% include "base/navbar.html" %}
    -
    -    
    -
    -    
    -
    -
    -

    {{ article.metadata.title }}

    -
    -
    - {{ article.article|safe }} -
    -
    - {% if relevant_links|length > 0 %} -
    - - -
    - {% endif %} -
    -
    -
    -
    -
    - -{% endblock %} diff --git a/pydis_site/templates/content/listing.html b/pydis_site/templates/content/listing.html index 8c06bccc..39eae1c2 100644 --- a/pydis_site/templates/content/listing.html +++ b/pydis_site/templates/content/listing.html @@ -1,12 +1,12 @@ {% extends 'base/base.html' %} {% load static %} -{% block title %}{{ category_info.name|default:"Articles" }}{% endblock %} +{% block title %}{{ category_info.name|default:"Pages" }}{% endblock %} {% block head %} - + - + {% endblock %} {% block content %} @@ -17,12 +17,12 @@
    @@ -31,7 +31,7 @@
    -

    {{ category_info.name|default:"Articles" }}

    +

    {{ category_info.name|default:"Pages" }}

    {% for category, data in categories.items %}
    @@ -39,18 +39,18 @@ - + {{ data.name }}

    {{ data.description }}

    {% endfor %} - {% for article, data in content.items %} + {% for page, data in content.items %}
    - + {{ data.title }}

    {{ data.short_description }}

    diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html new file mode 100644 index 00000000..3b0ebb5f --- /dev/null +++ b/pydis_site/templates/content/page.html @@ -0,0 +1,58 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}{{ page.metadata.title }}{% endblock %} +{% block head %} + + + + + + + +{% endblock %} + +{% block content %} + {% include "base/navbar.html" %} + + + +
    +
    +
    +

    {{ page.metadata.title }}

    +
    +
    + {{ page.page|safe }} +
    +
    + {% if relevant_links|length > 0 %} +
    + + +
    + {% endif %} +
    +
    +
    +
    +
    + +{% endblock %} diff --git a/pydis_site/templates/resources/resources.html b/pydis_site/templates/resources/resources.html index 2dc88a8c..491bc55e 100644 --- a/pydis_site/templates/resources/resources.html +++ b/pydis_site/templates/resources/resources.html @@ -15,7 +15,7 @@

    Resources

    - +

    Guides

    Made by us, for you

    -- cgit v1.2.3 From 82daedc766dc3986dc9ac17ea8a6b3da87a6b1ac Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 23 Mar 2021 23:17:55 +0800 Subject: Simplify content app. Rather than having two views for the base page and all other pages, all pages now use the same view. The view context handler is simplified to take advantage of pathlib features. The markdown folder is now /content/resources/* rather than /content/resources/content/*, as the latter is unnecessary nesting. --- pydis_site/apps/content/resources/_info.yml | 2 + .../content/resources/content/guides/_info.yml | 2 - .../content/guides/pydis-guides/_info.yml | 2 - .../guides/pydis-guides/how-to-write-a-article.md | 80 ---------------------- pydis_site/apps/content/resources/guides/_info.yml | 2 + .../resources/guides/pydis-guides/_info.yml | 2 + .../guides/pydis-guides/how-to-write-a-article.md | 80 ++++++++++++++++++++++ pydis_site/apps/content/urls.py | 2 +- pydis_site/apps/content/utils.py | 42 ++++-------- pydis_site/apps/content/views/__init__.py | 3 +- pydis_site/apps/content/views/page_category.py | 61 +++++++---------- pydis_site/apps/content/views/pages.py | 16 ----- pydis_site/settings.py | 2 +- pydis_site/templates/content/listing.html | 5 +- pydis_site/templates/content/page.html | 1 - 15 files changed, 127 insertions(+), 175 deletions(-) create mode 100644 pydis_site/apps/content/resources/_info.yml delete mode 100644 pydis_site/apps/content/resources/content/guides/_info.yml delete mode 100644 pydis_site/apps/content/resources/content/guides/pydis-guides/_info.yml delete mode 100644 pydis_site/apps/content/resources/content/guides/pydis-guides/how-to-write-a-article.md create mode 100644 pydis_site/apps/content/resources/guides/_info.yml create mode 100644 pydis_site/apps/content/resources/guides/pydis-guides/_info.yml create mode 100644 pydis_site/apps/content/resources/guides/pydis-guides/how-to-write-a-article.md delete mode 100644 pydis_site/apps/content/views/pages.py (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/resources/_info.yml b/pydis_site/apps/content/resources/_info.yml new file mode 100644 index 00000000..4ccdd7e1 --- /dev/null +++ b/pydis_site/apps/content/resources/_info.yml @@ -0,0 +1,2 @@ +name: Pages +description: Guides, articles and pages hosted on the site. diff --git a/pydis_site/apps/content/resources/content/guides/_info.yml b/pydis_site/apps/content/resources/content/guides/_info.yml deleted file mode 100644 index 369f05d4..00000000 --- a/pydis_site/apps/content/resources/content/guides/_info.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Guides -description: Python and PyDis guides. diff --git a/pydis_site/apps/content/resources/content/guides/pydis-guides/_info.yml b/pydis_site/apps/content/resources/content/guides/pydis-guides/_info.yml deleted file mode 100644 index 64111a83..00000000 --- a/pydis_site/apps/content/resources/content/guides/pydis-guides/_info.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Python Discord Guides -description: Python Discord server and community guides. diff --git a/pydis_site/apps/content/resources/content/guides/pydis-guides/how-to-write-a-article.md b/pydis_site/apps/content/resources/content/guides/pydis-guides/how-to-write-a-article.md deleted file mode 100644 index ec89988c..00000000 --- a/pydis_site/apps/content/resources/content/guides/pydis-guides/how-to-write-a-article.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: How to Write a Article -short_description: Learn how to write a article for this website -icon_class: fas -icon: fa-info ---- - -When you are interested about how to write articles for this site (like this), then you can learn about it here. -PyDis use Markdown (GitHub Markdown) files for articles. - -## Getting Started -Before you can get started with writing a article, you need idea. -Best way to find out is your idea good is to discuss about it in #dev-contrib channel. There can other peoples give their opinion about your idea. Even better, open issue in site repository first, then PyDis staff can see it and approve/decline this idea. -It's good idea to wait for staff decision before starting to write guide to avoid case when you write a long long article, but then this don't get approved. - -To start with contributing, you should read [how to contribute to site](https://pythondiscord.com/pages/contributing/site/). -You should also read our [Git workflow](https://pythondiscord.com/pages/contributing/working-with-git/), because you need to push your guide to GitHub. - -## Creating a File -All articles is located at `site` repository, in `pydis_site/apps/content/resources/content`. Under this is root level articles (.md files) and categories (directories). Learn more about categories in [categories section](#categories). - -When you are writing guides, then these are located under `guides` category. - -At this point, you will need your article name for filename. Replace all your article name spaces with `-` and make all lowercase. Save this as `.md` (Markdown) file. This name (without Markdown extension) is path of article in URL. - -## Markdown Metadata -Article files have some required metadata, like title, description, relevant pages. Metadata is first thing in file, YAML-like key-value pairs: - -```md ---- -title: My Article -short_description: This is my short description. -relevant_links: url1,url2,url3 -relevant_link_values: Text for url1,Text for url2,Text for url3 ---- - -Here comes content of article... -``` - -You can read more about Markdown metadata [here](https://github.com/trentm/python-markdown2/wiki/metadata). - -### Fields -- **Name:** Easily-readable name for your article. -- **Short Description:** Small, 1-2 line description that describe what your article explain. -- **Relevant Links and Values:** URLs and values is under different fields, separated with comma. -- **Icon class:** `icon_class` field have one of the favicons classes. Default is `fab`. -- **Icon:** `icon` field have favicon name. Default `fa-python`. - -## Content -For content, mostly you can use standard markdown, but there is a few addition that is available. - -### IDs for quick jumps -System automatically assign IDs to headers, so like this header will get ID `ids-for-quick-jumps`. - -### Tables -Tables like in GitHub is supported too: - -| This is header | This is too header | -| -------------- | ------------------ | -| My item | My item too | - -### Codeblocks -Also this system supports codeblocks and provides syntax highlighting with `highlight.js`. -To activate syntax highlight, just put language directly after starting backticks. - -```py -import os - -path = os.path.join("foo", "bar") -``` - -## Categories -To have some systematic sorting of guides, site support guides categories. Currently this system support only 1 level of categories. Categories live at `site` repo in `pydis_site/apps/content/resources/content` subdirectories. Directory name is path of category in URL. Inside category directory, there is 1 file required: `_info.yml`. This file need 2 key-value pairs defined: - -```yml -name: Category name -description: Category description -``` - -Then all Markdown files in this folder will be under this category. diff --git a/pydis_site/apps/content/resources/guides/_info.yml b/pydis_site/apps/content/resources/guides/_info.yml new file mode 100644 index 00000000..369f05d4 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/_info.yml @@ -0,0 +1,2 @@ +name: Guides +description: Python and PyDis guides. diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml new file mode 100644 index 00000000..64111a83 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml @@ -0,0 +1,2 @@ +name: Python Discord Guides +description: Python Discord server and community guides. diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-write-a-article.md b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-write-a-article.md new file mode 100644 index 00000000..ec89988c --- /dev/null +++ b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-write-a-article.md @@ -0,0 +1,80 @@ +--- +title: How to Write a Article +short_description: Learn how to write a article for this website +icon_class: fas +icon: fa-info +--- + +When you are interested about how to write articles for this site (like this), then you can learn about it here. +PyDis use Markdown (GitHub Markdown) files for articles. + +## Getting Started +Before you can get started with writing a article, you need idea. +Best way to find out is your idea good is to discuss about it in #dev-contrib channel. There can other peoples give their opinion about your idea. Even better, open issue in site repository first, then PyDis staff can see it and approve/decline this idea. +It's good idea to wait for staff decision before starting to write guide to avoid case when you write a long long article, but then this don't get approved. + +To start with contributing, you should read [how to contribute to site](https://pythondiscord.com/pages/contributing/site/). +You should also read our [Git workflow](https://pythondiscord.com/pages/contributing/working-with-git/), because you need to push your guide to GitHub. + +## Creating a File +All articles is located at `site` repository, in `pydis_site/apps/content/resources/content`. Under this is root level articles (.md files) and categories (directories). Learn more about categories in [categories section](#categories). + +When you are writing guides, then these are located under `guides` category. + +At this point, you will need your article name for filename. Replace all your article name spaces with `-` and make all lowercase. Save this as `.md` (Markdown) file. This name (without Markdown extension) is path of article in URL. + +## Markdown Metadata +Article files have some required metadata, like title, description, relevant pages. Metadata is first thing in file, YAML-like key-value pairs: + +```md +--- +title: My Article +short_description: This is my short description. +relevant_links: url1,url2,url3 +relevant_link_values: Text for url1,Text for url2,Text for url3 +--- + +Here comes content of article... +``` + +You can read more about Markdown metadata [here](https://github.com/trentm/python-markdown2/wiki/metadata). + +### Fields +- **Name:** Easily-readable name for your article. +- **Short Description:** Small, 1-2 line description that describe what your article explain. +- **Relevant Links and Values:** URLs and values is under different fields, separated with comma. +- **Icon class:** `icon_class` field have one of the favicons classes. Default is `fab`. +- **Icon:** `icon` field have favicon name. Default `fa-python`. + +## Content +For content, mostly you can use standard markdown, but there is a few addition that is available. + +### IDs for quick jumps +System automatically assign IDs to headers, so like this header will get ID `ids-for-quick-jumps`. + +### Tables +Tables like in GitHub is supported too: + +| This is header | This is too header | +| -------------- | ------------------ | +| My item | My item too | + +### Codeblocks +Also this system supports codeblocks and provides syntax highlighting with `highlight.js`. +To activate syntax highlight, just put language directly after starting backticks. + +```py +import os + +path = os.path.join("foo", "bar") +``` + +## Categories +To have some systematic sorting of guides, site support guides categories. Currently this system support only 1 level of categories. Categories live at `site` repo in `pydis_site/apps/content/resources/content` subdirectories. Directory name is path of category in URL. Inside category directory, there is 1 file required: `_info.yml`. This file need 2 key-value pairs defined: + +```yml +name: Category name +description: Category description +``` + +Then all Markdown files in this folder will be under this category. diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index 1406f672..c11b222a 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -4,6 +4,6 @@ from . import views app_name = "content" urlpatterns = [ - path("", views.PagesView.as_view(), name='pages'), + path("", views.PageOrCategoryView.as_view(), name='pages'), path("/", views.PageOrCategoryView.as_view(), name='page_category'), ] diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index db502a71..ef02a8cb 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,65 +1,49 @@ -import os -from typing import Dict, List, Optional, Union +from pathlib import Path +from typing import Dict, Union import yaml -from django.conf import settings from django.http import Http404 from markdown2 import markdown -def get_category(path: List[str]) -> Dict[str, str]: +def get_category(path: Path) -> Dict[str, str]: """Load category information by name from _info.yml.""" - path = settings.PAGES_PATH.joinpath(*path) if not path.exists() or not path.is_dir(): raise Http404("Category not found.") return yaml.safe_load(path.joinpath("_info.yml").read_text()) -def get_categories(path: Optional[List[str]] = None) -> Dict[str, Dict]: +def get_categories(path: Path) -> Dict[str, Dict]: """Get all categories information.""" categories = {} - if path is None: - categories_path = settings.PAGES_PATH - path = [] - else: - categories_path = settings.PAGES_PATH.joinpath(*path) - for name in categories_path.iterdir(): + for name in path.iterdir(): if name.is_dir(): - categories[name.name] = get_category([*path, name.name]) + categories[name.name] = get_category(path.joinpath(name.name)) return categories -def get_pages(path: Optional[List[str]] = None) -> Dict[str, Dict]: - """Get all root or category pages.""" - if path is None: - base_dir = settings.PAGES_PATH - else: - base_dir = settings.PAGES_PATH.joinpath(*path) - +def get_pages(path: Path) -> Dict[str, Dict]: + """Get all root or category page names and their metadata.""" pages = {} - for item in base_dir.iterdir(): + for item in path.iterdir(): if item.is_file() and item.name.endswith(".md"): md = markdown(item.read_text(), extras=["metadata"]) - pages[os.path.splitext(item.name)[0]] = md.metadata + pages[item.stem] = md.metadata return pages -def get_page(path: List[str]) -> Dict[str, Union[str, Dict]]: +def get_page(path: Path) -> Dict[str, Union[str, Dict]]: """Get one specific page. When category is specified, get it from there.""" - page_path = settings.PAGES_PATH.joinpath(*path[:-1]) - - # We need to include extension MD - page_path = page_path.joinpath(f"{path[-1]}.md") - if not page_path.exists() or not page_path.is_file(): + if not path.exists() or not path.is_file(): raise Http404("Page not found.") html = markdown( - page_path.read_text(), + path.read_text(), extras=[ "metadata", "fenced-code-blocks", diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py index 740d98e9..70ea1c7a 100644 --- a/pydis_site/apps/content/views/__init__.py +++ b/pydis_site/apps/content/views/__init__.py @@ -1,4 +1,3 @@ from .page_category import PageOrCategoryView -from .pages import PagesView -__all__ = ["PageOrCategoryView", "PagesView"] +__all__ = ["PageOrCategoryView"] diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py index f00a79ee..7e04e2f3 100644 --- a/pydis_site/apps/content/views/page_category.py +++ b/pydis_site/apps/content/views/page_category.py @@ -1,4 +1,5 @@ import typing as t +from pathlib import Path from django.conf import settings from django.http import Http404 @@ -10,14 +11,18 @@ from pydis_site.apps.content import utils class PageOrCategoryView(TemplateView): """Handles pages and page categories.""" + 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.full_location = settings.PAGES_PATH / self.location + + return super().dispatch(request, *args, **kwargs) + def get_template_names(self) -> t.List[str]: """Checks does this use page template or listing template.""" - location = self.kwargs["location"].split("/") - full_location = settings.PAGES_PATH.joinpath(*location) - - if full_location.is_dir(): + if self.full_location.is_dir(): template_name = "content/listing.html" - elif full_location.with_suffix(".md").is_file(): + elif self.full_location.with_suffix(".md").is_file(): template_name = "content/page.html" else: raise Http404 @@ -28,42 +33,24 @@ class PageOrCategoryView(TemplateView): """Assign proper context variables based on what resource user requests.""" context = super().get_context_data(**kwargs) - location: list = self.kwargs["location"].split("/") - full_location = settings.PAGES_PATH.joinpath(*location) - - if full_location.is_dir(): - context["category_info"] = utils.get_category(location) - context["content"] = utils.get_pages(location) - context["categories"] = utils.get_categories(location) - # Add trailing slash here to simplify template - context["path"] = "/".join(location) + "/" - context["in_category"] = True - elif full_location.with_suffix(".md").is_file(): - page_result = utils.get_page(location) - - if len(location) > 1: - context["category_data"] = utils.get_category(location[:-1]) - context["category_data"]["raw_name"] = location[:-1][-1] - else: - context["category_data"] = {"name": None, "raw_name": None} - + if self.full_location.is_dir(): + context["categories"] = utils.get_categories(self.full_location) + context["category_info"] = utils.get_category(self.full_location) + context["content"] = utils.get_pages(self.full_location) + context["path"] = f"{self.location}/" # Add trailing slash here to simplify template + elif self.full_location.with_suffix(".md").is_file(): + page_result = utils.get_page(self.full_location.with_suffix(".md")) context["page"] = page_result context["relevant_links"] = page_result["metadata"].get("relevant_links", {}) else: raise Http404 - location.pop() - breadcrumb_items = [] - while len(location): - breadcrumb_items.insert( - 0, - { - "name": utils.get_category(location)["name"], - "path": "/".join(location) - } - ) - location.pop() - - context["breadcrumb_items"] = breadcrumb_items + breadcrumb_items = [ + { + "name": utils.get_category(settings.PAGES_PATH / location)["name"], + "path": str(location) + } for location in self.location.parents + ] + context["breadcrumb_items"] = reversed(breadcrumb_items) return context diff --git a/pydis_site/apps/content/views/pages.py b/pydis_site/apps/content/views/pages.py deleted file mode 100644 index 11ac0eeb..00000000 --- a/pydis_site/apps/content/views/pages.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.views.generic import TemplateView - -from pydis_site.apps.content.utils import get_pages, get_categories - - -class PagesView(TemplateView): - """Shows all pages and categories.""" - - template_name = "content/listing.html" - - def get_context_data(self, **kwargs) -> dict: - """Add page and category data to template context.""" - context = super().get_context_data(**kwargs) - context["content"] = get_pages() - context["categories"] = get_categories() - return context diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 0b4d9fd1..3abf556a 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -287,4 +287,4 @@ SITE_REPOSITORY_OWNER = "python-discord" SITE_REPOSITORY_NAME = "site" SITE_REPOSITORY_BRANCH = "master" -PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources", "content") +PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources") diff --git a/pydis_site/templates/content/listing.html b/pydis_site/templates/content/listing.html index 39eae1c2..097cac4f 100644 --- a/pydis_site/templates/content/listing.html +++ b/pydis_site/templates/content/listing.html @@ -16,13 +16,10 @@
    diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html index 3b0ebb5f..433baa69 100644 --- a/pydis_site/templates/content/page.html +++ b/pydis_site/templates/content/page.html @@ -20,7 +20,6 @@
    {% endfor %} - {% for page, data in content.items %} + {% for page, data in pages.items %}
    diff --git a/pydis_site/templates/content/page.html b/pydis_site/templates/content/page.html index 2b366e50..5e820c26 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.page|safe }} + {{ page.content|safe }}
    @@ -26,6 +26,6 @@
    {% else %} -
    {{ page.page|safe }}
    +
    {{ page.content|safe }}
    {% endif %} {% endblock %} -- cgit v1.2.3 From 19191bb1f45d14522a8de00b70feae2fc624e58a Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 24 Mar 2021 14:28:45 +0800 Subject: Simplify pathlib code and specify file encoding. --- pydis_site/apps/content/utils.py | 18 +++++++++--------- pydis_site/apps/content/views/page_category.py | 5 ++--- 2 files changed, 11 insertions(+), 12 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 8cbcad91..0faf722c 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -8,19 +8,19 @@ from markdown2 import markdown def get_category(path: Path) -> Dict[str, str]: """Load category information by name from _info.yml.""" - if not path.exists() or not path.is_dir(): + if not path.is_dir(): raise Http404("Category not found.") - return yaml.safe_load(path.joinpath("_info.yml").read_text()) + return yaml.safe_load(path.joinpath("_info.yml").read_text(encoding="utf-8")) def get_categories(path: Path) -> Dict[str, Dict]: """Get information for all categories.""" categories = {} - for name in path.iterdir(): - if name.is_dir(): - categories[name.name] = get_category(path.joinpath(name.name)) + for item in path.iterdir(): + if item.is_dir(): + categories[item.name] = get_category(item) return categories @@ -29,8 +29,8 @@ def get_category_pages(path: Path) -> Dict[str, Dict]: """Get all page names and their metadata at a category path.""" pages = {} - for item in path.iterdir(): - if item.is_file() and item.name.endswith(".md"): + for item in path.glob("*.md"): + if item.is_file(): md = markdown(item.read_text(), extras=["metadata"]) pages[item.stem] = md.metadata @@ -39,11 +39,11 @@ def get_category_pages(path: Path) -> Dict[str, Dict]: def get_page(path: Path) -> Dict[str, Union[str, Dict]]: """Get one specific page.""" - if not path.exists() or not path.is_file(): + if not path.is_file(): raise Http404("Page not found.") html = markdown( - path.read_text(), + path.read_text(encoding="utf-8"), extras=[ "metadata", "fenced-code-blocks", diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py index 0bef82d0..91aed7f0 100644 --- a/pydis_site/apps/content/views/page_category.py +++ b/pydis_site/apps/content/views/page_category.py @@ -50,12 +50,11 @@ class PageOrCategoryView(TemplateView): else: raise Http404 - breadcrumb_items = [ + context["breadcrumb_items"] = [ { "name": utils.get_category(settings.PAGES_PATH / location)["name"], "path": str(location) - } for location in self.location.parents + } for location in reversed(self.location.parents) ] - context["breadcrumb_items"] = reversed(breadcrumb_items) return context -- cgit v1.2.3 From 6ccec0d866c44cd7f9789a396ef6ec6cd2cd5df8 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 24 Mar 2021 19:55:16 +0800 Subject: Replace `markdown2` with `markdown` and `python-frontmatter`. This allows us to properly escape codeblocks within markdown, permalink to headers on a page, and decouples getting metadata from a file and getting generated HTML from the Markdown content. --- Pipfile | 3 +- Pipfile.lock | 43 ++++++++++++++-- .../pydis-guides/how-to-contribute-a-page.md | 59 +++++++++++++++++++++- pydis_site/apps/content/utils.py | 32 ++++++------ pydis_site/apps/content/views/page_category.py | 15 +++--- pydis_site/static/css/content/page.css | 29 ++++++++--- pydis_site/templates/content/base.html | 2 +- pydis_site/templates/content/page.html | 4 +- 8 files changed, 146 insertions(+), 41 deletions(-) (limited to 'pydis_site/apps/content/utils.py') diff --git a/Pipfile b/Pipfile index 8af6e3ce..c2b87ab4 100644 --- a/Pipfile +++ b/Pipfile @@ -18,7 +18,8 @@ pyyaml = "~=5.1" pyuwsgi = {version = "~=2.0", sys_platform = "!='win32'"} sentry-sdk = "~=0.14" gitpython = "~=3.1.7" -markdown2 = "~=2.3.9" +markdown = "~=3.3.4" +python-frontmatter = "~=1.0" [dev-packages] coverage = "~=5.0" diff --git a/Pipfile.lock b/Pipfile.lock index 3dfb7ec6..4f8bd0d3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "893ab5cc0b64f7bb87c0304c9fb6eb24b1856c40286fbbdb1a2bf4a0d7a6d39f" + "sha256": "a338a377b64a5d25bf925646c97d932a2d1c783fc8f40d91b6a9ab8f30c2b14e" }, "pipfile-spec": 6, "requires": { @@ -118,6 +118,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, + "importlib-metadata": { + "hashes": [ + "sha256:742add720a20d0467df2f444ae41704000f50e1234f46174b51f9c6031a1bd71", + "sha256:b74159469b464a99cb8cc3e21973e4d96e05d3024d337313fedb618a6e86e6f4" + ], + "markers": "python_version < '3.8'", + "version": "==3.7.3" + }, "libsass": { "hashes": [ "sha256:1521d2a8d4b397c6ec90640a1f6b5529077035efc48ef1c2e53095544e713d1b", @@ -136,13 +144,13 @@ ], "version": "==0.20.1" }, - "markdown2": { + "markdown": { "hashes": [ - "sha256:85956d8119fa6378156fef65545d66705a842819d2e1b50379a2b9d2aaa17cf0", - "sha256:fef148e5fd68d4532286c3e2943e9d2c076a8ad781b0a70a9d599a0ffe91652d" + "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49", + "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c" ], "index": "pypi", - "version": "==2.3.10" + "version": "==3.3.4" }, "psycopg2-binary": { "hashes": [ @@ -185,6 +193,14 @@ "index": "pypi", "version": "==2.8.6" }, + "python-frontmatter": { + "hashes": [ + "sha256:766ae75f1b301ffc5fe3494339147e0fd80bc3deff3d7590a93991978b579b08", + "sha256:e98152e977225ddafea6f01f40b4b0f1de175766322004c826ca99842d19a7cd" + ], + "index": "pypi", + "version": "==1.0.0" + }, "pytz": { "hashes": [ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", @@ -288,6 +304,15 @@ "markers": "python_version >= '3.5'", "version": "==0.4.1" }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "markers": "python_version < '3.8'", + "version": "==3.7.4.3" + }, "urllib3": { "hashes": [ "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", @@ -303,6 +328,14 @@ ], "index": "pypi", "version": "==5.2.0" + }, + "zipp": { + "hashes": [ + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.1" } }, "develop": { 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 8967e7da..12969ba2 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 @@ -69,18 +69,75 @@ You can learn more about Markdown metadata [here](https://github.com/trentm/pyth Apart from standard Markdown, certain additions are available: +### Abbreviations +HTML `` tags can be used in markdown using this format: + +**Markdown:** +```nohighlight +This website is HTML generated from YAML and Markdown. + +*[HTML]: Hyper Text Markup Language +*[YAML]: YAML Ain't Markup Language +``` + +**Output:** + +This website is HTML +generated from YAML and Markdown. + +--- + +### Footnotes +**Markdown:** +```nohighlight +This footnote[^1] links to the bottom[^custom_label] of the page[^3]. + +[^1]: Footnote labels start with a caret `^`. +[^3]: The footnote link is numbered based on the order of the labels. +[^custom label]: Footnote labels can contain any text within square brackets. +``` + +**Output:** + +This footnote[^1] links to the bottom[^custom label] of the page[^3]. + +[^1]: Footnote labels start with a caret `^`. +[^3]: The footnote link is numbered based on the order of the labels. +[^custom label]: Footnote labels can contain any text within square brackets. + +--- + ### Tables +**Markdown:** +```nohighlight +| This is header | This is another header | +| -------------- | ---------------------- | +| An item | Another item | +``` + +**Output:** + | This is header | This is another header | | -------------- | ---------------------- | | An item | Another item | -| Big item | Small item | +--- ### Codeblock Syntax Highlighting Syntax highlighting is provided by `highlight.js`. To activate syntax highlighting, put the language directly after the starting backticks. +**Markdown:** +````nohighlight +```python +import os + +path = os.path.join("foo", "bar") +``` +```` + +**Output:** ```python import os diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index 0faf722c..11d2b792 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -1,9 +1,11 @@ from pathlib import Path -from typing import Dict, Union +from typing import Dict, Tuple +import frontmatter +import markdown import yaml from django.http import Http404 -from markdown2 import markdown +from markdown.extensions.toc import TocExtension def get_category(path: Path) -> Dict[str, str]: @@ -31,29 +33,25 @@ def get_category_pages(path: Path) -> Dict[str, Dict]: for item in path.glob("*.md"): if item.is_file(): - md = markdown(item.read_text(), extras=["metadata"]) - pages[item.stem] = md.metadata + pages[item.stem] = frontmatter.load(item) return pages -def get_page(path: Path) -> Dict[str, Union[str, Dict]]: +def get_page(path: Path) -> Tuple[str, Dict]: """Get one specific page.""" if not path.is_file(): raise Http404("Page not found.") - html = markdown( - path.read_text(encoding="utf-8"), - extras=[ - "metadata", - "fenced-code-blocks", - "highlightjs-lang", - "header-ids", - "strike", - "target-blank-links", - "tables", - "task_list" + metadata, content = frontmatter.parse(path.read_text(encoding="utf-8")) + html = markdown.markdown( + content, + 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="") ] ) - return {"content": str(html), "metadata": html.metadata} + 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 91aed7f0..4a2ed2d6 100644 --- a/pydis_site/apps/content/views/page_category.py +++ b/pydis_site/apps/content/views/page_category.py @@ -35,18 +35,19 @@ class PageOrCategoryView(TemplateView): if self.full_location.is_dir(): context["categories"] = utils.get_categories(self.full_location) + context["pages"] = utils.get_category_pages(self.full_location) + category = utils.get_category(self.full_location) - context["category_info"] = category context["page_title"] = category["name"] context["page_description"] = category["description"] - context["pages"] = utils.get_category_pages(self.full_location) + context["path"] = f"{self.location}/" # Add trailing slash here to simplify template elif self.full_location.with_suffix(".md").is_file(): - page_result = utils.get_page(self.full_location.with_suffix(".md")) - context["page"] = page_result - context["page_title"] = page_result["metadata"]["title"] - context["page_description"] = page_result["metadata"]["description"] - context["relevant_links"] = page_result["metadata"].get("relevant_links", {}) + page, metadata = utils.get_page(self.full_location.with_suffix(".md")) + context["page"] = page + context["page_title"] = metadata["title"] + context["page_description"] = metadata["description"] + context["relevant_links"] = metadata.get("relevant_links", {}) else: raise Http404 diff --git a/pydis_site/static/css/content/page.css b/pydis_site/static/css/content/page.css index f46d6b15..57d7472b 100644 --- a/pydis_site/static/css/content/page.css +++ b/pydis_site/static/css/content/page.css @@ -6,11 +6,26 @@ i.has-icon-padding { padding: 0 10px 25px 0; } -pre { - /* - * Style it the same as the 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