From 3ea412e13cd01a17ceb3bfbdef1ec122f4eca113 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:49:16 +0300 Subject: Include guides app to project --- pydis_site/settings.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pydis_site/settings.py') diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 1f042c1b..6aff0fc1 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -91,6 +91,7 @@ INSTALLED_APPS = [ 'pydis_site.apps.api', 'pydis_site.apps.home', 'pydis_site.apps.staff', + 'pydis_site.apps.guides', 'django.contrib.admin', 'django.contrib.auth', -- cgit v1.2.3 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/__init__.py | 0 pydis_site/apps/content/apps.py | 7 ++ pydis_site/apps/content/migrations/__init__.py | 0 .../content/resources/content/guides/_info.yml | 2 + .../content/guides/how-to-write-a-guide.md | 63 +++++++++++ pydis_site/apps/content/tests/__init__.py | 0 .../content/tests/test_content/category/_info.yml | 2 + .../content/tests/test_content/category/test3.md | 5 + pydis_site/apps/content/tests/test_content/test.md | 11 ++ .../apps/content/tests/test_content/test2.md | 5 + pydis_site/apps/content/tests/test_utils.py | 122 +++++++++++++++++++++ pydis_site/apps/content/tests/test_views.py | 104 ++++++++++++++++++ pydis_site/apps/content/urls.py | 11 ++ pydis_site/apps/content/utils.py | 77 +++++++++++++ pydis_site/apps/content/views/__init__.py | 5 + pydis_site/apps/content/views/article.py | 50 +++++++++ pydis_site/apps/content/views/articles.py | 14 +++ pydis_site/apps/content/views/category.py | 18 +++ pydis_site/apps/guides/__init__.py | 0 pydis_site/apps/guides/apps.py | 7 -- pydis_site/apps/guides/migrations/__init__.py | 0 .../resources/guides/how-to-write-a-guide.md | 63 ----------- pydis_site/apps/guides/tests/__init__.py | 0 .../guides/tests/test_guides/category/_info.yml | 2 - .../guides/tests/test_guides/category/test3.md | 5 - pydis_site/apps/guides/tests/test_guides/test.md | 11 -- pydis_site/apps/guides/tests/test_guides/test2.md | 5 - pydis_site/apps/guides/tests/test_utils.py | 122 --------------------- pydis_site/apps/guides/tests/test_views.py | 104 ------------------ pydis_site/apps/guides/urls.py | 11 -- pydis_site/apps/guides/utils.py | 77 ------------- pydis_site/apps/guides/views/__init__.py | 5 - pydis_site/apps/guides/views/category.py | 18 --- pydis_site/apps/guides/views/guide.py | 48 -------- pydis_site/apps/guides/views/guides.py | 14 --- pydis_site/apps/home/urls.py | 2 +- pydis_site/settings.py | 2 +- pydis_site/static/css/content/articles.css | 7 ++ pydis_site/static/css/guides/guide.css | 7 -- pydis_site/templates/content/article.html | 61 +++++++++++ pydis_site/templates/content/articles.html | 53 +++++++++ pydis_site/templates/content/category.html | 44 ++++++++ pydis_site/templates/guides/category.html | 44 -------- pydis_site/templates/guides/guide.html | 61 ----------- pydis_site/templates/guides/guides.html | 53 --------- 45 files changed, 663 insertions(+), 659 deletions(-) create mode 100644 pydis_site/apps/content/__init__.py create mode 100644 pydis_site/apps/content/apps.py create mode 100644 pydis_site/apps/content/migrations/__init__.py create mode 100644 pydis_site/apps/content/resources/content/guides/_info.yml create mode 100644 pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md create mode 100644 pydis_site/apps/content/tests/__init__.py create mode 100644 pydis_site/apps/content/tests/test_content/category/_info.yml create mode 100644 pydis_site/apps/content/tests/test_content/category/test3.md create mode 100644 pydis_site/apps/content/tests/test_content/test.md create mode 100644 pydis_site/apps/content/tests/test_content/test2.md create mode 100644 pydis_site/apps/content/tests/test_utils.py create mode 100644 pydis_site/apps/content/tests/test_views.py create mode 100644 pydis_site/apps/content/urls.py create mode 100644 pydis_site/apps/content/utils.py create mode 100644 pydis_site/apps/content/views/__init__.py create mode 100644 pydis_site/apps/content/views/article.py create mode 100644 pydis_site/apps/content/views/articles.py create mode 100644 pydis_site/apps/content/views/category.py delete mode 100644 pydis_site/apps/guides/__init__.py delete mode 100644 pydis_site/apps/guides/apps.py delete mode 100644 pydis_site/apps/guides/migrations/__init__.py delete mode 100644 pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md delete mode 100644 pydis_site/apps/guides/tests/__init__.py delete mode 100644 pydis_site/apps/guides/tests/test_guides/category/_info.yml delete mode 100644 pydis_site/apps/guides/tests/test_guides/category/test3.md delete mode 100644 pydis_site/apps/guides/tests/test_guides/test.md delete mode 100644 pydis_site/apps/guides/tests/test_guides/test2.md delete mode 100644 pydis_site/apps/guides/tests/test_utils.py delete mode 100644 pydis_site/apps/guides/tests/test_views.py delete mode 100644 pydis_site/apps/guides/urls.py delete mode 100644 pydis_site/apps/guides/utils.py delete mode 100644 pydis_site/apps/guides/views/__init__.py delete mode 100644 pydis_site/apps/guides/views/category.py delete mode 100644 pydis_site/apps/guides/views/guide.py delete mode 100644 pydis_site/apps/guides/views/guides.py create mode 100644 pydis_site/static/css/content/articles.css delete mode 100644 pydis_site/static/css/guides/guide.css create mode 100644 pydis_site/templates/content/article.html create mode 100644 pydis_site/templates/content/articles.html create mode 100644 pydis_site/templates/content/category.html delete mode 100644 pydis_site/templates/guides/category.html delete mode 100644 pydis_site/templates/guides/guide.html delete mode 100644 pydis_site/templates/guides/guides.html (limited to 'pydis_site/settings.py') diff --git a/pydis_site/apps/content/__init__.py b/pydis_site/apps/content/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/content/apps.py b/pydis_site/apps/content/apps.py new file mode 100644 index 00000000..1e300a48 --- /dev/null +++ b/pydis_site/apps/content/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ContentConfig(AppConfig): + """Django AppConfig for content app.""" + + name = 'content' diff --git a/pydis_site/apps/content/migrations/__init__.py b/pydis_site/apps/content/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/content/resources/content/guides/_info.yml b/pydis_site/apps/content/resources/content/guides/_info.yml new file mode 100644 index 00000000..8a38271d --- /dev/null +++ b/pydis_site/apps/content/resources/content/guides/_info.yml @@ -0,0 +1,2 @@ +name: Guides +description: Python and PyDis guides. \ No newline at end of file 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 new file mode 100644 index 00000000..072c2538 --- /dev/null +++ b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md @@ -0,0 +1,63 @@ +Title: How to Write a Guide +ShortDescription: Learn how to write a guide for this website +Contributors: ks129 + +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" } +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. +It's good idea to wait for staff decision before starting to write guide to avoid case when you write a long long guide, 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](#creating-a-file){: id="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" } +Guide files have some required metadata, like title, contributors, description, relevant pages. Metadata is first thing in file, YAML-like key-value pairs: + +```md +Title: My Guide +ShortDescription: This is my short description. +Contributors: person1 + person2 + person3 +RelevantLinks: url1 + url2 + url3 +RelevantLinkValues: Text for url1 + Text for url2 + Text for url3 + +Here comes content of guide... +``` + +You can read more about Markdown metadata [here](https://python-markdown.github.io/extensions/meta_data/). + +### Fields +- **Name:** Easily-readable name for your guide. +- **Short Description:** Small, 1-2 line description that describe what your guide explain. +- **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" } +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" } +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 +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/tests/__init__.py b/pydis_site/apps/content/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/content/tests/test_content/category/_info.yml b/pydis_site/apps/content/tests/test_content/category/_info.yml new file mode 100644 index 00000000..8311509d --- /dev/null +++ b/pydis_site/apps/content/tests/test_content/category/_info.yml @@ -0,0 +1,2 @@ +name: My Category +description: My Description diff --git a/pydis_site/apps/content/tests/test_content/category/test3.md b/pydis_site/apps/content/tests/test_content/category/test3.md new file mode 100644 index 00000000..bdde6188 --- /dev/null +++ b/pydis_site/apps/content/tests/test_content/category/test3.md @@ -0,0 +1,5 @@ +Title: Test 3 +ShortDescription: Testing 3 +Contributors: user3 + +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 new file mode 100644 index 00000000..7a917899 --- /dev/null +++ b/pydis_site/apps/content/tests/test_content/test.md @@ -0,0 +1,11 @@ +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 + +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 new file mode 100644 index 00000000..f0852356 --- /dev/null +++ b/pydis_site/apps/content/tests/test_content/test2.md @@ -0,0 +1,5 @@ +Title: Test 2 +ShortDescription: Testing 2 +Contributors: user2 + +This is too test content. \ No newline at end of file diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py new file mode 100644 index 00000000..82e1ac5f --- /dev/null +++ b/pydis_site/apps/content/tests/test_utils.py @@ -0,0 +1,122 @@ +import os +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 pydis_site.apps.content import utils + +BASE_PATH = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "content", "tests", "test_content") + + +class TestGetBasePath(TestCase): + def test_get_base_path(self): + """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") + ) + + +class TestGetCategory(TestCase): + def test_get_category_successfully(self): + """Check does this get right data from category data file.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + result = utils.get_category("category") + + self.assertEqual(result, {"name": "My Category", "description": "My Description"}) + + def test_get_category_not_exists(self): + """Check does this raise 404 error when category don't exists.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + with self.assertRaises(Http404): + utils.get_category("invalid") + + def test_get_category_not_directory(self): + """Check does this raise 404 error when category isn't directory.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + with self.assertRaises(Http404): + utils.get_category("test.md") + + +class TestGetCategories(TestCase): + def test_get_categories(self): + """Check does this return test content categories.""" + 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"}}) + + +class TestGetArticles(TestCase): + def test_get_all_root_articles(self): + """Check does this return all root level testing content.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + result = utils.get_articles() + + 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()) + + self.assertIn(case, result) + self.assertEqual(md.Meta, 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']) + with open(os.path.join(BASE_PATH, "category", "test3.md")) as f: + md.convert(f.read()) + + self.assertIn("test3", result) + self.assertEqual(md.Meta, result["test3"]) + + +class TestGetArticle(TestCase): + def test_get_root_article_success(self): + """Check does this return article HTML and metadata when root article exist.""" + 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']) + + with open(os.path.join(BASE_PATH, "test.md")) as f: + html = md.convert(f.read()) + + self.assertEqual(result, {"article": html, "metadata": md.Meta}) + + def test_get_root_article_dont_exist(self): + """Check does this raise Http404 when root article don't exist.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + with self.assertRaises(Http404): + utils.get_article("invalid", None) + + def test_get_category_article_success(self): + """Check does this return article HTML and metadata when category guide exist.""" + 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']) + + with open(os.path.join(BASE_PATH, "category", "test3.md")) as f: + html = md.convert(f.read()) + + self.assertEqual(result, {"article": html, "metadata": md.Meta}) + + def test_get_category_article_dont_exist(self): + """Check does this raise Http404 when category article don't exist.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + with self.assertRaises(Http404): + utils.get_article("invalid", "category") + + def test_get_category_article_category_dont_exist(self): + """Check does this raise Http404 when category don't exist.""" + with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): + with self.assertRaises(Http404): + utils.get_article("some-guide", "invalid") diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py new file mode 100644 index 00000000..98054534 --- /dev/null +++ b/pydis_site/apps/content/tests/test_views.py @@ -0,0 +1,104 @@ +from unittest.mock import patch + +from django.http import Http404 +from django.test import TestCase +from django_hosts.resolvers import reverse + + +class TestGuidesIndexView(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): + """Check that content index return HTTP code 200.""" + get_categories_mock.return_value = {} + get_articles_mock.return_value = {} + + url = reverse('content:content') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + get_articles_mock.assert_called_once() + get_categories_mock.assert_called_once() + + +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): + get_article_mock.return_value = {"guide": "test", "metadata": {}} + + url = reverse("content:article", args=["test-guide"]) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + 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): + """Check that return code is 404 when invalid article provided.""" + get_article_mock.side_effect = Http404("Article not found.") + + url = reverse("content:article", args=["invalid-guide"]) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + get_article_mock.assert_called_once_with("invalid-guide", None) + get_category_mock.assert_not_called() + + +class TestCategoryView(TestCase): + @patch("pydis_site.apps.content.views.category.get_category") + @patch("pydis_site.apps.content.views.category.get_articles") + def test_valid_category_code_200(self, get_articles_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 = {} + + url = reverse("content:category", args=["category"]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + get_articles_mock.assert_called_once_with("category") + get_category_mock.assert_called_once_with("category") + + @patch("pydis_site.apps.content.views.category.get_category") + @patch("pydis_site.apps.content.views.category.get_articles") + def test_invalid_category_code_404(self, get_articles_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:category", args=["invalid-category"]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + get_category_mock.assert_called_once_with("invalid-category") + get_articles_mock.assert_not_called() + + +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): + """Check that return code is 200 when visiting valid category article.""" + get_article_mock.return_value = {"guide": "test", "metadata": {}} + + url = reverse("content:category_article", args=["category", "test3"]) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + 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): + """Check that return code is 200 when trying to visit invalid category article.""" + get_article_mock.side_effect = Http404("Article not found.") + + url = reverse("content:category_article", args=["category", "invalid"]) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + get_article_mock.assert_called_once_with("invalid", "category") + get_category_mock.assert_not_called() diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py new file mode 100644 index 00000000..ae9b1e57 --- /dev/null +++ b/pydis_site/apps/content/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +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("/", views.ArticleView.as_view(), name='article') +] 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} diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py new file mode 100644 index 00000000..b50d487b --- /dev/null +++ b/pydis_site/apps/content/views/__init__.py @@ -0,0 +1,5 @@ +from .category import CategoryView +from .article import ArticleView +from .articles import ArticlesView + +__all__ = ["ArticleView", "ArticlesView", "CategoryView"] diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py new file mode 100644 index 00000000..b34ca3ee --- /dev/null +++ b/pydis_site/apps/content/views/article.py @@ -0,0 +1,50 @@ +import os +from datetime import datetime +from typing import Optional + +from django.conf import settings +from django.core.handlers.wsgi import WSGIRequest +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 + + +class ArticleView(View): + """Shows specific guide page.""" + + 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) + + if category is not None: + path = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content", category, f"{article}.md" + ) + else: + path = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content", f"{article}.md" + ) + + if category is not None: + category_data = get_category(category) + category_data["raw_name"] = category + else: + category_data = {"name": None, "raw_name": None} + + return render( + request, + "content/article.html", + { + "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", []) + ) + } + } + ) diff --git a/pydis_site/apps/content/views/articles.py b/pydis_site/apps/content/views/articles.py new file mode 100644 index 00000000..ff945a19 --- /dev/null +++ b/pydis_site/apps/content/views/articles.py @@ -0,0 +1,14 @@ +from django.core.handlers.wsgi import WSGIRequest +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 + + +class ArticlesView(View): + """Shows all content and categories.""" + + def get(self, request: WSGIRequest) -> HttpResponse: + """Shows all content and 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 new file mode 100644 index 00000000..62e80a47 --- /dev/null +++ b/pydis_site/apps/content/views/category.py @@ -0,0 +1,18 @@ +from django.core.handlers.wsgi import WSGIRequest +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 + + +class CategoryView(View): + """Handles content category page.""" + + def get(self, request: WSGIRequest, category: str) -> HttpResponse: + """Handles page that displays category content.""" + return render( + request, + "content/category.html", + {"category_info": get_category(category), "content": get_articles(category), "category_name": category} + ) diff --git a/pydis_site/apps/guides/__init__.py b/pydis_site/apps/guides/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/guides/apps.py b/pydis_site/apps/guides/apps.py deleted file mode 100644 index 8dfa4f65..00000000 --- a/pydis_site/apps/guides/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class GuidesConfig(AppConfig): - """Django AppConfig for guides app.""" - - name = 'guides' diff --git a/pydis_site/apps/guides/migrations/__init__.py b/pydis_site/apps/guides/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md b/pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md deleted file mode 100644 index 072c2538..00000000 --- a/pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md +++ /dev/null @@ -1,63 +0,0 @@ -Title: How to Write a Guide -ShortDescription: Learn how to write a guide for this website -Contributors: ks129 - -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" } -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. -It's good idea to wait for staff decision before starting to write guide to avoid case when you write a long long guide, 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](#creating-a-file){: id="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" } -Guide files have some required metadata, like title, contributors, description, relevant pages. Metadata is first thing in file, YAML-like key-value pairs: - -```md -Title: My Guide -ShortDescription: This is my short description. -Contributors: person1 - person2 - person3 -RelevantLinks: url1 - url2 - url3 -RelevantLinkValues: Text for url1 - Text for url2 - Text for url3 - -Here comes content of guide... -``` - -You can read more about Markdown metadata [here](https://python-markdown.github.io/extensions/meta_data/). - -### Fields -- **Name:** Easily-readable name for your guide. -- **Short Description:** Small, 1-2 line description that describe what your guide explain. -- **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" } -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" } -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 -name: Category name -description: Category description -``` - -Then all Markdown files in this folder will be under this category. diff --git a/pydis_site/apps/guides/tests/__init__.py b/pydis_site/apps/guides/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/guides/tests/test_guides/category/_info.yml b/pydis_site/apps/guides/tests/test_guides/category/_info.yml deleted file mode 100644 index 8311509d..00000000 --- a/pydis_site/apps/guides/tests/test_guides/category/_info.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: My Category -description: My Description diff --git a/pydis_site/apps/guides/tests/test_guides/category/test3.md b/pydis_site/apps/guides/tests/test_guides/category/test3.md deleted file mode 100644 index bdde6188..00000000 --- a/pydis_site/apps/guides/tests/test_guides/category/test3.md +++ /dev/null @@ -1,5 +0,0 @@ -Title: Test 3 -ShortDescription: Testing 3 -Contributors: user3 - -This is too test content, but in category. diff --git a/pydis_site/apps/guides/tests/test_guides/test.md b/pydis_site/apps/guides/tests/test_guides/test.md deleted file mode 100644 index 7a917899..00000000 --- a/pydis_site/apps/guides/tests/test_guides/test.md +++ /dev/null @@ -1,11 +0,0 @@ -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 - -This is test content. diff --git a/pydis_site/apps/guides/tests/test_guides/test2.md b/pydis_site/apps/guides/tests/test_guides/test2.md deleted file mode 100644 index f0852356..00000000 --- a/pydis_site/apps/guides/tests/test_guides/test2.md +++ /dev/null @@ -1,5 +0,0 @@ -Title: Test 2 -ShortDescription: Testing 2 -Contributors: user2 - -This is too test content. \ No newline at end of file diff --git a/pydis_site/apps/guides/tests/test_utils.py b/pydis_site/apps/guides/tests/test_utils.py deleted file mode 100644 index e7448be6..00000000 --- a/pydis_site/apps/guides/tests/test_utils.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -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 pydis_site.apps.guides import utils - -BASE_PATH = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides") - - -class TestGetBasePath(TestCase): - def test_get_base_path(self): - """Test does function return guides base path.""" - self.assertEqual( - utils._get_base_path(), - os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") - ) - - -class TestGetCategory(TestCase): - def test_get_category_successfully(self): - """Check does this get right data from category data file.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_category("category") - - self.assertEqual(result, {"name": "My Category", "description": "My Description"}) - - def test_get_category_not_exists(self): - """Check does this raise 404 error when category don't exists.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - with self.assertRaises(Http404): - utils.get_category("invalid") - - def test_get_category_not_directory(self): - """Check does this raise 404 error when category isn't directory.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - with self.assertRaises(Http404): - utils.get_category("test.md") - - -class TestGetCategories(TestCase): - def test_get_categories(self): - """Check does this return test guides categories.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_categories() - - self.assertEqual(result, {"category": {"name": "My Category", "description": "My Description"}}) - - -class TestGetGuides(TestCase): - def test_get_all_root_guides(self): - """Check does this return all root level testing guides.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_guides() - - 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()) - - self.assertIn(case, result) - self.assertEqual(md.Meta, result[case]) - - def test_get_all_category_guides(self): - """Check does this return all category testing guides.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_guides("category") - - md = Markdown(extensions=['meta']) - with open(os.path.join(BASE_PATH, "category", "test3.md")) as f: - md.convert(f.read()) - - self.assertIn("test3", result) - self.assertEqual(md.Meta, result["test3"]) - - -class TestGetGuide(TestCase): - def test_get_root_guide_success(self): - """Check does this return guide HTML and metadata when root guide exist.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_guide("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()) - - self.assertEqual(result, {"guide": html, "metadata": md.Meta}) - - def test_get_root_guide_dont_exist(self): - """Check does this raise Http404 when root guide don't exist.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - with self.assertRaises(Http404): - result = utils.get_guide("invalid", None) - - def test_get_category_guide_success(self): - """Check does this return guide HTML and metadata when category guide exist.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - result = utils.get_guide("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()) - - self.assertEqual(result, {"guide": html, "metadata": md.Meta}) - - def test_get_category_guide_dont_exist(self): - """Check does this raise Http404 when category guide don't exist.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - with self.assertRaises(Http404): - result = utils.get_guide("invalid", "category") - - def test_get_category_guide_category_dont_exist(self): - """Check does this raise Http404 when category don't exist.""" - with patch("pydis_site.apps.guides.utils._get_base_path", return_value=BASE_PATH): - with self.assertRaises(Http404): - result = utils.get_guide("some-guide", "invalid") diff --git a/pydis_site/apps/guides/tests/test_views.py b/pydis_site/apps/guides/tests/test_views.py deleted file mode 100644 index e3945136..00000000 --- a/pydis_site/apps/guides/tests/test_views.py +++ /dev/null @@ -1,104 +0,0 @@ -from unittest.mock import patch - -from django.http import Http404 -from django.test import TestCase -from django_hosts.resolvers import reverse - - -class TestGuidesIndexView(TestCase): - @patch("pydis_site.apps.guides.views.guides.get_guides") - @patch("pydis_site.apps.guides.views.guides.get_categories") - def test_guides_index_return_200(self, get_categories_mock, get_guides_mock): - """Check that guides index return HTTP code 200.""" - get_categories_mock.return_value = {} - get_guides_mock.return_value = {} - - url = reverse('guide:guides') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - get_guides_mock.assert_called_once() - get_categories_mock.assert_called_once() - - -class TestGuideView(TestCase): - @patch("pydis_site.apps.guides.views.guide.os.path.getmtime") - @patch("pydis_site.apps.guides.views.guide.get_guide") - @patch("pydis_site.apps.guides.views.guide.get_category") - def test_guide_return_code_200(self, get_category_mock, get_guide_mock, get_time_mock): - get_guide_mock.return_value = {"guide": "test", "metadata": {}} - - url = reverse("guide:guide", args=["test-guide"]) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - get_category_mock.assert_not_called() - get_guide_mock.assert_called_once_with("test-guide", None) - - @patch("pydis_site.apps.guides.views.guide.os.path.getmtime") - @patch("pydis_site.apps.guides.views.guide.get_guide") - @patch("pydis_site.apps.guides.views.guide.get_category") - def test_guide_return_404(self, get_category_mock, get_guide_mock, get_time_mock): - """Check that return code is 404 when invalid guide provided.""" - get_guide_mock.side_effect = Http404("Guide not found.") - - url = reverse("guide:guide", args=["invalid-guide"]) - response = self.client.get(url) - self.assertEqual(response.status_code, 404) - get_guide_mock.assert_called_once_with("invalid-guide", None) - get_category_mock.assert_not_called() - - -class TestCategoryView(TestCase): - @patch("pydis_site.apps.guides.views.category.get_category") - @patch("pydis_site.apps.guides.views.category.get_guides") - def test_valid_category_code_200(self, get_guides_mock, get_category_mock): - """Check that return code is 200 when visiting valid category.""" - get_category_mock.return_value = {"name": "test", "description": "test"} - get_guides_mock.return_value = {} - - url = reverse("guide:category", args=["category"]) - response = self.client.get(url) - - self.assertEqual(response.status_code, 200) - get_guides_mock.assert_called_once_with("category") - get_category_mock.assert_called_once_with("category") - - @patch("pydis_site.apps.guides.views.category.get_category") - @patch("pydis_site.apps.guides.views.category.get_guides") - def test_invalid_category_code_404(self, get_guides_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("guide:category", args=["invalid-category"]) - response = self.client.get(url) - - self.assertEqual(response.status_code, 404) - get_category_mock.assert_called_once_with("invalid-category") - get_guides_mock.assert_not_called() - - -class TestCategoryGuidesView(TestCase): - @patch("pydis_site.apps.guides.views.guide.os.path.getmtime") - @patch("pydis_site.apps.guides.views.guide.get_guide") - @patch("pydis_site.apps.guides.views.guide.get_category") - def test_valid_category_guide_code_200(self, get_category_mock, get_guide_mock, get_time_mock): - """Check that return code is 200 when visiting valid category article.""" - get_guide_mock.return_value = {"guide": "test", "metadata": {}} - - url = reverse("guide:category_guide", args=["category", "test3"]) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - get_guide_mock.assert_called_once_with("test3", "category") - get_category_mock.assert_called_once_with("category") - - @patch("pydis_site.apps.guides.views.guide.os.path.getmtime") - @patch("pydis_site.apps.guides.views.guide.get_guide") - @patch("pydis_site.apps.guides.views.guide.get_category") - def test_invalid_category_guide_code_404(self, get_category_mock, get_guide_mock, get_time_mock): - """Check that return code is 200 when trying to visit invalid category article.""" - get_guide_mock.side_effect = Http404("Guide not found.") - - url = reverse("guide:category_guide", args=["category", "invalid"]) - response = self.client.get(url) - self.assertEqual(response.status_code, 404) - get_guide_mock.assert_called_once_with("invalid", "category") - get_category_mock.assert_not_called() diff --git a/pydis_site/apps/guides/urls.py b/pydis_site/apps/guides/urls.py deleted file mode 100644 index 69641638..00000000 --- a/pydis_site/apps/guides/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "guides" -urlpatterns = [ - path("", views.GuidesView.as_view(), name='guides'), - path("category//", views.CategoryView.as_view(), name='category'), - path("category///", views.GuideView.as_view(), name='category_guide'), - path("/", views.GuideView.as_view(), name='guide') -] diff --git a/pydis_site/apps/guides/utils.py b/pydis_site/apps/guides/utils.py deleted file mode 100644 index c6f668f7..00000000 --- a/pydis_site/apps/guides/utils.py +++ /dev/null @@ -1,77 +0,0 @@ -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", "guides", "resources", "guides") - - -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_guides(category: Optional[str] = None) -> Dict[str, Dict]: - """Get all root guides when category is not specified. Otherwise get all this category guides.""" - if category is None: - base_dir = _get_base_path() - else: - base_dir = os.path.join(_get_base_path(), category) - - guides = {} - - 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()) - - guides[os.path.splitext(filename)[0]] = md.Meta - - return guides - - -def get_guide(guide: str, category: Optional[str]) -> Dict[str, Union[str, Dict]]: - """Get one specific guide. 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.") - - guide_path = os.path.join(base_path, f"{guide}.md") - if not os.path.exists(guide_path) or not os.path.isfile(guide_path): - raise Http404("Guide not found.") - - md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - - with open(guide_path) as f: - html = md.convert(f.read()) - - return {"guide": html, "metadata": md.Meta} diff --git a/pydis_site/apps/guides/views/__init__.py b/pydis_site/apps/guides/views/__init__.py deleted file mode 100644 index 17a244c1..00000000 --- a/pydis_site/apps/guides/views/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .category import CategoryView -from .guide import GuideView -from .guides import GuidesView - -__all__ = ["GuideView", "GuidesView", "CategoryView"] diff --git a/pydis_site/apps/guides/views/category.py b/pydis_site/apps/guides/views/category.py deleted file mode 100644 index 33e8c97b..00000000 --- a/pydis_site/apps/guides/views/category.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse -from django.shortcuts import render -from django.views import View - -from pydis_site.apps.guides.utils import get_category, get_guides - - -class CategoryView(View): - """Handles guides category page.""" - - def get(self, request: WSGIRequest, category: str) -> HttpResponse: - """Handles page that displays category guides.""" - return render( - request, - "guides/category.html", - {"category_info": get_category(category), "guides": get_guides(category), "category_name": category} - ) diff --git a/pydis_site/apps/guides/views/guide.py b/pydis_site/apps/guides/views/guide.py deleted file mode 100644 index bcd68bc4..00000000 --- a/pydis_site/apps/guides/views/guide.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from datetime import datetime -from typing import Optional - -from django.conf import settings -from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse -from django.shortcuts import render -from django.views import View - -from pydis_site.apps.guides.utils import get_category, get_guide - - -class GuideView(View): - """Shows specific guide page.""" - - def get(self, request: WSGIRequest, guide: str, category: Optional[str] = None) -> HttpResponse: - """Collect guide content and display it. When guide don't exist, return 404.""" - guide_result = get_guide(guide, category) - - if category is not None: - path = os.path.join( - settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", category, f"{guide}.md" - ) - else: - path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", f"{guide}.md") - - if category is not None: - category_data = get_category(category) - category_data["raw_name"] = category - else: - category_data = {"name": None, "raw_name": None} - - return render( - request, - "guides/guide.html", - { - "guide": guide_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( - guide_result["metadata"].get("relevantlinks", []), - guide_result["metadata"].get("relevantlinkvalues", []) - ) - } - } - ) diff --git a/pydis_site/apps/guides/views/guides.py b/pydis_site/apps/guides/views/guides.py deleted file mode 100644 index bb8b565e..00000000 --- a/pydis_site/apps/guides/views/guides.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse -from django.shortcuts import render -from django.views import View - -from pydis_site.apps.guides.utils import get_categories, get_guides - - -class GuidesView(View): - """Shows all guides and categories.""" - - def get(self, request: WSGIRequest) -> HttpResponse: - """Shows all guides and categories.""" - return render(request, "guides/guides.html", {"guides": get_guides(), "categories": get_categories()}) diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 06d62352..e49fd4e0 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -39,5 +39,5 @@ urlpatterns = [ path('admin/', admin.site.urls), path('notifications/', include('django_nyt.urls')), - path('guides/', include('pydis_site.apps.guides.urls', namespace='guide')), + path('content/', include('pydis_site.apps.content.urls', namespace='content')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 6aff0fc1..aaac3bfe 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -91,7 +91,7 @@ INSTALLED_APPS = [ 'pydis_site.apps.api', 'pydis_site.apps.home', 'pydis_site.apps.staff', - 'pydis_site.apps.guides', + 'pydis_site.apps.content', 'django.contrib.admin', 'django.contrib.auth', diff --git a/pydis_site/static/css/content/articles.css b/pydis_site/static/css/content/articles.css new file mode 100644 index 00000000..fa7a0ba5 --- /dev/null +++ b/pydis_site/static/css/content/articles.css @@ -0,0 +1,7 @@ +.breadcrumb-section { + padding: 1rem; +} + +i.has-icon-padding { + padding: 0 10px 25px 0; +} diff --git a/pydis_site/static/css/guides/guide.css b/pydis_site/static/css/guides/guide.css deleted file mode 100644 index fa7a0ba5..00000000 --- a/pydis_site/static/css/guides/guide.css +++ /dev/null @@ -1,7 +0,0 @@ -.breadcrumb-section { - padding: 1rem; -} - -i.has-icon-padding { - padding: 0 10px 25px 0; -} diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html new file mode 100644 index 00000000..de6cd28d --- /dev/null +++ b/pydis_site/templates/content/article.html @@ -0,0 +1,61 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}{{ metadata.title|first }}{% endblock %} +{% block head %} + + + + + + + +{% endblock %} + +{% block content %} + {% include "base/navbar.html" %} + + + +
+
+
+

{{ article.metadata.title|first }}

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

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

+
+
+ {% if relevant_links|length > 0 %} +
+ + +
+ {% endif %} +
+
+
+
+
+ +{% endblock %} diff --git a/pydis_site/templates/content/articles.html b/pydis_site/templates/content/articles.html new file mode 100644 index 00000000..6fea66e5 --- /dev/null +++ b/pydis_site/templates/content/articles.html @@ -0,0 +1,53 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}Guides{% endblock %} +{% block head %} + +{% endblock %} + +{% block content %} + {% include "base/navbar.html" %} + + + +
+
+
+

