diff options
Diffstat (limited to 'pydis_site/apps')
39 files changed, 218 insertions, 216 deletions
diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index e123d150..f3cc0405 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -1,7 +1,7 @@  from __future__ import annotations  import json -from typing import Iterable, Optional, Tuple +from collections.abc import Iterable  from django import urls  from django.contrib import admin @@ -62,16 +62,16 @@ class InfractionActorFilter(admin.SimpleListFilter):      title = "Actor"      parameter_name = "actor" -    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[Tuple[int, str]]: +    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]:          """Selectable values for viewer to filter by."""          actor_ids = Infraction.objects.order_by().values_list("actor").distinct()          actors = User.objects.filter(id__in=actor_ids)          return ((a.id, a.username) for a in actors) -    def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: +    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:          """Query to filter the list of Users against."""          if not self.value(): -            return +            return None          return queryset.filter(actor__id=self.value()) @@ -149,7 +149,7 @@ class DeletedMessageAdmin(admin.ModelAdmin):      list_display = ("id", "author", "channel_id") -    def embed_data(self, message: DeletedMessage) -> Optional[str]: +    def embed_data(self, message: DeletedMessage) -> str | None:          """Format embed data in a code block for better readability."""          if message.embeds:              return format_html( @@ -157,6 +157,7 @@ class DeletedMessageAdmin(admin.ModelAdmin):                  "<code>{0}</code></pre>",                  json.dumps(message.embeds, indent=4)              ) +        return None      embed_data.short_description = "Embeds" @@ -229,16 +230,16 @@ class NominationActorFilter(admin.SimpleListFilter):      title = "Actor"      parameter_name = "actor" -    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[Tuple[int, str]]: +    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]:          """Selectable values for viewer to filter by."""          actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct()          actors = User.objects.filter(id__in=actor_ids)          return ((a.id, a.username) for a in actors) -    def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: +    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:          """Query to filter the list of Users against."""          if not self.value(): -            return +            return None          nomination_ids = NominationEntry.objects.filter(              actor__id=self.value()          ).values_list("nomination_id").distinct() @@ -292,16 +293,16 @@ class NominationEntryActorFilter(admin.SimpleListFilter):      title = "Actor"      parameter_name = "actor" -    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[Tuple[int, str]]: +    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]:          """Selectable values for viewer to filter by."""          actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct()          actors = User.objects.filter(id__in=actor_ids)          return ((a.id, a.username) for a in actors) -    def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: +    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:          """Query to filter the list of Users against."""          if not self.value(): -            return +            return None          return queryset.filter(actor__id=self.value()) @@ -425,15 +426,15 @@ class UserRoleFilter(admin.SimpleListFilter):      title = "Role"      parameter_name = "role" -    def lookups(self, request: HttpRequest, model: UserAdmin) -> Iterable[Tuple[str, str]]: +    def lookups(self, request: HttpRequest, model: UserAdmin) -> Iterable[tuple[str, str]]:          """Selectable values for viewer to filter by."""          roles = Role.objects.all()          return ((r.name, r.name) for r in roles) -    def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: +    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:          """Query to filter the list of Users against."""          if not self.value(): -            return +            return None          role = Role.objects.get(name=self.value())          return queryset.filter(roles__contains=[role.id]) diff --git a/pydis_site/apps/api/github_utils.py b/pydis_site/apps/api/github_utils.py index 44c571c3..af659195 100644 --- a/pydis_site/apps/api/github_utils.py +++ b/pydis_site/apps/api/github_utils.py @@ -82,7 +82,7 @@ def generate_token() -> str:      Refer to:      https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app      """ -    now = datetime.datetime.now() +    now = datetime.datetime.now(tz=datetime.timezone.utc)      return jwt.encode(          {              "iat": math.floor((now - datetime.timedelta(seconds=60)).timestamp()),  # Issued at @@ -145,8 +145,12 @@ def authorize(owner: str, repo: str) -> httpx.Client:  def check_run_status(run: WorkflowRun) -> str:      """Check if the provided run has been completed, otherwise raise an exception.""" -    created_at = datetime.datetime.strptime(run.created_at, settings.GITHUB_TIMESTAMP_FORMAT) -    run_time = datetime.datetime.utcnow() - created_at +    created_at = ( +        datetime.datetime +        .strptime(run.created_at, settings.GITHUB_TIMESTAMP_FORMAT) +        .replace(tzinfo=datetime.timezone.utc) +    ) +    run_time = datetime.datetime.now(tz=datetime.timezone.utc) - created_at      if run.status != "completed":          if run_time <= MAX_RUN_TIME: @@ -154,8 +158,7 @@ def check_run_status(run: WorkflowRun) -> str:                  f"The requested run is still pending. It was created "                  f"{run_time.seconds // 60}:{run_time.seconds % 60 :>02} minutes ago."              ) -        else: -            raise RunTimeoutError("The requested workflow was not ready in time.") +        raise RunTimeoutError("The requested workflow was not ready in time.")      if run.conclusion != "success":          # The action failed, or did not run diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 89ae27e4..d8147cd4 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -61,9 +61,10 @@ class Message(ModelReprMixin, models.Model):      @property      def timestamp(self) -> datetime.datetime:          """Attribute that represents the message timestamp as derived from the snowflake id.""" -        return datetime.datetime.utcfromtimestamp( -            ((self.id >> 22) + 1420070400000) / 1000 -        ).replace(tzinfo=datetime.timezone.utc) +        return datetime.datetime.fromtimestamp( +            ((self.id >> 22) + 1420070400000) / 1000, +            tz=datetime.timezone.utc, +        )      class Meta:          """Metadata provided for Django's ORM.""" diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index f53dd33c..a55f5e5b 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -1,4 +1,3 @@ -from typing import List, Tuple  from django.db import connections @@ -10,10 +9,9 @@ EXCLUDE_CHANNELS = (  ) -class NotFoundError(Exception):  # noqa: N818 +class NotFoundError(Exception):      """Raised when an entity cannot be found.""" -    pass  class Metricity: @@ -31,15 +29,14 @@ class Metricity:      def user(self, user_id: str) -> dict:          """Query a user's data."""          # TODO: Swap this back to some sort of verified at date -        columns = ["joined_at"] -        query = f"SELECT {','.join(columns)} FROM users WHERE id = '%s'" +        query = "SELECT joined_at FROM users WHERE id = '%s'"          self.cursor.execute(query, [user_id])          values = self.cursor.fetchone()          if not values: -            raise NotFoundError() +            raise NotFoundError -        return dict(zip(columns, values)) +        return {'joined_at': values[0]}      def total_messages(self, user_id: str) -> int:          """Query total number of messages for a user.""" @@ -58,7 +55,7 @@ class Metricity:          values = self.cursor.fetchone()          if not values: -            raise NotFoundError() +            raise NotFoundError          return values[0] @@ -88,11 +85,11 @@ class Metricity:          values = self.cursor.fetchone()          if not values: -            raise NotFoundError() +            raise NotFoundError          return values[0] -    def top_channel_activity(self, user_id: str) -> List[Tuple[str, int]]: +    def top_channel_activity(self, user_id: str) -> list[tuple[str, int]]:          """          Query the top three channels in which the user is most active. @@ -127,7 +124,7 @@ class Metricity:          values = self.cursor.fetchall()          if not values: -            raise NotFoundError() +            raise NotFoundError          return values diff --git a/pydis_site/apps/api/tests/base.py b/pydis_site/apps/api/tests/base.py index c9f3cb7e..704b22cf 100644 --- a/pydis_site/apps/api/tests/base.py +++ b/pydis_site/apps/api/tests/base.py @@ -61,6 +61,7 @@ class AuthenticatedAPITestCase(APITestCase):      ...         self.assertEqual(response.status_code, 200)      """ -    def setUp(self): +    def setUp(self) -> None: +        """Bootstrap the user and authenticate it."""          super().setUp()          self.client.force_authenticate(test_user) diff --git a/pydis_site/apps/api/tests/test_bumped_threads.py b/pydis_site/apps/api/tests/test_bumped_threads.py index 316e3f0b..2e3892c7 100644 --- a/pydis_site/apps/api/tests/test_bumped_threads.py +++ b/pydis_site/apps/api/tests/test_bumped_threads.py @@ -1,7 +1,7 @@  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import BumpedThread +from pydis_site.apps.api.models import BumpedThread  class UnauthedBumpedThreadAPITests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_deleted_messages.py b/pydis_site/apps/api/tests/test_deleted_messages.py index 1eb535d8..62d17e58 100644 --- a/pydis_site/apps/api/tests/test_deleted_messages.py +++ b/pydis_site/apps/api/tests/test_deleted_messages.py @@ -1,10 +1,9 @@ -from datetime import datetime +from datetime import datetime, timezone  from django.urls import reverse -from django.utils import timezone  from .base import AuthenticatedAPITestCase -from ..models import MessageDeletionContext, User +from pydis_site.apps.api.models import MessageDeletionContext, User  class DeletedMessagesWithoutActorTests(AuthenticatedAPITestCase): @@ -18,7 +17,7 @@ class DeletedMessagesWithoutActorTests(AuthenticatedAPITestCase):          cls.data = {              'actor': None, -            'creation': datetime.utcnow().isoformat(), +            'creation': datetime.now(tz=timezone.utc).isoformat(),              'deletedmessage_set': [                  {                      'author': cls.author.id, @@ -58,7 +57,7 @@ class DeletedMessagesWithActorTests(AuthenticatedAPITestCase):          cls.data = {              'actor': cls.actor.id, -            'creation': datetime.utcnow().isoformat(), +            'creation': datetime.now(tz=timezone.utc).isoformat(),              'deletedmessage_set': [                  {                      'author': cls.author.id, @@ -90,7 +89,7 @@ class DeletedMessagesLogURLTests(AuthenticatedAPITestCase):          cls.deletion_context = MessageDeletionContext.objects.create(              actor=cls.actor, -            creation=timezone.now() +            creation=datetime.now(tz=timezone.utc),          )      def test_valid_log_url(self): diff --git a/pydis_site/apps/api/tests/test_documentation_links.py b/pydis_site/apps/api/tests/test_documentation_links.py index 4e238cbb..f4a332cb 100644 --- a/pydis_site/apps/api/tests/test_documentation_links.py +++ b/pydis_site/apps/api/tests/test_documentation_links.py @@ -1,7 +1,7 @@  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import DocumentationLink +from pydis_site.apps.api.models import DocumentationLink  class UnauthedDocumentationLinkAPITests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_filters.py b/pydis_site/apps/api/tests/test_filters.py index 5059d651..4cef1c8f 100644 --- a/pydis_site/apps/api/tests/test_filters.py +++ b/pydis_site/apps/api/tests/test_filters.py @@ -1,7 +1,7 @@  import contextlib  from dataclasses import dataclass  from datetime import timedelta -from typing import Any, Dict, Tuple, Type +from typing import Any  from django.db.models import Model  from django.urls import reverse @@ -12,22 +12,22 @@ from pydis_site.apps.api.tests.base import AuthenticatedAPITestCase  @dataclass()  class TestSequence: -    model: Type[Model] +    model: type[Model]      route: str -    object: Dict[str, Any] -    ignored_fields: Tuple[str, ...] = () +    object: dict[str, Any] +    ignored_fields: tuple[str, ...] = ()      def url(self, detail: bool = False) -> str:          return reverse(f'api:bot:{self.route}-{"detail" if detail else "list"}') -FK_FIELDS: Dict[Type[Model], Tuple[str, ...]] = { +FK_FIELDS: dict[type[Model], tuple[str, ...]] = {      FilterList: (),      Filter: ("filter_list",),  } -def get_test_sequences() -> Dict[str, TestSequence]: +def get_test_sequences() -> dict[str, TestSequence]:      filter_list1_deny_dict = {          "name": "testname",          "list_type": 0, diff --git a/pydis_site/apps/api/tests/test_github_utils.py b/pydis_site/apps/api/tests/test_github_utils.py index 95bafec0..34fae875 100644 --- a/pydis_site/apps/api/tests/test_github_utils.py +++ b/pydis_site/apps/api/tests/test_github_utils.py @@ -12,7 +12,7 @@ import rest_framework.test  from django.urls import reverse  from pydis_site import settings -from .. import github_utils +from pydis_site.apps.api import github_utils  class GeneralUtilityTests(unittest.TestCase): @@ -39,7 +39,8 @@ class GeneralUtilityTests(unittest.TestCase):          delta = datetime.timedelta(minutes=10)          self.assertAlmostEqual(decoded["exp"] - decoded["iat"], delta.total_seconds()) -        self.assertLess(decoded["exp"], (datetime.datetime.now() + delta).timestamp()) +        then = datetime.datetime.now(tz=datetime.timezone.utc) + delta +        self.assertLess(decoded["exp"], then.timestamp())  class CheckRunTests(unittest.TestCase): @@ -50,7 +51,7 @@ class CheckRunTests(unittest.TestCase):          "head_sha": "sha",          "status": "completed",          "conclusion": "success", -        "created_at": datetime.datetime.utcnow().strftime(settings.GITHUB_TIMESTAMP_FORMAT), +        "created_at": datetime.datetime.now(tz=datetime.timezone.utc).strftime(settings.GITHUB_TIMESTAMP_FORMAT),          "artifacts_url": "url",      } @@ -74,7 +75,8 @@ class CheckRunTests(unittest.TestCase):          # Set the creation time to well before the MAX_RUN_TIME          # to guarantee the right conclusion          kwargs["created_at"] = ( -            datetime.datetime.utcnow() - github_utils.MAX_RUN_TIME - datetime.timedelta(minutes=10) +            datetime.datetime.now(tz=datetime.timezone.utc) +            - github_utils.MAX_RUN_TIME - datetime.timedelta(minutes=10)          ).strftime(settings.GITHUB_TIMESTAMP_FORMAT)          with self.assertRaises(github_utils.RunTimeoutError): @@ -103,29 +105,26 @@ def get_response_authorize(_: httpx.Client, request: httpx.Request, **__) -> htt                      "account": {"login": "VALID_OWNER"},                      "access_tokens_url": "https://example.com/ACCESS_TOKEN_URL"                  }]) -            else: -                return httpx.Response( -                    401, json={"error": "auth app/installations"}, request=request -                ) +            return httpx.Response( +                401, json={"error": "auth app/installations"}, request=request +            ) -        elif path == "/installation/repositories": +        elif path == "/installation/repositories":  # noqa: RET505              if auth == "bearer app access token":                  return httpx.Response(200, request=request, json={                      "repositories": [{                          "name": "VALID_REPO"                      }]                  }) -            else:  # pragma: no cover -                return httpx.Response( -                    401, json={"error": "auth installation/repositories"}, request=request -                ) +        return httpx.Response(  # pragma: no cover +            401, json={"error": "auth installation/repositories"}, request=request +        ) -    elif request.method == "POST": +    elif request.method == "POST":  # noqa: RET505          if path == "/ACCESS_TOKEN_URL":              if auth == "bearer JWT initial token":                  return httpx.Response(200, request=request, json={"token": "app access token"}) -            else:  # pragma: no cover -                return httpx.Response(401, json={"error": "auth access_token"}, request=request) +            return httpx.Response(401, json={"error": "auth access_token"}, request=request)  # pragma: no cover      # Reaching this point means something has gone wrong      return httpx.Response(500, request=request)  # pragma: no cover @@ -138,7 +137,7 @@ class AuthorizeTests(unittest.TestCase):      def test_invalid_apps_auth(self):          """Test that an exception is raised if authorization was attempted with an invalid token.""" -        with mock.patch.object(github_utils, "generate_token", return_value="Invalid token"): +        with mock.patch.object(github_utils, "generate_token", return_value="Invalid token"):  # noqa: SIM117              with self.assertRaises(httpx.HTTPStatusError) as error:                  github_utils.authorize("VALID_OWNER", "VALID_REPO") @@ -179,7 +178,11 @@ class ArtifactFetcherTests(unittest.TestCase):                  run = github_utils.WorkflowRun(                      name="action_name",                      head_sha="action_sha", -                    created_at=datetime.datetime.now().strftime(settings.GITHUB_TIMESTAMP_FORMAT), +                    created_at=( +                        datetime.datetime +                        .now(tz=datetime.timezone.utc) +                        .strftime(settings.GITHUB_TIMESTAMP_FORMAT) +                    ),                      status="completed",                      conclusion="success",                      artifacts_url="artifacts_url" @@ -187,7 +190,7 @@ class ArtifactFetcherTests(unittest.TestCase):                  return httpx.Response(                      200, request=request, json={"workflow_runs": [dataclasses.asdict(run)]}                  ) -            elif path == "/artifact_url": +            elif path == "/artifact_url":  # noqa: RET505                  return httpx.Response(                      200, request=request, json={"artifacts": [{                          "name": "artifact_name", diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index ceb5591b..71611ee9 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -8,8 +8,8 @@ from django.db.utils import IntegrityError  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import Infraction, User -from ..serializers import InfractionSerializer +from pydis_site.apps.api.models import Infraction, User +from pydis_site.apps.api.serializers import InfractionSerializer  class UnauthenticatedTests(AuthenticatedAPITestCase): @@ -152,8 +152,8 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_filter_after(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) -        response = self.client.get(f'{url}?type=superstar&expires_after={target_time.isoformat()}') +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=5) +        response = self.client.get(url, {'type': 'superstar', 'expires_after': target_time.isoformat()})          self.assertEqual(response.status_code, 200)          infractions = response.json() @@ -161,8 +161,8 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_filter_before(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) -        response = self.client.get(f'{url}?type=superstar&expires_before={target_time.isoformat()}') +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=5) +        response = self.client.get(url, {'type': 'superstar', 'expires_before': target_time.isoformat()})          self.assertEqual(response.status_code, 200)          infractions = response.json() @@ -185,11 +185,12 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_after_before_before(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=4) -        target_time_late = datetime.datetime.utcnow() + datetime.timedelta(hours=6) +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=4) +        target_time_late = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=6)          response = self.client.get( -            f'{url}?expires_before={target_time_late.isoformat()}' -            f'&expires_after={target_time.isoformat()}' +            url, +            {'expires_before': target_time_late.isoformat(), +             'expires_after': target_time.isoformat()},          )          self.assertEqual(response.status_code, 200) @@ -198,11 +199,12 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_after_after_before_invalid(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) -        target_time_late = datetime.datetime.utcnow() + datetime.timedelta(hours=9) +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=5) +        target_time_late = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=9)          response = self.client.get( -            f'{url}?expires_before={target_time.isoformat()}' -            f'&expires_after={target_time_late.isoformat()}' +            url, +            {'expires_before': target_time.isoformat(), +             'expires_after': target_time_late.isoformat()},          )          self.assertEqual(response.status_code, 400) @@ -212,8 +214,11 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_permanent_after_invalid(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) -        response = self.client.get(f'{url}?permanent=true&expires_after={target_time.isoformat()}') +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=5) +        response = self.client.get( +            url, +            {'permanent': 'true', 'expires_after': target_time.isoformat()}, +        )          self.assertEqual(response.status_code, 400)          errors = list(response.json()) @@ -221,8 +226,11 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_permanent_before_invalid(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=5) -        response = self.client.get(f'{url}?permanent=true&expires_before={target_time.isoformat()}') +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=5) +        response = self.client.get( +            url, +            {'permanent': 'true', 'expires_before': target_time.isoformat()}, +        )          self.assertEqual(response.status_code, 400)          errors = list(response.json()) @@ -230,9 +238,10 @@ class InfractionTests(AuthenticatedAPITestCase):      def test_nonpermanent_before(self):          url = reverse('api:bot:infraction-list') -        target_time = datetime.datetime.utcnow() + datetime.timedelta(hours=6) +        target_time = datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(hours=6)          response = self.client.get( -            f'{url}?permanent=false&expires_before={target_time.isoformat()}' +            url, +            {'permanent': 'false', 'expires_before': target_time.isoformat()},          )          self.assertEqual(response.status_code, 200) @@ -522,39 +531,38 @@ class CreationTests(AuthenticatedAPITestCase):          active_infraction_types = ('timeout', 'ban', 'superstar')          for infraction_type in active_infraction_types: -            with self.subTest(infraction_type=infraction_type): -                with transaction.atomic(): -                    first_active_infraction = { -                        'user': self.user.id, -                        'actor': self.user.id, -                        'type': infraction_type, -                        'reason': 'Take me on!', -                        'active': True, -                        'expires_at': '2019-10-04T12:52:00+00:00' -                    } +            with self.subTest(infraction_type=infraction_type), transaction.atomic(): +                first_active_infraction = { +                    'user': self.user.id, +                    'actor': self.user.id, +                    'type': infraction_type, +                    'reason': 'Take me on!', +                    'active': True, +                    'expires_at': '2019-10-04T12:52:00+00:00' +                } + +                # Post the first active infraction of a type and confirm it's accepted. +                first_response = self.client.post(url, data=first_active_infraction) +                self.assertEqual(first_response.status_code, 201) -                    # Post the first active infraction of a type and confirm it's accepted. -                    first_response = self.client.post(url, data=first_active_infraction) -                    self.assertEqual(first_response.status_code, 201) - -                    second_active_infraction = { -                        'user': self.user.id, -                        'actor': self.user.id, -                        'type': infraction_type, -                        'reason': 'Take on me!', -                        'active': True, -                        'expires_at': '2019-10-04T12:52:00+00:00' +                second_active_infraction = { +                    'user': self.user.id, +                    'actor': self.user.id, +                    'type': infraction_type, +                    'reason': 'Take on me!', +                    'active': True, +                    'expires_at': '2019-10-04T12:52:00+00:00' +                } +                second_response = self.client.post(url, data=second_active_infraction) +                self.assertEqual(second_response.status_code, 400) +                self.assertEqual( +                    second_response.json(), +                    { +                        'non_field_errors': [ +                            'This user already has an active infraction of this type.' +                        ]                      } -                    second_response = self.client.post(url, data=second_active_infraction) -                    self.assertEqual(second_response.status_code, 400) -                    self.assertEqual( -                        second_response.json(), -                        { -                            'non_field_errors': [ -                                'This user already has an active infraction of this type.' -                            ] -                        } -                    ) +                )      def test_returns_201_for_second_active_infraction_of_different_type(self):          """Test if the API accepts a second active infraction of a different type than the first.""" diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index d3341b35..1cca133d 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -118,7 +118,7 @@ class StringDunderMethodTests(SimpleTestCase):              OffensiveMessage(                  id=602951077675139072,                  channel_id=291284109232308226, -                delete_date=dt(3000, 1, 1) +                delete_date=dt(3000, 1, 1, tzinfo=timezone.utc)              ),              OffTopicChannelName(name='bob-the-builders-playground'),              Role( @@ -132,7 +132,7 @@ class StringDunderMethodTests(SimpleTestCase):                      name='shawn',                      discriminator=555,                  ), -                creation=dt.utcnow() +                creation=dt.now(tz=timezone.utc)              ),              User(                  id=5, diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index b3742cdd..ee6b1fbd 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -3,7 +3,7 @@ from datetime import datetime as dt, timedelta, timezone  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import Nomination, NominationEntry, User +from pydis_site.apps.api.models import Nomination, NominationEntry, User  class CreationTests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_off_topic_channel_names.py b/pydis_site/apps/api/tests/test_off_topic_channel_names.py index 34098c92..315f707d 100644 --- a/pydis_site/apps/api/tests/test_off_topic_channel_names.py +++ b/pydis_site/apps/api/tests/test_off_topic_channel_names.py @@ -1,7 +1,7 @@  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import OffTopicChannelName +from pydis_site.apps.api.models import OffTopicChannelName  class UnauthenticatedTests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_offensive_message.py b/pydis_site/apps/api/tests/test_offensive_message.py index 3cf95b75..53f9cb48 100644 --- a/pydis_site/apps/api/tests/test_offensive_message.py +++ b/pydis_site/apps/api/tests/test_offensive_message.py @@ -3,13 +3,13 @@ import datetime  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import OffensiveMessage +from pydis_site.apps.api.models import OffensiveMessage  class CreationTests(AuthenticatedAPITestCase):      def test_accept_valid_data(self):          url = reverse('api:bot:offensivemessage-list') -        delete_at = datetime.datetime.now() + datetime.timedelta(days=1) +        delete_at = datetime.datetime.now() + datetime.timedelta(days=1)  # noqa: DTZ005          data = {              'id': '602951077675139072',              'channel_id': '291284109232308226', @@ -32,7 +32,7 @@ class CreationTests(AuthenticatedAPITestCase):      def test_returns_400_on_non_future_date(self):          url = reverse('api:bot:offensivemessage-list') -        delete_at = datetime.datetime.now() - datetime.timedelta(days=1) +        delete_at = datetime.datetime.now() - datetime.timedelta(days=1)  # noqa: DTZ005          data = {              'id': '602951077675139072',              'channel_id': '291284109232308226', @@ -46,7 +46,7 @@ class CreationTests(AuthenticatedAPITestCase):      def test_returns_400_on_negative_id_or_channel_id(self):          url = reverse('api:bot:offensivemessage-list') -        delete_at = datetime.datetime.now() + datetime.timedelta(days=1) +        delete_at = datetime.datetime.now() + datetime.timedelta(days=1)  # noqa: DTZ005          data = {              'id': '602951077675139072',              'channel_id': '291284109232308226', @@ -72,7 +72,7 @@ class CreationTests(AuthenticatedAPITestCase):  class ListTests(AuthenticatedAPITestCase):      @classmethod      def setUpTestData(cls): -        delete_at = datetime.datetime.now() + datetime.timedelta(days=1) +        delete_at = datetime.datetime.now() + datetime.timedelta(days=1)  # noqa: DTZ005          aware_delete_at = delete_at.replace(tzinfo=datetime.timezone.utc)          cls.messages = [ diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index e17569f0..9bb5fe4d 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -4,7 +4,7 @@ from django.forms.models import model_to_dict  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import Reminder, User +from pydis_site.apps.api.models import Reminder, User  class UnauthedReminderAPITests(AuthenticatedAPITestCase): @@ -59,7 +59,7 @@ class ReminderCreationTests(AuthenticatedAPITestCase):          data = {              'author': self.author.id,              'content': 'Remember to...wait what was it again?', -            'expiration': datetime.utcnow().isoformat(), +            'expiration': datetime.now(tz=timezone.utc).isoformat(),              'jump_url': "https://www.google.com",              'channel_id': 123,              'mentions': [8888, 9999], diff --git a/pydis_site/apps/api/tests/test_roles.py b/pydis_site/apps/api/tests/test_roles.py index 73c80c77..d3031990 100644 --- a/pydis_site/apps/api/tests/test_roles.py +++ b/pydis_site/apps/api/tests/test_roles.py @@ -1,7 +1,7 @@  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import Role, User +from pydis_site.apps.api.models import Role, User  class CreationTests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_rules.py b/pydis_site/apps/api/tests/test_rules.py index 3ee2d4e0..662fb8e9 100644 --- a/pydis_site/apps/api/tests/test_rules.py +++ b/pydis_site/apps/api/tests/test_rules.py @@ -5,7 +5,7 @@ from pathlib import Path  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..views import RulesView +from pydis_site.apps.api.views import RulesView  class RuleAPITests(AuthenticatedAPITestCase): diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index d86e80bb..cff4a825 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -4,9 +4,9 @@ from unittest.mock import Mock, patch  from django.urls import reverse  from .base import AuthenticatedAPITestCase -from ..models import Infraction, Role, User -from ..models.bot.metricity import NotFoundError -from ..viewsets.bot.user import UserListPagination +from pydis_site.apps.api.models import Infraction, Role, User +from pydis_site.apps.api.models.bot.metricity import NotFoundError +from pydis_site.apps.api.viewsets.bot.user import UserListPagination  class UnauthedUserAPITests(AuthenticatedAPITestCase): @@ -469,18 +469,17 @@ class UserMetricityTests(AuthenticatedAPITestCase):              with self.subTest(                  voice_infractions=case['voice_infractions'],                  voice_gate_blocked=case['voice_gate_blocked'] -            ): -                with patch("pydis_site.apps.api.viewsets.bot.user.Infraction.objects.filter") as p: -                    p.return_value = case['voice_infractions'] - -                    url = reverse('api:bot:user-metricity-data', args=[0]) -                    response = self.client.get(url) - -                    self.assertEqual(response.status_code, 200) -                    self.assertEqual( -                        response.json()["voice_gate_blocked"], -                        case["voice_gate_blocked"] -                    ) +            ), patch("pydis_site.apps.api.viewsets.bot.user.Infraction.objects.filter") as p: +                p.return_value = case['voice_infractions'] + +                url = reverse('api:bot:user-metricity-data', args=[0]) +                response = self.client.get(url) + +                self.assertEqual(response.status_code, 200) +                self.assertEqual( +                    response.json()["voice_gate_blocked"], +                    case["voice_gate_blocked"] +                )      def test_metricity_review_data(self):          # Given diff --git a/pydis_site/apps/api/tests/test_validators.py b/pydis_site/apps/api/tests/test_validators.py index 8c46fcbc..a7ec6e38 100644 --- a/pydis_site/apps/api/tests/test_validators.py +++ b/pydis_site/apps/api/tests/test_validators.py @@ -3,8 +3,8 @@ from datetime import datetime, timezone  from django.core.exceptions import ValidationError  from django.test import TestCase -from ..models.bot.bot_setting import validate_bot_setting_name -from ..models.bot.offensive_message import future_date_validator +from pydis_site.apps.api.models.bot.bot_setting import validate_bot_setting_name +from pydis_site.apps.api.models.bot.offensive_message import future_date_validator  REQUIRED_KEYS = ( diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py index b1b7dc0f..32f41667 100644 --- a/pydis_site/apps/api/views.py +++ b/pydis_site/apps/api/views.py @@ -93,7 +93,7 @@ class RulesView(APIView):          """          if target == 'html':              return f'<a href="{link}">{description}</a>' -        elif target == 'md': +        elif target == 'md':  # noqa: RET505              return f'[{description}]({link})'          else:              raise ValueError( @@ -101,7 +101,7 @@ class RulesView(APIView):              )      # `format` here is the result format, we have a link format here instead. -    def get(self, request, format=None):  # noqa: D102,ANN001,ANN201 +    def get(self, request, format=None):  # noqa: ANN001, ANN201          """          Returns a list of our community rules coupled with their keywords. diff --git a/pydis_site/apps/api/viewsets/bot/filters.py b/pydis_site/apps/api/viewsets/bot/filters.py index d6c2d18c..9c9e8338 100644 --- a/pydis_site/apps/api/viewsets/bot/filters.py +++ b/pydis_site/apps/api/viewsets/bot/filters.py @@ -1,10 +1,10 @@  from rest_framework.viewsets import ModelViewSet -from pydis_site.apps.api.models.bot.filters import (  # noqa: I101 - Preserving the filter order +from pydis_site.apps.api.models.bot.filters import (  # - Preserving the filter order      FilterList,      Filter  ) -from pydis_site.apps.api.serializers import (  # noqa: I101 - Preserving the filter order +from pydis_site.apps.api.serializers import (  # - Preserving the filter order      FilterListSerializer,      FilterSerializer,  ) diff --git a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py index d0519e86..1774004c 100644 --- a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py +++ b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py @@ -85,10 +85,9 @@ class OffTopicChannelNameViewSet(ModelViewSet):              serializer.save()              return Response(create_data, status=HTTP_201_CREATED) -        else: -            raise ParseError(detail={ -                'name': ["This query parameter is required."] -            }) +        raise ParseError(detail={ +            'name': ["This query parameter is required."] +        })      def list(self, request: Request, *args, **kwargs) -> Response:          """ diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index db73a83c..88fa3415 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -1,4 +1,3 @@ -import typing  from collections import OrderedDict  from django.db.models import Q @@ -24,14 +23,14 @@ class UserListPagination(PageNumberPagination):      page_size = 2500      page_size_query_param = "page_size" -    def get_next_page_number(self) -> typing.Optional[int]: +    def get_next_page_number(self) -> int | None:          """Get the next page number."""          if not self.page.has_next():              return None          page_number = self.page.next_page_number()          return page_number -    def get_previous_page_number(self) -> typing.Optional[int]: +    def get_previous_page_number(self) -> int | None:          """Get the previous page number."""          if not self.page.has_previous():              return None diff --git a/pydis_site/apps/content/models/tag.py b/pydis_site/apps/content/models/tag.py index 1a20d775..7c49902f 100644 --- a/pydis_site/apps/content/models/tag.py +++ b/pydis_site/apps/content/models/tag.py @@ -30,8 +30,7 @@ class Commit(models.Model):      def lines(self) -> collections.abc.Iterable[str]:          """Return each line in the commit message.""" -        for line in self.message.split("\n"): -            yield line +        yield from self.message.split("\n")      def format_authors(self) -> collections.abc.Iterable[str]:          """Return a nice representation of the author(s)' name and email.""" diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/linting.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/linting.md index f6f8a5f2..b634f513 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/linting.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/linting.md @@ -4,7 +4,7 @@ description: A guide for linting and setting up pre-commit.  ---  Your commit will be rejected by the build server if it fails to lint. -On most of our projects, we use `flake8` and `pre-commit` to ensure that the code style is consistent across the code base. +On most of our projects, we use `ruff` and `pre-commit` to ensure that the code style is consistent across the code base.  `pre-commit` is a powerful tool that helps you automatically lint before you commit.  If the linter complains, the commit is aborted so that you can fix the linting errors before committing again. diff --git a/pydis_site/apps/content/tests/helpers.py b/pydis_site/apps/content/tests/helpers.py index fad91050..0e7562e8 100644 --- a/pydis_site/apps/content/tests/helpers.py +++ b/pydis_site/apps/content/tests/helpers.py @@ -62,19 +62,19 @@ class MockPagesTestCase(TestCase):      ├── not_a_page.md      ├── tmp.md      ├── tmp -    |   ├── _info.yml -    |   └── category -    |       ├── _info.yml -    |       └── subcategory_without_info +    |   ├── _info.yml +    |   └── category +    |       ├── _info.yml +    |       └── subcategory_without_info      └── category -        ├── _info.yml -        ├── with_metadata.md -        └── subcategory -            ├── with_metadata.md -            └── without_metadata.md +        ├── _info.yml +        ├── with_metadata.md +        └── subcategory +            ├── with_metadata.md +            └── without_metadata.md      """ -    def setUp(self): +    def setUp(self) -> None:          """Create the fake filesystem."""          Path(f"{BASE_PATH}/_info.yml").write_text(CATEGORY_INFO)          Path(f"{BASE_PATH}/root.md").write_text(MARKDOWN_WITH_METADATA) diff --git a/pydis_site/apps/content/urls.py b/pydis_site/apps/content/urls.py index a7695a27..baae154d 100644 --- a/pydis_site/apps/content/urls.py +++ b/pydis_site/apps/content/urls.py @@ -8,7 +8,7 @@ from . import utils, views  app_name = "content" -def __get_all_files(root: Path, folder: typing.Optional[Path] = None) -> list[str]: +def __get_all_files(root: Path, folder: Path | None = None) -> list[str]:      """Find all folders and markdown files recursively starting from `root`."""      if not folder:          folder = root diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py index c12893ef..347640dd 100644 --- a/pydis_site/apps/content/utils.py +++ b/pydis_site/apps/content/utils.py @@ -151,8 +151,11 @@ def set_tag_commit(tag: Tag) -> None:      commit = data["commit"]      author, committer = commit["author"], commit["committer"] -    date = datetime.datetime.strptime(committer["date"], settings.GITHUB_TIMESTAMP_FORMAT) -    date = date.replace(tzinfo=datetime.timezone.utc) +    date = ( +        datetime.datetime +        .strptime(committer["date"], settings.GITHUB_TIMESTAMP_FORMAT) +        .replace(tzinfo=datetime.timezone.utc) +    )      if author["email"] == committer["email"]:          authors = [author] @@ -212,9 +215,8 @@ def get_tags() -> list[Tag]:              record_tags(tags)          return tags -    else: -        # Get tags from database -        return list(Tag.objects.all()) + +    return list(Tag.objects.all())  def get_tag(path: str, *, skip_sync: bool = False) -> Tag | list[Tag]: @@ -242,13 +244,13 @@ def get_tag(path: str, *, skip_sync: bool = False) -> Tag | list[Tag]:              if tag.last_commit is None and not skip_sync:                  set_tag_commit(tag)              return tag -        elif tag.group == name and group is None: +        elif tag.group == name and group is None:  # noqa: RET505              matches.append(tag)      if matches:          return matches -    raise Tag.DoesNotExist() +    raise Tag.DoesNotExist  def get_tag_category(tags: list[Tag] | None = None, *, collapse_groups: bool) -> dict[str, dict]: diff --git a/pydis_site/apps/content/views/tags.py b/pydis_site/apps/content/views/tags.py index 4f4bb5a2..8d3e3321 100644 --- a/pydis_site/apps/content/views/tags.py +++ b/pydis_site/apps/content/views/tags.py @@ -1,5 +1,4 @@  import re -import typing  import frontmatter  import markdown @@ -22,7 +21,7 @@ COMMAND_REGEX = re.compile(r"`*!tags? (?P<first>[\w-]+)(?P<second> [\w-]+)?`*")  class TagView(TemplateView):      """Handles tag pages.""" -    tag: typing.Union[Tag, list[Tag]] +    tag: Tag | list[Tag]      is_group: bool      def setup(self, *args, **kwargs) -> None: diff --git a/pydis_site/apps/events/urls.py b/pydis_site/apps/events/urls.py index 7ea65a31..6121d264 100644 --- a/pydis_site/apps/events/urls.py +++ b/pydis_site/apps/events/urls.py @@ -8,7 +8,7 @@ from pydis_site.apps.events.views import IndexView, PageView  app_name = "events" -def __get_all_files(root: Path, folder: typing.Optional[Path] = None) -> list[str]: +def __get_all_files(root: Path, folder: Path | None = None) -> list[str]:      """Find all folders and HTML files recursively starting from `root`."""      if not folder:          folder = root diff --git a/pydis_site/apps/events/views/page.py b/pydis_site/apps/events/views/page.py index 1622ad70..adf9e952 100644 --- a/pydis_site/apps/events/views/page.py +++ b/pydis_site/apps/events/views/page.py @@ -1,4 +1,3 @@ -from typing import List  from django.conf import settings  from django.http import Http404 @@ -8,7 +7,7 @@ from django.views.generic import TemplateView  class PageView(TemplateView):      """Handles event pages showing.""" -    def get_template_names(self) -> List[str]: +    def get_template_names(self) -> list[str]:          """Get specific template names."""          path: str = self.kwargs['path']          page_path = settings.EVENTS_PAGES_PATH / path diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py index a963f733..acf4a817 100644 --- a/pydis_site/apps/home/tests/test_repodata_helpers.py +++ b/pydis_site/apps/home/tests/test_repodata_helpers.py @@ -22,7 +22,7 @@ def mocked_requests_get(*args, **kwargs) -> "MockResponse":  # noqa: F821      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: +        with open(json_path) as json_file:              mock_data = json.load(json_file)          return MockResponse(mock_data, 200) diff --git a/pydis_site/apps/home/views.py b/pydis_site/apps/home/views.py index 8a165682..bfa9e02d 100644 --- a/pydis_site/apps/home/views.py +++ b/pydis_site/apps/home/views.py @@ -1,5 +1,4 @@  import logging -from typing import Dict, List  import httpx  from django.core.handlers.wsgi import WSGIRequest @@ -45,7 +44,7 @@ class HomeView(View):          else:              self.headers = {} -    def _get_api_data(self) -> Dict[str, Dict[str, str]]: +    def _get_api_data(self) -> dict[str, dict[str, str]]:          """          Call the GitHub API and get information about our repos. @@ -54,7 +53,7 @@ class HomeView(View):          repo_dict = {}          try:              # Fetch the data from the GitHub API -            api_data: List[dict] = httpx.get( +            api_data: list[dict] = httpx.get(                  self.github_api,                  headers=self.headers,                  timeout=settings.TIMEOUT_PERIOD @@ -89,7 +88,7 @@ class HomeView(View):          return repo_dict -    def _get_repo_data(self) -> List[RepositoryMetadata]: +    def _get_repo_data(self) -> list[RepositoryMetadata]:          """Build a list of RepositoryMetadata objects that we can use to populate the front page."""          # First off, load the timestamp of the least recently updated entry.          if settings.STATIC_BUILD: @@ -121,8 +120,7 @@ class HomeView(View):              if settings.STATIC_BUILD:                  return data -            else: -                return RepositoryMetadata.objects.bulk_create(data) +            return RepositoryMetadata.objects.bulk_create(data)          # If the data is stale, we should refresh it.          if (timezone.now() - last_update).seconds > self.repository_cache_ttl: @@ -149,8 +147,7 @@ class HomeView(View):              return database_repositories          # Otherwise, if the data is fresher than 2 minutes old, we should just return it. -        else: -            return RepositoryMetadata.objects.all() +        return RepositoryMetadata.objects.all()      def get(self, request: WSGIRequest) -> HttpResponse:          """Collect repo data and render the homepage view.""" diff --git a/pydis_site/apps/redirect/urls.py b/pydis_site/apps/redirect/urls.py index 067cccc3..a221ea12 100644 --- a/pydis_site/apps/redirect/urls.py +++ b/pydis_site/apps/redirect/urls.py @@ -83,22 +83,21 @@ def map_redirect(name: str, data: Redirect) -> list[URLPattern]:          return paths +    redirect_path_name = "pages" if new_app_name == "content" else new_app_name +    if len(data.redirect_arguments) > 0: +        redirect_arg = data.redirect_arguments[0]      else: -        redirect_path_name = "pages" if new_app_name == "content" else new_app_name -        if len(data.redirect_arguments) > 0: -            redirect_arg = data.redirect_arguments[0] -        else: -            redirect_arg = "resources/" -        new_redirect = f"/{redirect_path_name}/{redirect_arg}" +        redirect_arg = "resources/" +    new_redirect = f"/{redirect_path_name}/{redirect_arg}" -        if new_redirect == "/resources/resources/": -            new_redirect = "/resources/" +    if new_redirect == "/resources/resources/": +        new_redirect = "/resources/" -        return [distill_path( -            data.original_path, -            lambda *args: HttpResponse(REDIRECT_TEMPLATE.format(url=new_redirect)), -            name=name, -        )] +    return [distill_path( +        data.original_path, +        lambda *args: HttpResponse(REDIRECT_TEMPLATE.format(url=new_redirect)), +        name=name, +    )]  urlpatterns = [] diff --git a/pydis_site/apps/redirect/views.py b/pydis_site/apps/redirect/views.py index 21180cdf..374daf2b 100644 --- a/pydis_site/apps/redirect/views.py +++ b/pydis_site/apps/redirect/views.py @@ -1,4 +1,3 @@ -import typing as t  from django.views.generic import RedirectView @@ -15,7 +14,7 @@ class CustomRedirectView(RedirectView):          """Overwrites original as_view to add static args."""          return super().as_view(**initkwargs) -    def get_redirect_url(self, *args, **kwargs) -> t.Optional[str]: +    def get_redirect_url(self, *args, **kwargs) -> str | None:          """Extends default behaviour to use static args."""          args = self.static_args + args + tuple(kwargs.values())          if self.prefix_redirect: diff --git a/pydis_site/apps/resources/views.py b/pydis_site/apps/resources/views.py index 2375f722..a2cd8d0c 100644 --- a/pydis_site/apps/resources/views.py +++ b/pydis_site/apps/resources/views.py @@ -1,5 +1,4 @@  import json -import typing as t  from pathlib import Path  import yaml @@ -22,7 +21,7 @@ class ResourceView(View):          """Sort a tuple by its key alphabetically, disregarding 'the' as a prefix."""          name, resource = tuple_          name = name.casefold() -        if name.startswith("the ") or name.startswith("the_"): +        if name.startswith(("the ", "the_")):              return name[4:]          return name @@ -48,7 +47,7 @@ class ResourceView(View):          }          for resource_name, resource in self.resources.items():              css_classes = [] -            for tag_type in resource_tags.keys(): +            for tag_type in resource_tags:                  # Store the tags into `resource_tags`                  tags = resource.get("tags", {}).get(tag_type, [])                  for tag in tags: @@ -102,7 +101,7 @@ class ResourceView(View):              "difficulty": [to_kebabcase(tier) for tier in self.filters["Difficulty"]["filters"]],          } -    def get(self, request: WSGIRequest, resource_type: t.Optional[str] = None) -> HttpResponse: +    def get(self, request: WSGIRequest, resource_type: str | None = None) -> HttpResponse:          """List out all the resources, and any filtering options from the URL."""          # Add type filtering if the request is made to somewhere like /resources/video.          # We also convert all spaces to dashes, so they'll correspond with the filters. diff --git a/pydis_site/apps/staff/templatetags/deletedmessage_filters.py b/pydis_site/apps/staff/templatetags/deletedmessage_filters.py index 9d8f1819..c6638a3b 100644 --- a/pydis_site/apps/staff/templatetags/deletedmessage_filters.py +++ b/pydis_site/apps/staff/templatetags/deletedmessage_filters.py @@ -1,5 +1,4 @@  from datetime import datetime -from typing import Union  from django import template @@ -7,7 +6,7 @@ register = template.Library()  @register.filter -def hex_colour(colour: Union[str, int]) -> str: +def hex_colour(colour: str | int) -> str:      """      Converts the given representation of a colour to its RGB hex string. diff --git a/pydis_site/apps/staff/tests/test_deletedmessage_filters.py b/pydis_site/apps/staff/tests/test_deletedmessage_filters.py index 31215784..5e49f103 100644 --- a/pydis_site/apps/staff/tests/test_deletedmessage_filters.py +++ b/pydis_site/apps/staff/tests/test_deletedmessage_filters.py @@ -3,7 +3,7 @@ import enum  from django.test import TestCase  from django.utils import timezone -from ..templatetags import deletedmessage_filters +from pydis_site.apps.staff.templatetags import deletedmessage_filters  class Colour(enum.IntEnum):  |