From f29472a56f925127a077c4cadcc13fa852dc665b Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sat, 13 Apr 2019 16:16:11 +0200 Subject: refactoring slightly to use a class-based view, changing home to main. Ready to write the API search stuff now. --- Pipfile | 1 + 1 file changed, 1 insertion(+) (limited to 'Pipfile') diff --git a/Pipfile b/Pipfile index 932746d2..bb2c88ee 100644 --- a/Pipfile +++ b/Pipfile @@ -27,6 +27,7 @@ uwsgi = "~=2.0.18" psycopg2-binary = "~=2.8" django-simple-bulma = ">=1.1.6,<2.0" django-crispy-bulma = ">=0.1.2,<2.0" +requests = "*" [requires] python_version = "3.7" -- cgit v1.2.3 From 1583b86a6215c879a667f304512d4708c77e9f82 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Tue, 16 Apr 2019 17:58:29 +0200 Subject: makemigrations pipeline created, RepoData model created, and a really naive implementation of the API stuff which makes 6 requests and takes forever to load. I've found a better approach so I'm gonna switch to that next. --- Pipfile | 4 ++ docker-compose.yml | 4 +- docker/pysite.dockerapp | 2 + pydis_site/apps/main/models/__init__.py | 3 + pydis_site/apps/main/models/repo_data.py | 33 +++++++++++ pydis_site/apps/main/views/home.py | 94 ++++++++++++++++++++------------ 6 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 pydis_site/apps/main/models/__init__.py create mode 100644 pydis_site/apps/main/models/repo_data.py (limited to 'Pipfile') diff --git a/Pipfile b/Pipfile index 1e106f07..62d475e4 100644 --- a/Pipfile +++ b/Pipfile @@ -30,3 +30,7 @@ requests = "*" [requires] python_version = "3.7" + +[scripts] +makemigrations = "python manage.py makemigrations" +django_shell = "python manage.py shell" diff --git a/docker-compose.yml b/docker-compose.yml index 04ec51de..0c504c40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ version: "3.6" services: postgres: image: postgres:11-alpine + ports: + - "127.0.0.1:7777:5432" environment: POSTGRES_DB: pysite POSTGRES_PASSWORD: supersecretpassword @@ -30,7 +32,7 @@ services: - .:/app:ro - staticfiles:/var/www/static environment: - DATABASE_URL: postgres://pysite:supersecretpassword@postgres/pysite + DATABASE_URL: postgres://pysite:supersecretpassword@postgres:5432/pysite DEBUG: "true" SECRET_KEY: suitable-for-development-only STATIC_ROOT: /var/www/static diff --git a/docker/pysite.dockerapp b/docker/pysite.dockerapp index dc472b2e..2426008e 100644 --- a/docker/pysite.dockerapp +++ b/docker/pysite.dockerapp @@ -25,6 +25,8 @@ services: postgres: image: postgres:11-alpine + ports: + - "127.0.0.1:5432:5432" environment: POSTGRES_DB: "${pg_db}" POSTGRES_USER: "${pg_user}" diff --git a/pydis_site/apps/main/models/__init__.py b/pydis_site/apps/main/models/__init__.py new file mode 100644 index 00000000..7a2cbb0b --- /dev/null +++ b/pydis_site/apps/main/models/__init__.py @@ -0,0 +1,3 @@ +from .repo_data import RepoData + +__all__ = ["RepoData"] diff --git a/pydis_site/apps/main/models/repo_data.py b/pydis_site/apps/main/models/repo_data.py new file mode 100644 index 00000000..40540410 --- /dev/null +++ b/pydis_site/apps/main/models/repo_data.py @@ -0,0 +1,33 @@ +from django.db import models +from django.utils import timezone + + +class RepoData(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/main/views/home.py b/pydis_site/apps/main/views/home.py index 8f45b912..883177bb 100644 --- a/pydis_site/apps/main/views/home.py +++ b/pydis_site/apps/main/views/home.py @@ -1,47 +1,69 @@ import requests + from django.shortcuts import render +from django.utils import timezone from django.views import View +from pydis_site.apps.main.models import RepoData +GITHUB_API = "https://api.github.com/repos" +REPOS = [ + "python-discord/site", + "python-discord/bot", + "python-discord/snekbox", + "python-discord/seasonalbot", + "python-discord/django-simple-bulma", + "python-discord/django-crispy-bulma", +] - -class Home(View): - - projects = [ - "site", - "bot", - "snekbox", - "seasonalbot", - "django-simple-bulma", - "django-crispy-bulma", - ] - - def _get_repo_data(self): - """ - This will get language, stars and forks for the projects listed in Home.projects. - - Returns a dictionary with the data, in a template-friendly manner. The rate limit for - this particular endpoint is 30 requests per minute. This should be plenty for now, - but if we ever run into rate limiting issues, we should implement some form of caching - for this data. - """ - - # Gotta authenticate, or we get terrible rate limits. - - # We need to query the Search API https://developer.github.com/v3/search/, using a single - # query to query for all of the projects at the same time, and making sure we cache that data - # and make the request no more often than once per minute or something reasonable - # like that. - - endpoint = "https://api.github.com/search/repositories?q=" + "repo+name+separated+by+pluses" - - # And finally - - - +# https://api.github.com/users/python-discord/repos gets all the data in one query. +class Home(View): + def _get_repo_data(self, repo_name): + """This will get language, stars and forks for the requested GitHub repo.""" + + # Try to get the data from the cache + try: + repo_data = RepoData.objects.get(repo_name=repo_name) + + # If the data is older than 2 minutes, we should refresh it + if (timezone.now() - repo_data.last_updated).seconds > 120: + + # Fetch the data from the GitHub API + api_data = requests.get(f"{GITHUB_API}/{repo_name}") + api_data = api_data.json() + + # Update the current object, and save it. + 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"] + repo_data.save() + return repo_data + + # Otherwise, if the data is fresher than 2 minutes old, we should just return it. + else: + return repo_data + + # If this is raised, the data isn't there at all, so we'll need to create it. + except RepoData.DoesNotExist: + api_data = requests.get(f"{GITHUB_API}/{repo_name}") + api_data = api_data.json() + repo_data = RepoData( + description=api_data["description"], + forks=api_data["forks_count"], + stargazers=api_data["stargazers_count"], + language=api_data["language"], + ) + repo_data.save() + return repo_data def get(self, request): + # Collect the repo data + repo_data = [] + for repo in REPOS: + repo_data.append(self._get_repo_data(repo)) + # Call the GitHub API and ask it for some data - return render(request, "home/index.html", {}) + return render(request, "home/index.html", {"repo_data": repo_data}) -- cgit v1.2.3 From 52d178d7e469fad802bf4dec06453d1764a08787 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 18 Apr 2019 23:38:31 +0200 Subject: Addressing all of volcyys review comments, and adding linting and full test coverage. --- Pipfile | 3 +- pydis_site/apps/main/migrations/0001_initial.py | 2 +- pydis_site/apps/main/models/__init__.py | 4 +-- pydis_site/apps/main/models/repo_data.py | 2 +- pydis_site/apps/main/templatetags/__init__.py | 3 ++ pydis_site/apps/main/tests.py | 16 ---------- pydis_site/apps/main/tests/__init__.py | 0 .../apps/main/tests/test_repodata_helpers.py | 29 ++++++++++++++++++ pydis_site/apps/main/tests/test_templatetags.py | 8 +++++ pydis_site/apps/main/tests/test_views.py | 9 ++++++ pydis_site/apps/main/urls.py | 6 ++-- pydis_site/apps/main/views/__init__.py | 4 +-- pydis_site/apps/main/views/home.py | 35 ++++++++++------------ 13 files changed, 75 insertions(+), 46 deletions(-) delete mode 100644 pydis_site/apps/main/tests.py create mode 100644 pydis_site/apps/main/tests/__init__.py create mode 100644 pydis_site/apps/main/tests/test_repodata_helpers.py create mode 100644 pydis_site/apps/main/tests/test_templatetags.py create mode 100644 pydis_site/apps/main/tests/test_views.py (limited to 'Pipfile') diff --git a/Pipfile b/Pipfile index 62d475e4..fb9bee12 100644 --- a/Pipfile +++ b/Pipfile @@ -26,7 +26,7 @@ djangorestframework-bulk = "~=0.2.1" psycopg2-binary = "~=2.8" django-simple-bulma = ">=1.1.6,<2.0" django-crispy-bulma = ">=0.1.2,<2.0" -requests = "*" +requests = "~=2.21" [requires] python_version = "3.7" @@ -34,3 +34,4 @@ python_version = "3.7" [scripts] makemigrations = "python manage.py makemigrations" django_shell = "python manage.py shell" +test = "python manage.py test -v 2" diff --git a/pydis_site/apps/main/migrations/0001_initial.py b/pydis_site/apps/main/migrations/0001_initial.py index 20a43d39..a2bf9f3e 100644 --- a/pydis_site/apps/main/migrations/0001_initial.py +++ b/pydis_site/apps/main/migrations/0001_initial.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='RepoData', + 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)), diff --git a/pydis_site/apps/main/models/__init__.py b/pydis_site/apps/main/models/__init__.py index 7a2cbb0b..f327795a 100644 --- a/pydis_site/apps/main/models/__init__.py +++ b/pydis_site/apps/main/models/__init__.py @@ -1,3 +1,3 @@ -from .repo_data import RepoData +from .repo_data import RepositoryMetadata -__all__ = ["RepoData"] +__all__ = ["RepositoryMetadata"] diff --git a/pydis_site/apps/main/models/repo_data.py b/pydis_site/apps/main/models/repo_data.py index 40540410..c975c904 100644 --- a/pydis_site/apps/main/models/repo_data.py +++ b/pydis_site/apps/main/models/repo_data.py @@ -2,7 +2,7 @@ from django.db import models from django.utils import timezone -class RepoData(models.Model): +class RepositoryMetadata(models.Model): """Information about one of our repos fetched from the GitHub API.""" last_updated = models.DateTimeField( diff --git a/pydis_site/apps/main/templatetags/__init__.py b/pydis_site/apps/main/templatetags/__init__.py index e69de29b..70aca169 100644 --- a/pydis_site/apps/main/templatetags/__init__.py +++ b/pydis_site/apps/main/templatetags/__init__.py @@ -0,0 +1,3 @@ +from .extra_filters import starts_with + +__all__ = ["starts_with"] diff --git a/pydis_site/apps/main/tests.py b/pydis_site/apps/main/tests.py deleted file mode 100644 index 733ddaa3..00000000 --- a/pydis_site/apps/main/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/main/tests/__init__.py b/pydis_site/apps/main/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/main/tests/test_repodata_helpers.py b/pydis_site/apps/main/tests/test_repodata_helpers.py new file mode 100644 index 00000000..66aaa3d6 --- /dev/null +++ b/pydis_site/apps/main/tests/test_repodata_helpers.py @@ -0,0 +1,29 @@ +from django.conf import settings +from django.test import TestCase + +from pydis_site.apps.main.models import RepositoryMetadata +from pydis_site.apps.main.views import HomeView + + +class TestRepositoryMetadataHelpers(TestCase): + + def test_returns_metadata(self): + """Test if the _get_repo_data helper actually returns what it should.""" + + home_view = HomeView() + metadata = home_view._get_repo_data() + + self.assertIsInstance(metadata[0], RepositoryMetadata) + self.assertEquals(len(metadata), len(settings.HOMEPAGE_REPOS)) + + def test_returns_api_data(self): + """Tests if the _get_api_data helper returns what it should.""" + + home_view = HomeView() + api_data = home_view._get_api_data() + repo = settings.HOMEPAGE_REPOS[0] + + self.assertIsInstance(api_data, dict) + self.assertEquals(len(api_data), len(settings.HOMEPAGE_REPOS)) + self.assertIn(repo, api_data.keys()) + self.assertIn("stargazers_count", api_data[repo]) diff --git a/pydis_site/apps/main/tests/test_templatetags.py b/pydis_site/apps/main/tests/test_templatetags.py new file mode 100644 index 00000000..44a5c491 --- /dev/null +++ b/pydis_site/apps/main/tests/test_templatetags.py @@ -0,0 +1,8 @@ +from django.test import TestCase + +from pydis_site.apps.main.templatetags import starts_with + + +class TestTemplateTags(TestCase): + def test_starts_with(self): + self.assertTrue(starts_with('foo', 'f')) diff --git a/pydis_site/apps/main/tests/test_views.py b/pydis_site/apps/main/tests/test_views.py new file mode 100644 index 00000000..73678b0a --- /dev/null +++ b/pydis_site/apps/main/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/main/urls.py b/pydis_site/apps/main/urls.py index 11142040..edef9873 100644 --- a/pydis_site/apps/main/urls.py +++ b/pydis_site/apps/main/urls.py @@ -1,11 +1,11 @@ from django.contrib import admin from django.urls import path -from .views import Home +from .views import HomeView -app_name = 'home' +app_name = 'main' urlpatterns = [ - path('', Home.as_view(), name='home.index'), + path('', HomeView.as_view(), name='home'), path('admin/', admin.site.urls) ] diff --git a/pydis_site/apps/main/views/__init__.py b/pydis_site/apps/main/views/__init__.py index ab52894b..971d73a3 100644 --- a/pydis_site/apps/main/views/__init__.py +++ b/pydis_site/apps/main/views/__init__.py @@ -1,3 +1,3 @@ -from .home import Home +from .home import HomeView -__all__ = ["Home"] +__all__ = ["HomeView"] diff --git a/pydis_site/apps/main/views/home.py b/pydis_site/apps/main/views/home.py index e06b3783..d9255ed2 100644 --- a/pydis_site/apps/main/views/home.py +++ b/pydis_site/apps/main/views/home.py @@ -4,15 +4,17 @@ from django.shortcuts import render from django.utils import timezone from django.views import View -from pydis_site.apps.main.models import RepoData +from pydis_site.apps.main.models import RepositoryMetadata GITHUB_API = "https://api.github.com/users/python-discord/repos" -class Home(View): +class HomeView(View): - def _get_api_data(self): + @staticmethod + def _get_api_data(): """Call the GitHub API and get information about our repos.""" + repo_dict = {repo_name: {} for repo_name in settings.HOMEPAGE_REPOS} # Fetch the data from the GitHub API @@ -20,7 +22,6 @@ class Home(View): api_data = api_data.json() # Process the API data into our dict - print(f"repo_dict = {repo_dict}") for repo in api_data: full_name = repo["full_name"] @@ -32,22 +33,18 @@ class Home(View): "forks_count": repo["forks_count"], "stargazers_count": repo["stargazers_count"], } - print(f"repo_dict after processing = {repo_dict}") return repo_dict def _get_repo_data(self): - """Build a list of RepoData objects that we can use to populate the front page.""" + """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 = RepoData.objects.get(repo_name="python-discord/site") + repo_data = RepositoryMetadata.objects.get(repo_name="python-discord/site") - # If the data is older than 2 minutes, we should refresh it. THIS PROBABLY ALWAYS FAILS? + # If the data is older than 2 minutes, we should refresh it. if (timezone.now() - repo_data.last_updated).seconds > 120: - diff = (timezone.now() - repo_data.last_updated).seconds - print(f"okay baby, it's old! the seconds difference comes to: {diff}") - # Get new data from API api_data_container = self._get_api_data() repo_data_container = [] @@ -55,13 +52,13 @@ class Home(View): # Update or create all RepoData objects in settings.HOMEPAGE_REPOS for repo_name, api_data in api_data_container.items(): try: - repo_data = RepoData.objects.get(repo_name=repo_name) + 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 RepoData.DoesNotExist: - repo_data = RepoData( + except RepositoryMetadata.DoesNotExist: + repo_data = RepositoryMetadata( repo_name=api_data["full_name"], description=api_data["description"], forks=api_data["forks_count"], @@ -74,10 +71,10 @@ class Home(View): # Otherwise, if the data is fresher than 2 minutes old, we should just return it. else: - return list(RepoData.objects.all()) + return RepositoryMetadata.objects.all() # If this is raised, the database has no repodata at all, we will create them all. - except RepoData.DoesNotExist: + except RepositoryMetadata.DoesNotExist: # Get new data from API api_data_container = self._get_api_data() @@ -85,7 +82,7 @@ class Home(View): # Create all the repodata records in the database. for api_data in api_data_container.values(): - repo_data = RepoData( + repo_data = RepositoryMetadata( repo_name=api_data["full_name"], description=api_data["description"], forks=api_data["forks_count"], @@ -98,9 +95,7 @@ class Home(View): return repo_data_container def get(self, request): + """Collect repo data and render the homepage view""" - # Collect the repo data repo_data = self._get_repo_data() - - # Call the GitHub API and ask it for some data return render(request, "home/index.html", {"repo_data": repo_data}) -- cgit v1.2.3 From 508ed84cf39a44d1676b93ac0021947d1fb05d96 Mon Sep 17 00:00:00 2001 From: Scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:17:38 +1000 Subject: Pin flake8-bandit to 1.0.2 to fix ExceptHandler error. --- Pipfile | 2 +- Pipfile.lock | 30 ++++++++++-------------------- 2 files changed, 11 insertions(+), 21 deletions(-) (limited to 'Pipfile') diff --git a/Pipfile b/Pipfile index fb9bee12..7f8ac937 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,7 @@ verify_ssl = true [dev-packages] flake8 = "~=3.7.7" -flake8-bandit = "~=2.1.0" +flake8-bandit = "==1.0.2" flake8-bugbear = "~=19.3.0" flake8-import-order = "~=0.18.1" flake8-string-format = "~=0.2.3" diff --git a/Pipfile.lock b/Pipfile.lock index 95229e67..a20d1687 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ddbf8b15e4460b7b8467fd97cbcfe8a8dddae27fb627f1576e1d2280be7aa422" + "sha256": "0ff5e3b68fa729ba88aa32dc3aae4012d1d54e5ad620f635bc5136603900ebc0" }, "pipfile-spec": 6, "requires": { @@ -80,11 +80,11 @@ }, "django-simple-bulma": { "hashes": [ - "sha256:420042e26dd4bc70b148fc721bd77a48130b62b3d91b977e56f8232a4cfac555", - "sha256:beb323aa1c51e5efee398b6e5a2b481b31a0be7505e3118d140c65349f637100" + "sha256:7dcc04a11b5a3aefb6ec57cb211c161df8421ea333638e03c9e7db87465fead2", + "sha256:f4bb4833c3272ec49e4901a53254de8e8d267da48b88f6f79af4c32f9f70c504" ], "index": "pypi", - "version": "==1.1.6" + "version": "==1.1.7" }, "djangorestframework": { "hashes": [ @@ -194,10 +194,10 @@ }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" ], - "version": "==1.24.1" + "version": "==1.24.2" } }, "develop": { @@ -217,42 +217,32 @@ }, "coverage": { "hashes": [ - "sha256:0c5fe441b9cfdab64719f24e9684502a59432df7570521563d7b1aff27ac755f", - "sha256:2b412abc4c7d6e019ce7c27cbc229783035eef6d5401695dccba80f481be4eb3", "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", - "sha256:42692db854d13c6c5e9541b6ffe0fe921fe16c9c446358d642ccae1462582d3b", "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", - "sha256:4ec30ade438d1711562f3786bea33a9da6107414aed60a5daa974d50a8c2c351", "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", - "sha256:6899797ac384b239ce1926f3cb86ffc19996f6fa3a1efbb23cb49e0c12d8c18c", "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", - "sha256:8e679d1bde5e2de4a909efb071f14b472a678b788904440779d2c449c0355b27", "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", - "sha256:93f965415cc51604f571e491f280cff0f5be35895b4eb5e55b47ae90c02a497b", "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", - "sha256:a9abc8c480e103dc05d9b332c6cc9fb1586330356fc14f1aa9c0ca5745097d19", "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", - "sha256:c22ab9f96cbaff05c6a84e20ec856383d27eae09e511d3e6ac4479489195861d", "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", - "sha256:ca58eba39c68010d7e87a823f22a081b5290e3e3c64714aac3c91481d8b34d22", "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", @@ -279,11 +269,11 @@ }, "flake8-bandit": { "hashes": [ - "sha256:5eac24fa9fef532e4e4ce599c5b3c5248c5cc435d2927537b529b0a7bcb72467", - "sha256:be5840923ccf06cac6a8893a2f0abc17f03b6b9fdb5284d796f722b69c8f840b" + "sha256:a66c7b42af9530d5e988851ccee02958a51a85d46f1f4609ecc3546948f809b8", + "sha256:f7c3421fd9aebc63689c0693511e16dcad678fd4a0ce624b78ca91ae713eacdc" ], "index": "pypi", - "version": "==2.1.0" + "version": "==1.0.2" }, "flake8-bugbear": { "hashes": [ -- cgit v1.2.3 From 5d9d6eec3159a9c87ca45dc5b294534daf8495fa Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Fri, 19 Apr 2019 15:29:27 +0200 Subject: Addressing all comments in volcyy's second review. The tests now mock the API calls so we don't have to actually call the API every time we run tests. --- Pipfile | 2 +- pydis_site/apps/home/__init__.py | 0 pydis_site/apps/home/apps.py | 5 + pydis_site/apps/home/migrations/0001_initial.py | 26 +++++ pydis_site/apps/home/migrations/__init__.py | 0 pydis_site/apps/home/models/__init__.py | 3 + pydis_site/apps/home/models/repo_data.py | 33 +++++++ pydis_site/apps/home/templatetags/__init__.py | 3 + pydis_site/apps/home/templatetags/extra_filters.py | 8 ++ pydis_site/apps/home/tests/__init__.py | 0 .../apps/home/tests/mock_github_api_response.json | 44 +++++++++ .../apps/home/tests/test_repodata_helpers.py | 92 +++++++++++++++++ pydis_site/apps/home/tests/test_templatetags.py | 8 ++ pydis_site/apps/home/tests/test_views.py | 9 ++ pydis_site/apps/home/urls.py | 10 ++ pydis_site/apps/home/views/__init__.py | 3 + pydis_site/apps/home/views/home.py | 110 +++++++++++++++++++++ pydis_site/apps/main/__init__.py | 0 pydis_site/apps/main/apps.py | 5 - pydis_site/apps/main/migrations/0001_initial.py | 26 ----- pydis_site/apps/main/migrations/__init__.py | 0 pydis_site/apps/main/models/__init__.py | 3 - pydis_site/apps/main/models/repo_data.py | 33 ------- pydis_site/apps/main/templatetags/__init__.py | 3 - pydis_site/apps/main/templatetags/extra_filters.py | 8 -- pydis_site/apps/main/tests/__init__.py | 0 .../apps/main/tests/test_repodata_helpers.py | 66 ------------- pydis_site/apps/main/tests/test_templatetags.py | 8 -- pydis_site/apps/main/tests/test_views.py | 9 -- pydis_site/apps/main/urls.py | 11 --- pydis_site/apps/main/views/__init__.py | 3 - pydis_site/apps/main/views/home.py | 101 ------------------- pydis_site/hosts.py | 2 +- pydis_site/settings.py | 12 +-- pydis_site/static/css/base/base.css | 32 +++--- pydis_site/static/css/home/index.css | 12 ++- pydis_site/templates/base/navbar.html | 2 +- pydis_site/templates/home/index.html | 16 +-- pydis_site/urls.py | 2 +- 39 files changed, 391 insertions(+), 319 deletions(-) create mode 100644 pydis_site/apps/home/__init__.py create mode 100644 pydis_site/apps/home/apps.py create mode 100644 pydis_site/apps/home/migrations/0001_initial.py create mode 100644 pydis_site/apps/home/migrations/__init__.py create mode 100644 pydis_site/apps/home/models/__init__.py create mode 100644 pydis_site/apps/home/models/repo_data.py create mode 100644 pydis_site/apps/home/templatetags/__init__.py create mode 100644 pydis_site/apps/home/templatetags/extra_filters.py create mode 100644 pydis_site/apps/home/tests/__init__.py create mode 100644 pydis_site/apps/home/tests/mock_github_api_response.json create mode 100644 pydis_site/apps/home/tests/test_repodata_helpers.py create mode 100644 pydis_site/apps/home/tests/test_templatetags.py create mode 100644 pydis_site/apps/home/tests/test_views.py create mode 100644 pydis_site/apps/home/urls.py create mode 100644 pydis_site/apps/home/views/__init__.py create mode 100644 pydis_site/apps/home/views/home.py delete mode 100644 pydis_site/apps/main/__init__.py delete mode 100644 pydis_site/apps/main/apps.py delete mode 100644 pydis_site/apps/main/migrations/0001_initial.py delete mode 100644 pydis_site/apps/main/migrations/__init__.py delete mode 100644 pydis_site/apps/main/models/__init__.py delete mode 100644 pydis_site/apps/main/models/repo_data.py delete mode 100644 pydis_site/apps/main/templatetags/__init__.py delete mode 100644 pydis_site/apps/main/templatetags/extra_filters.py delete mode 100644 pydis_site/apps/main/tests/__init__.py delete mode 100644 pydis_site/apps/main/tests/test_repodata_helpers.py delete mode 100644 pydis_site/apps/main/tests/test_templatetags.py delete mode 100644 pydis_site/apps/main/tests/test_views.py delete mode 100644 pydis_site/apps/main/urls.py delete mode 100644 pydis_site/apps/main/views/__init__.py delete mode 100644 pydis_site/apps/main/views/home.py (limited to 'Pipfile') diff --git a/Pipfile b/Pipfile index 7f8ac937..953de854 100644 --- a/Pipfile +++ b/Pipfile @@ -34,4 +34,4 @@ python_version = "3.7" [scripts] makemigrations = "python manage.py makemigrations" django_shell = "python manage.py shell" -test = "python manage.py test -v 2" +test = "python manage.py test" diff --git a/pydis_site/apps/home/__init__.py b/pydis_site/apps/home/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/home/apps.py b/pydis_site/apps/home/apps.py new file mode 100644 index 00000000..90dc7137 --- /dev/null +++ b/pydis_site/apps/home/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class HomeConfig(AppConfig): + name = 'home' 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/migrations/__init__.py b/pydis_site/apps/home/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/apps/home/models/__init__.py b/pydis_site/apps/home/models/__init__.py new file mode 100644 index 00000000..f327795a --- /dev/null +++ b/pydis_site/apps/home/models/__init__.py @@ -0,0 +1,3 @@ +from .repo_data import RepositoryMetadata + +__all__ = ["RepositoryMetadata"] diff --git a/pydis_site/apps/home/models/repo_data.py b/pydis_site/apps/home/models/repo_data.py new file mode 100644 index 00000000..c975c904 --- /dev/null +++ b/pydis_site/apps/home/models/repo_data.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 new file mode 100644 index 00000000..70aca169 --- /dev/null +++ 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/templatetags/extra_filters.py b/pydis_site/apps/home/templatetags/extra_filters.py new file mode 100644 index 00000000..edffe9ac --- /dev/null +++ b/pydis_site/apps/home/templatetags/extra_filters.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + + +@register.filter +def starts_with(value: str, arg: str): + return value.startswith(arg) diff --git a/pydis_site/apps/home/tests/__init__.py b/pydis_site/apps/home/tests/__init__.py new file mode 100644 index 00000000..e69de29b 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..8ab2a467 --- /dev/null +++ b/pydis_site/apps/home/tests/test_repodata_helpers.py @@ -0,0 +1,92 @@ +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): + """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, _): + """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, _): + """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=121), # Make the data 2 minutes old. + ) + 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, _): + """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]) 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 new file mode 100644 index 00000000..d8dba2f6 --- /dev/null +++ b/pydis_site/apps/home/urls.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from django.urls import path + +from .views import HomeView + +app_name = 'home' +urlpatterns = [ + path('', HomeView.as_view(), name='home'), + path('admin/', admin.site.urls) +] 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..c91d13e2 --- /dev/null +++ b/pydis_site/apps/home/views/home.py @@ -0,0 +1,110 @@ +import requests +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 view""" + + github_api = "https://api.github.com/users/python-discord/repos" + + # 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): + """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): + """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 older than 2 minutes, we should refresh it. + if (timezone.now() - repo_data.last_updated).seconds > 120: + + # 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): + """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/main/__init__.py b/pydis_site/apps/main/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/main/apps.py b/pydis_site/apps/main/apps.py deleted file mode 100644 index 90dc7137..00000000 --- a/pydis_site/apps/main/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class HomeConfig(AppConfig): - name = 'home' diff --git a/pydis_site/apps/main/migrations/0001_initial.py b/pydis_site/apps/main/migrations/0001_initial.py deleted file mode 100644 index a2bf9f3e..00000000 --- a/pydis_site/apps/main/migrations/0001_initial.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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/main/migrations/__init__.py b/pydis_site/apps/main/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/main/models/__init__.py b/pydis_site/apps/main/models/__init__.py deleted file mode 100644 index f327795a..00000000 --- a/pydis_site/apps/main/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .repo_data import RepositoryMetadata - -__all__ = ["RepositoryMetadata"] diff --git a/pydis_site/apps/main/models/repo_data.py b/pydis_site/apps/main/models/repo_data.py deleted file mode 100644 index c975c904..00000000 --- a/pydis_site/apps/main/models/repo_data.py +++ /dev/null @@ -1,33 +0,0 @@ -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/main/templatetags/__init__.py b/pydis_site/apps/main/templatetags/__init__.py deleted file mode 100644 index 70aca169..00000000 --- a/pydis_site/apps/main/templatetags/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .extra_filters import starts_with - -__all__ = ["starts_with"] diff --git a/pydis_site/apps/main/templatetags/extra_filters.py b/pydis_site/apps/main/templatetags/extra_filters.py deleted file mode 100644 index edffe9ac..00000000 --- a/pydis_site/apps/main/templatetags/extra_filters.py +++ /dev/null @@ -1,8 +0,0 @@ -from django import template - -register = template.Library() - - -@register.filter -def starts_with(value: str, arg: str): - return value.startswith(arg) diff --git a/pydis_site/apps/main/tests/__init__.py b/pydis_site/apps/main/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/main/tests/test_repodata_helpers.py b/pydis_site/apps/main/tests/test_repodata_helpers.py deleted file mode 100644 index 040acb21..00000000 --- a/pydis_site/apps/main/tests/test_repodata_helpers.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import timedelta - -from django.conf import settings -from django.test import TestCase -from django.utils import timezone - -from pydis_site.apps.main.models import RepositoryMetadata -from pydis_site.apps.main.views import HomeView - - -class TestRepositoryMetadataHelpers(TestCase): - - def test_returns_metadata(self): - """Test if the _get_repo_data helper actually returns what it should.""" - - home_view = HomeView() - metadata = home_view._get_repo_data() - - self.assertIsInstance(metadata[0], RepositoryMetadata) - self.assertEquals(len(metadata), len(settings.HOMEPAGE_REPOS)) - - def test_returns_cached_metadata(self): - """Test if the _get_repo_data helper returns cached data when available.""" - - home_view = HomeView() - repo_data = RepositoryMetadata( - repo_name="python-discord/site", - description="testrepo", - forks=42, - stargazers=42, - language="English", - ) - repo_data.save() - metadata = home_view._get_repo_data() - - self.assertIsInstance(metadata[0], RepositoryMetadata) - print(metadata[0]) # Tests the __str__ in the model - - def test_refresh_stale_metadata(self): - """Test if the _get_repo_data helper will refresh when the data is stale""" - - home_view = HomeView() - repo_data = RepositoryMetadata( - repo_name="python-discord/site", - description="testrepo", - forks=42, - stargazers=42, - language="English", - last_updated=timezone.now() - timedelta(seconds=121), # Make the data 2 minutes old. - ) - repo_data.save() - metadata = home_view._get_repo_data() - - self.assertIsInstance(metadata[0], RepositoryMetadata) - - def test_returns_api_data(self): - """Tests if the _get_api_data helper returns what it should.""" - - home_view = HomeView() - api_data = home_view._get_api_data() - repo = settings.HOMEPAGE_REPOS[0] - - self.assertIsInstance(api_data, dict) - self.assertEquals(len(api_data), len(settings.HOMEPAGE_REPOS)) - self.assertIn(repo, api_data.keys()) - self.assertIn("stargazers_count", api_data[repo]) diff --git a/pydis_site/apps/main/tests/test_templatetags.py b/pydis_site/apps/main/tests/test_templatetags.py deleted file mode 100644 index 44a5c491..00000000 --- a/pydis_site/apps/main/tests/test_templatetags.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.test import TestCase - -from pydis_site.apps.main.templatetags import starts_with - - -class TestTemplateTags(TestCase): - def test_starts_with(self): - self.assertTrue(starts_with('foo', 'f')) diff --git a/pydis_site/apps/main/tests/test_views.py b/pydis_site/apps/main/tests/test_views.py deleted file mode 100644 index 73678b0a..00000000 --- a/pydis_site/apps/main/tests/test_views.py +++ /dev/null @@ -1,9 +0,0 @@ -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/main/urls.py b/pydis_site/apps/main/urls.py deleted file mode 100644 index edef9873..00000000 --- a/pydis_site/apps/main/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.contrib import admin -from django.urls import path - -from .views import HomeView - - -app_name = 'main' -urlpatterns = [ - path('', HomeView.as_view(), name='home'), - path('admin/', admin.site.urls) -] diff --git a/pydis_site/apps/main/views/__init__.py b/pydis_site/apps/main/views/__init__.py deleted file mode 100644 index 971d73a3..00000000 --- a/pydis_site/apps/main/views/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .home import HomeView - -__all__ = ["HomeView"] diff --git a/pydis_site/apps/main/views/home.py b/pydis_site/apps/main/views/home.py deleted file mode 100644 index d9255ed2..00000000 --- a/pydis_site/apps/main/views/home.py +++ /dev/null @@ -1,101 +0,0 @@ -import requests -from django.conf import settings -from django.shortcuts import render -from django.utils import timezone -from django.views import View - -from pydis_site.apps.main.models import RepositoryMetadata - -GITHUB_API = "https://api.github.com/users/python-discord/repos" - - -class HomeView(View): - - @staticmethod - def _get_api_data(): - """Call the GitHub API and get information about our repos.""" - - repo_dict = {repo_name: {} for repo_name in settings.HOMEPAGE_REPOS} - - # Fetch the data from the GitHub API - api_data = requests.get(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 settings.HOMEPAGE_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): - """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 older than 2 minutes, we should refresh it. - if (timezone.now() - repo_data.last_updated).seconds > 120: - - # Get new data from API - api_data_container = self._get_api_data() - repo_data_container = [] - - # Update or create all RepoData objects in settings.HOMEPAGE_REPOS - for repo_name, api_data in api_data_container.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() - repo_data_container.append(repo_data) - return repo_data_container - - # 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_data_container = self._get_api_data() - repo_data_container = [] - - # Create all the repodata records in the database. - for api_data in api_data_container.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() - repo_data_container.append(repo_data) - - return repo_data_container - - def get(self, request): - """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/hosts.py b/pydis_site/hosts.py index 9e58628c..0fa4793d 100644 --- a/pydis_site/hosts.py +++ b/pydis_site/hosts.py @@ -5,5 +5,5 @@ host_patterns = patterns( '', host(r'admin', 'pydis_site.apps.admin.urls', name="admin"), host(r'api', 'pydis_site.apps.api.urls', name='api'), - host(r'.*', 'pydis_site.apps.main.urls', name=settings.DEFAULT_HOST) + host(r'.*', 'pydis_site.apps.home.urls', name=settings.DEFAULT_HOST) ) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index eb21de10..891084c9 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -63,7 +63,7 @@ else: INSTALLED_APPS = [ 'pydis_site.apps.api', - 'pydis_site.apps.main', + 'pydis_site.apps.home', 'django.contrib.admin', 'django.contrib.auth', @@ -258,13 +258,3 @@ BULMA_SETTINGS = { "footer-padding": "1rem 1.5rem 1rem", } } - -# Which of our GitHub repos should be displayed on the front page, and in which order? -HOMEPAGE_REPOS = [ - "python-discord/site", - "python-discord/bot", - "python-discord/snekbox", - "python-discord/seasonalbot", - "python-discord/django-simple-bulma", - "python-discord/django-crispy-bulma", -] diff --git a/pydis_site/static/css/base/base.css b/pydis_site/static/css/base/base.css index 969511f4..4f656189 100644 --- a/pydis_site/static/css/base/base.css +++ b/pydis_site/static/css/base/base.css @@ -1,15 +1,15 @@ html { - overflow:auto; + overflow: auto; } body.site { - display: flex; - min-height: 100vh; - flex-direction: column; + display: flex; + min-height: 100vh; + flex-direction: column; } main.site-content { - flex: 1; + flex: 1; } div.card.has-equal-height { @@ -19,20 +19,20 @@ div.card.has-equal-height { } #django-logo { - padding-bottom: 2px; - background: url(https://static.djangoproject.com/img/logos/django-logo-negative.png) no-repeat center; - filter: grayscale(1) invert(0.02); - background-size: 52px 25.5px; - background-position: -1px -2px; - color: #00000000; + padding-bottom: 2px; + background: url(https://static.djangoproject.com/img/logos/django-logo-negative.png) no-repeat center; + filter: grayscale(1) invert(0.02); + background-size: 52px 25.5px; + background-position: -1px -2px; + color: #00000000; } #bulma-logo { - padding-left: 18px; - height: 20px; - background: url(https://bulma.io/images/bulma-logo-white.png) no-repeat center; - background-size: 60px; - color: #00000000; + padding-left: 18px; + height: 20px; + background: url(https://bulma.io/images/bulma-logo-white.png) no-repeat center; + background-size: 60px; + color: #00000000; } #pydis-text { diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index 9aad02e5..0ad2e6b5 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -18,7 +18,7 @@ } .navbar-item.is-fullsize { - padding:0; + padding: 0; } .navbar-item.is-fullsize img { @@ -45,7 +45,7 @@ .video-container object, .video-container embed { width: 100%; - height: calc(92vw*0.5625); + height: calc(92vw * 0.5625); margin: 8px auto auto auto; } @@ -103,11 +103,13 @@ span.repo-language-dot.css { flex: none; width: 50%; } + .columns:not(.is-desktop) { display: flex; } + .video-container iframe { - height: calc(42vw*0.5625); + height: calc(42vw * 0.5625); max-height: 371px; max-width: 660px; } @@ -118,11 +120,13 @@ span.repo-language-dot.css { flex: none; width: 100%; } + .columns:not(.is-desktop) { display: block; } + .video-container iframe { - height: calc(92vw*0.5625); + height: calc(92vw * 0.5625); max-height: none; max-width: none; } diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index 6a7e683f..ed6ae118 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -8,7 +8,7 @@ Python Discord logo - + {# The navbar "burger" which appears when rendered on screens that can't fit the entire nav #}
diff --git a/pydis_site/urls.py b/pydis_site/urls.py index e4ef058b..c68375da 100644 --- a/pydis_site/urls.py +++ b/pydis_site/urls.py @@ -2,5 +2,5 @@ from django.urls import include, path urlpatterns = ( - path('', include('pydis_site.apps.main.urls', namespace='home')), + path('', include('pydis_site.apps.home.urls', namespace='home')), ) -- cgit v1.2.3