aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps/home/views/home.py
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2021-07-11 22:56:17 +0200
committerGravatar Johannes Christ <[email protected]>2021-07-11 23:04:49 +0200
commitc52c606e6ae09291378c6eae17938d642dc2e463 (patch)
tree8c4ad3e710a3576dc414aba34cb59e6365b12c8f /pydis_site/apps/home/views/home.py
parentMerge pull request #552 from python-discord/cj/minor-adjustments (diff)
Improvements & fixes to homepage repository display.
These changes mainly aim to prevent multiple concurrent requests from deadlocking the table due to the lazy evaluation imposed by `objects.all()`, and introduce some quality of life changes whilst doing so. To prevent unwanted locks, the following is done: - Instead of evaluating the first item of `objects.all()` only in order to determine when the table was most recently updated, we query for the least recent updated entry and use that instead. As we use `.first()`, the model instance is loaded directly. The following quality of life changes are introduced: - Instead of manual "update or create" logic for repositories retrieved from the API, use Django's `update_or_create` instead. - Instead of manual bulk creation of entries on initial data retrieve, Django's `bulk_create` method is used instead. - To allow for local testing of this endpoint without having to manually set up GitHub REST authorization, anonymous GitHub access is used when no `GITHUB_TOKEN` is configured.
Diffstat (limited to 'pydis_site/apps/home/views/home.py')
-rw-r--r--pydis_site/apps/home/views/home.py62
1 files changed, 31 insertions, 31 deletions
diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py
index b3767d37..23b6ab24 100644
--- a/pydis_site/apps/home/views/home.py
+++ b/pydis_site/apps/home/views/home.py
@@ -19,7 +19,6 @@ class HomeView(View):
github_api = "https://api.github.com/users/python-discord/repos?per_page=100"
repository_cache_ttl = 3600
- headers = {"Authorization": f"token {GITHUB_TOKEN}"}
# Which of our GitHub repos should be displayed on the front page, and in which order?
repos = [
@@ -35,6 +34,16 @@ class HomeView(View):
"""Clean up stale RepositoryMetadata."""
RepositoryMetadata.objects.exclude(repo_name__in=self.repos).delete()
+ # If no token is defined (for example in local development), then
+ # it does not make sense to pass the Authorization header. More
+ # specifically, GitHub will reject any requests from us due to the
+ # invalid header. We can make a limited number of anonymous requests
+ # though, which is useful for testing.
+ if GITHUB_TOKEN:
+ self.headers = {"Authorization": f"token {GITHUB_TOKEN}"}
+ else:
+ self.headers = {}
+
def _get_api_data(self) -> Dict[str, Dict[str, str]]:
"""
Call the GitHub API and get information about our repos.
@@ -74,35 +83,30 @@ class HomeView(View):
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()
+ # First off, load the timestamp of the least recently updated entry.
+ oldest_entry = RepositoryMetadata.objects.order_by("last_updated").first()
- # If we don't, we have to create some!
- if not cached_data:
+ # If we did not retrieve any results here, we should import them!
+ if oldest_entry is None:
# 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():
- repo_data = RepositoryMetadata(
+ return RepositoryMetadata.objects.bulk_create(
+ 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
+ for api_data in api_repositories.values()
+ )
# If the data is stale, we should refresh it.
- if (timezone.now() - cached_data[0].last_updated).seconds > self.repository_cache_ttl:
+ if (timezone.now() - oldest_entry.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()
@@ -110,22 +114,18 @@ class HomeView(View):
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 = []
+ for api_data in api_repositories.values():
+ repo_data, _created = RepositoryMetadata.objects.update_or_create(
+ repo_name=api_data["full_name"],
+ defaults={
+ 'repo_name': api_data["full_name"],
+ 'description': api_data["description"],
+ 'forks': api_data["forks_count"],
+ 'stargazers': api_data["stargazers_count"],
+ 'language': api_data["language"],
+ }
+ )
database_repositories.append(repo_data)
return database_repositories