Articles

+ {% for article, data in content.items %} +
+ + + + + {{ data.title.0 }} + +

{{ data.shortdescription.0 }}

+
+ {% endfor %} + {% for category, data in categories.items %} +
+ + + + + + + {{ data.name }} + +

{{ data.description }}

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/pydis_site/templates/content/category.html b/pydis_site/templates/content/category.html new file mode 100644 index 00000000..61e20c43 --- /dev/null +++ b/pydis_site/templates/content/category.html @@ -0,0 +1,44 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}{{ category_info.name }}{% endblock %} +{% block head %} + + + + +{% endblock %} + +{% block content %} + {% include "base/navbar.html" %} + + + +
+
+
+

{{ category_info.name }}

+ {% for article, data in content.items %} +
+ + + + + {{ data.title.0 }} + +

{{ data.shortdescription.0 }}

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/pydis_site/templates/guides/category.html b/pydis_site/templates/guides/category.html deleted file mode 100644 index f3a8c2ce..00000000 --- a/pydis_site/templates/guides/category.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends 'base/base.html' %} -{% load static %} - -{% block title %}{{ category_info.name }}{% endblock %} -{% block head %} - - - - -{% endblock %} - -{% block content %} - {% include "base/navbar.html" %} - - - -
-
-
-

{{ category_info.name }}

