Repository: python-discord/site

Issue:
Remove useless output from `manage.py test`
Currently, `python manage.py test` looks like this:

```
jc@zulu:~/workspace/pydis-site$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........................./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/whitenoise/base.py:115: UserWarning: No directory at: /home/jc/workspace/pydis-site/staticfiles/
  warnings.warn(u"No directory at: {}".format(root))
...................................................../home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-24 00:49:32.508957) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
/home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-28 19:49:32.509695) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-23 23:49:32.513969) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
/home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Inf
raction.expires_at received a naive datetime (2022-02-24 01:49:32.513972) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-24 00:49:32.520141) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
../home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-24 00:49:32.527153) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
........./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Infraction.expires_at received a naive datetime (2022-02-24 01:49:32.572470) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
................./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField MessageDeletionContext.creation received a naive datetime (2022-02-23 19:49:32.652731) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField MessageDeletionContext.creation received a naive datetime (2022-02-23 19:49:32.655606) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
................................................................./home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/py
thon3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Reminder.expiration received a naive datetime (2022-02-23 19:49:33.030946) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
../home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Reminder.expiration received a naive datetime (2022-02-23 19:49:33.044206) whi
le time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
/home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Reminder.expiration received a naive datetime (2022-02-23 19:49:33.045060) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
.../home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Reminder.expiration received a naive datetime (2022-02-23 19:49:33.062039) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
../home/jc/.cache/pypoetry/virtualenvs/site-_f36zgtP-py3.9/lib/python3.9/site-packages/django/db/models/fields/__init__.py:1367: RuntimeWarning: DateTimeField Reminder.expiration received a naive datetime (2022-02-23 19:49:33.074047) while time zone support is active.
  warnings.warn("DateTimeField %s received a naive datetime (%s)"
........................................................................................Unable to parse the GitHub repository metadata from response!
...............................................
----------------------------------------------------------------------
Ran 316 tests in 6.241s

OK
Destroying test database for alias 'default'...
```

Especially when trying to debug a test failure, this is hard to read through.

- Remove the warnings
- Ensure that the warnings don't appear again (e.g. with `-Werror`)
- Capture log output by default


Discussion:
> Ensure that the warnings don't appear again

If warnings don't appear then how will we know of their existence so that they can be addressed rather than ignored?
-Werror

> Am 23.02.2022 um 22:47 schrieb Mark ***@***.***>:
> 
> ﻿
> Ensure that the warnings don't appear again
> 
> If warnings don't appear then how will we know of their existence so that they can be addressed rather than ignored?
> 
> —
> Reply to this email directly, view it on GitHub, or unsubscribe.
> Triage notifications on the go with GitHub Mobile for iOS or Android. 
> You are receiving this because you were assigned.

Oops I thought that hid them, but it treats warnings as errors right?

Resolving patch:
diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py
--- a/pydis_site/apps/api/viewsets/bot/infraction.py
+++ b/pydis_site/apps/api/viewsets/bot/infraction.py
@@ -3,6 +3,7 @@
 from django.db import IntegrityError
 from django.db.models import QuerySet
 from django.http.request import HttpRequest
+from django.utils import timezone
 from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.decorators import action
 from rest_framework.exceptions import ValidationError
@@ -184,20 +185,24 @@ def get_queryset(self) -> QuerySet:
         filter_expires_after = self.request.query_params.get('expires_after')
         if filter_expires_after:
             try:
-                additional_filters['expires_at__gte'] = datetime.fromisoformat(
-                    filter_expires_after
-                )
+                expires_after_parsed = datetime.fromisoformat(filter_expires_after)
             except ValueError:
                 raise ValidationError({'expires_after': ['failed to convert to datetime']})
+            additional_filters['expires_at__gte'] = timezone.make_aware(
+                expires_after_parsed,
+                timezone=timezone.utc,
+            )
 
         filter_expires_before = self.request.query_params.get('expires_before')
         if filter_expires_before:
             try:
-                additional_filters['expires_at__lte'] = datetime.fromisoformat(
-                    filter_expires_before
-                )
+                expires_before_parsed = datetime.fromisoformat(filter_expires_before)
             except ValueError:
                 raise ValidationError({'expires_before': ['failed to convert to datetime']})
+            additional_filters['expires_at__lte'] = timezone.make_aware(
+                expires_before_parsed,
+                timezone=timezone.utc,
+            )
 
         if 'expires_at__lte' in additional_filters and 'expires_at__gte' in additional_filters:
             if additional_filters['expires_at__gte'] > additional_filters['expires_at__lte']:
diff --git a/pydis_site/settings.py b/pydis_site/settings.py
--- a/pydis_site/settings.py
+++ b/pydis_site/settings.py
@@ -13,6 +13,7 @@
 import os
 import secrets
 import sys
+import warnings
 from pathlib import Path
 from socket import gethostbyname, gethostname
 
@@ -53,10 +54,26 @@
     ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*'])
     SECRET_KEY = "yellow polkadot bikini"  # noqa: S105
 
+    # Prevent verbose warnings emitted when passing a non-timezone aware
+    # datetime object to the database, whilst we have time zone support
+    # active. See the Django documentation for more details:
+    # https://docs.djangoproject.com/en/dev/topics/i18n/timezones/
+    warnings.filterwarnings(
+        'error', r"DateTimeField .* received a naive datetime",
+        RuntimeWarning, r'django\.db\.models\.fields',
+    )
+
 elif 'CI' in os.environ:
     ALLOWED_HOSTS = ['*']
     SECRET_KEY = secrets.token_urlsafe(32)
 
