aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site
diff options
context:
space:
mode:
authorGravatar Hassan Abouelela <[email protected]>2022-08-14 05:34:27 +0200
committerGravatar Hassan Abouelela <[email protected]>2022-08-14 05:46:36 +0200
commit45cdb27a82297ede18d7bd908213dde54fef06a9 (patch)
treeb31c1aa6fa7d9676837f7dd8488b5e5a8eed6b4f /pydis_site
parentMove Tag URL To Property And Add Group (diff)
Add Tag Group Support
Adds support for tag groups in content. This involves some modification to the routing, and templating. Signed-off-by: Hassan Abouelela <[email protected]>
Diffstat (limited to 'pydis_site')
-rw-r--r--pydis_site/apps/content/urls.py12
-rw-r--r--pydis_site/apps/content/utils.py101
-rw-r--r--pydis_site/apps/content/views/page_category.py5
-rw-r--r--pydis_site/apps/content/views/tags.py79
-rw-r--r--pydis_site/static/css/content/color.css7
-rw-r--r--pydis_site/static/css/content/tag.css8
-rw-r--r--pydis_site/static/js/content/listing.js5
-rw-r--r--pydis_site/templates/content/listing.html17
-rw-r--r--pydis_site/templates/content/tag.html5
9 files changed, 187 insertions, 52 deletions
diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py
index b4ffc07d..03c0015a 100644
--- a/pydis_site/apps/content/urls.py
+++ b/pydis_site/apps/content/urls.py
@@ -39,15 +39,21 @@ def get_all_pages() -> DISTILL_RETURN:
def get_all_tags() -> DISTILL_RETURN:
- """Return all tag names in the repository in static builds."""
+ """Return all tag names and groups in static builds."""
+ groups = {None}
for tag in utils.get_tags_static():
- yield {"name": tag.name}
+ groups.add(tag.group)
+ yield {"location": (f"{tag.group}/" if tag.group else "") + tag.name}
+
+ groups.remove(None)
+ for group in groups:
+ yield {"location": group}
urlpatterns = [
distill_path("", views.PageOrCategoryView.as_view(), name='pages'),
distill_path(
- "tags/<str:name>/",
+ "tags/<path:location>/",
views.TagView.as_view(),
name="tag",
distill_func=get_all_tags
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index cc08f81f..da6a024d 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -2,6 +2,7 @@ import datetime
import functools
import tarfile
import tempfile
+import typing
from io import BytesIO
from pathlib import Path
@@ -16,7 +17,6 @@ from markdown.extensions.toc import TocExtension
from pydis_site import settings
from .models import Tag
-TAG_URL_BASE = "https://github.com/python-discord/bot/tree/main/bot/resources/tags"
TAG_CACHE_TTL = datetime.timedelta(hours=1)
@@ -44,9 +44,13 @@ def get_tags_static() -> list[Tag]:
"""
Fetch tag information in static builds.
+ This also includes some fake tags to preview the tag groups feature.
This will return a cached value, so it should only be used for static builds.
"""
- return fetch_tags()
+ tags = fetch_tags()
+ for tag in tags[3:5]:
+ tag.group = "very-cool-group"
+ return tags
def fetch_tags() -> list[Tag]:
@@ -79,10 +83,15 @@ def fetch_tags() -> list[Tag]:
repo.extractall(folder, included)
for tag_file in Path(folder).rglob("*.md"):
+ group = None
+ if tag_file.parent.name != "tags":
+ # Tags in sub-folders are considered part of a group
+ group = tag_file.parent.name
+
tags.append(Tag(
name=tag_file.name.removesuffix(".md"),
+ group=group,
body=tag_file.read_text(encoding="utf-8"),
- url=f"{TAG_URL_BASE}/{tag_file.name}"
))
return tags
@@ -114,31 +123,85 @@ def get_tags() -> list[Tag]:
return Tag.objects.all()
-def get_tag(name: str) -> Tag:
- """Return a tag by name."""
- tags = get_tags()
- for tag in tags:
- if tag.name == name:
+def get_tag(path: str) -> typing.Union[Tag, list[Tag]]:
+ """
+ Return a tag based on the search location.
+
+ The tag name and group must match. If only one argument is provided in the path,
+ it's assumed to either be a group name, or a no-group tag name.
+
+ If it's a group name, a list of tags which belong to it is returned.
+ """
+ path = path.split("/")
+ if len(path) == 2:
+ group, name = path[0], path[1]
+ else:
+ name = path[0]
+ group = None
+
+ matches = []
+ for tag in get_tags():
+ if tag.name == name and tag.group == group:
return tag
+ elif tag.group == name and group is None:
+ matches.append(tag)
+
+ if matches:
+ return matches
raise Tag.DoesNotExist()
-def get_category_pages(path: Path) -> dict[str, dict]:
- """Get all page names and their metadata at a category path."""
- # Special handling for tags
- if path == Path(__file__).parent / "resources/tags":
- tags = {}
- for tag in get_tags():
- content = frontmatter.parse(tag.body)[1]
+def get_tag_category(
+ tags: typing.Optional[list[Tag]] = None, *, collapse_groups: bool
+) -> dict[str, dict]:
+ """
+ Generate context data for `tags`, or all tags if None.
+
+ If `tags` is None, `get_tag` is used to populate the data.
+ If `collapse_groups` is True, tags with parent groups are not included in the list,
+ and instead the parent itself is included as a single entry with it's sub-tags
+ in the description.
+ """
+ if not tags:
+ tags = get_tags()
+
+ data = []
+ groups = {}
- tags[tag.name] = {
+ # Create all the metadata for the tags
+ for tag in tags:
+ if tag.group is None or not collapse_groups:
+ content = frontmatter.parse(tag.body)[1]
+ data.append({
"title": tag.name,
"description": markdown.markdown(content, extensions=["pymdownx.superfences"]),
- "icon": "fas fa-tag"
- }
+ "icon": "fas fa-tag",
+ })
+ else:
+ if tag.group not in groups:
+ groups[tag.group] = {
+ "title": tag.group,
+ "description": [tag.name],
+ "icon": "fas fa-tags",
+ }
+ else:
+ groups[tag.group]["description"].append(tag.name)
- return {name: tags[name] for name in sorted(tags)}
+ # Flatten group description into a single string
+ for group in groups.values():
+ group["description"] = "Contains the following tags: " + ", ".join(group["description"])
+ data.append(group)
+
+ # Sort the tags, and return them in the proper format
+ return {tag["title"]: tag for tag in sorted(data, key=lambda tag: tag["title"].lower())}
+
+
+def get_category_pages(path: Path) -> dict[str, dict]:
+ """Get all page names and their metadata at a category path."""
+ # Special handling for tags
+ if path == Path(__file__).parent / "resources/tags":
+ return get_tag_category(collapse_groups=True)
pages = {}
diff --git a/pydis_site/apps/content/views/page_category.py b/pydis_site/apps/content/views/page_category.py
index 01ce8402..062c2bc1 100644
--- a/pydis_site/apps/content/views/page_category.py
+++ b/pydis_site/apps/content/views/page_category.py
@@ -5,7 +5,7 @@ from django.conf import settings
from django.http import Http404, HttpRequest, HttpResponse
from django.views.generic import TemplateView
-from pydis_site.apps.content import utils
+from pydis_site.apps.content import models, utils
class PageOrCategoryView(TemplateView):
@@ -91,4 +91,7 @@ class PageOrCategoryView(TemplateView):
"page_title": category["title"],
"page_description": category["description"],
"icon": category.get("icon"),
+ "app_name": "content:page_category",
+ "is_tag_listing": "/resources/tags" in path.as_posix(),
+ "tag_url": models.Tag.URL_BASE,
}
diff --git a/pydis_site/apps/content/views/tags.py b/pydis_site/apps/content/views/tags.py
index 5295537d..a8df65db 100644
--- a/pydis_site/apps/content/views/tags.py
+++ b/pydis_site/apps/content/views/tags.py
@@ -1,4 +1,5 @@
import re
+import typing
import frontmatter
import markdown
@@ -16,23 +17,65 @@ COMMAND_REGEX = re.compile(r"`*!tags? (?P<name>[\w\d-]+)`*")
class TagView(TemplateView):
"""Handles tag pages."""
- template_name = "content/tag.html"
+ tag: typing.Union[Tag, list[Tag]]
+ is_group: bool
+
+ def setup(self, *args, **kwargs) -> None:
+ """Look for a tag, and configure the view."""
+ super().setup(*args, **kwargs)
- def get_context_data(self, **kwargs) -> dict:
- """Get the relevant context for this tag page."""
try:
- tag = utils.get_tag(kwargs.get("name"))
+ self.tag = utils.get_tag(kwargs.get("location"))
+ self.is_group = isinstance(self.tag, list)
except Tag.DoesNotExist:
raise Http404
+ def get_template_names(self) -> list[str]:
+ """Either return the tag page template, or the listing."""
+ if self.is_group:
+ template_name = "content/listing.html"
+ else:
+ template_name = "content/tag.html"
+
+ return [template_name]
+
+ def get_context_data(self, **kwargs) -> dict:
+ """Get the relevant context for this tag page or group."""
context = super().get_context_data(**kwargs)
- context["page_title"] = tag.name
+ context["breadcrumb_items"] = [{
+ "name": utils.get_category(settings.CONTENT_PAGES_PATH / location)["title"],
+ "path": location,
+ } for location in (".", "tags")]
+
+ if self.is_group:
+ self._set_group_context(context, self.tag)
+ else:
+ self._set_tag_context(context, self.tag)
+
+ return context
+
+ @staticmethod
+ def _set_tag_context(context: dict[str, any], tag: Tag) -> None:
+ """Update the context with the information for a tag page."""
+ context.update({
+ "page_title": tag.name,
+ "tag": tag,
+ })
+
+ if tag.group:
+ # Add group names to the breadcrumbs
+ context["breadcrumb_items"].append({
+ "name": tag.group,
+ "path": f"tags/{tag.group}",
+ })
+
+ # Clean up tag body
body = frontmatter.parse(tag.body)
content = body[1]
# Check for tags which can be hyperlinked
def sub(match: re.Match) -> str:
- link = reverse("content:tag", kwargs={"name": match.group("name")})
+ link = reverse("content:tag", kwargs={"location": match.group("name")})
return f"[{match.group()}]({link})"
content = COMMAND_REGEX.sub(sub, content)
@@ -42,14 +85,20 @@ class TagView(TemplateView):
if image := embed.get("image"):
content = f"![{embed['title']}]({image['url']})\n\n" + content
+ # Insert the content
+ context["page"] = markdown.markdown(content, extensions=["pymdownx.superfences"])
+
+ @staticmethod
+ def _set_group_context(context: dict[str, any], tags: list[Tag]) -> None:
+ """Update the context with the information for a group of tags."""
+ group = tags[0].group
context.update({
- "page": markdown.markdown(content, extensions=["pymdownx.superfences"]),
- "tag": tag,
+ "categories": {},
+ "pages": utils.get_tag_category(tags, collapse_groups=False),
+ "page_title": group,
+ "icon": "fab fa-tags",
+ "is_tag_listing": True,
+ "app_name": "content:tag",
+ "path": f"{group}/",
+ "tag_url": f"{tags[0].URL_BASE}/{group}"
})
-
- context["breadcrumb_items"] = [{
- "name": utils.get_category(settings.CONTENT_PAGES_PATH / location)["title"],
- "path": str(location)
- } for location in [".", "tags"]]
-
- return context
diff --git a/pydis_site/static/css/content/color.css b/pydis_site/static/css/content/color.css
new file mode 100644
index 00000000..f4801c28
--- /dev/null
+++ b/pydis_site/static/css/content/color.css
@@ -0,0 +1,7 @@
+.content .fa-github {
+ color: black;
+}
+
+.content .fa-github:hover {
+ color: #7289DA;
+}
diff --git a/pydis_site/static/css/content/tag.css b/pydis_site/static/css/content/tag.css
index a3db046c..ec45bfc7 100644
--- a/pydis_site/static/css/content/tag.css
+++ b/pydis_site/static/css/content/tag.css
@@ -1,11 +1,3 @@
-h1.title a {
- color: black;
-}
-
-h1.title a:hover {
- color: #7289DA;
-}
-
.content a * {
/* This is the original color, but propagated down the chain */
/* which allows for elements inside links, such as codeblocks */
diff --git a/pydis_site/static/js/content/listing.js b/pydis_site/static/js/content/listing.js
index 3502cb2a..4b722632 100644
--- a/pydis_site/static/js/content/listing.js
+++ b/pydis_site/static/js/content/listing.js
@@ -4,6 +4,11 @@
function trimTag() {
const containers = document.getElementsByClassName("tag-container");
for (const container of containers) {
+ if (container.textContent.startsWith("Contains the following tags:")) {
+ // Tag group, no need to trim
+ continue;
+ }
+
// Remove every element after the first two paragraphs
while (container.children.length > 2) {
container.removeChild(container.lastChild);
diff --git a/pydis_site/templates/content/listing.html b/pydis_site/templates/content/listing.html
index 098f4237..934b95f6 100644
--- a/pydis_site/templates/content/listing.html
+++ b/pydis_site/templates/content/listing.html
@@ -2,6 +2,19 @@
{% extends 'content/base.html' %}
{% load static %}
+{# Show a GitHub button on tag pages #}
+{% block title_element %}
+{% if is_tag_listing %}
+ <link rel="stylesheet" href="{% static "css/content/color.css" %}">
+ <div class="level">
+ <div class="level-left">{{ block.super }}</div>
+ <div class="level-right">
+ <a class="level-item fab fa-github" href="{{ tag_url }}"></a>
+ </div>
+ </div>
+{% endif %}
+{% endblock %}
+
{% block page_content %}
{# Nested Categories #}
{% for category, data in categories.items %}
@@ -23,10 +36,10 @@
<span class="icon is-size-4 is-medium">
<i class="{{ data.icon|default:"fab fa-python" }} is-size-3 is-black has-icon-padding" aria-hidden="true"></i>
</span>
- <a href="{% url "content:page_category" location=path|add:page %}">
+ <a href="{% url app_name location=path|add:page %}">
<span class="is-size-4 has-text-weight-bold">{{ data.title }}</span>
</a>
- {% if "tags" in location %}
+ {% if is_tag_listing %}
<div class="tag-container">{{ data.description | safe }}</div>
{% else %}
<p class="is-italic">{{ data.description }}</p>
diff --git a/pydis_site/templates/content/tag.html b/pydis_site/templates/content/tag.html
index 264f63d0..9bd65744 100644
--- a/pydis_site/templates/content/tag.html
+++ b/pydis_site/templates/content/tag.html
@@ -3,6 +3,7 @@
{% block head %}
{{ block.super }}
+ <link rel="stylesheet" href="{% static 'css/content/color.css' %}"/>
<link rel="stylesheet" href="{% static 'css/content/tag.css' %}"/>
<title>{{ tag.name }}</title>
{% endblock %}
@@ -15,7 +16,3 @@
</div>
</div>
{% endblock %}
-
-{% block page_content %}
- {{ block.super }}
-{% endblock %}