- {% for guide, data in guides.items %} -
- - - - - {{ data.title.0 }} - -

{{ data.shortdescription.0 }}

-
- {% endfor %} -
-
-
-{% endblock %} diff --git a/pydis_site/templates/guides/guide.html b/pydis_site/templates/guides/guide.html deleted file mode 100644 index 97fc8262..00000000 --- a/pydis_site/templates/guides/guide.html +++ /dev/null @@ -1,61 +0,0 @@ -{% extends 'base/base.html' %} -{% load static %} - -{% block title %}{{ metadata.title|first }}{% endblock %} -{% block head %} - - - - - - - -{% endblock %} - -{% block content %} - {% include "base/navbar.html" %} - - - -
-
-
-

{{ guide.metadata.title|first }}

-
-
- {{ guide.guide|safe }} -

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

-
-
- {% if relevant_links|length > 0 %} -
- - -
- {% endif %} -
-
-
-
-
- -{% endblock %} diff --git a/pydis_site/templates/guides/guides.html b/pydis_site/templates/guides/guides.html deleted file mode 100644 index 0e6f2073..00000000 --- a/pydis_site/templates/guides/guides.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends 'base/base.html' %} -{% load static %} - -{% block title %}Guides{% endblock %} -{% block head %} - -{% endblock %} - -{% block content %} - {% include "base/navbar.html" %} - - - -
-
-
-

