| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
 | import json
import typing as t
from pathlib import Path
import yaml
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render
from django.views import View
from pydis_site import settings
from pydis_site.apps.resources.templatetags.to_kebabcase import to_kebabcase
RESOURCES_PATH = Path(settings.BASE_DIR, "pydis_site", "apps", "resources", "resources")
class ResourceView(View):
    """Our curated list of good learning resources."""
    @staticmethod
    def _sort_key_disregard_the(tuple_: tuple) -> str:
        """Sort a tuple by its key alphabetically, disregarding 'the' as a prefix."""
        name, resource = tuple_
        name = name.casefold()
        if name.startswith("the ") or name.startswith("the_"):
            return name[4:]
        return name
    def __init__(self, *args, **kwargs):
        """Set up all the resources."""
        super().__init__(*args, **kwargs)
        # Load the resources from the yaml files in /resources/
        self.resources = {
            path.stem: yaml.safe_load(path.read_text())
            for path in RESOURCES_PATH.rglob("*.yaml")
        }
        # Sort the resources alphabetically
        self.resources = dict(sorted(self.resources.items(), key=self._sort_key_disregard_the))
        # Parse out all current tags
        resource_tags = {
            "topics": set(),
            "payment_tiers": set(),
            "difficulty": set(),
            "type": set(),
        }
        for resource_name, resource in self.resources.items():
            css_classes = []
            for tag_type in resource_tags.keys():
                # Store the tags into `resource_tags`
                tags = resource.get("tags", {}).get(tag_type, [])
                for tag in tags:
                    tag = tag.title()
                    tag = tag.replace("And", "and")
                    resource_tags[tag_type].add(tag)
                # Make a CSS class friendly representation too, while we're already iterating.
                for tag in tags:
                    css_tag = to_kebabcase(f"{tag_type}-{tag}")
                    css_classes.append(css_tag)
            # Now add the css classes back to the resource, so we can use them in the template.
            self.resources[resource_name]["css_classes"] = " ".join(css_classes)
        # Set up all the filter checkbox metadata
        self.filters = {
            "Difficulty": {
                "filters": sorted(resource_tags.get("difficulty")),
                "icon": "fas fa-brain",
                "hidden": False,
            },
            "Type": {
                "filters": sorted(resource_tags.get("type")),
                "icon": "fas fa-photo-video",
                "hidden": False,
            },
            "Payment tiers": {
                "filters": sorted(resource_tags.get("payment_tiers")),
                "icon": "fas fa-dollar-sign",
                "hidden": True,
            },
            "Topics": {
                "filters": sorted(resource_tags.get("topics")),
                "icon": "fas fa-lightbulb",
                "hidden": True,
            }
        }
        # The bottom topic should always be "Other".
        self.filters["Topics"]["filters"].remove("Other")
        self.filters["Topics"]["filters"].append("Other")
        # A complete list of valid filter names
        self.valid_filters = {
            "topics": [to_kebabcase(topic) for topic in self.filters["Topics"]["filters"]],
            "payment_tiers": [
                to_kebabcase(tier) for tier in self.filters["Payment tiers"]["filters"]
            ],
            "type": [to_kebabcase(type_) for type_ in self.filters["Type"]["filters"]],
            "difficulty": [to_kebabcase(tier) for tier in self.filters["Difficulty"]["filters"]],
        }
    def get(self, request: WSGIRequest, resource_type: t.Optional[str] = None) -> HttpResponse:
        """List out all the resources, and any filtering options from the URL."""
        # Add type filtering if the request is made to somewhere like /resources/video.
        # We also convert all spaces to dashes, so they'll correspond with the filters.
        if resource_type:
            dashless_resource_type = resource_type.replace("-", " ")
            if dashless_resource_type.title() not in self.filters["Type"]["filters"]:
                return HttpResponseNotFound()
            resource_type = resource_type.replace(" ", "-")
        return render(
            request,
            template_name="resources/resources.html",
            context={
                "resources": self.resources,
                "filters": self.filters,
                "valid_filters": json.dumps(self.valid_filters),
                "resource_type": resource_type,
            }
        )
 |