aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps/home/views/home.py
blob: 77496121c7ee696ab5ea2c79c29a942c075ef6b7 (plain) (blame)
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import logging
from typing import Dict, List

import requests
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django.shortcuts import render
from django.utils import timezone
from django.views import View

from pydis_site.apps.home.models import RepositoryMetadata

log = logging.getLogger(__name__)


class HomeView(View):
    """The main landing page for the website."""

    github_api = "https://api.github.com/users/python-discord/repos"
    repository_cache_ttl = 3600

    # Which of our GitHub repos should be displayed on the front page, and in which order?
    repos = [
        "python-discord/site",
        "python-discord/bot",
        "python-discord/snekbox",
        "python-discord/sir-lancebot",
        "python-discord/metricity",
        "python-discord/django-simple-bulma",
    ]

    def _get_api_data(self) -> Dict[str, Dict[str, str]]:
        """
        Call the GitHub API and get information about our repos.

        If we're unable to get that info for any reason, return an empty dict.
        """
        repo_dict = {}

        # Fetch the data from the GitHub API
        api_data: List[dict] = requests.get(self.github_api).json()

        # Process the API data into our dict
        for repo in api_data:
            try:
                full_name = repo["full_name"]

                if full_name in self.repos:
                    repo_dict[full_name] = {
                        "full_name": repo["full_name"],
                        "description": repo["description"],
                        "language": repo["language"],
                        "forks_count": repo["forks_count"],
                        "stargazers_count": repo["stargazers_count"],
                    }
            # Something is not right about the API data we got back from GitHub.
            except (TypeError, ConnectionError, KeyError) as e:
                log.error(
                    "Unable to parse the GitHub repository metadata from response!",
                    extra={
                        'api_data': api_data,
                        'error': e
                    }
                )
                continue

        return repo_dict

    def _get_repo_data(self) -> List[RepositoryMetadata]:
        """Build a list of RepositoryMetadata objects that we can use to populate the front page."""
        database_repositories = []

        # First, let's see if we have any metadata cached.
        cached_data = RepositoryMetadata.objects.all()

        # If we don't, we have to create some!
        if not cached_data:

            # Try to get new data from the API. If it fails, we'll return an empty list.
            # In this case, we simply don't display our projects on the site.
            api_repositories = self._get_api_data()

            # Create all the repodata records in the database.
            for api_data in api_repositories.values():
                try:
                    repo_data = RepositoryMetadata(
                        repo_name=api_data["full_name"],
                        description=api_data["description"],
                        forks=api_data["forks_count"],
                        stargazers=api_data["stargazers_count"],
                        language=api_data["language"],
                    )
                # This error indicates there's something not quite right about the api_data types.
                # In that case, just skip this repo.
                except TypeError:
                    log.error(
                        "Encountered a TypeError while processing RepositoryMetadata "
                        "from the GitHub API.",
                        extra=api_data
                    )
                    continue

                repo_data.save()
                database_repositories.append(repo_data)

            return database_repositories

        # If the data is stale, we should refresh it.
        if (timezone.now() - cached_data[0].last_updated).seconds > self.repository_cache_ttl:
            # Try to get new data from the API. If it fails, return the cached data.
            api_repositories = self._get_api_data()

            if not api_repositories:
                return RepositoryMetadata.objects.all()

            # Update or create all RepoData objects in self.repos
            for repo_name, api_data in api_repositories.items():
                try:
                    repo_data = RepositoryMetadata.objects.get(repo_name=repo_name)
                    repo_data.description = api_data["description"]
                    repo_data.language = api_data["language"]
                    repo_data.forks = api_data["forks_count"]
                    repo_data.stargazers = api_data["stargazers_count"]
                except RepositoryMetadata.DoesNotExist:
                    repo_data = RepositoryMetadata(
                        repo_name=api_data["full_name"],
                        description=api_data["description"],
                        forks=api_data["forks_count"],
                        stargazers=api_data["stargazers_count"],
                        language=api_data["language"],
                    )
                repo_data.save()
                database_repositories.append(repo_data)
            return database_repositories

        # Otherwise, if the data is fresher than 2 minutes old, we should just return it.
        else:
            return RepositoryMetadata.objects.all()

    def get(self, request: WSGIRequest) -> HttpResponse:
        """Collect repo data and render the homepage view."""
        repo_data = self._get_repo_data()
        return render(request, "home/index.html", {"repo_data": repo_data})


def timeline(request: WSGIRequest) -> HttpResponse:
    """Render timeline view."""
    return render(request, 'home/timeline.html')