diff options
author | 2021-03-30 13:26:11 +0300 | |
---|---|---|
committer | 2021-03-30 13:26:11 +0300 | |
commit | 397d143161563fcdfcec32637e8d4d03dd6541f2 (patch) | |
tree | f2d32e6f9b8dae443ab8936cbcd4d03bc78fcaa4 /pydis_site/apps | |
parent | Fix applying description changes to wrong places (diff) | |
parent | Merge pull request #393 from ks129/guides-app (diff) |
Merge branch 'dewikification' into resources-lists
Diffstat (limited to 'pydis_site/apps')
25 files changed, 702 insertions, 0 deletions
diff --git a/pydis_site/apps/content/__init__.py b/pydis_site/apps/content/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pydis_site/apps/content/__init__.py 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 --- /dev/null +++ b/pydis_site/apps/content/migrations/__init__.py diff --git a/pydis_site/apps/content/resources/_info.yml b/pydis_site/apps/content/resources/_info.yml new file mode 100644 index 00000000..583cab18 --- /dev/null +++ b/pydis_site/apps/content/resources/_info.yml @@ -0,0 +1,2 @@ +name: Pages +description: Guides, articles, and pages hosted on the site. diff --git a/pydis_site/apps/content/resources/guides/_info.yml b/pydis_site/apps/content/resources/guides/_info.yml new file mode 100644 index 00000000..59c60a7b --- /dev/null +++ b/pydis_site/apps/content/resources/guides/_info.yml @@ -0,0 +1,2 @@ +name: Guides +description: Made by us, for you. diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml new file mode 100644 index 00000000..7c9a2225 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/pydis-guides/_info.yml @@ -0,0 +1,2 @@ +name: Python Discord Guides +description: Guides related to the Python Discord server and community. diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md new file mode 100644 index 00000000..f258ef74 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/pydis-guides/how-to-contribute-a-page.md @@ -0,0 +1,143 @@ +--- +title: How to Contribute a Page +description: Learn how to write and publish a page to this website. +icon_class: fas +icon: fa-info +relevant_links: + Contributing to Site: https://pythondiscord.com/pages/contributing/site/ + Using Git: https://pythondiscord.com/pages/contributing/working-with-git/ +--- + +Pages, which include guides, articles, and other static content, are stored in markdown files in the `site` repository on Github. +If you are interested in writing or modifying pages seen here on the site, follow the steps below. + +For further assistance and help with contributing pages, send a message to the `#dev-contrib` channel in the Discord server! + +## Prerequisites +Before working on a new page, you have to [setup the site project locally](https://pythondiscord.com/pages/contributing/site/). +It is also a good idea to familiarize yourself with the [git workflow](https://pythondiscord.com/pages/contributing/working-with-git/), as it is part of the contribution workflow. + +Additionally, please submit your proposed page or modification to a page as an [issue in the site repository](https://github.com/python-discord/site/issues), or discuss it in the `#dev-contrib` channel in the server. +As website changes require staff approval, discussing the page content beforehand helps with accelerating the contribution process, and avoids wasted work in the event the proposed page is not accepted. + +## Creating the Page +All pages are located in the `site` repo, at the path `pydis_site/apps/content/resources/`. This is the root folder, which corresponds to the URL `www.pythondiscord.com/pages/`. +For example, the file `pydis_site/apps/content/resources/hello-world.md` will result in a page available at `www.pythondiscord.com/pages/hello-world`. + +Nested folders represent page categories on the website. Each folder under the root folder must include a `_info.yml` file with the following: + +```yml +name: Category name +description: Category description +``` + +All the markdown files in this folder will then be under this category. + +## Writing the Page +Files representing pages are in `.md` (Markdown) format, with all-lowercase filenames and spaces replaced with `-` characters. + +Each page must include required metadata, and optionally additional metadata to modify the appearance of the page. +The metadata is written in YAML, and should be enclosed in triple dashes `---` *at the top of the markdown file*. + +**Example:** +```yaml +--- +title: How to Contribute a Page +description: Learn how to write and publish a page to this website. +icon_class: fas +icon: fa-info +relevant_links: + Contributing to Site: https://pythondiscord.com/pages/contributing/site/ + Using Git: https://pythondiscord.com/pages/contributing/working-with-git/ +--- + +Pages, which include guides, articles, and other static content,... +``` + +### Required Fields +- **title:** Easily-readable title for your article. +- **description:** Short, 1-2 line description of the page's content. + +### Optional Fields +- **icon_class:** Favicon class for the category entry for the page. Default: `fab` +- **icon:** Favicon for the category entry for the page. Default: `fa-python` <i class="fab fa-python is-black" aria-hidden="true"></i> +- **relevant_links:** A YAML dictionary containing `text:link` pairs. See the example above. + +## Extended Markdown + +Apart from standard Markdown, certain additions are available: + +### Abbreviations +HTML `<abbr>` tags can be used in markdown using this format: + +**Markdown:** +```nohighlight +This website is HTML generated from YAML and Markdown. + +*[HTML]: Hyper Text Markup Language +*[YAML]: YAML Ain't Markup Language +``` + +**Output:** + +This website is <abbr title="Hyper Text Markup Language">HTML</abbr> +generated from <abbr title="YAML Ain't Markup Language">YAML</abbr> and Markdown. + +--- + +### Footnotes +**Markdown:** +```nohighlight +This footnote[^1] links to the bottom[^custom_label] of the page[^3]. + +[^1]: Footnote labels start with a caret `^`. +[^3]: The footnote link is numbered based on the order of the labels. +[^custom label]: Footnote labels can contain any text within square brackets. +``` + +**Output:** + +This footnote[^1] links to the bottom[^custom label] of the page[^3]. + +[^1]: Footnote labels start with a caret `^`. +[^3]: The footnote link is numbered based on the order of the labels. +[^custom label]: Footnote labels can contain any text within square brackets. + +--- + +### Tables + +**Markdown:** +```nohighlight +| This is header | This is another header | +| -------------- | ---------------------- | +| An item | Another item | +``` + +**Output:** + +| This is header | This is another header | +| -------------- | ---------------------- | +| An item | Another item | + +--- + +### Codeblock Syntax Highlighting +Syntax highlighting is provided by `highlight.js`. +To activate syntax highlighting, put the language directly after the starting backticks. + +**Markdown:** +````nohighlight +```python +import os + +path = os.path.join("foo", "bar") +``` +```` + +**Output:** +```python +import os + +path = os.path.join("foo", "bar") +``` 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 --- /dev/null +++ b/pydis_site/apps/content/tests/__init__.py diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py new file mode 100644 index 00000000..4e0cca34 --- /dev/null +++ b/pydis_site/apps/content/tests/helpers.py @@ -0,0 +1,84 @@ +from pyfakefs.fake_filesystem_unittest import TestCase + +# Valid markdown content with YAML metadata +MARKDOWN_WITH_METADATA = """ +--- +title: TestTitle +description: TestDescription +relevant_links: + Python Discord: https://pythondiscord.com + Discord: https://discord.com +--- +# This is a header. +""" + +MARKDOWN_WITHOUT_METADATA = """#This is a header.""" + +# Valid YAML in a _info.yml file +CATEGORY_INFO = """ +name: Category Name +description: Description +""" + +# The HTML generated from the above markdown data +PARSED_HTML = ( + '<h1 id="this-is-a-header">This is a header.' + '<a class="headerlink" href="#this-is-a-header" title="Permanent link">¶</a></h1>' +) + +# The YAML metadata parsed from the above markdown data +PARSED_METADATA = { + "title": "TestTitle", "description": "TestDescription", + "relevant_links": { + "Python Discord": "https://pythondiscord.com", + "Discord": "https://discord.com" + } +} + +# The YAML data parsed from the above _info.yml file +PARSED_CATEGORY_INFO = {"name": "Category Name", "description": "Description"} + + +class MockPagesTestCase(TestCase): + """ + TestCase with a fake filesystem for testing. + + Structure: + ├── _info.yml + ├── root.md + ├── root_without_metadata.md + ├── not_a_page.md + ├── tmp + | ├── _info.yml + | └── category_without_info + └── category + ├── _info.yml + ├── with_metadata.md + └── subcategory + ├── with_metadata.md + └── without_metadata.md + """ + + def setUp(self): + """Create the fake filesystem.""" + self.setUpPyfakefs() + + self.fs.create_file("_info.yml", contents=CATEGORY_INFO) + self.fs.create_file("root.md", contents=MARKDOWN_WITH_METADATA) + self.fs.create_file("root_without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA) + self.fs.create_file("not_a_page.md/_info.yml", contents=CATEGORY_INFO) + self.fs.create_file("category/_info.yml", contents=CATEGORY_INFO) + self.fs.create_file("category/with_metadata.md", contents=MARKDOWN_WITH_METADATA) + self.fs.create_file("category/subcategory/_info.yml", contents=CATEGORY_INFO) + self.fs.create_file( + "category/subcategory/with_metadata.md", contents=MARKDOWN_WITH_METADATA + ) + self.fs.create_file( + "category/subcategory/without_metadata.md", contents=MARKDOWN_WITHOUT_METADATA + ) + + # There is always a `tmp` directory in the filesystem, so make it a category + # for testing purposes. + # See: https://jmcgeheeiv.github.io/pyfakefs/release/usage.html#os-temporary-directories + self.fs.create_file("tmp/_info.yml", contents=CATEGORY_INFO) + self.fs.create_dir("tmp/category_without_info") diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py new file mode 100644 index 00000000..58175d6f --- /dev/null +++ b/pydis_site/apps/content/tests/test_utils.py @@ -0,0 +1,91 @@ +from pathlib import Path + +from django.http import Http404 + +from pydis_site.apps.content import utils +from pydis_site.apps.content.tests.helpers import ( + MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA +) + + +class GetCategoryTests(MockPagesTestCase): + """Tests for the get_category function.""" + + def test_get_valid_category(self): + result = utils.get_category(Path("category")) + + self.assertEqual(result, {"name": "Category Name", "description": "Description"}) + + def test_get_nonexistent_category(self): + with self.assertRaises(Http404): + utils.get_category(Path("invalid")) + + def test_get_category_with_path_to_file(self): + # Valid categories are directories, not files + with self.assertRaises(Http404): + utils.get_category(Path("root.md")) + + def test_get_category_without_info_yml(self): + # Categories should provide an _info.yml file + with self.assertRaises(FileNotFoundError): + utils.get_category(Path("tmp/category_without_info")) + + +class GetCategoriesTests(MockPagesTestCase): + """Tests for the get_categories function.""" + + def test_get_root_categories(self): + result = utils.get_categories(Path(".")) + + info = PARSED_CATEGORY_INFO + self.assertEqual(result, {"category": info, "tmp": info, "not_a_page.md": info}) + + def test_get_categories_with_subcategories(self): + result = utils.get_categories(Path("category")) + + self.assertEqual(result, {"subcategory": PARSED_CATEGORY_INFO}) + + def test_get_categories_without_subcategories(self): + result = utils.get_categories(Path("category/subcategory")) + + self.assertEqual(result, {}) + + +class GetCategoryPagesTests(MockPagesTestCase): + """Tests for the get_category_pages function.""" + + def test_get_pages_in_root_category_successfully(self): + """The method should successfully retrieve page metadata.""" + root_category_pages = utils.get_category_pages(Path(".")) + self.assertEqual( + root_category_pages, {"root": PARSED_METADATA, "root_without_metadata": {}} + ) + + def test_get_pages_in_subcategories_successfully(self): + """The method should successfully retrieve page metadata.""" + category_pages = utils.get_category_pages(Path("category")) + + # Page metadata is properly retrieved + self.assertEqual(category_pages, {"with_metadata": PARSED_METADATA}) + + +class GetPageTests(MockPagesTestCase): + """Tests for the get_page function.""" + + def test_get_page(self): + cases = [ + ("Root page with metadata", "root.md", PARSED_HTML, PARSED_METADATA), + ("Root page without metadata", "root_without_metadata.md", PARSED_HTML, {}), + ("Page with metadata", "category/with_metadata.md", PARSED_HTML, PARSED_METADATA), + ("Page without metadata", "category/subcategory/without_metadata.md", PARSED_HTML, {}), + ] + + for msg, page_path, expected_html, expected_metadata in cases: + with self.subTest(msg=msg): + html, metadata = utils.get_page(Path(page_path)) + self.assertEqual(html, expected_html) + self.assertEqual(metadata, expected_metadata) + + def test_get_nonexistent_page_returns_404(self): + with self.assertRaises(Http404): + utils.get_page(Path("invalid")) diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py new file mode 100644 index 00000000..560378bc --- /dev/null +++ b/pydis_site/apps/content/tests/test_views.py @@ -0,0 +1,145 @@ +from pathlib import Path +from unittest import TestCase + +from django.http import Http404 +from django.test import RequestFactory, SimpleTestCase, override_settings +from pyfakefs import fake_filesystem_unittest + +from pydis_site.apps.content.tests.helpers import ( + MockPagesTestCase, PARSED_CATEGORY_INFO, PARSED_HTML, PARSED_METADATA +) +from pydis_site.apps.content.views import PageOrCategoryView + + +# Set the module constant within Patcher to use the fake filesystem +# https://jmcgeheeiv.github.io/pyfakefs/master/usage.html#modules-to-reload +with fake_filesystem_unittest.Patcher() as _: + BASE_PATH = Path(".") + + +@override_settings(PAGES_PATH=BASE_PATH) +class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase): + """Tests for the PageOrCategoryView class.""" + + def setUp(self): + """Set test helpers, then set up fake filesystem.""" + self.factory = RequestFactory() + self.view = PageOrCategoryView.as_view() + self.ViewClass = PageOrCategoryView() + super().setUp() + + # Integration tests + def test_valid_page_or_category_returns_200(self): + cases = [ + ("Page at root", "root"), + ("Category page", "category"), + ("Page in category", "category/with_metadata"), + ("Subcategory page", "category/subcategory"), + ("Page in subcategory", "category/subcategory/with_metadata"), + ] + for msg, path in cases: + with self.subTest(msg=msg, path=path): + request = self.factory.get(f"/{path}") + response = self.view(request, location=path) + self.assertEqual(response.status_code, 200) + + def test_nonexistent_page_returns_404(self): + with self.assertRaises(Http404): + request = self.factory.get("/invalid") + self.view(request, location="invalid") + + # Unit tests + def test_get_template_names_returns_correct_templates(self): + category_template = "content/listing.html" + page_template = "content/page.html" + cases = [ + ("root", page_template), + ("root_without_metadata", page_template), + ("category/with_metadata", page_template), + ("category/subcategory/with_metadata", page_template), + ("category", category_template), + ("category/subcategory", category_template), + ] + + for path, expected_template in cases: + with self.subTest(path=path, expected_template=expected_template): + self.ViewClass.full_location = Path(path) + self.assertEqual(self.ViewClass.get_template_names(), [expected_template]) + + def test_get_template_names_with_nonexistent_paths_returns_404(self): + for path in ("invalid", "another_invalid", "nonexistent"): + with self.subTest(path=path): + self.ViewClass.full_location = Path(path) + with self.assertRaises(Http404): + self.ViewClass.get_template_names() + + def test_get_context_data_with_valid_page(self): + """The method should return required fields in the template context.""" + request = self.factory.get("/root") + self.ViewClass.setup(request) + self.ViewClass.dispatch(request, location="root") + + cases = [ + ("Context includes HTML page content", "page", PARSED_HTML), + ("Context includes page title", "page_title", PARSED_METADATA["title"]), + ( + "Context includes page description", + "page_description", + PARSED_METADATA["description"] + ), + ( + "Context includes relevant link names and URLs", + "relevant_links", + PARSED_METADATA["relevant_links"] + ), + ] + context = self.ViewClass.get_context_data() + for msg, key, expected_value in cases: + with self.subTest(msg=msg): + self.assertEqual(context[key], expected_value) + + def test_get_context_data_with_valid_category(self): + """The method should return required fields in the template context.""" + request = self.factory.get("/category") + self.ViewClass.setup(request) + self.ViewClass.dispatch(request, location="category") + + cases = [ + ( + "Context includes subcategory names and their information", + "categories", + {"subcategory": PARSED_CATEGORY_INFO} + ), + ( + "Context includes page names and their metadata", + "pages", + {"with_metadata": PARSED_METADATA} + ), + ( + "Context includes page description", + "page_description", + PARSED_CATEGORY_INFO["description"] + ), + ("Context includes page title", "page_title", PARSED_CATEGORY_INFO["name"]), + ] + + context = self.ViewClass.get_context_data() + for msg, key, expected_value in cases: + with self.subTest(msg=msg): + self.assertEqual(context[key], expected_value) + + def test_get_context_data_breadcrumbs(self): + """The method should return correct breadcrumbs.""" + request = self.factory.get("/category/subcategory/with_metadata") + self.ViewClass.setup(request) + self.ViewClass.dispatch(request, location="category/subcategory/with_metadata") + + context = self.ViewClass.get_context_data() + self.assertEquals( + context["breadcrumb_items"], + [ + {"name": PARSED_CATEGORY_INFO["name"], "path": "."}, + {"name": PARSED_CATEGORY_INFO["name"], "path": "category"}, + {"name": PARSED_CATEGORY_INFO["name"], "path": "category/subcategory"}, + ] + ) diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py new file mode 100644 index 00000000..c11b222a --- /dev/null +++ b/pydis_site/apps/content/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "content" +urlpatterns = [ + path("", views.PageOrCategoryView.as_view(), name='pages'), + path("<path:location>/", views.PageOrCategoryView.as_view(), name='page_category'), +] diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py new file mode 100644 index 00000000..726c991f --- /dev/null +++ b/pydis_site/apps/content/utils.py @@ -0,0 +1,57 @@ +from pathlib import Path +from typing import Dict, Tuple + +import frontmatter +import markdown +import yaml +from django.http import Http404 +from markdown.extensions.toc import TocExtension + + +def get_category(path: Path) -> Dict[str, str]: + """Load category information by name from _info.yml.""" + if not path.is_dir(): + raise Http404("Category not found.") + + return yaml.safe_load(path.joinpath("_info.yml").read_text(encoding="utf-8")) + + +def get_categories(path: Path) -> Dict[str, Dict]: + """Get information for all categories.""" + categories = {} + + for item in path.iterdir(): + if item.is_dir(): + categories[item.name] = get_category(item) + + return categories + + +def get_category_pages(path: Path) -> Dict[str, Dict]: + """Get all page names and their metadata at a category path.""" + pages = {} + + for item in path.glob("*.md"): + if item.is_file(): + pages[item.stem] = frontmatter.load(item).metadata + + return pages + + +def get_page(path: Path) -> Tuple[str, Dict]: + """Get one specific page.""" + if not path.is_file(): + raise Http404("Page not found.") + + metadata, content = frontmatter.parse(path.read_text(encoding="utf-8")) + html = markdown.markdown( + content, + extensions=[ + "extra", + # Empty string for marker to disable text searching for [TOC] + # By using a metadata key instead, we save time on long markdown documents + TocExtension(title="Table of Contents:", permalink=True, marker="") + ] + ) + + return str(html), metadata diff --git a/pydis_site/apps/content/views/__init__.py b/pydis_site/apps/content/views/__init__.py new file mode 100644 index 00000000..70ea1c7a --- /dev/null +++ b/pydis_site/apps/content/views/__init__.py @@ -0,0 +1,3 @@ +from .page_category import PageOrCategoryView + +__all__ = ["PageOrCategoryView"] diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py new file mode 100644 index 00000000..eec4e7e5 --- /dev/null +++ b/pydis_site/apps/content/views/page_category.py @@ -0,0 +1,61 @@ +import typing as t +from pathlib import Path + +from django.conf import settings +from django.http import Http404 +from django.views.generic import TemplateView + +from pydis_site.apps.content import utils + + +class PageOrCategoryView(TemplateView): + """Handles pages and page categories.""" + + def dispatch(self, request: t.Any, *args, **kwargs) -> t.Any: + """Conform URL path location to the filesystem path.""" + self.location = Path(kwargs.get("location", "")) + self.full_location = settings.PAGES_PATH / self.location + + return super().dispatch(request, *args, **kwargs) + + def get_template_names(self) -> t.List[str]: + """Checks if the view uses the page template or listing template.""" + if self.full_location.is_dir(): + template_name = "content/listing.html" + elif self.full_location.with_suffix(".md").is_file(): + template_name = "content/page.html" + else: + raise Http404 + + return [template_name] + + def get_context_data(self, **kwargs) -> t.Dict[str, t.Any]: + """Assign proper context variables based on what resource user requests.""" + context = super().get_context_data(**kwargs) + + if self.full_location.is_dir(): + context["categories"] = utils.get_categories(self.full_location) + context["pages"] = utils.get_category_pages(self.full_location) + + category = utils.get_category(self.full_location) + context["page_title"] = category["name"] + context["page_description"] = category["description"] + + context["path"] = f"{self.location}/" # Add trailing slash here to simplify template + elif self.full_location.with_suffix(".md").is_file(): + page, metadata = utils.get_page(self.full_location.with_suffix(".md")) + context["page"] = page + context["page_title"] = metadata["title"] + context["page_description"] = metadata["description"] + context["relevant_links"] = metadata.get("relevant_links", {}) + else: + raise Http404 + + context["breadcrumb_items"] = [ + { + "name": utils.get_category(settings.PAGES_PATH / location)["name"], + "path": str(location) + } for location in reversed(self.location.parents) + ] + + return context diff --git a/pydis_site/apps/events/__init__.py b/pydis_site/apps/events/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pydis_site/apps/events/__init__.py diff --git a/pydis_site/apps/events/apps.py b/pydis_site/apps/events/apps.py new file mode 100644 index 00000000..a1cf09ef --- /dev/null +++ b/pydis_site/apps/events/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class EventsConfig(AppConfig): + """Django AppConfig for events app.""" + + name = 'events' diff --git a/pydis_site/apps/events/migrations/__init__.py b/pydis_site/apps/events/migrations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pydis_site/apps/events/migrations/__init__.py diff --git a/pydis_site/apps/events/tests/__init__.py b/pydis_site/apps/events/tests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pydis_site/apps/events/tests/__init__.py diff --git a/pydis_site/apps/events/tests/test_views.py b/pydis_site/apps/events/tests/test_views.py new file mode 100644 index 00000000..0db0ef9a --- /dev/null +++ b/pydis_site/apps/events/tests/test_views.py @@ -0,0 +1,42 @@ +from pathlib import Path + +from django.conf import settings +from django.test import TestCase, override_settings +from django_hosts.resolvers import reverse + + +PAGES_PATH = Path(settings.BASE_DIR, "pydis_site", "templates", "events", "test-pages") + + +class IndexTests(TestCase): + def test_events_index_response_200(self): + """Should return response code 200 when visiting index of events.""" + url = reverse("events:index") + resp = self.client.get(url) + self.assertEqual(resp.status_code, 200) + + +class PageTests(TestCase): + @override_settings(PAGES_PATH=PAGES_PATH) + def test_valid_event_page_reponse_200(self): + """Should return response code 200 when visiting valid event page.""" + pages = ( + reverse("events:page", ("my-event",)), + reverse("events:page", ("my-event/subpage",)), + ) + for page in pages: + with self.subTest(page=page): + resp = self.client.get(page) + self.assertEqual(resp.status_code, 200) + + @override_settings(PAGES_PATH=PAGES_PATH) + def test_invalid_event_page_404(self): + """Should return response code 404 when visiting invalid event page.""" + pages = ( + reverse("events:page", ("invalid",)), + reverse("events:page", ("invalid/invalid",)) + ) + for page in pages: + with self.subTest(page=page): + resp = self.client.get(page) + self.assertEqual(resp.status_code, 404) diff --git a/pydis_site/apps/events/urls.py b/pydis_site/apps/events/urls.py new file mode 100644 index 00000000..9a65cf1f --- /dev/null +++ b/pydis_site/apps/events/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from pydis_site.apps.events.views import IndexView, PageView + +app_name = "events" +urlpatterns = [ + path("", IndexView.as_view(), name="index"), + path("<path:path>/", PageView.as_view(), name="page"), +] diff --git a/pydis_site/apps/events/views/__init__.py b/pydis_site/apps/events/views/__init__.py new file mode 100644 index 00000000..8a107e2f --- /dev/null +++ b/pydis_site/apps/events/views/__init__.py @@ -0,0 +1,4 @@ +from .index import IndexView +from .page import PageView + +__all__ = ["IndexView", "PageView"] diff --git a/pydis_site/apps/events/views/index.py b/pydis_site/apps/events/views/index.py new file mode 100644 index 00000000..7ffba74a --- /dev/null +++ b/pydis_site/apps/events/views/index.py @@ -0,0 +1,7 @@ +from django.views.generic import TemplateView + + +class IndexView(TemplateView): + """Events index page view.""" + + template_name = "events/index.html" diff --git a/pydis_site/apps/events/views/page.py b/pydis_site/apps/events/views/page.py new file mode 100644 index 00000000..f4c37aeb --- /dev/null +++ b/pydis_site/apps/events/views/page.py @@ -0,0 +1,25 @@ +from typing import List + +from django.conf import settings +from django.http import Http404 +from django.views.generic import TemplateView + + +class PageView(TemplateView): + """Handles event pages showing.""" + + def get_template_names(self) -> List[str]: + """Get specific template names.""" + path: str = self.kwargs['path'] + page_path = settings.PAGES_PATH / path + if page_path.is_dir(): + page_path = page_path / "_index.html" + path = f"{path}/_index.html" + else: + page_path = settings.PAGES_PATH / f"{path}.html" + path = f"{path}.html" + + if not page_path.exists(): + raise Http404 + + return [f"events/{settings.PAGES_PATH.name}/{path}"] diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py index 1578e0ac..3c716875 100644 --- a/pydis_site/apps/home/urls.py +++ b/pydis_site/apps/home/urls.py @@ -8,4 +8,6 @@ urlpatterns = [ path('', HomeView.as_view(), name='home'), path('admin/', admin.site.urls), path('resources/', include('pydis_site.apps.resources.urls')), + path('pages/', include('pydis_site.apps.content.urls')), + path('events/', include('pydis_site.apps.events.urls', namespace='events')), ] |