+    # See above. We run with `CI=true`, but debug unset in GitHub Actions,
+    # so we also want to filter it there.
+    warnings.filterwarnings(
+        'error', r"DateTimeField .* received a naive datetime",
+        RuntimeWarning, r'django\.db\.models\.fields',
+    )
+
 else:
     ALLOWED_HOSTS = env.list(
         'ALLOWED_HOSTS',


Test patch:
diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py
--- a/pydis_site/apps/api/tests/test_infractions.py
+++ b/pydis_site/apps/api/tests/test_infractions.py
@@ -80,7 +80,7 @@ def setUpTestData(cls):
             type='superstar',
             reason='This one doesn\'t matter anymore.',
             active=True,
-            expires_at=datetime.datetime.utcnow() + datetime.timedelta(hours=5)
+            expires_at=dt.now(timezone.utc) + datetime.timedelta(hours=5)
         )
         cls.voiceban_expires_later = Infraction.objects.create(
             user_id=cls.user.id,
@@ -88,7 +88,7 @@ def setUpTestData(cls):
             type='voice_ban',
             reason='Jet engine mic',
             active=True,
-            expires_at=datetime.datetime.utcnow() + datetime.timedelta(days=5)
+            expires_at=dt.now(timezone.utc) + datetime.timedelta(days=5)
         )
 
     def test_list_all(self):
diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py
--- a/pydis_site/apps/api/tests/test_models.py
+++ b/pydis_site/apps/api/tests/test_models.py
@@ -1,8 +1,7 @@
-from datetime import datetime as dt
+from datetime import datetime as dt, timezone
 
 from django.core.exceptions import ValidationError
 from django.test import SimpleTestCase, TestCase
-from django.utils import timezone
 
 from pydis_site.apps.api.models import (
     DeletedMessage,
@@ -41,7 +40,7 @@ def setUp(self):
         self.context = MessageDeletionContext.objects.create(
             id=50,
             actor=self.user,
-            creation=dt.utcnow()
+            creation=dt.now(timezone.utc)
         )
 
     def test_create(self):
@@ -99,7 +98,7 @@ def setUp(self):
                         name='shawn',
                         discriminator=555,
                     ),
-                    creation=dt.utcnow()
+                    creation=dt.now(timezone.utc)
                 ),
                 embeds=[]
             ),
diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py
--- a/pydis_site/apps/api/tests/test_reminders.py
+++ b/pydis_site/apps/api/tests/test_reminders.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import datetime, timezone
 
 from django.forms.models import model_to_dict
 from django.urls import reverse
@@ -91,7 +91,7 @@ def setUpTestData(cls):
         cls.reminder = Reminder.objects.create(
             author=cls.author,
             content="Don't forget to set yourself a reminder",
-            expiration=datetime.utcnow().isoformat(),
+            expiration=datetime.now(timezone.utc),
             jump_url="https://www.decliningmentalfaculties.com",
             channel_id=123
         )
@@ -122,7 +122,7 @@ def setUpTestData(cls):
         cls.reminder_one = Reminder.objects.create(
             author=cls.author,
             content="We should take Bikini Bottom, and push it somewhere else!",
-            expiration=datetime.utcnow().isoformat(),
+            expiration=datetime.now(timezone.utc),
             jump_url="https://www.icantseemyforehead.com",
             channel_id=123
         )
@@ -130,16 +130,17 @@ def setUpTestData(cls):
         cls.reminder_two = Reminder.objects.create(
             author=cls.author,
             content="Gahhh-I love being purple!",
-            expiration=datetime.utcnow().isoformat(),
+            expiration=datetime.now(timezone.utc),
             jump_url="https://www.goofygoobersicecreampartyboat.com",
             channel_id=123,
             active=False
         )
 
+        drf_format = '%Y-%m-%dT%H:%M:%S.%fZ'
         cls.rem_dict_one = model_to_dict(cls.reminder_one)
-        cls.rem_dict_one['expiration'] += 'Z'  # Massaging a quirk of the response time format
+        cls.rem_dict_one['expiration'] = cls.rem_dict_one['expiration'].strftime(drf_format)
         cls.rem_dict_two = model_to_dict(cls.reminder_two)
-        cls.rem_dict_two['expiration'] += 'Z'  # Massaging a quirk of the response time format
+        cls.rem_dict_two['expiration'] = cls.rem_dict_two['expiration'].strftime(drf_format)
 
     def test_reminders_in_full_list(self):
         url = reverse('api:bot:reminder-list')
@@ -175,7 +176,7 @@ def setUpTestData(cls):
         cls.reminder = Reminder.objects.create(
             author=cls.author,
             content="Reminder content",
-            expiration=datetime.utcnow().isoformat(),
+            expiration=datetime.now(timezone.utc),
             jump_url="http://example.com/",
             channel_id=123
         )
@@ -203,7 +204,7 @@ def setUpTestData(cls):
         cls.reminder = Reminder.objects.create(
             author=cls.author,
             content="Squash those do-gooders",
-            expiration=datetime.utcnow().isoformat(),
+            expiration=datetime.now(timezone.utc),
             jump_url="https://www.decliningmentalfaculties.com",
             channel_id=123
         )
diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py
--- a/pydis_site/apps/home/tests/test_repodata_helpers.py
+++ b/pydis_site/apps/home/tests/test_repodata_helpers.py
@@ -122,7 +122,10 @@ def test_falls_back_to_database_on_error_without_entries(self, mock_get: mock.Ma
         """Tests that fallback to the database is performed when we get garbage back."""
         mock_get.return_value.json.return_value = ['garbage']
 
-        metadata = self.home_view._get_repo_data()
+        # Capture logs and ensure the problematic response is logged
+        with self.assertLogs():
+            metadata = self.home_view._get_repo_data()
+
         self.assertEquals(len(metadata), 0)
 
     def test_cleans_up_stale_metadata(self):