From 4739b4b72fa2a5563258819c0fda74277cb673c3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:48:55 +0300 Subject: Create base Guides app --- pydis_site/apps/guides/__init__.py | 0 pydis_site/apps/guides/apps.py | 7 +++++++ pydis_site/apps/guides/migrations/__init__.py | 0 3 files changed, 7 insertions(+) create mode 100644 pydis_site/apps/guides/__init__.py create mode 100644 pydis_site/apps/guides/apps.py create mode 100644 pydis_site/apps/guides/migrations/__init__.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/__init__.py b/pydis_site/apps/guides/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/guides/apps.py b/pydis_site/apps/guides/apps.py new file mode 100644 index 00000000..8dfa4f65 --- /dev/null +++ b/pydis_site/apps/guides/apps.py @@ -0,0 +1,7 @@ +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 new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 75f8295e79cd8287f46e80517641ccacbf49499c Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:51:26 +0300 Subject: Create all guides (+ categories) displaying view --- pydis_site/apps/guides/views/guides.py | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pydis_site/apps/guides/views/guides.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/guides.py b/pydis_site/apps/guides/views/guides.py new file mode 100644 index 00000000..f457adc1 --- /dev/null +++ b/pydis_site/apps/guides/views/guides.py @@ -0,0 +1,38 @@ +import os + +import yaml +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 markdown import Markdown + + +class GuidesView(View): + """Shows all guides and categories.""" + + def get(self, request: WSGIRequest) -> HttpResponse: + """Shows all guides and categories.""" + guides = {} + categories = {} + + guides_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") + for name in os.listdir(guides_path): + full_path = os.path.join(guides_path, name) + if os.path.isdir(full_path): + with open(os.path.join(full_path, "_info.yml")) as f: + category = yaml.load(f.read()) + + categories[name] = {"name": category["name"], "description": category["description"]} + elif os.path.isfile(full_path) and name.endswith(".md"): + md = Markdown(extensions=['meta']) + with open(full_path) as f: + md.convert(f.read()) + + guides[os.path.splitext(name)[0]] = { + "name": md.Meta["title"], + "short_description": md.Meta["shortdescription"] + } + + return render(request, "guides/guides.html", {"guides": guides, "categories": categories}) -- cgit v1.2.3 From 8cdbd50d1bea7497cb1d9d573dabac828096c69b Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:52:58 +0300 Subject: Create view for displaying single guide --- pydis_site/apps/guides/views/guide.py | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 pydis_site/apps/guides/views/guide.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/guide.py b/pydis_site/apps/guides/views/guide.py new file mode 100644 index 00000000..b8b697e3 --- /dev/null +++ b/pydis_site/apps/guides/views/guide.py @@ -0,0 +1,56 @@ +import os +from datetime import datetime +from typing import Optional + +import yaml +from django.conf import settings +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpResponse, Http404 +from django.shortcuts import render +from django.views import View +from markdown import Markdown + + +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.""" + if category is None: + path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", f"{guide}.md") + category_name = None + else: + dir_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", category) + path = os.path.join(dir_path, f"{guide}.md") + with open(os.path.join(dir_path, "_info.yml")) as f: + category_name = yaml.load(f.read())["name"] + + if not os.path.exists(path) or not os.path.isfile(path): + raise Http404(f"Guide not found {path}") + + md = Markdown(extensions=['meta', 'attr_list']) + with open(path) as f: + html = md.convert(f.read()) + f.close() + + category_data = { + "title": category_name, + "name": category, + } + + return render( + request, + "guides/guide.html", + { + "guide": html, + "metadata": md.Meta, + "last_modified": datetime.fromtimestamp(os.path.getmtime(path)).strftime("%dth %B %Y"), + "relevant_links": { + link: value for link, value in zip( + md.Meta.get("relevantlinks", []), + md.Meta.get("relevantlinkvalues", []) + ) + }, + "category_data": category_data, + } + ) -- cgit v1.2.3 From 2de631161ea23bec5d403fdeeea41a7be8fde769 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:53:23 +0300 Subject: Create view for displaying guide categories --- pydis_site/apps/guides/views/category.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pydis_site/apps/guides/views/category.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/category.py b/pydis_site/apps/guides/views/category.py new file mode 100644 index 00000000..e51b3fed --- /dev/null +++ b/pydis_site/apps/guides/views/category.py @@ -0,0 +1,41 @@ +import os + +import yaml +from django.conf import settings +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpResponse, Http404 +from django.shortcuts import render +from django.views import View +from markdown import Markdown + + +class CategoryView(View): + """Handles guides category page.""" + + def get(self, request: WSGIRequest, category: str) -> HttpResponse: + """Handles page that displays category guides.""" + path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", 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: + category_info = yaml.load(f.read()) + + guides = {} + + for filename in os.listdir(path): + if filename.endswith(".md"): + md = Markdown(extensions=["meta"]) + with open(os.path.join(path, filename)) as f: + md.convert(f.read()) + + guides[os.path.splitext(filename)[0]] = { + "title": md.Meta["title"], + "short_description": md.Meta["shortdescription"] + } + + return render( + request, + "guides/category.html", + {"category_info": category_info, "guides": guides, "category_name": category} + ) -- cgit v1.2.3 From 400d773575463d587489b40275ee92069a6c309c Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:53:44 +0300 Subject: Add __init__.py to guides app views directory --- pydis_site/apps/guides/views/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 pydis_site/apps/guides/views/__init__.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/__init__.py b/pydis_site/apps/guides/views/__init__.py new file mode 100644 index 00000000..17a244c1 --- /dev/null +++ b/pydis_site/apps/guides/views/__init__.py @@ -0,0 +1,5 @@ +from .category import CategoryView +from .guide import GuideView +from .guides import GuidesView + +__all__ = ["GuideView", "GuidesView", "CategoryView"] -- cgit v1.2.3 From 672381ae267f1576abb9a267aaa9579c8c301fb0 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:54:43 +0300 Subject: Define guides app URLs --- pydis_site/apps/guides/urls.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 pydis_site/apps/guides/urls.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/urls.py b/pydis_site/apps/guides/urls.py new file mode 100644 index 00000000..69641638 --- /dev/null +++ b/pydis_site/apps/guides/urls.py @@ -0,0 +1,11 @@ +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') +] -- cgit v1.2.3 From c3aec02b78490bd9ab16e6774ff1adee587f1e48 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:55:05 +0300 Subject: Add guides app URLs to home app URLs --- pydis_site/apps/home/urls.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 61e87a39..06d62352 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -38,4 +38,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('notifications/', include('django_nyt.urls')), + + path('guides/', include('pydis_site.apps.guides.urls', namespace='guide')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -- cgit v1.2.3 From 1b11ad2fd181edd87a29834544112b02a884b04f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 11:40:45 +0300 Subject: Add codeblock parsing to Markdown parser for guide --- pydis_site/apps/guides/views/guide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/guide.py b/pydis_site/apps/guides/views/guide.py index b8b697e3..470606fb 100644 --- a/pydis_site/apps/guides/views/guide.py +++ b/pydis_site/apps/guides/views/guide.py @@ -28,7 +28,7 @@ class GuideView(View): if not os.path.exists(path) or not os.path.isfile(path): raise Http404(f"Guide not found {path}") - md = Markdown(extensions=['meta', 'attr_list']) + md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) with open(path) as f: html = md.convert(f.read()) f.close() -- cgit v1.2.3 From 434e37552ec282c0cc055074f5a5164ea13f1244 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 14:04:52 +0300 Subject: Add example guide "How to Write a Guide" --- .../resources/guides/how-to-write-a-guide.md | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md (limited to 'pydis_site/apps') 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 new file mode 100644 index 00000000..58e36d29 --- /dev/null +++ b/pydis_site/apps/guides/resources/guides/how-to-write-a-guide.md @@ -0,0 +1,60 @@ +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. + +## [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. -- cgit v1.2.3 From e2d7eb157bb5dc2909380f30812f11bd47e6056a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 14:05:35 +0300 Subject: Remove unnecessary debug path displaying for 404 on guide view --- pydis_site/apps/guides/views/guide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/guide.py b/pydis_site/apps/guides/views/guide.py index 470606fb..cb30a525 100644 --- a/pydis_site/apps/guides/views/guide.py +++ b/pydis_site/apps/guides/views/guide.py @@ -26,7 +26,7 @@ class GuideView(View): category_name = yaml.load(f.read())["name"] if not os.path.exists(path) or not os.path.isfile(path): - raise Http404(f"Guide not found {path}") + raise Http404(f"Guide not found") md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) with open(path) as f: -- cgit v1.2.3 From f674709a84e24a2ae9f2c32e0f937432b269af50 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 18:14:08 +0300 Subject: Add handling for invalid category for category guide --- pydis_site/apps/guides/views/guide.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/views/guide.py b/pydis_site/apps/guides/views/guide.py index cb30a525..deb1b8c4 100644 --- a/pydis_site/apps/guides/views/guide.py +++ b/pydis_site/apps/guides/views/guide.py @@ -21,6 +21,9 @@ class GuideView(View): category_name = None else: dir_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", category) + if not os.path.exists(dir_path) or not os.path.isdir(dir_path): + raise Http404("Category not found.") + path = os.path.join(dir_path, f"{guide}.md") with open(os.path.join(dir_path, "_info.yml")) as f: category_name = yaml.load(f.read())["name"] -- cgit v1.2.3 From 57d5f7a5404828a7930e92fbb7e9237c3b598e29 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 19:11:45 +0300 Subject: Add test data for guides --- pydis_site/apps/guides/tests/test_guides/category/_info.yml | 2 ++ pydis_site/apps/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 +++++ 4 files changed, 23 insertions(+) create mode 100644 pydis_site/apps/guides/tests/test_guides/category/_info.yml create mode 100644 pydis_site/apps/guides/tests/test_guides/category/test3.md create mode 100644 pydis_site/apps/guides/tests/test_guides/test.md create mode 100644 pydis_site/apps/guides/tests/test_guides/test2.md (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/tests/test_guides/category/_info.yml b/pydis_site/apps/guides/tests/test_guides/category/_info.yml new file mode 100644 index 00000000..8311509d --- /dev/null +++ b/pydis_site/apps/guides/tests/test_guides/category/_info.yml @@ -0,0 +1,2 @@ +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 new file mode 100644 index 00000000..bdde6188 --- /dev/null +++ b/pydis_site/apps/guides/tests/test_guides/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/guides/tests/test_guides/test.md b/pydis_site/apps/guides/tests/test_guides/test.md new file mode 100644 index 00000000..7a917899 --- /dev/null +++ b/pydis_site/apps/guides/tests/test_guides/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/guides/tests/test_guides/test2.md b/pydis_site/apps/guides/tests/test_guides/test2.md new file mode 100644 index 00000000..f0852356 --- /dev/null +++ b/pydis_site/apps/guides/tests/test_guides/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 -- cgit v1.2.3 From 9e852985487fbe287d4ef3a9044e6865044cf706 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 20 Sep 2020 19:13:03 +0300 Subject: Add unit tests for guides app --- pydis_site/apps/guides/tests/__init__.py | 0 pydis_site/apps/guides/tests/test_views.py | 126 +++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 pydis_site/apps/guides/tests/__init__.py create mode 100644 pydis_site/apps/guides/tests/test_views.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/tests/__init__.py b/pydis_site/apps/guides/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/guides/tests/test_views.py b/pydis_site/apps/guides/tests/test_views.py new file mode 100644 index 00000000..a8885aa6 --- /dev/null +++ b/pydis_site/apps/guides/tests/test_views.py @@ -0,0 +1,126 @@ +import os +from unittest.mock import patch + +from django.conf import settings +from django.test import TestCase +from django_hosts.resolvers import reverse + + +class TestGuidesIndexView(TestCase): + def test_guides_index_return_200(self): + """Check that guides index return HTTP code 200.""" + url = reverse('guide:guides') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + +class TestGuideView(TestCase): + def test_guide_return_code_200(self): + """Check that return code is 200 when valid guide provided.""" + test_cases = ( + "test", + "test2", + ) + for case in test_cases: + url = reverse("guide:guide", args=[case]) + join_return_value = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", f"{case}.md" + ) + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.return_value = join_return_value + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + + def test_guide_return_404(self): + """Check that return code is 404 when invalid guide provided.""" + url = reverse("guide:guide", args=["invalid-guide"]) + join_return_value = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "invalid-guide.md" + ) + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.return_value = join_return_value + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + +class TestCategoryView(TestCase): + def test_valid_category_code_200(self): + """Check that return code is 200 when visiting valid category.""" + url = reverse("guide:category", args=["category"]) + base = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "category" + ) + join_return_value = [base, os.path.join(base, "_info.yml")] + + for filename in os.listdir(base): + if filename.endswith(".md"): + join_return_value.append(os.path.join(base, filename)) + + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.side_effect = join_return_value + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + + def test_invalid_category_code_404(self): + """Check that return code is 404 when trying to visit invalid category.""" + url = reverse("guide:category", args=["invalid-category"]) + join_return_value = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "invalid-category" + ) + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.return_value = join_return_value + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + +class TestCategoryGuidesView(TestCase): + def test_valid_category_guide_code_200(self): + """Check that return code is 200 when visiting valid category article.""" + url = reverse("guide:category_guide", args=["category", "test3"]) + category_directory = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "category" + ) + + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.side_effect = ( + category_directory, + os.path.join(category_directory, "test3.md"), + os.path.join(category_directory, "_info.yml") + ) + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + + def test_invalid_category_guide_code_404(self): + """Check that return code is 200 when trying to visit invalid category article.""" + url = reverse("guide:category_guide", args=["category", "invalid"]) + category_directory = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "category" + ) + + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.side_effect = ( + category_directory, + os.path.join(category_directory, "invalid.md"), + os.path.join(category_directory, "_info.yml") + ) + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + def test_invalid_category_code_404(self): + """Check that response code is 404 when provided category for article is incorrect.""" + url = reverse("guide:category_guide", args=["invalid", "guide"]) + category_directory = os.path.join( + settings.BASE_DIR, "pydis_site", "apps", "guides", "tests", "test_guides", "invalid" + ) + + with patch("pydis_site.apps.guides.views.guide.os.path.join") as p: + p.return_value = category_directory + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) -- cgit v1.2.3 From 27315dacdc76e07ee910e31fe65c9357de34bcaf Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 21 Sep 2020 16:10:56 +0300 Subject: Move guides and categories getting logic to utils for better testability --- pydis_site/apps/guides/utils.py | 71 +++++++++++++++++++++++++++++++ pydis_site/apps/guides/views/category.py | 31 ++------------ pydis_site/apps/guides/views/guide.py | 51 +++++++++------------- pydis_site/apps/guides/views/guides.py | 30 ++----------- pydis_site/templates/guides/category.html | 2 +- pydis_site/templates/guides/guide.html | 16 +++---- pydis_site/templates/guides/guides.html | 4 +- 7 files changed, 109 insertions(+), 96 deletions(-) create mode 100644 pydis_site/apps/guides/utils.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/guides/utils.py b/pydis_site/apps/guides/utils.py new file mode 100644 index 00000000..c7d03dc3 --- /dev/null +++ b/pydis_site/apps/guides/utils.py @@ -0,0 +1,71 @@ +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_category(category: str) -> Dict[str, str]: + """Load category information by name from _info.yml.""" + path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", 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.load(f.read()) + + +def get_categories() -> Dict[str, Dict]: + """Get all categories information.""" + base_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") + 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 = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") + else: + base_dir = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", category) + + guides = {} + + for filename in os.listdir(base_dir): + if os.path.isfile(os.path.join(base_dir, filename)) and filename.endswith(".md"): + md = Markdown(extensions=['meta']) + with open(os.path.join(base_dir, filename)) 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 = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") + else: + base_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", 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/category.py b/pydis_site/apps/guides/views/category.py index e51b3fed..33e8c97b 100644 --- a/pydis_site/apps/guides/views/category.py +++ b/pydis_site/apps/guides/views/category.py @@ -1,12 +1,9 @@ -import os - -import yaml -from django.conf import settings from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse, Http404 +from django.http import HttpResponse from django.shortcuts import render from django.views import View -from markdown import Markdown + +from pydis_site.apps.guides.utils import get_category, get_guides class CategoryView(View): @@ -14,28 +11,8 @@ class CategoryView(View): def get(self, request: WSGIRequest, category: str) -> HttpResponse: """Handles page that displays category guides.""" - path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", 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: - category_info = yaml.load(f.read()) - - guides = {} - - for filename in os.listdir(path): - if filename.endswith(".md"): - md = Markdown(extensions=["meta"]) - with open(os.path.join(path, filename)) as f: - md.convert(f.read()) - - guides[os.path.splitext(filename)[0]] = { - "title": md.Meta["title"], - "short_description": md.Meta["shortdescription"] - } - return render( request, "guides/category.html", - {"category_info": category_info, "guides": guides, "category_name": category} + {"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 index deb1b8c4..bcd68bc4 100644 --- a/pydis_site/apps/guides/views/guide.py +++ b/pydis_site/apps/guides/views/guide.py @@ -2,13 +2,13 @@ import os from datetime import datetime from typing import Optional -import yaml from django.conf import settings from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpResponse, Http404 +from django.http import HttpResponse from django.shortcuts import render from django.views import View -from markdown import Markdown + +from pydis_site.apps.guides.utils import get_category, get_guide class GuideView(View): @@ -16,44 +16,33 @@ class GuideView(View): 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.""" - if category is None: - path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", f"{guide}.md") - category_name = None - else: - dir_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides", category) - if not os.path.exists(dir_path) or not os.path.isdir(dir_path): - raise Http404("Category not found.") - - path = os.path.join(dir_path, f"{guide}.md") - with open(os.path.join(dir_path, "_info.yml")) as f: - category_name = yaml.load(f.read())["name"] - - if not os.path.exists(path) or not os.path.isfile(path): - raise Http404(f"Guide not found") + guide_result = get_guide(guide, category) - md = Markdown(extensions=['meta', 'attr_list', 'fenced_code']) - with open(path) as f: - html = md.convert(f.read()) - f.close() + 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") - category_data = { - "title": category_name, - "name": category, - } + 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": html, - "metadata": md.Meta, + "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( - md.Meta.get("relevantlinks", []), - md.Meta.get("relevantlinkvalues", []) + guide_result["metadata"].get("relevantlinks", []), + guide_result["metadata"].get("relevantlinkvalues", []) ) - }, - "category_data": category_data, + } } ) diff --git a/pydis_site/apps/guides/views/guides.py b/pydis_site/apps/guides/views/guides.py index f457adc1..bb8b565e 100644 --- a/pydis_site/apps/guides/views/guides.py +++ b/pydis_site/apps/guides/views/guides.py @@ -1,12 +1,9 @@ -import os - -import yaml -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 markdown import Markdown + +from pydis_site.apps.guides.utils import get_categories, get_guides class GuidesView(View): @@ -14,25 +11,4 @@ class GuidesView(View): def get(self, request: WSGIRequest) -> HttpResponse: """Shows all guides and categories.""" - guides = {} - categories = {} - - guides_path = os.path.join(settings.BASE_DIR, "pydis_site", "apps", "guides", "resources", "guides") - for name in os.listdir(guides_path): - full_path = os.path.join(guides_path, name) - if os.path.isdir(full_path): - with open(os.path.join(full_path, "_info.yml")) as f: - category = yaml.load(f.read()) - - categories[name] = {"name": category["name"], "description": category["description"]} - elif os.path.isfile(full_path) and name.endswith(".md"): - md = Markdown(extensions=['meta']) - with open(full_path) as f: - md.convert(f.read()) - - guides[os.path.splitext(name)[0]] = { - "name": md.Meta["title"], - "short_description": md.Meta["shortdescription"] - } - - return render(request, "guides/guides.html", {"guides": guides, "categories": categories}) + return render(request, "guides/guides.html", {"guides": get_guides(), "categories": get_categories()}) diff --git a/pydis_site/templates/guides/category.html b/pydis_site/templates/guides/category.html index f70a668f..b5cd9ce0 100644 --- a/pydis_site/templates/guides/category.html +++ b/pydis_site/templates/guides/category.html @@ -32,7 +32,7 @@ {{ data.title.0 }} -

{{ data.short_description.0 }}

+

{{ data.shortdescription.0 }}

{% endfor %} diff --git a/pydis_site/templates/guides/guide.html b/pydis_site/templates/guides/guide.html index 8f841c53..bab82415 100644 --- a/pydis_site/templates/guides/guide.html +++ b/pydis_site/templates/guides/guide.html @@ -4,9 +4,9 @@ {% block title %}{{ metadata.title|first }}{% endblock %} {% block head %} - + - + @@ -21,10 +21,10 @@ @@ -33,13 +33,13 @@
-

{{ metadata.title|first }}

+

{{ guide.metadata.title|first }}

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

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

diff --git a/pydis_site/templates/guides/guides.html b/pydis_site/templates/guides/guides.html index 66878048..0e6f2073 100644 --- a/pydis_site/templates/guides/guides.html +++ b/pydis_site/templates/guides/guides.html @@ -29,9 +29,9 @@ - {{ data.name.0 }} + {{ data.title.0 }} -

{{ data.short_description.0 }}

+

{{ data.shortdescription.0 }}

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

    {{ article.metadata.title|first }}

    +

    {{ article.metadata.title }}

    {{ article.article|safe }}

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

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

    {{ data.shortdescription.0 }}

    +

    {{ data.short_description }}

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

    {{ data.shortdescription.0 }}

    +

    {{ data.short_description }}

    {% endfor %}
    -- cgit v1.2.3 From 483c710595feb69b574a4ae24d94fb14a50dfac6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 16:44:39 +0300 Subject: Remove last modified field from article --- pydis_site/apps/content/views/article.py | 14 -------------- pydis_site/templates/content/article.html | 3 --- 2 files changed, 17 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index ede3ba43..51c9a199 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -1,9 +1,5 @@ -import os -from datetime import datetime -from pathlib import Path 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 @@ -19,15 +15,6 @@ class ArticleView(View): """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 = Path( - settings.BASE_DIR, "pydis_site", "apps", "content", "resources", "content", category, f"{article}.md" - ) - else: - path = Path( - 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 @@ -49,7 +36,6 @@ class ArticleView(View): "content/article.html", { "article": article_result, - "last_modified": datetime.fromtimestamp(os.path.getmtime(path)).strftime("%dth %B %Y"), "category_data": category_data, "relevant_links": relevant_links } diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html index f4282df2..c340cdf6 100644 --- a/pydis_site/templates/content/article.html +++ b/pydis_site/templates/content/article.html @@ -36,9 +36,6 @@
    {{ article.article|safe }} -

    - Last modified: {{ last_modified }}
    -

    {% if relevant_links|length > 0 %} -- cgit v1.2.3 From 48c092bc9ce79e469d7a8ec9242eefe7fdf1ceff Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 18:41:50 +0300 Subject: Implement custom icons for articles --- .../apps/content/resources/content/guides/how-to-write-a-guide.md | 2 ++ pydis_site/templates/content/articles.html | 2 +- pydis_site/templates/content/category.html | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md index 8ea438a2..dc822d14 100644 --- a/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md +++ b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md @@ -1,6 +1,8 @@ --- title: How to Write a Guide short_description: Learn how to write a guide for this website +icon_class: fas +icon: fa-info --- When you are interested about how to write guide for this site (like this), then you can learn about it here. diff --git a/pydis_site/templates/content/articles.html b/pydis_site/templates/content/articles.html index 363bbb4f..35e5db16 100644 --- a/pydis_site/templates/content/articles.html +++ b/pydis_site/templates/content/articles.html @@ -26,7 +26,7 @@ {% for article, data in content.items %}
    - + {{ data.title }} diff --git a/pydis_site/templates/content/category.html b/pydis_site/templates/content/category.html index c2201745..3dec9259 100644 --- a/pydis_site/templates/content/category.html +++ b/pydis_site/templates/content/category.html @@ -30,7 +30,7 @@ {% for article, data in content.items %}
    - + {{ data.title }} -- cgit v1.2.3 From cee633e9dd1454087ed85ea5c38c93608b2647eb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 19:02:38 +0300 Subject: Apply recent changes to article writing guide --- .../content/guides/how-to-write-a-article.md | 78 ++++++++++++++++++++++ .../content/guides/how-to-write-a-guide.md | 66 ------------------ 2 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 pydis_site/apps/content/resources/content/guides/how-to-write-a-article.md delete mode 100644 pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/resources/content/guides/how-to-write-a-article.md b/pydis_site/apps/content/resources/content/guides/how-to-write-a-article.md new file mode 100644 index 00000000..0ad45cc3 --- /dev/null +++ b/pydis_site/apps/content/resources/content/guides/how-to-write-a-article.md @@ -0,0 +1,78 @@ +--- +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. + +## 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/content/guides/how-to-write-a-guide.md b/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md deleted file mode 100644 index dc822d14..00000000 --- a/pydis_site/apps/content/resources/content/guides/how-to-write-a-guide.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: How to Write a Guide -short_description: Learn how to write a guide for this website -icon_class: fas -icon: fa-info ---- - -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 -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 -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 -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 -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 -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. -- cgit v1.2.3 From 2e3eec0998a735a7b9512889c3a7491e4a73ea68 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 5 Oct 2020 19:11:50 +0300 Subject: Fix content app linting --- pydis_site/apps/content/tests/test_utils.py | 4 +++- pydis_site/apps/content/tests/test_views.py | 12 ++++-------- pydis_site/apps/content/urls.py | 6 +++++- pydis_site/apps/content/utils.py | 12 ++++++++++-- pydis_site/apps/content/views/__init__.py | 2 +- pydis_site/apps/content/views/article.py | 9 +++++++-- pydis_site/apps/content/views/articles.py | 8 ++++++-- pydis_site/apps/content/views/category.py | 8 ++++++-- 8 files changed, 42 insertions(+), 19 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py index bba998fe..9c7c4f31 100644 --- a/pydis_site/apps/content/tests/test_utils.py +++ b/pydis_site/apps/content/tests/test_utils.py @@ -47,7 +47,9 @@ class TestGetCategories(TestCase): with patch("pydis_site.apps.content.utils._get_base_path", return_value=BASE_PATH): result = utils.get_categories() - self.assertEqual(result, {"category": {"name": "My Category", "description": "My Description"}}) + self.assertEqual( + result, {"category": {"name": "My Category", "description": "My Description"}} + ) class TestGetArticles(TestCase): diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 98054534..0901c67f 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -21,10 +21,9 @@ class TestGuidesIndexView(TestCase): class TestGuideView(TestCase): - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_guide_return_code_200(self, get_category_mock, get_article_mock, get_time_mock): + def test_guide_return_code_200(self, get_category_mock, get_article_mock): get_article_mock.return_value = {"guide": "test", "metadata": {}} url = reverse("content:article", args=["test-guide"]) @@ -33,10 +32,9 @@ class TestGuideView(TestCase): get_category_mock.assert_not_called() get_article_mock.assert_called_once_with("test-guide", None) - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_guide_return_404(self, get_category_mock, get_article_mock, get_time_mock): + def test_guide_return_404(self, get_category_mock, get_article_mock): """Check that return code is 404 when invalid article provided.""" get_article_mock.side_effect = Http404("Article not found.") @@ -77,10 +75,9 @@ class TestCategoryView(TestCase): class TestCategoryGuidesView(TestCase): - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_valid_category_article_code_200(self, get_category_mock, get_article_mock, get_time_mock): + def test_valid_category_article_code_200(self, get_category_mock, get_article_mock): """Check that return code is 200 when visiting valid category article.""" get_article_mock.return_value = {"guide": "test", "metadata": {}} @@ -90,10 +87,9 @@ class TestCategoryGuidesView(TestCase): get_article_mock.assert_called_once_with("test3", "category") get_category_mock.assert_called_once_with("category") - @patch("pydis_site.apps.content.views.article.os.path.getmtime") @patch("pydis_site.apps.content.views.article.get_article") @patch("pydis_site.apps.content.views.article.get_category") - def test_invalid_category_article_code_404(self, get_category_mock, get_article_mock, get_time_mock): + def test_invalid_category_article_code_404(self, get_category_mock, get_article_mock): """Check that return code is 200 when trying to visit invalid category article.""" get_article_mock.side_effect = Http404("Article not found.") diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index ae9b1e57..5a4ee37a 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -6,6 +6,10 @@ app_name = "content" urlpatterns = [ path("", views.ArticlesView.as_view(), name='content'), path("category//", views.CategoryView.as_view(), name='category'), - path("category///", views.ArticleView.as_view(), name='category_article'), + path( + "category///", + views.ArticleView.as_view(), + name='category_article' + ), path("/", views.ArticleView.as_view(), name='article') ] diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index b2451745..e164c39f 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -35,7 +35,7 @@ def get_categories() -> Dict[str, Dict]: def get_articles(category: Optional[str] = None) -> Dict[str, Dict]: - """Get all root content when category is not specified. Otherwise get all this category content.""" + """Get all root or category articles.""" if category is None: base_dir = _get_base_path() else: @@ -67,7 +67,15 @@ def get_article(article: str, category: Optional[str]) -> Dict[str, Union[str, D html = markdown( article_path.read_text(), - extras=["metadata", "fenced-code-blocks", "header-ids", "strike", "target-blank-links", "tables", "task_list"] + extras=[ + "metadata", + "fenced-code-blocks", + "header-ids", + "strike", + "target-blank-links", + "tables", + "task_list" + ] ) return {"article": str(html), "metadata": html.metadata} diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py index b50d487b..616bc850 100644 --- a/pydis_site/apps/content/views/__init__.py +++ b/pydis_site/apps/content/views/__init__.py @@ -1,5 +1,5 @@ -from .category import CategoryView from .article import ArticleView from .articles import ArticlesView +from .category import CategoryView __all__ = ["ArticleView", "ArticlesView", "CategoryView"] diff --git a/pydis_site/apps/content/views/article.py b/pydis_site/apps/content/views/article.py index 51c9a199..f4c834db 100644 --- a/pydis_site/apps/content/views/article.py +++ b/pydis_site/apps/content/views/article.py @@ -5,13 +5,18 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_category, get_article +from pydis_site.apps.content.utils import get_article, get_category class ArticleView(View): """Shows specific guide page.""" - def get(self, request: WSGIRequest, article: str, category: Optional[str] = None) -> HttpResponse: + def get( + self, + request: WSGIRequest, + article: str, + category: Optional[str] = None + ) -> HttpResponse: """Collect guide content and display it. When guide don't exist, return 404.""" article_result = get_article(article, category) diff --git a/pydis_site/apps/content/views/articles.py b/pydis_site/apps/content/views/articles.py index ff945a19..cce601e1 100644 --- a/pydis_site/apps/content/views/articles.py +++ b/pydis_site/apps/content/views/articles.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_categories, get_articles +from pydis_site.apps.content.utils import get_articles, get_categories class ArticlesView(View): @@ -11,4 +11,8 @@ class ArticlesView(View): def get(self, request: WSGIRequest) -> HttpResponse: """Shows all content and categories.""" - return render(request, "content/articles.html", {"content": get_articles(), "categories": get_categories()}) + return render( + request, + "content/articles.html", + {"content": get_articles(), "categories": get_categories()} + ) diff --git a/pydis_site/apps/content/views/category.py b/pydis_site/apps/content/views/category.py index 62e80a47..9d2a978e 100644 --- a/pydis_site/apps/content/views/category.py +++ b/pydis_site/apps/content/views/category.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.shortcuts import render from django.views import View -from pydis_site.apps.content.utils import get_category, get_articles +from pydis_site.apps.content.utils import get_articles, get_category class CategoryView(View): @@ -14,5 +14,9 @@ class CategoryView(View): return render( request, "content/category.html", - {"category_info": get_category(category), "content": get_articles(category), "category_name": category} + { + "category_info": get_category(category), + "content": get_articles(category), + "category_name": category + } ) -- cgit v1.2.3 From d6a56e63ae64b2e3df40a9a4468289456b2182dc Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 6 Oct 2020 16:18:11 +0300 Subject: Rename content -> articles for visual part --- pydis_site/apps/content/tests/test_views.py | 14 +++++++------- pydis_site/apps/content/urls.py | 2 +- pydis_site/apps/home/urls.py | 6 ++++-- pydis_site/templates/content/article.html | 6 +++--- pydis_site/templates/content/articles.html | 4 ++-- pydis_site/templates/content/category.html | 4 ++-- 6 files changed, 19 insertions(+), 17 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py index 0901c67f..06e6d526 100644 --- a/pydis_site/apps/content/tests/test_views.py +++ b/pydis_site/apps/content/tests/test_views.py @@ -13,7 +13,7 @@ class TestGuidesIndexView(TestCase): get_categories_mock.return_value = {} get_articles_mock.return_value = {} - url = reverse('content:content') + url = reverse('articles:articles') response = self.client.get(url) self.assertEqual(response.status_code, 200) get_articles_mock.assert_called_once() @@ -26,7 +26,7 @@ class TestGuideView(TestCase): def test_guide_return_code_200(self, get_category_mock, get_article_mock): get_article_mock.return_value = {"guide": "test", "metadata": {}} - url = reverse("content:article", args=["test-guide"]) + url = reverse("articles:article", args=["test-guide"]) response = self.client.get(url) self.assertEqual(response.status_code, 200) get_category_mock.assert_not_called() @@ -38,7 +38,7 @@ class TestGuideView(TestCase): """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"]) + url = reverse("articles: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) @@ -53,7 +53,7 @@ class TestCategoryView(TestCase): get_category_mock.return_value = {"name": "test", "description": "test"} get_articles_mock.return_value = {} - url = reverse("content:category", args=["category"]) + url = reverse("articles:category", args=["category"]) response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -66,7 +66,7 @@ class TestCategoryView(TestCase): """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"]) + url = reverse("articles:category", args=["invalid-category"]) response = self.client.get(url) self.assertEqual(response.status_code, 404) @@ -81,7 +81,7 @@ class TestCategoryGuidesView(TestCase): """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"]) + url = reverse("articles: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") @@ -93,7 +93,7 @@ class TestCategoryGuidesView(TestCase): """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"]) + url = reverse("articles: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") diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index 5a4ee37a..cd41751b 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -4,7 +4,7 @@ from . import views app_name = "content" urlpatterns = [ - path("", views.ArticlesView.as_view(), name='content'), + path("", views.ArticlesView.as_view(), name='articles'), path("category//", views.CategoryView.as_view(), name='category'), path( "category///", diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index c7e36156..7e41a1b9 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -1,4 +1,6 @@ from allauth.account.views import LogoutView +from django.conf import settings +from django.conf.urls.static import static from django.contrib import admin from django.contrib.messages import ERROR from django.urls import include, path @@ -33,5 +35,5 @@ urlpatterns = [ path('logout', LogoutView.as_view(), name="logout"), path('admin/', admin.site.urls), - path('content/', include('pydis_site.apps.content.urls', namespace='content')), -] + path('articles/', include('pydis_site.apps.content.urls', namespace='articles')), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/pydis_site/templates/content/article.html b/pydis_site/templates/content/article.html index c340cdf6..5e090050 100644 --- a/pydis_site/templates/content/article.html +++ b/pydis_site/templates/content/article.html @@ -3,7 +3,7 @@ {% block title %}{{ article.metadata.title }}{% endblock %} {% block head %} - + @@ -19,9 +19,9 @@