aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2019-04-20 14:41:40 +0200
committerGravatar GitHub <[email protected]>2019-04-20 14:41:40 +0200
commit14f8ca26b85f7cc3e009fb9ba2ac549e751c8068 (patch)
tree041b093b2c9296fe23eb6e1bb54d0748fbe58dfa /pydis_site/apps
parentRevert linter to non-verbose. (diff)
parentMake the flakes happy. (diff)
Merge pull request #213 from python-discord/django_front_page
Django front page
Diffstat (limited to 'pydis_site/apps')
-rw-r--r--pydis_site/apps/home/admin.py3
-rw-r--r--pydis_site/apps/home/migrations/0001_initial.py26
-rw-r--r--pydis_site/apps/home/models.py3
-rw-r--r--pydis_site/apps/home/models/__init__.py3
-rw-r--r--pydis_site/apps/home/models/repository_metadata.py33
-rw-r--r--pydis_site/apps/home/templatetags/__init__.py3
-rw-r--r--pydis_site/apps/home/tests.py16
-rw-r--r--pydis_site/apps/home/tests/__init__.py (renamed from pydis_site/apps/wiki/__init__.py)0
-rw-r--r--pydis_site/apps/home/tests/mock_github_api_response.json44
-rw-r--r--pydis_site/apps/home/tests/test_repodata_helpers.py105
-rw-r--r--pydis_site/apps/home/tests/test_templatetags.py8
-rw-r--r--pydis_site/apps/home/tests/test_views.py9
-rw-r--r--pydis_site/apps/home/urls.py4
-rw-r--r--pydis_site/apps/home/views.py3
-rw-r--r--pydis_site/apps/home/views/__init__.py3
-rw-r--r--pydis_site/apps/home/views/home.py115
-rw-r--r--pydis_site/apps/wiki/admin.py3
-rw-r--r--pydis_site/apps/wiki/apps.py5
-rw-r--r--pydis_site/apps/wiki/migrations/__init__.py0
-rw-r--r--pydis_site/apps/wiki/models.py3
-rw-r--r--pydis_site/apps/wiki/tests.py3
-rw-r--r--pydis_site/apps/wiki/views.py3
22 files changed, 351 insertions, 44 deletions
diff --git a/pydis_site/apps/home/admin.py b/pydis_site/apps/home/admin.py
deleted file mode 100644
index 4185d360..00000000
--- a/pydis_site/apps/home/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.contrib import admin
-
-# Register your models here.
diff --git a/pydis_site/apps/home/migrations/0001_initial.py b/pydis_site/apps/home/migrations/0001_initial.py
new file mode 100644
index 00000000..a2bf9f3e
--- /dev/null
+++ b/pydis_site/apps/home/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.2 on 2019-04-16 15:27
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RepositoryMetadata',
+ fields=[
+ ('last_updated', models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time this data was last fetched.')),
+ ('repo_name', models.CharField(help_text='The full name of the repo, e.g. python-discord/site', max_length=40, primary_key=True, serialize=False)),
+ ('description', models.CharField(help_text='The description of the repo.', max_length=400)),
+ ('forks', models.IntegerField(help_text='The number of forks of this repo')),
+ ('stargazers', models.IntegerField(help_text='The number of stargazers for this repo')),
+ ('language', models.CharField(help_text='The primary programming language used for this repo.', max_length=20)),
+ ],
+ ),
+ ]
diff --git a/pydis_site/apps/home/models.py b/pydis_site/apps/home/models.py
deleted file mode 100644
index 0b4331b3..00000000
--- a/pydis_site/apps/home/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.db import models
-
-# Create your models here.
diff --git a/pydis_site/apps/home/models/__init__.py b/pydis_site/apps/home/models/__init__.py
new file mode 100644
index 00000000..6c68df9c
--- /dev/null
+++ b/pydis_site/apps/home/models/__init__.py
@@ -0,0 +1,3 @@
+from .repository_metadata import RepositoryMetadata
+
+__all__ = ["RepositoryMetadata"]
diff --git a/pydis_site/apps/home/models/repository_metadata.py b/pydis_site/apps/home/models/repository_metadata.py
new file mode 100644
index 00000000..c975c904
--- /dev/null
+++ b/pydis_site/apps/home/models/repository_metadata.py
@@ -0,0 +1,33 @@
+from django.db import models
+from django.utils import timezone
+
+
+class RepositoryMetadata(models.Model):
+ """Information about one of our repos fetched from the GitHub API."""
+
+ last_updated = models.DateTimeField(
+ default=timezone.now,
+ help_text="The date and time this data was last fetched."
+ )
+ repo_name = models.CharField(
+ primary_key=True,
+ max_length=40,
+ help_text="The full name of the repo, e.g. python-discord/site"
+ )
+ description = models.CharField(
+ max_length=400,
+ help_text="The description of the repo."
+ )
+ forks = models.IntegerField(
+ help_text="The number of forks of this repo"
+ )
+ stargazers = models.IntegerField(
+ help_text="The number of stargazers for this repo"
+ )
+ language = models.CharField(
+ max_length=20,
+ help_text="The primary programming language used for this repo."
+ )
+
+ def __str__(self):
+ return self.repo_name
diff --git a/pydis_site/apps/home/templatetags/__init__.py b/pydis_site/apps/home/templatetags/__init__.py
index e69de29b..70aca169 100644
--- a/pydis_site/apps/home/templatetags/__init__.py
+++ b/pydis_site/apps/home/templatetags/__init__.py
@@ -0,0 +1,3 @@
+from .extra_filters import starts_with
+
+__all__ = ["starts_with"]
diff --git a/pydis_site/apps/home/tests.py b/pydis_site/apps/home/tests.py
deleted file mode 100644
index 733ddaa3..00000000
--- a/pydis_site/apps/home/tests.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.test import TestCase
-from django_hosts.resolvers import reverse
-
-from pydis_site.apps.home.templatetags import extra_filters
-
-
-class TestIndexReturns200(TestCase):
- def test_index_returns_200(self):
- url = reverse('home.index')
- resp = self.client.get(url)
- self.assertEqual(resp.status_code, 200)
-
-
-class TestExtraFilterTemplateTags(TestCase):
- def test_starts_with(self):
- self.assertTrue(extra_filters.starts_with('foo', 'f'))
diff --git a/pydis_site/apps/wiki/__init__.py b/pydis_site/apps/home/tests/__init__.py
index e69de29b..e69de29b 100644
--- a/pydis_site/apps/wiki/__init__.py
+++ b/pydis_site/apps/home/tests/__init__.py
diff --git a/pydis_site/apps/home/tests/mock_github_api_response.json b/pydis_site/apps/home/tests/mock_github_api_response.json
new file mode 100644
index 00000000..37dc672e
--- /dev/null
+++ b/pydis_site/apps/home/tests/mock_github_api_response.json
@@ -0,0 +1,44 @@
+[
+ {
+ "full_name": "python-discord/bot",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ },
+ {
+ "full_name": "python-discord/site",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ },
+ {
+ "full_name": "python-discord/snekbox",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ },
+ {
+ "full_name": "python-discord/django-simple-bulma",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ },
+ {
+ "full_name": "python-discord/django-crispy-bulma",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ },
+ {
+ "full_name": "python-discord/seasonalbot",
+ "description": "test",
+ "stargazers_count": 97,
+ "language": "Python",
+ "forks_count": 31
+ }
+]
diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py
new file mode 100644
index 00000000..59cb2331
--- /dev/null
+++ b/pydis_site/apps/home/tests/test_repodata_helpers.py
@@ -0,0 +1,105 @@
+import json
+from datetime import timedelta
+from pathlib import Path
+from unittest import mock
+
+from django.test import TestCase
+from django.utils import timezone
+
+from pydis_site.apps.home.models import RepositoryMetadata
+from pydis_site.apps.home.views import HomeView
+
+
+def mocked_requests_get(*args, **kwargs) -> "MockResponse": # noqa
+ """A mock version of requests.get, so we don't need to call the API every time we run a test"""
+ class MockResponse:
+ def __init__(self, json_data, status_code):
+ self.json_data = json_data
+ self.status_code = status_code
+
+ def json(self):
+ return self.json_data
+
+ if args[0] == HomeView.github_api:
+ json_path = Path(__file__).resolve().parent / "mock_github_api_response.json"
+ with open(json_path, 'r') as json_file:
+ mock_data = json.load(json_file)
+
+ return MockResponse(mock_data, 200)
+
+ return MockResponse(None, 404)
+
+
+class TestRepositoryMetadataHelpers(TestCase):
+
+ def setUp(self):
+ """Executed before each test method."""
+
+ self.home_view = HomeView()
+
+ @mock.patch('requests.get', side_effect=mocked_requests_get)
+ def test_returns_metadata(self, _: mock.MagicMock):
+ """Test if the _get_repo_data helper actually returns what it should."""
+
+ metadata = self.home_view._get_repo_data()
+
+ self.assertIsInstance(metadata[0], RepositoryMetadata)
+ self.assertEquals(len(metadata), len(self.home_view.repos))
+
+ def test_returns_cached_metadata(self):
+ """Test if the _get_repo_data helper returns cached data when available."""
+
+ repo_data = RepositoryMetadata(
+ repo_name="python-discord/site",
+ description="testrepo",
+ forks=42,
+ stargazers=42,
+ language="English",
+ )
+ repo_data.save()
+ metadata = self.home_view._get_repo_data()
+
+ self.assertIsInstance(metadata[0], RepositoryMetadata)
+ self.assertIsInstance(str(metadata[0]), str)
+
+ @mock.patch('requests.get', side_effect=mocked_requests_get)
+ def test_refresh_stale_metadata(self, _: mock.MagicMock):
+ """Test if the _get_repo_data helper will refresh when the data is stale"""
+
+ repo_data = RepositoryMetadata(
+ repo_name="python-discord/site",
+ description="testrepo",
+ forks=42,
+ stargazers=42,
+ language="English",
+ last_updated=timezone.now() - timedelta(seconds=HomeView.repository_cache_ttl + 1),
+ )
+ repo_data.save()
+ metadata = self.home_view._get_repo_data()
+
+ self.assertIsInstance(metadata[0], RepositoryMetadata)
+
+ @mock.patch('requests.get', side_effect=mocked_requests_get)
+ def test_returns_api_data(self, _: mock.MagicMock):
+ """Tests if the _get_api_data helper returns what it should."""
+
+ api_data = self.home_view._get_api_data()
+ repo = self.home_view.repos[0]
+
+ self.assertIsInstance(api_data, dict)
+ self.assertEquals(len(api_data), len(self.home_view.repos))
+ self.assertIn(repo, api_data.keys())
+ self.assertIn("stargazers_count", api_data[repo])
+
+ @mock.patch('requests.get', side_effect=mocked_requests_get)
+ def test_mocked_requests_get(self, mock_get: mock.MagicMock):
+ """Tests if our mocked_requests_get is returning what it should."""
+
+ success_data = mock_get(HomeView.github_api)
+ fail_data = mock_get("failtest")
+
+ self.assertEqual(success_data.status_code, 200)
+ self.assertEqual(fail_data.status_code, 404)
+
+ self.assertIsNotNone(success_data.json_data)
+ self.assertIsNone(fail_data.json_data)
diff --git a/pydis_site/apps/home/tests/test_templatetags.py b/pydis_site/apps/home/tests/test_templatetags.py
new file mode 100644
index 00000000..813588c8
--- /dev/null
+++ b/pydis_site/apps/home/tests/test_templatetags.py
@@ -0,0 +1,8 @@
+from django.test import TestCase
+
+from pydis_site.apps.home.templatetags import starts_with
+
+
+class TestTemplateTags(TestCase):
+ def test_starts_with(self):
+ self.assertTrue(starts_with('foo', 'f'))
diff --git a/pydis_site/apps/home/tests/test_views.py b/pydis_site/apps/home/tests/test_views.py
new file mode 100644
index 00000000..73678b0a
--- /dev/null
+++ b/pydis_site/apps/home/tests/test_views.py
@@ -0,0 +1,9 @@
+from django.test import TestCase
+from django_hosts.resolvers import reverse
+
+
+class TestIndexReturns200(TestCase):
+ def test_index_returns_200(self):
+ url = reverse('home')
+ resp = self.client.get(url)
+ self.assertEqual(resp.status_code, 200)
diff --git a/pydis_site/apps/home/urls.py b/pydis_site/apps/home/urls.py
index 56525af8..d8dba2f6 100644
--- a/pydis_site/apps/home/urls.py
+++ b/pydis_site/apps/home/urls.py
@@ -1,10 +1,10 @@
from django.contrib import admin
from django.urls import path
-from django.views.generic import TemplateView
+from .views import HomeView
app_name = 'home'
urlpatterns = [
- path('', TemplateView.as_view(template_name='home/index.html'), name='home.index'),
+ path('', HomeView.as_view(), name='home'),
path('admin/', admin.site.urls)
]
diff --git a/pydis_site/apps/home/views.py b/pydis_site/apps/home/views.py
deleted file mode 100644
index fd0e0449..00000000
--- a/pydis_site/apps/home/views.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.shortcuts import render
-
-# Create your views here.
diff --git a/pydis_site/apps/home/views/__init__.py b/pydis_site/apps/home/views/__init__.py
new file mode 100644
index 00000000..971d73a3
--- /dev/null
+++ b/pydis_site/apps/home/views/__init__.py
@@ -0,0 +1,3 @@
+from .home import HomeView
+
+__all__ = ["HomeView"]
diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py
new file mode 100644
index 00000000..e4daf380
--- /dev/null
+++ b/pydis_site/apps/home/views/home.py
@@ -0,0 +1,115 @@
+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
+
+
+class HomeView(View):
+ """The main landing page for the website."""
+
+ github_api = "https://api.github.com/users/python-discord/repos"
+ repository_cache_ttl = 600
+
+ # 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/seasonalbot",
+ "python-discord/django-simple-bulma",
+ "python-discord/django-crispy-bulma",
+ ]
+
+ def _get_api_data(self) -> Dict[str, Dict[str, str]]:
+ """Call the GitHub API and get information about our repos."""
+
+ repo_dict = {repo_name: {} for repo_name in self.repos}
+
+ # Fetch the data from the GitHub API
+ api_data = requests.get(self.github_api)
+ api_data = api_data.json()
+
+ # Process the API data into our dict
+ for repo in api_data:
+ 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"],
+ }
+ 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."""
+
+ # Try to get site data from the cache
+ try:
+ repo_data = RepositoryMetadata.objects.get(repo_name="python-discord/site")
+
+ # If the data is stale, we should refresh it.
+ if (timezone.now() - repo_data.last_updated).seconds > self.repository_cache_ttl:
+
+ # Get new data from API
+ api_repositories = self._get_api_data()
+ database_repositories = []
+
+ # 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()
+
+ # If this is raised, the database has no repodata at all, we will create them all.
+ except RepositoryMetadata.DoesNotExist:
+
+ # Get new data from API
+ api_repositories = self._get_api_data()
+ database_repositories = []
+
+ # Create all the repodata records in the database.
+ for api_data in api_repositories.values():
+ 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
+
+ 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})
diff --git a/pydis_site/apps/wiki/admin.py b/pydis_site/apps/wiki/admin.py
deleted file mode 100644
index 4185d360..00000000
--- a/pydis_site/apps/wiki/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.contrib import admin
-
-# Register your models here.
diff --git a/pydis_site/apps/wiki/apps.py b/pydis_site/apps/wiki/apps.py
deleted file mode 100644
index fce4708e..00000000
--- a/pydis_site/apps/wiki/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class WikiConfig(AppConfig):
- name = 'wiki'
diff --git a/pydis_site/apps/wiki/migrations/__init__.py b/pydis_site/apps/wiki/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pydis_site/apps/wiki/migrations/__init__.py
+++ /dev/null
diff --git a/pydis_site/apps/wiki/models.py b/pydis_site/apps/wiki/models.py
deleted file mode 100644
index 0b4331b3..00000000
--- a/pydis_site/apps/wiki/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.db import models
-
-# Create your models here.
diff --git a/pydis_site/apps/wiki/tests.py b/pydis_site/apps/wiki/tests.py
deleted file mode 100644
index a79ca8be..00000000
--- a/pydis_site/apps/wiki/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.test import TestCase
-
-# Create your tests here.
diff --git a/pydis_site/apps/wiki/views.py b/pydis_site/apps/wiki/views.py
deleted file mode 100644
index fd0e0449..00000000
--- a/pydis_site/apps/wiki/views.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.shortcuts import render
-
-# Create your views here.