From 9d1ea1667d501e515957909d5c261525ff6aeaa6 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Wed, 23 Feb 2022 19:36:43 +0100 Subject: Remove the admin app This app is completely unused. I assume it was planned to be a space for customizing the Django admin, but we don't even have it in `INSTALLED_APPS`, nor our URLs. --- pydis_site/apps/admin/__init__.py | 0 pydis_site/apps/admin/urls.py | 8 -------- 2 files changed, 8 deletions(-) delete mode 100644 pydis_site/apps/admin/__init__.py delete mode 100644 pydis_site/apps/admin/urls.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/admin/__init__.py b/pydis_site/apps/admin/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydis_site/apps/admin/urls.py b/pydis_site/apps/admin/urls.py deleted file mode 100644 index a4f3e517..00000000 --- a/pydis_site/apps/admin/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.contrib import admin -from django.urls import path - - -app_name = 'admin' -urlpatterns = ( - path('', admin.site.urls), -) -- cgit v1.2.3 From 7f617657068e2cf877edfb06a70d435dd5dfbdc0 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Fri, 25 Feb 2022 01:09:58 +0100 Subject: Remove and merge superfluous modules The constants module more or less did what belongs to the settings. --- pydis_site/apps/home/views/home.py | 8 ++++---- pydis_site/constants.py | 6 ------ pydis_site/context_processors.py | 5 ++--- pydis_site/settings.py | 10 +++++++++- 4 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 pydis_site/constants.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index e28a3a00..dafe32c0 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -2,6 +2,7 @@ import logging from typing import Dict, List import requests +from django.conf import settings from django.core.handlers.wsgi import WSGIRequest from django.http import HttpResponse from django.shortcuts import render @@ -10,7 +11,6 @@ from django.views import View from pydis_site import settings from pydis_site.apps.home.models import RepositoryMetadata -from pydis_site.constants import GITHUB_TOKEN, TIMEOUT_PERIOD log = logging.getLogger(__name__) @@ -43,8 +43,8 @@ class HomeView(View): # 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}"} + if settings.GITHUB_TOKEN: + self.headers = {"Authorization": f"token {settings.GITHUB_TOKEN}"} else: self.headers = {} @@ -60,7 +60,7 @@ class HomeView(View): api_data: List[dict] = requests.get( self.github_api, headers=self.headers, - timeout=TIMEOUT_PERIOD + timeout=settings.TIMEOUT_PERIOD ).json() except requests.exceptions.Timeout: log.error("Request to fetch GitHub repository metadata for timed out!") diff --git a/pydis_site/constants.py b/pydis_site/constants.py deleted file mode 100644 index e913f40f..00000000 --- a/pydis_site/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -GIT_SHA = os.environ.get("GIT_SHA", "development") -GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") -# How long to wait for synchronous requests before timing out -TIMEOUT_PERIOD = int(os.environ.get("TIMEOUT_PERIOD", 5)) diff --git a/pydis_site/context_processors.py b/pydis_site/context_processors.py index 6937a3db..0e8b4a94 100644 --- a/pydis_site/context_processors.py +++ b/pydis_site/context_processors.py @@ -1,8 +1,7 @@ +from django.conf import settings from django.template import RequestContext -from pydis_site.constants import GIT_SHA - def git_sha_processor(_: RequestContext) -> dict: """Expose the git SHA for this repo to all views.""" - return {'git_sha': GIT_SHA} + return {'git_sha': settings.GIT_SHA} diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 3b146f2c..4f2b4aef 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -20,15 +20,20 @@ import environ import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration -from pydis_site.constants import GIT_SHA env = environ.Env( DEBUG=(bool, False), SITE_DSN=(str, ""), BUILDING_DOCKER=(bool, False), STATIC_BUILD=(bool, False), + GIT_SHA=(str, 'development'), + TIMEOUT_PERIOD=(int, 5), + GITHUB_TOKEN=(str, None), ) +GIT_SHA = env("GIT_SHA") +GITHUB_TOKEN = env("GITHUB_TOKEN") + sentry_sdk.init( dsn=env('SITE_DSN'), integrations=[DjangoIntegration()], @@ -288,3 +293,6 @@ CONTENT_PAGES_PATH = Path(BASE_DIR, "pydis_site", "apps", "content", "resources" # Path for redirection links REDIRECTIONS_PATH = Path(BASE_DIR, "pydis_site", "apps", "redirect", "redirects.yaml") + +# How long to wait for synchronous requests before timing out +TIMEOUT_PERIOD = env("TIMEOUT_PERIOD") -- cgit v1.2.3 From b78d9496867a13ed096b4b1f561a47a25001a901 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Fri, 25 Feb 2022 01:10:45 +0100 Subject: Add a readme to the Django project directory --- pydis_site/README.md | 52 ++++++++++++++++++++++++++++++++++++++ pydis_site/apps/home/views/home.py | 1 - 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 pydis_site/README.md (limited to 'pydis_site/apps') diff --git a/pydis_site/README.md b/pydis_site/README.md new file mode 100644 index 00000000..456f7e9e --- /dev/null +++ b/pydis_site/README.md @@ -0,0 +1,52 @@ +# `pydis_site` project directory + +This directory hosts the root of our **Django project**[^1], and is responsible +for all logic powering our website. Let's go over the directories in detail: + +- [`apps`](./apps) contains our **Django apps**. If you want to add your own API + endpoint or new functionality to our homepage, that's the place to go. + + +- [`static`](./static) contains our **static files**, such as CSS, JavaScript, + images, and anything else that isn't either content or Python code. Static + files relevant for a specific application are put into subdirectories named + after the application. + +- [`templates`](./templates) contains our **Django templates**. Like with static + files, templates specific to a single application are stored in a subdirectory + named after that application. We also have two special templates here: + + - `404.html`, which is our error page shown when a site was not found. + + - `500.html`, which is our error page shown in the astronomically rare case + that we encounter an internal server error. + + +We also have a few files in here that are relevant or useful in large parts of +the website: + +- [`context_processors.py`](./context_processors.py), which contains custom + *context processors* that add variables to the Django template context. To + read more, see the [`RequestContext` documentation from + Django](https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.RequestContext) + +- [`settings.py`](./settings.py), our Django settings file. This mostly just + parses configuration out of your environment variables, so you shouldn't need + to edit it directly unless you want to add new settings. + +- [`urls.py`](./urls.py), which configures our Django URL routing by installing + our apps into the routing tree. + +- [`wsgi.py`](./wsgi.py), which serves as an adapter for + [`gunicorn`](https://github.com/benoitc/gunicorn), + [`uwsgi`](https://github.com/unbit/uwsgi) or other application servers to run + our application in production. Unless you want to test an interaction between + our application and those servers, you probably won't need to touch this. + +Note that for both `static` and `templates`, we are not using the default Django +directory structure which puts these directories in a directory per app (in our +case, this would for example be ``pydis_site/apps/content/static/``). + + +[^1]: See [Django Glossary: project](https://docs.djangoproject.com/en/dev/glossary/#term-project) diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index dafe32c0..69e706c5 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -2,7 +2,6 @@ import logging from typing import Dict, List import requests -from django.conf import settings from django.core.handlers.wsgi import WSGIRequest from django.http import HttpResponse from django.shortcuts import render -- cgit v1.2.3 From 66061855ca2098fae7e80fa2e1abb29f810495f1 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 26 Feb 2022 14:36:53 +0100 Subject: Fix timezone awareness warnings Add a `warnings.warnings` clause to prevent these from being raised again in the future, and raise a full traceback if they don't. --- pydis_site/apps/api/tests/test_infractions.py | 4 ++-- pydis_site/apps/api/tests/test_models.py | 7 +++---- pydis_site/apps/api/tests/test_reminders.py | 17 +++++++++-------- pydis_site/apps/api/viewsets/bot/infraction.py | 11 +++++------ pydis_site/settings.py | 17 +++++++++++++++++ 5 files changed, 36 insertions(+), 20 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index aa0604f6..f1107734 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -80,7 +80,7 @@ class InfractionTests(AuthenticatedAPITestCase): 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 @@ class InfractionTests(AuthenticatedAPITestCase): 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 index 5c9ddea4..0fad467c 100644 --- 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 @@ class NitroMessageLengthTest(TestCase): 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 @@ class StringDunderMethodTests(SimpleTestCase): 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 index 709685bc..e17569f0 100644 --- 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 @@ class ReminderDeletionTests(AuthenticatedAPITestCase): 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 @@ class ReminderListTests(AuthenticatedAPITestCase): 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 @@ class ReminderListTests(AuthenticatedAPITestCase): 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 @@ class ReminderRetrieveTests(AuthenticatedAPITestCase): 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 @@ class ReminderUpdateTests(AuthenticatedAPITestCase): 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/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index 7e7adbca..d82090aa 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -3,6 +3,7 @@ from datetime import datetime 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,18 @@ class InfractionViewSet( 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) 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) 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 index 4f2b4aef..17f220f3 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ import os import secrets import sys +import warnings from pathlib import Path from socket import gethostbyname, gethostname @@ -53,10 +54,26 @@ if DEBUG: 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', -- cgit v1.2.3 From b7af4cf75c09983a1fdebcb92c9b98aa86548918 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 26 Feb 2022 14:57:58 +0100 Subject: Capture GitHub response logs --- pydis_site/apps/home/tests/test_repodata_helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py index 5634bc9b..d43bd28e 100644 --- a/pydis_site/apps/home/tests/test_repodata_helpers.py +++ b/pydis_site/apps/home/tests/test_repodata_helpers.py @@ -122,7 +122,10 @@ class TestRepositoryMetadataHelpers(TestCase): """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): -- cgit v1.2.3 From 2734a68ed35598fcf0615353077a875549db4b0d Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Wed, 2 Mar 2022 19:45:25 +0100 Subject: Explicitly pass timezone --- pydis_site/apps/api/viewsets/bot/infraction.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index d82090aa..7f31292f 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -188,7 +188,10 @@ class InfractionViewSet( 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) + 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: @@ -196,7 +199,10 @@ class InfractionViewSet( 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) + 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']: -- cgit v1.2.3 From 0d1fb628199cf80c0d12d283b8323905d9419ae9 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Thu, 3 Mar 2022 23:53:01 +0100 Subject: Add a README for the API app --- pydis_site/apps/api/README.md | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 pydis_site/apps/api/README.md (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/README.md b/pydis_site/apps/api/README.md new file mode 100644 index 00000000..837a89fc --- /dev/null +++ b/pydis_site/apps/api/README.md @@ -0,0 +1,69 @@ +# The "api" app + +This application takes care of most of the heavy lifting in the site, that is, +allowing our bot to manipulate and query information stored in the site's +database. + +We make heavy use of [Django REST +Framework](https://www.django-rest-framework.org) here, which builds on top of +Django to allow us to easily build out the +[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API +consumed by our bot. Working with the API app requires basic knowledge of DRF - +the [quickstart +guide](https://www.django-rest-framework.org/tutorial/quickstart/) is a great +resource to get started. + +## Directory structure + +Let's look over each of the subdirectories here: + +- `migrations` is the standard Django migrations folder. You usually won't need + to edit this manually, as `python manage.py makemigrations` handles this for + you in case you change our models. + +- `models` contains our Django model definitions. We put models into subfolders + relevant as to where they are used - in our case, the `bot` folder contains + models used by our bot when working with the API. Each model is contained + within its own module, such as `api/models/bot/message_deletion_context.py`, + which contains the `MessageDeletionContext` model. + +- `tests` contains tests for our API. If you're unfamilar with Django testing, + the [Django tutorial introducing automated + testing](https://docs.djangoproject.com/en/dev/intro/tutorial05/) is a great + resource, and you can also check out code in there to see how we test it. + +- `viewsets` contains our [DRF + viewsets](https://www.django-rest-framework.org/api-guide/viewsets/), and is + structured similarly to the `models` folder: The `bot` subfolder contains + viewsets relevant to the Python Bot, and each viewset is contained within its + own module. + +The remaining modules mostly do what their name suggests: + +- `admin.py`, which hooks up our models to the [Django admin + site](https://docs.djangoproject.com/en/dev/ref/contrib/admin/). + +- `apps.py` contains the Django [application + config](https://docs.djangoproject.com/en/dev/ref/applications/) for the `api` + app, and is used to run any code that should run when the app is loaded. + +- `pagination.py` contains custom + [paginators](https://www.django-rest-framework.org/api-guide/pagination/) used + within our DRF viewsets. + +- `serializers.py` contains [DRF + serializers](https://www.django-rest-framework.org/api-guide/serializers/) for + our models, and also includes validation logic for the models. + +- `signals.py` contains [Django + Signals](https://docs.djangoproject.com/en/dev/topics/signals/) for running + custom functionality in response to events such as deletion of a model + instance. + +- `urls.py` configures Django's [URL + dispatcher](https://docs.djangoproject.com/en/dev/topics/http/urls/) for our + API endpoints. + +- `views.py` is for any standard Django views that don't make sense to be put + into DRF viewsets as they provide static data or other functionality that + doesn't interact with our models. -- cgit v1.2.3 From 073e09a07723db01691b9fdfc22cfbab037dad96 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 6 Mar 2022 11:19:38 +0100 Subject: Mention human-readable migration names --- pydis_site/apps/api/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/README.md b/pydis_site/apps/api/README.md index 837a89fc..1c6358b3 100644 --- a/pydis_site/apps/api/README.md +++ b/pydis_site/apps/api/README.md @@ -19,7 +19,9 @@ Let's look over each of the subdirectories here: - `migrations` is the standard Django migrations folder. You usually won't need to edit this manually, as `python manage.py makemigrations` handles this for - you in case you change our models. + you in case you change our models. (Note that when generating migrations and + Django doesn't generate a human-readable name for you, please supply one + manually using `-n add_this_field`.) - `models` contains our Django model definitions. We put models into subfolders relevant as to where they are used - in our case, the `bot` folder contains -- cgit v1.2.3 From a6b8c27e68b529b1060b1213b465457c5c0d685a Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:18:18 +0100 Subject: Add support for storing AoC related data in site --- .../apps/api/migrations/0080_add_aoc_tables.py | 33 +++++++++++ pydis_site/apps/api/models/__init__.py | 2 + pydis_site/apps/api/models/bot/__init__.py | 2 + .../apps/api/models/bot/aoc_completionist_block.py | 21 +++++++ pydis_site/apps/api/models/bot/aoc_link.py | 20 +++++++ pydis_site/apps/api/serializers.py | 22 +++++++ pydis_site/apps/api/urls.py | 10 ++++ pydis_site/apps/api/viewsets/__init__.py | 2 + pydis_site/apps/api/viewsets/bot/__init__.py | 2 + .../api/viewsets/bot/aoc_completionist_block.py | 69 ++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/aoc_link.py | 69 ++++++++++++++++++++++ 11 files changed, 252 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0080_add_aoc_tables.py create mode 100644 pydis_site/apps/api/models/bot/aoc_completionist_block.py create mode 100644 pydis_site/apps/api/models/bot/aoc_link.py create mode 100644 pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py create mode 100644 pydis_site/apps/api/viewsets/bot/aoc_link.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py new file mode 100644 index 00000000..f129d86f --- /dev/null +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -0,0 +1,33 @@ +# Generated by Django 3.1.14 on 2022-03-06 16:07 + +from django.db import migrations, models +import django.db.models.deletion +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0079_merge_20220125_2022'), + ] + + operations = [ + migrations.CreateModel( + name='AocCompletionistBlock', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.CreateModel( + name='AocAccountLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), + ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index fd5bf220..4f616986 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -10,6 +10,8 @@ from .bot import ( Nomination, NominationEntry, OffensiveMessage, + AocAccountLink, + AocCompletionistBlock, OffTopicChannelName, Reminder, Role, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index ac864de3..ec0e701c 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -5,6 +5,8 @@ from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink from .infraction import Infraction from .message import Message +from .aoc_completionist_block import AocCompletionistBlock +from .aoc_link import AocAccountLink from .message_deletion_context import MessageDeletionContext from .nomination import Nomination, NominationEntry from .off_topic_channel_name import OffTopicChannelName diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py new file mode 100644 index 00000000..cac41ff1 --- /dev/null +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -0,0 +1,21 @@ +from django.db import models + +from pydis_site.apps.api.models.bot.user import User +from pydis_site.apps.api.models.mixins import ModelReprMixin + + +class AocCompletionistBlock(ModelReprMixin, models.Model): + """A Discord user blocked from getting the AoC completionist Role.""" + + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + help_text="The user that is blocked from getting the AoC Completionist Role" + ) + + is_blocked = models.BooleanField( + default=True, + help_text="Whether this user is actively being blocked " + "from getting the AoC Completionist Role", + verbose_name="Blocked" + ) diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py new file mode 100644 index 00000000..6c7cc591 --- /dev/null +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -0,0 +1,20 @@ +from django.db import models + +from pydis_site.apps.api.models.bot.user import User +from pydis_site.apps.api.models.mixins import ModelReprMixin + + +class AocAccountLink(ModelReprMixin, models.Model): + """An AoC account link for a Discord User.""" + + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + help_text="The user that is blocked from getting the AoC Completionist Role" + ) + + aoc_username = models.CharField( + max_length=120, + help_text="The AoC username associated with the Discord User.", + blank=False + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 745aff42..0b0e4237 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -13,6 +13,8 @@ from rest_framework.settings import api_settings from rest_framework.validators import UniqueTogetherValidator from .models import ( + AocAccountLink, + AocCompletionistBlock, BotSetting, DeletedMessage, DocumentationLink, @@ -250,6 +252,26 @@ class ReminderSerializer(ModelSerializer): ) +class AocCompletionistBlockSerializer(ModelSerializer): + """A class providing (de-)serialization of `AocCompletionistBlock` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = AocCompletionistBlock + fields = ("user", "is_blocked") + + +class AocAccountLinkSerializer(ModelSerializer): + """A class providing (de-)serialization of `AocAccountLink` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = AocAccountLink + fields = ("user", "aoc_username") + + class RoleSerializer(ModelSerializer): """A class providing (de-)serialization of `Role` instances.""" diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index b0ab545b..7c55fc92 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,6 +3,8 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( + AocAccountLinkViewSet, + AocCompletionistBlockViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, @@ -34,6 +36,14 @@ bot_router.register( 'documentation-links', DocumentationLinkViewSet ) +bot_router.register( + "aoc-account-links", + AocAccountLinkViewSet +) +bot_router.register( + "aoc-completionist-blocks", + AocCompletionistBlockViewSet +) bot_router.register( 'infractions', InfractionViewSet diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index f133e77f..5fc1d64f 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -7,6 +7,8 @@ from .bot import ( InfractionViewSet, NominationViewSet, OffensiveMessageViewSet, + AocAccountLinkViewSet, + AocCompletionistBlockViewSet, OffTopicChannelNameViewSet, ReminderViewSet, RoleViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 84b87eab..f1d84729 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -7,6 +7,8 @@ from .infraction import InfractionViewSet from .nomination import NominationViewSet from .off_topic_channel_name import OffTopicChannelNameViewSet from .offensive_message import OffensiveMessageViewSet +from .aoc_link import AocAccountLinkViewSet +from .aoc_completionist_block import AocCompletionistBlockViewSet from .reminder import ReminderViewSet from .role import RoleViewSet from .user import UserViewSet diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py new file mode 100644 index 00000000..53bcb546 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -0,0 +1,69 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +) +from rest_framework.viewsets import GenericViewSet + +from pydis_site.apps.api.models.bot import AocCompletionistBlock +from pydis_site.apps.api.serializers import AocCompletionistBlockSerializer + + +class AocCompletionistBlockViewSet( + GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin +): + """ + View providing management for Users blocked from gettign the AoC completionist Role. + + ## Routes + + ### GET /bot/aoc-completionist-blocks/ + Returns all the AoC completionist blocks + + #### Response format + >>> [ + ... { + ... "user": 2, + ... "is_blocked": False + ... } + ... ] + + + ### GET /bot/aoc-completionist-blocks/ + Retrieve a single Block by User ID + + #### Response format + >>> + ... { + ... "user": 2, + ... "is_blocked": False + ... } + + #### Status codes + - 200: returned on success + - 404: returned if an AoC completionist block with the given user__id was not found. + + ### POST /bot/aoc-completionist-blocks + Adds a single AoC completionist block + + #### Request body + >>> { + ... 'user': int, + ... 'is_blocked': bool + ... } + + #### Status codes + - 204: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/aoc-completionist-blocks/ + Deletes the AoC Completionist block item with the given `user__id`. + #### Status codes + - 204: returned on success + - 404: if the AoC Completionist block with the given user__id does not exist + + """ + + serializer_class = AocCompletionistBlockSerializer + queryset = AocCompletionistBlock.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ("user__id",) diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py new file mode 100644 index 00000000..b5b5420e --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -0,0 +1,69 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +) +from rest_framework.viewsets import GenericViewSet + +from pydis_site.apps.api.models.bot import AocAccountLink +from pydis_site.apps.api.serializers import AocAccountLinkSerializer + + +class AocAccountLinkViewSet( + GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin +): + """ + View providing management for Users who linked their AoC accounts to their Discord Account. + + ## Routes + + ### GET /bot/aoc-account-links + Returns all the AoC account links + + #### Response format + >>> [ + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } + ... ] + + + ### GET /bot/aoc-account-links + Retrieve a AoC account link by User ID + + #### Response format + >>> + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } + + #### Status codes + - 200: returned on success + - 404: returned if an AoC account link with the given user__id was not found. + + ### POST /bot/aoc-account-links + Adds a single AoC account link block + + #### Request body + >>> { + ... 'user': int, + ... 'aoc_username': str + ... } + + #### Status codes + - 204: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/aoc-account-links/ + Deletes the AoC account link item with the given `user__id`. + #### Status codes + - 204: returned on success + - 404: if the AoC account link with the given user__id does not exist + + """ + + serializer_class = AocAccountLinkSerializer + queryset = AocAccountLink.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ("user__id",) -- cgit v1.2.3 From 19c6e7b66cf5078a198f4a70fec38d66dd029564 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:54:34 +0100 Subject: Enhance comments and table structure in AoC related modules - Set the user reference to be a OneToOne relation, on tables: AocCompletionistBlock and AocAccountLink. --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 4 ++-- pydis_site/apps/api/models/bot/aoc_completionist_block.py | 2 +- pydis_site/apps/api/models/bot/aoc_link.py | 2 +- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 4 ++-- pydis_site/apps/api/viewsets/bot/aoc_link.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index f129d86f..c58a5d29 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), - ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), @@ -26,7 +26,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), - ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index cac41ff1..a89f9760 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -7,7 +7,7 @@ from pydis_site.apps.api.models.mixins import ModelReprMixin class AocCompletionistBlock(ModelReprMixin, models.Model): """A Discord user blocked from getting the AoC completionist Role.""" - user = models.ForeignKey( + user = models.OneToOneField( User, on_delete=models.CASCADE, help_text="The user that is blocked from getting the AoC Completionist Role" diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py index 6c7cc591..9b47456d 100644 --- a/pydis_site/apps/api/models/bot/aoc_link.py +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -7,7 +7,7 @@ from pydis_site.apps.api.models.mixins import ModelReprMixin class AocAccountLink(ModelReprMixin, models.Model): """An AoC account link for a Discord User.""" - user = models.ForeignKey( + user = models.OneToOneField( User, on_delete=models.CASCADE, help_text="The user that is blocked from getting the AoC Completionist Role" diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index 53bcb546..c5568129 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -47,8 +47,8 @@ class AocCompletionistBlockViewSet( #### Request body >>> { - ... 'user': int, - ... 'is_blocked': bool + ... 'user': int, + ... 'is_blocked': bool ... } #### Status codes diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index b5b5420e..263b548d 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -47,8 +47,8 @@ class AocAccountLinkViewSet( #### Request body >>> { - ... 'user': int, - ... 'aoc_username': str + ... 'user': int, + ... 'aoc_username': str ... } #### Status codes -- cgit v1.2.3 From b93dce5abcf225579b9407358f938ca3932e67a2 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Wed, 9 Mar 2022 19:37:30 +0100 Subject: Add reason field to AoC completionist block table --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 1 + pydis_site/apps/api/models/bot/aoc_completionist_block.py | 4 ++++ pydis_site/apps/api/serializers.py | 2 +- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 7 +++++-- 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index c58a5d29..917c5b7f 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -17,6 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index a89f9760..6605cbc4 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -19,3 +19,7 @@ class AocCompletionistBlock(ModelReprMixin, models.Model): "from getting the AoC Completionist Role", verbose_name="Blocked" ) + reason = models.TextField( + null=True, + help_text="The reason for the AoC Completionist Role Block." + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 0b0e4237..c97f7dba 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -259,7 +259,7 @@ class AocCompletionistBlockSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = AocCompletionistBlock - fields = ("user", "is_blocked") + fields = ("user", "is_blocked", "reason") class AocAccountLinkSerializer(ModelSerializer): diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index c5568129..8e7d821c 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -24,6 +24,7 @@ class AocCompletionistBlockViewSet( ... { ... "user": 2, ... "is_blocked": False + ... "reason": "Too good to be true" ... } ... ] @@ -36,6 +37,7 @@ class AocCompletionistBlockViewSet( ... { ... "user": 2, ... "is_blocked": False + ... "reason": "Too good to be true" ... } #### Status codes @@ -47,8 +49,9 @@ class AocCompletionistBlockViewSet( #### Request body >>> { - ... 'user': int, - ... 'is_blocked': bool + ... "user": int, + ... "is_blocked": bool + ... "reason": string ... } #### Status codes -- cgit v1.2.3 From d18d0198f2a43066b7f6cb9542a25adea6e6b3f4 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Wed, 9 Mar 2022 23:07:38 +0100 Subject: Patch AoC tables to use the Discord user as PK. --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 16 +++++++--------- .../apps/api/models/bot/aoc_completionist_block.py | 3 ++- pydis_site/apps/api/models/bot/aoc_link.py | 3 ++- pydis_site/apps/api/viewsets/bot/aoc_link.py | 8 +++++--- 4 files changed, 16 insertions(+), 14 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index 917c5b7f..2c0c689a 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -13,21 +13,19 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='AocCompletionistBlock', + name='AocAccountLink', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), - ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), - ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')), + ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), migrations.CreateModel( - name='AocAccountLink', + name='AocCompletionistBlock', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), - ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')), + ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index 6605cbc4..acbc0eba 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -10,7 +10,8 @@ class AocCompletionistBlock(ModelReprMixin, models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, - help_text="The user that is blocked from getting the AoC Completionist Role" + help_text="The user that is blocked from getting the AoC Completionist Role", + primary_key=True ) is_blocked = models.BooleanField( diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py index 9b47456d..4e9d4882 100644 --- a/pydis_site/apps/api/models/bot/aoc_link.py +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -10,7 +10,8 @@ class AocAccountLink(ModelReprMixin, models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, - help_text="The user that is blocked from getting the AoC Completionist Role" + help_text="The user that is blocked from getting the AoC Completionist Role", + primary_key=True ) aoc_username = models.CharField( diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index 263b548d..c3fa6854 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -24,7 +24,8 @@ class AocAccountLinkViewSet( ... { ... "user": 2, ... "aoc_username": "AoCUser1" - ... } + ... }, + ... ... ... ] @@ -32,11 +33,12 @@ class AocAccountLinkViewSet( Retrieve a AoC account link by User ID #### Response format - >>> + >>> [ ... { ... "user": 2, ... "aoc_username": "AoCUser1" - ... } + ... }, + ... ] #### Status codes - 200: returned on success -- cgit v1.2.3 From 8c95f13c96d16d6f4d0736ee136563d603926c63 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Thu, 10 Mar 2022 17:28:39 +0100 Subject: Enhance code, documentation consistency in AoC related code Co-authored-by: Mark <1515135+MarkKoz@users.noreply.github.com> --- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 7 ++++--- pydis_site/apps/api/viewsets/bot/aoc_link.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index 8e7d821c..d3167d7b 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -23,7 +23,7 @@ class AocCompletionistBlockViewSet( >>> [ ... { ... "user": 2, - ... "is_blocked": False + ... "is_blocked": False, ... "reason": "Too good to be true" ... } ... ] @@ -36,7 +36,7 @@ class AocCompletionistBlockViewSet( >>> ... { ... "user": 2, - ... "is_blocked": False + ... "is_blocked": False, ... "reason": "Too good to be true" ... } @@ -50,7 +50,7 @@ class AocCompletionistBlockViewSet( #### Request body >>> { ... "user": int, - ... "is_blocked": bool + ... "is_blocked": bool, ... "reason": string ... } @@ -60,6 +60,7 @@ class AocCompletionistBlockViewSet( ### DELETE /bot/aoc-completionist-blocks/ Deletes the AoC Completionist block item with the given `user__id`. + #### Status codes - 204: returned on success - 404: if the AoC Completionist block with the given user__id does not exist diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index c3fa6854..5f6f3a84 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -59,6 +59,7 @@ class AocAccountLinkViewSet( ### DELETE /bot/aoc-account-links/ Deletes the AoC account link item with the given `user__id`. + #### Status codes - 204: returned on success - 404: if the AoC account link with the given user__id does not exist -- cgit v1.2.3 From 083cdf3f49805fd3c6d01fe538b7e01e12ca9b79 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Thu, 10 Mar 2022 19:18:05 +0100 Subject: Add new filter field, and patch the docs in AoC viewsets - Add the possibility to filter by `is_blocked` in the AoC completionist block viewset. - Patch various tense, and formatting inconsistencies in AoC viewsets --- .../apps/api/viewsets/bot/aoc_completionist_block.py | 6 +++--- pydis_site/apps/api/viewsets/bot/aoc_link.py | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index d3167d7b..3a4cec60 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -42,7 +42,7 @@ class AocCompletionistBlockViewSet( #### Status codes - 200: returned on success - - 404: returned if an AoC completionist block with the given user__id was not found. + - 404: returned if an AoC completionist block with the given `user__id` was not found. ### POST /bot/aoc-completionist-blocks Adds a single AoC completionist block @@ -63,11 +63,11 @@ class AocCompletionistBlockViewSet( #### Status codes - 204: returned on success - - 404: if the AoC Completionist block with the given user__id does not exist + - 404: returned if the AoC Completionist block with the given `user__id` was not found """ serializer_class = AocCompletionistBlockSerializer queryset = AocCompletionistBlock.objects.all() filter_backends = (DjangoFilterBackend,) - filter_fields = ("user__id",) + filter_fields = ("user__id", "is_blocked") diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index 5f6f3a84..9f22c1a1 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -29,20 +29,19 @@ class AocAccountLinkViewSet( ... ] - ### GET /bot/aoc-account-links + ### GET /bot/aoc-account-links/ Retrieve a AoC account link by User ID #### Response format - >>> [ - ... { - ... "user": 2, - ... "aoc_username": "AoCUser1" - ... }, - ... ] + >>> + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } #### Status codes - 200: returned on success - - 404: returned if an AoC account link with the given user__id was not found. + - 404: returned if an AoC account link with the given `user__id` was not found. ### POST /bot/aoc-account-links Adds a single AoC account link block @@ -55,14 +54,14 @@ class AocAccountLinkViewSet( #### Status codes - 204: returned on success - - 400: if one of the given fields is invalid + - 400: if one of the given fields was invalid ### DELETE /bot/aoc-account-links/ Deletes the AoC account link item with the given `user__id`. #### Status codes - 204: returned on success - - 404: if the AoC account link with the given user__id does not exist + - 404: returned if the AoC account link with the given `user__id` was not found """ -- cgit v1.2.3 From c0666e5095c6e427b786fb934520d63fabaa8840 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 12 Mar 2022 16:41:11 +0100 Subject: Add a README for the content directory --- pydis_site/apps/content/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pydis_site/apps/content/README.md (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/README.md b/pydis_site/apps/content/README.md new file mode 100644 index 00000000..b2719a9f --- /dev/null +++ b/pydis_site/apps/content/README.md @@ -0,0 +1,37 @@ +# The "content" app + +This application serves static, markdown-based content. Django-wise there is +relatively little code in there, most of it is concerned with serving our +content. + +The markdown files hosting our content can be found in the +[`resources/`](./resources) directory, and contain + + +## Contributing pages + +Contributing pages is covered extensively in our online guide, which you can +find +[here](https://www.pythondiscord.com/pages/guides/pydis-guides/how-to-contribute-a-page/) +or alternatively read it directly at +[`resources/guides/pydis-guides/how-to-contribute-a-page.md`](./resources/guides/pydis-guides/how-to-contribute-a-page.md). + + +## Directory structure + +Let's look at the structure in here: + +- `resources` contains the static Markdown files that make up our site's + [pages](https://www.pythondiscord.com/pages/) + +- `migrations` contains standard Django migrations. As the `content` app + contains purely static markdown files, no migrations are present here. + +- `tests` contains unit tests for verifying that the app works properly. + +- `views` contains our Django views generating and serving the pages from the + input markdown. + +As for the modules, apart from the standard Django modules in here, the +`utils.py` module contains utility functions for discovering markdown files to +serve on the filesystem. -- cgit v1.2.3 From ca2c583b15d46d7821a03ec047678429f96c7e03 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 12 Mar 2022 21:51:28 +0100 Subject: Reword documentation per review Co-authored-by: MarkKoz --- pydis_site/apps/content/README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/README.md b/pydis_site/apps/content/README.md index b2719a9f..d36d6bee 100644 --- a/pydis_site/apps/content/README.md +++ b/pydis_site/apps/content/README.md @@ -1,19 +1,17 @@ # The "content" app -This application serves static, markdown-based content. Django-wise there is -relatively little code in there, most of it is concerned with serving our +This application serves static, Markdown-based content. Django-wise there is +relatively little code in there; most of it is concerned with serving our content. -The markdown files hosting our content can be found in the -[`resources/`](./resources) directory, and contain - ## Contributing pages -Contributing pages is covered extensively in our online guide, which you can -find -[here](https://www.pythondiscord.com/pages/guides/pydis-guides/how-to-contribute-a-page/) -or alternatively read it directly at +The Markdown files hosting our content can be found in the +[`resources/`](./resources) directory. The process of contributing to pages is +covered extensively in our online guide which you can find +[here](https://www.pythondiscord.com/pages/guides/pydis-guides/how-to-contribute-a-page/). +Alternatively, read it directly at [`resources/guides/pydis-guides/how-to-contribute-a-page.md`](./resources/guides/pydis-guides/how-to-contribute-a-page.md). @@ -25,13 +23,13 @@ Let's look at the structure in here: [pages](https://www.pythondiscord.com/pages/) - `migrations` contains standard Django migrations. As the `content` app - contains purely static markdown files, no migrations are present here. + contains purely static Markdown files, no migrations are present here. - `tests` contains unit tests for verifying that the app works properly. -- `views` contains our Django views generating and serving the pages from the - input markdown. +- `views` contains Django views which generating and serve the pages from the + input Markdown. As for the modules, apart from the standard Django modules in here, the -`utils.py` module contains utility functions for discovering markdown files to -serve on the filesystem. +`utils.py` module contains utility functions for discovering Markdown files to +serve. -- cgit v1.2.3 From 4ad92b98a0df047b12e302ce8d849c2bd85862f1 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 12 Mar 2022 21:53:53 +0100 Subject: Remove unused migrations package --- pydis_site/apps/content/README.md | 3 --- pydis_site/apps/content/migrations/__init__.py | 0 2 files changed, 3 deletions(-) delete mode 100644 pydis_site/apps/content/migrations/__init__.py (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/README.md b/pydis_site/apps/content/README.md index d36d6bee..4149a6f0 100644 --- a/pydis_site/apps/content/README.md +++ b/pydis_site/apps/content/README.md @@ -22,9 +22,6 @@ Let's look at the structure in here: - `resources` contains the static Markdown files that make up our site's [pages](https://www.pythondiscord.com/pages/) -- `migrations` contains standard Django migrations. As the `content` app - contains purely static Markdown files, no migrations are present here. - - `tests` contains unit tests for verifying that the app works properly. - `views` contains Django views which generating and serve the pages from the diff --git a/pydis_site/apps/content/migrations/__init__.py b/pydis_site/apps/content/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3 From 2bf88327c6298e9004aeda028576946b1704d799 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 12 Mar 2022 22:43:52 +0100 Subject: Correct typo Co-authored-by: Mark <1515135+MarkKoz@users.noreply.github.com> --- pydis_site/apps/content/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/README.md b/pydis_site/apps/content/README.md index 4149a6f0..e7061207 100644 --- a/pydis_site/apps/content/README.md +++ b/pydis_site/apps/content/README.md @@ -24,7 +24,7 @@ Let's look at the structure in here: - `tests` contains unit tests for verifying that the app works properly. -- `views` contains Django views which generating and serve the pages from the +- `views` contains Django views which generate and serve the pages from the input Markdown. As for the modules, apart from the standard Django modules in here, the -- cgit v1.2.3 From a52a2cf631d92173f1a5573a56d3347d6456ac39 Mon Sep 17 00:00:00 2001 From: Cam Caswell Date: Sun, 13 Mar 2022 14:31:09 -0400 Subject: Update Project Lead color --- pydis_site/apps/content/resources/server-info/roles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/resources/server-info/roles.md b/pydis_site/apps/content/resources/server-info/roles.md index 716f5b1e..2888f837 100644 --- a/pydis_site/apps/content/resources/server-info/roles.md +++ b/pydis_site/apps/content/resources/server-info/roles.md @@ -68,7 +68,7 @@ In addition to the informal descriptions below, we've also written down a more f ### Domain Leads **Description:** Staff in charge of a certain domain such as moderation, events, and outreach. A lead will have a second role specifying their domain. -### Project Leads +### Project Leads **Description:** Staff in charge of a certain project that require special attention, such as a YouTube video series or our new forms page. ### Moderators -- cgit v1.2.3 From 06079418f5610a63c84dcc6c979ade347c6c5451 Mon Sep 17 00:00:00 2001 From: Cam Caswell Date: Sun, 13 Mar 2022 14:40:14 -0400 Subject: Add Events Team --- pydis_site/apps/content/resources/server-info/roles.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps') diff --git a/pydis_site/apps/content/resources/server-info/roles.md b/pydis_site/apps/content/resources/server-info/roles.md index 2888f837..edc02066 100644 --- a/pydis_site/apps/content/resources/server-info/roles.md +++ b/pydis_site/apps/content/resources/server-info/roles.md @@ -80,8 +80,8 @@ In addition to the informal descriptions below, we've also written down a more f ### DevOps **Description:** A role for staff involved with the DevOps toolchain of our core projects. -### Project Teams -**Description:** Staff can join teams which work on specific projects in the organisation, such as our code jams, media projects, and more. +### Events Team +**Description:** The events team are staff members who help plan and execute Python Discord events. This can range from the Code Jam, to Pixels, to our survey, specific workshops we want to run, and more. ### Helpers **Description:** This is the core staff role in our organization: All staff members have the Helpers role. -- cgit v1.2.3