Guides

- {% for guide, data in guides.items %} -
- - - - - {{ data.title.0 }} - -

{{ data.shortdescription.0 }}

-
- {% endfor %} - {% for category, data in categories.items %} -
- - - - - - - {{ data.name }} - -

{{ data.description }}

-
- {% endfor %} -
-
-
-{% endblock %} -- cgit v1.2.3 From 70e97cb3ad7ffa7596d5976ff405e733d611dc5e Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 6 Oct 2020 17:28:31 +0300 Subject: Add repository configuration to settings --- pydis_site/settings.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pydis_site/settings.py') diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 01807dc5..830ca889 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -315,3 +315,8 @@ ACCOUNT_USERNAME_VALIDATORS = "pydis_site.VALIDATORS" LOGIN_REDIRECT_URL = "home" SOCIALACCOUNT_ADAPTER = "pydis_site.utils.account.SocialAccountAdapter" + +# Information about site repository +SITE_REPOSITORY_OWNER = "python-discord" +SITE_REPOSITORY_NAME = "site" +SITE_REPOSITORY_BRANCH = "master" -- cgit v1.2.3 From 124f6391575a896fb0c2a25f3ad8d8f29c6767b3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 6 Oct 2020 17:37:11 +0300 Subject: Move repository configuration to environment variables --- pydis_site/settings.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'pydis_site/settings.py') diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 830ca889..5d8353fe 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -23,7 +23,10 @@ from pydis_site.constants import GIT_SHA env = environ.Env( DEBUG=(bool, False), - SITE_SENTRY_DSN=(str, "") + SITE_SENTRY_DSN=(str, ""), + SITE_REPOSITORY_OWNER=(str, "python-discord"), + SITE_REPOSITORY_NAME=(str, "site"), + SITE_REPOSITORY_BRANCH=(str, "master") ) sentry_sdk.init( @@ -317,6 +320,6 @@ LOGIN_REDIRECT_URL = "home" SOCIALACCOUNT_ADAPTER = "pydis_site.utils.account.SocialAccountAdapter" # Information about site repository -SITE_REPOSITORY_OWNER = "python-discord" -SITE_REPOSITORY_NAME = "site" -SITE_REPOSITORY_BRANCH = "master" +SITE_REPOSITORY_OWNER = env("SITE_REPOSITORY_OWNER") +SITE_REPOSITORY_NAME = env("SITE_REPOSITORY_NAME") +SITE_REPOSITORY_BRANCH = env("SITE_REPOSITORY_BRANCH") -- cgit v1.2.3 From a750cfdd35724c56e32db0a312798265ababa879 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 30 Oct 2020 19:00:33 +0200 Subject: Use just plain strings for site repo information --- pydis_site/settings.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'pydis_site/settings.py') diff --git a/pydis_site/settings.py b/pydis_site/settings.py index c5937bb6..47d3aa24 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -22,10 +22,7 @@ from pydis_site.constants import GIT_SHA env = environ.Env( DEBUG=(bool, False), - SITE_SENTRY_DSN=(str, ""), - SITE_REPOSITORY_OWNER=(str, "python-discord"), - SITE_REPOSITORY_NAME=(str, "site"), - SITE_REPOSITORY_BRANCH=(str, "master") + SITE_SENTRY_DSN=(str, "") ) sentry_sdk.init( @@ -281,6 +278,6 @@ BULMA_SETTINGS = { } # Information about site repository -SITE_REPOSITORY_OWNER = env("SITE_REPOSITORY_OWNER") -SITE_REPOSITORY_NAME = env("SITE_REPOSITORY_NAME") -SITE_REPOSITORY_BRANCH = env("SITE_REPOSITORY_BRANCH") +SITE_REPOSITORY_OWNER = "python-discord" +SITE_REPOSITORY_NAME = "site" +SITE_REPOSITORY_BRANCH = "master" -- cgit v1.2.3 From 3d83876a30d620fdede01c9fb2b79d2941004bb9 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 22 Nov 2020 15:51:21 +0200 Subject: Add articles path to settings --- pydis_site/settings.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pydis_site/settings.py') diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 47d3aa24..20707570 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ import os import secrets import sys +from pathlib import Path import environ import sentry_sdk @@ -281,3 +282,5 @@ BULMA_SETTINGS = { SITE_REPOSITORY_OWNER = "python-discord" SITE_REPOSITORY_NAME = "site" SITE_REPOSITORY_BRANCH = "master" + +ARTICLES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources", "content") -- 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/settings.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/settings.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 @@