diff options
author | 2021-12-13 11:14:16 +0530 | |
---|---|---|
committer | 2021-12-13 11:14:16 +0530 | |
commit | 4693de033391977d925e8af2bd956202d6f81d11 (patch) | |
tree | 722fe0e9771c64965d89d11db603a99a64db231b /pydis_site | |
parent | Remove get_queryset() and add new class variable . (diff) | |
parent | Merge pull request #633 from python-discord/dependabot/pip/django-3.1.14 (diff) |
Merge branch 'main' into otn_softdel
Diffstat (limited to 'pydis_site')
26 files changed, 339 insertions, 54 deletions
diff --git a/pydis_site/apps/api/__init__.py b/pydis_site/apps/api/__init__.py index e69de29b..afa5b4d5 100644 --- a/pydis_site/apps/api/__init__.py +++ b/pydis_site/apps/api/__init__.py @@ -0,0 +1 @@ +default_app_config = 'pydis_site.apps.api.apps.ApiConfig' diff --git a/pydis_site/apps/api/apps.py b/pydis_site/apps/api/apps.py index 76810b2e..18eda9e3 100644 --- a/pydis_site/apps/api/apps.py +++ b/pydis_site/apps/api/apps.py @@ -4,4 +4,12 @@ from django.apps import AppConfig class ApiConfig(AppConfig): """Django AppConfig for the API app.""" - name = 'api' + name = 'pydis_site.apps.api' + + def ready(self) -> None: + """ + Gets called as soon as the registry is fully populated. + + https://docs.djangoproject.com/en/3.2/ref/applications/#django.apps.AppConfig.ready + """ + import pydis_site.apps.api.signals # noqa: F401 diff --git a/pydis_site/apps/api/migrations/0074_reminder_failures.py b/pydis_site/apps/api/migrations/0074_reminder_failures.py new file mode 100644 index 00000000..2860046e --- /dev/null +++ b/pydis_site/apps/api/migrations/0074_reminder_failures.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.14 on 2021-10-27 17:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0073_otn_allow_GT_and_LT'), + ] + + operations = [ + migrations.AddField( + model_name='reminder', + name='failures', + field=models.IntegerField(default=0, help_text='Number of times we attempted to send the reminder and failed.'), + ), + ] diff --git a/pydis_site/apps/api/migrations/0075_add_redirects_filter.py b/pydis_site/apps/api/migrations/0075_add_redirects_filter.py new file mode 100644 index 00000000..23dc176f --- /dev/null +++ b/pydis_site/apps/api/migrations/0075_add_redirects_filter.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.14 on 2021-11-17 10:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0074_reminder_failures'), + ] + + operations = [ + migrations.AlterField( + model_name='filterlist', + name='type', + field=models.CharField(choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token'), ('REDIRECT', 'Redirect')], help_text='The type of allowlist this is on.', max_length=50), + ), + ] diff --git a/pydis_site/apps/api/migrations/0075_infraction_dm_sent.py b/pydis_site/apps/api/migrations/0075_infraction_dm_sent.py new file mode 100644 index 00000000..c0ac709d --- /dev/null +++ b/pydis_site/apps/api/migrations/0075_infraction_dm_sent.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.14 on 2021-11-10 22:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0074_reminder_failures'), + ] + + operations = [ + migrations.AddField( + model_name='infraction', + name='dm_sent', + field=models.BooleanField(help_text='Whether a DM was sent to the user when infraction was applied.', null=True), + ), + ] diff --git a/pydis_site/apps/api/migrations/0076_merge_20211125_1941.py b/pydis_site/apps/api/migrations/0076_merge_20211125_1941.py new file mode 100644 index 00000000..097d0a0c --- /dev/null +++ b/pydis_site/apps/api/migrations/0076_merge_20211125_1941.py @@ -0,0 +1,14 @@ +# Generated by Django 3.0.14 on 2021-11-25 19:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0075_infraction_dm_sent'), + ('api', '0075_add_redirects_filter'), + ] + + operations = [ + ] diff --git a/pydis_site/apps/api/migrations/0077_use_generic_jsonfield.py b/pydis_site/apps/api/migrations/0077_use_generic_jsonfield.py new file mode 100644 index 00000000..9e8f2fb9 --- /dev/null +++ b/pydis_site/apps/api/migrations/0077_use_generic_jsonfield.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.13 on 2021-11-27 12:27 + +import django.contrib.postgres.fields +from django.db import migrations, models +import pydis_site.apps.api.models.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0076_merge_20211125_1941'), + ] + + operations = [ + migrations.AlterField( + model_name='botsetting', + name='data', + field=models.JSONField(help_text='The actual settings of this setting.'), + ), + migrations.AlterField( + model_name='deletedmessage', + name='embeds', + field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(validators=[pydis_site.apps.api.models.utils.validate_embed]), blank=True, help_text='Embeds attached to this message.', size=None), + ), + ] diff --git a/pydis_site/apps/api/models/bot/bot_setting.py b/pydis_site/apps/api/models/bot/bot_setting.py index 2a3944f8..1bcb1ae6 100644 --- a/pydis_site/apps/api/models/bot/bot_setting.py +++ b/pydis_site/apps/api/models/bot/bot_setting.py @@ -1,4 +1,3 @@ -from django.contrib.postgres import fields as pgfields from django.core.exceptions import ValidationError from django.db import models @@ -24,6 +23,6 @@ class BotSetting(ModelReprMixin, models.Model): max_length=50, validators=(validate_bot_setting_name,) ) - data = pgfields.JSONField( + data = models.JSONField( help_text="The actual settings of this setting." ) diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py index d279e137..d30f7213 100644 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -12,6 +12,7 @@ class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): 'FILE_FORMAT ' 'DOMAIN_NAME ' 'FILTER_TOKEN ' + 'REDIRECT ' ) type = models.CharField( max_length=50, diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py index 60c1e8dd..913631d4 100644 --- a/pydis_site/apps/api/models/bot/infraction.py +++ b/pydis_site/apps/api/models/bot/infraction.py @@ -57,6 +57,10 @@ class Infraction(ModelReprMixin, models.Model): default=False, help_text="Whether the infraction is a shadow infraction." ) + dm_sent = models.BooleanField( + null=True, + help_text="Whether a DM was sent to the user when infraction was applied." + ) def __str__(self): """Returns some info on the current infraction, for display purposes.""" diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 60e2a553..bab3368d 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -48,7 +48,7 @@ class Message(ModelReprMixin, models.Model): blank=True ) embeds = pgfields.ArrayField( - pgfields.JSONField( + models.JSONField( validators=(validate_embed,) ), blank=True, diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 33fb7ad7..52e946ac 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -4,10 +4,10 @@ from django.db import connections BLOCK_INTERVAL = 10 * 60 # 10 minute blocks -EXCLUDE_CHANNELS = [ +EXCLUDE_CHANNELS = ( "267659945086812160", # Bot commands "607247579608121354" # SeasonalBot commands -] +) class NotFoundError(Exception): @@ -46,14 +46,12 @@ class Metricity: self.cursor.execute( """ SELECT - COUNT(*) - FROM messages + message_count + FROM user_has_approx_message_count WHERE - author_id = '%s' - AND NOT is_deleted - AND NOT %s::varchar[] @> ARRAY[channel_id] + author_id = '%s' """, - [user_id, EXCLUDE_CHANNELS] + [user_id] ) values = self.cursor.fetchone() @@ -79,7 +77,7 @@ class Metricity: WHERE author_id='%s' AND NOT is_deleted - AND NOT %s::varchar[] @> ARRAY[channel_id] + AND channel_id NOT IN %s GROUP BY interval ) block_query; """, diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py index 7d968a0e..173900ee 100644 --- a/pydis_site/apps/api/models/bot/reminder.py +++ b/pydis_site/apps/api/models/bot/reminder.py @@ -59,6 +59,10 @@ class Reminder(ModelReprMixin, models.Model): blank=True, help_text="IDs of roles or users to ping with the reminder." ) + failures = models.IntegerField( + default=0, + help_text="Number of times we attempted to send the reminder and failed." + ) def __str__(self): """Returns some info on the current reminder, for display purposes.""" diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py index 0e220a1d..859394d2 100644 --- a/pydis_site/apps/api/models/utils.py +++ b/pydis_site/apps/api/models/utils.py @@ -103,11 +103,10 @@ def validate_embed(embed: Any) -> None: Example: - >>> from django.contrib.postgres import fields as pgfields >>> from django.db import models >>> from pydis_site.apps.api.models.utils import validate_embed >>> class MyMessage(models.Model): - ... embed = pgfields.JSONField( + ... embed = models.JSONField( ... validators=( ... validate_embed, ... ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 9b351be2..ac05ebd4 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -145,7 +145,16 @@ class InfractionSerializer(ModelSerializer): model = Infraction fields = ( - 'id', 'inserted_at', 'expires_at', 'active', 'user', 'actor', 'type', 'reason', 'hidden' + 'id', + 'inserted_at', + 'expires_at', + 'active', + 'user', + 'actor', + 'type', + 'reason', + 'hidden', + 'dm_sent' ) validators = [ UniqueTogetherValidator( @@ -236,7 +245,15 @@ class ReminderSerializer(ModelSerializer): model = Reminder fields = ( - 'active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id', 'mentions' + 'active', + 'author', + 'jump_url', + 'channel_id', + 'content', + 'expiration', + 'id', + 'mentions', + 'failures' ) diff --git a/pydis_site/apps/api/signals.py b/pydis_site/apps/api/signals.py new file mode 100644 index 00000000..5c26bfb6 --- /dev/null +++ b/pydis_site/apps/api/signals.py @@ -0,0 +1,12 @@ +from django.db.models.signals import post_delete +from django.dispatch import receiver + +from pydis_site.apps.api.models.bot import Role, User + + +@receiver(signal=post_delete, sender=Role) +def delete_role_from_user(sender: Role, instance: Role, **kwargs) -> None: + """Unassigns the Role (instance) that is being deleted from every user that has it.""" + for user in User.objects.filter(roles__contains=[instance.id]): + del user.roles[user.roles.index(instance.id)] + user.save() diff --git a/pydis_site/apps/api/tests/test_roles.py b/pydis_site/apps/api/tests/test_roles.py index d39cea4d..73c80c77 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 +from ..models import Role, User class CreationTests(AuthenticatedAPITestCase): @@ -35,6 +35,20 @@ class CreationTests(AuthenticatedAPITestCase): permissions=6, position=0, ) + cls.role_to_delete = Role.objects.create( + id=7, + name="role to delete", + colour=7, + permissions=7, + position=0, + ) + cls.role_unassigned_test_user = User.objects.create( + id=8, + name="role_unassigned_test_user", + discriminator="0000", + roles=[cls.role_to_delete.id], + in_guild=True + ) def _validate_roledict(self, role_dict: dict) -> None: """Helper method to validate a dict representing a role.""" @@ -81,11 +95,11 @@ class CreationTests(AuthenticatedAPITestCase): url = reverse('api:bot:role-list') response = self.client.get(url) - self.assertContains(response, text="id", count=4, status_code=200) + self.assertContains(response, text="id", count=5, status_code=200) roles = response.json() self.assertIsInstance(roles, list) - self.assertEqual(len(roles), 4) + self.assertEqual(len(roles), 5) for role in roles: self._validate_roledict(role) @@ -181,6 +195,12 @@ class CreationTests(AuthenticatedAPITestCase): response = self.client.delete(url) self.assertEqual(response.status_code, 204) + def test_role_delete_unassigned(self): + """Tests if the deleted Role gets unassigned from the user.""" + self.role_to_delete.delete() + self.role_unassigned_test_user.refresh_from_db() + self.assertEqual(self.role_unassigned_test_user.roles, []) + def test_role_detail_404_all_methods(self): """Tests detail view with non-existing ID.""" url = reverse('api:bot:role-detail', args=(20190815,)) diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 295bcf64..81bfd43b 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -408,7 +408,7 @@ class UserMetricityTests(AuthenticatedAPITestCase): in_guild=True, ) - def test_get_metricity_data(self): + def test_get_metricity_data_under_1k(self): # Given joined_at = "foo" total_messages = 1 @@ -421,13 +421,32 @@ class UserMetricityTests(AuthenticatedAPITestCase): # Then self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), { + self.assertCountEqual(response.json(), { "joined_at": joined_at, "total_messages": total_messages, "voice_banned": False, "activity_blocks": total_blocks }) + def test_get_metricity_data_over_1k(self): + # Given + joined_at = "foo" + total_messages = 1001 + total_blocks = 1001 + self.mock_metricity_user(joined_at, total_messages, total_blocks, []) + + # When + url = reverse('api:bot:user-metricity-data', args=[0]) + response = self.client.get(url) + + # Then + self.assertEqual(response.status_code, 200) + self.assertCountEqual(response.json(), { + "joined_at": joined_at, + "total_messages": total_messages, + "voice_banned": False, + }) + def test_no_metricity_user(self): # Given self.mock_no_metricity_user() diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py index 2cb21ab9..4b05acee 100644 --- a/pydis_site/apps/api/viewsets/bot/filter_list.py +++ b/pydis_site/apps/api/viewsets/bot/filter_list.py @@ -59,7 +59,8 @@ class FilterListViewSet(ModelViewSet): ... ["GUILD_INVITE","Guild Invite"], ... ["FILE_FORMAT","File Format"], ... ["DOMAIN_NAME","Domain Name"], - ... ["FILTER_TOKEN","Filter Token"] + ... ["FILTER_TOKEN","Filter Token"], + ... ["REDIRECT", "Redirect"] ... ] #### Status codes diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index f8b0cb9d..8a48ed1f 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -70,7 +70,8 @@ class InfractionViewSet( ... 'actor': 125435062127820800, ... 'type': 'ban', ... 'reason': 'He terk my jerb!', - ... 'hidden': True + ... 'hidden': True, + ... 'dm_sent': True ... } ... ] @@ -100,7 +101,8 @@ class InfractionViewSet( ... 'hidden': True, ... 'type': 'ban', ... 'reason': 'He terk my jerb!', - ... 'user': 172395097705414656 + ... 'user': 172395097705414656, + ... 'dm_sent': False ... } #### Response format @@ -118,7 +120,8 @@ class InfractionViewSet( >>> { ... 'active': True, ... 'expires_at': '4143-02-15T21:04:31+00:00', - ... 'reason': 'durka derr' + ... 'reason': 'durka derr', + ... 'dm_sent': True ... } #### Response format diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 111660d9..78d7cb3b 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -42,7 +42,8 @@ class ReminderViewSet( ... 'expiration': '5018-11-20T15:52:00Z', ... 'id': 11, ... 'channel_id': 634547009956872193, - ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>" + ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>", + ... 'failures': 3 ... }, ... ... ... ] @@ -67,7 +68,8 @@ class ReminderViewSet( ... 'expiration': '5018-11-20T15:52:00Z', ... 'id': 11, ... 'channel_id': 634547009956872193, - ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>" + ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>", + ... 'failures': 3 ... } #### Status codes @@ -80,7 +82,7 @@ class ReminderViewSet( #### Request body >>> { ... 'author': int, - ... 'mentions': List[int], + ... 'mentions': list[int], ... 'content': str, ... 'expiration': str, # ISO-formatted datetime ... 'channel_id': int, @@ -98,9 +100,10 @@ class ReminderViewSet( #### Request body >>> { - ... 'mentions': List[int], + ... 'mentions': list[int], ... 'content': str, - ... 'expiration': str # ISO-formatted datetime + ... 'expiration': str, # ISO-formatted datetime + ... 'failures': int ... } #### Status codes diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 22d13dc4..ed661323 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -271,9 +271,15 @@ class UserViewSet(ModelViewSet): with Metricity() as metricity: try: data = metricity.user(user.id) + data["total_messages"] = metricity.total_messages(user.id) + if data["total_messages"] < 1000: + # Only calculate and return activity_blocks if the user has a small amount + # of messages, as calculating activity_blocks is expensive. + # 1000 message chosen as an arbitrarily large number. + data["activity_blocks"] = metricity.total_message_blocks(user.id) + data["voice_banned"] = voice_banned - data["activity_blocks"] = metricity.total_message_blocks(user.id) return Response(data, status=status.HTTP_200_OK) except NotFoundError: return Response(dict(detail="User not found in metricity"), diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md index a0d3d463..e3cd8f0c 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md @@ -18,12 +18,17 @@ toc: 1 ## Using Gitpod Sir Lancebot can be edited and tested on Gitpod. Gitpod will automatically install the correct dependencies and Python version, so you can get straight to coding. -To do this, you will need a Gitpod account, which you can get [here](https://www.gitpod.io/#get-started), and a fork of Sir Lancebot. This guide covers forking the repository [here](#fork-the-project). Afterwards, either click the button on Sir Lancebot's README or go to [https://gitpod.io/#/python-discord/sir-lancebot]() and run the following commands in the terminal: + +To do this, you will need a Gitpod account, which you can get [here](https://www.gitpod.io/#get-started), and a fork of Sir Lancebot. This guide covers forking the repository [here](#fork-the-project). + +Afterwards, click on [this link](https://gitpod.io/#/github.com/python-discord/sir-lancebot) to spin up a new workspace for Sir Lancebot. Then run the following commands in the terminal after the existing tasks have finished running: ```sh git remote rename origin upstream git add remote origin https://github.com/{your_username}/sir-lancebot ``` -Make sure you replace `{your_username}` with your Github username. These commands will set Python Discord as the parent repository, and your branch as the fork. This means you can easily grab new changes from the parent repository. Once you set your environment variables to test your code, you are ready to begin contributing to Sir Lancebot. +Make sure you replace `{your_username}` with your Github username. These commands will set the Sir Lancebot repository as the secondary remote, and your fork as the primary remote. This means you can easily grab new changes from the main Sir Lancebot repository. + +Once you've set up [a test server and bot account](#test-server-and-bot-account) and your [environment variables](#environment-variables), you are ready to begin contributing to Sir Lancebot! ## Using Docker Sir Lancebot can be started using Docker. Using Docker is generally recommended (but not strictly required) because it abstracts away some additional set up work. diff --git a/pydis_site/apps/redirect/redirects.yaml b/pydis_site/apps/redirect/redirects.yaml index def4154b..9bcf3afd 100644 --- a/pydis_site/apps/redirect/redirects.yaml +++ b/pydis_site/apps/redirect/redirects.yaml @@ -182,7 +182,7 @@ events_game_jams_twenty_twenty_rules_redirect: redirect_arguments: ["game-jams/2020/rules"] events_game_jams_twenty_twenty_technical_requirements_redirect: - original_path: pages/events/game-jam-2020/technical-requirements + original_path: pages/events/game-jam-2020/technical-requirements/ redirect_route: "events:page" redirect_arguments: ["game-jams/2020/technical-requirements"] diff --git a/pydis_site/apps/redirect/urls.py b/pydis_site/apps/redirect/urls.py index 6187af17..f7ddf45b 100644 --- a/pydis_site/apps/redirect/urls.py +++ b/pydis_site/apps/redirect/urls.py @@ -1,19 +1,105 @@ +import dataclasses +import re + import yaml -from django.conf import settings -from django.urls import path +from django import conf +from django.urls import URLPattern, path +from django_distill import distill_path +from pydis_site import settings +from pydis_site.apps.content import urls as pages_urls from pydis_site.apps.redirect.views import CustomRedirectView +from pydis_site.apps.resources import urls as resources_urls app_name = "redirect" -urlpatterns = [ - path( - data["original_path"], - CustomRedirectView.as_view( - pattern_name=data["redirect_route"], - static_args=tuple(data.get("redirect_arguments", ())), - prefix_redirect=data.get("prefix_redirect", False) - ), - name=name - ) - for name, data in yaml.safe_load(settings.REDIRECTIONS_PATH.read_text()).items() -] + + +__PARAMETER_REGEX = re.compile(r"<\w+:\w+>") +REDIRECT_TEMPLATE = "<meta http-equiv=\"refresh\" content=\"0; URL={url}\"/>" + + [email protected](frozen=True) +class Redirect: + """Metadata about a redirect route.""" + + original_path: str + redirect_route: str + redirect_arguments: tuple[str] = tuple() + + prefix_redirect: bool = False + + +def map_redirect(name: str, data: Redirect) -> list[URLPattern]: + """Return a pattern using the Redirects app, or a static HTML redirect for static builds.""" + if not settings.env("STATIC_BUILD"): + # Normal dynamic redirect + return [path( + data.original_path, + CustomRedirectView.as_view( + pattern_name=data.redirect_route, + static_args=tuple(data.redirect_arguments), + prefix_redirect=data.prefix_redirect + ), + name=name + )] + + # Create static HTML redirects for static builds + new_app_name = data.redirect_route.split(":")[0] + + if __PARAMETER_REGEX.search(data.original_path): + # Redirects for paths which accept parameters + # We generate an HTML redirect file for all possible entries + paths = [] + + class RedirectFunc: + def __init__(self, new_url: str, _name: str): + self.result = REDIRECT_TEMPLATE.format(url=new_url) + self.__qualname__ = _name + + def __call__(self, *args, **kwargs): + return self.result + + if new_app_name == resources_urls.app_name: + items = resources_urls.get_all_resources() + elif new_app_name == pages_urls.app_name: + items = pages_urls.get_all_pages() + else: + raise ValueError(f"Unknown app in redirect: {new_app_name}") + + for item in items: + entry = list(item.values())[0] + + # Replace dynamic redirect with concrete path + concrete_path = __PARAMETER_REGEX.sub(entry, data.original_path) + new_redirect = f"/{new_app_name}/{entry}" + pattern_name = f"{name}_{entry}" + + paths.append(distill_path( + concrete_path, + RedirectFunc(new_redirect, pattern_name), + name=pattern_name + )) + + return paths + + 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}" + + if new_redirect == "/resources/resources/": + new_redirect = "/resources/" + + return [distill_path( + data.original_path, + lambda *args: REDIRECT_TEMPLATE.format(url=new_redirect), + name=name, + )] + + +urlpatterns = [] +for _name, _data in yaml.safe_load(conf.settings.REDIRECTIONS_PATH.read_text()).items(): + urlpatterns.extend(map_redirect(_name, Redirect(**_data))) diff --git a/pydis_site/urls.py b/pydis_site/urls.py index 51ef4214..6cd31f26 100644 --- a/pydis_site/urls.py +++ b/pydis_site/urls.py @@ -11,19 +11,25 @@ NON_STATIC_PATTERNS = [ # Internal API ingress (cluster local) path('pydis-api/', include('pydis_site.apps.api.urls', namespace='internal_api')), - # This must be mounted before the `content` app to prevent Django - # from wildcard matching all requests to `pages/...`. - path('', include('pydis_site.apps.redirect.urls')), path('', include('django_prometheus.urls')), - - path('staff/', include('pydis_site.apps.staff.urls', namespace='staff')), ] if not settings.env("STATIC_BUILD") else [] urlpatterns = ( *NON_STATIC_PATTERNS, + + # This must be mounted before the `content` app to prevent Django + # from wildcard matching all requests to `pages/...`. + path('', include('pydis_site.apps.redirect.urls')), + path('pages/', include('pydis_site.apps.content.urls', namespace='content')), path('resources/', include('pydis_site.apps.resources.urls')), path('events/', include('pydis_site.apps.events.urls', namespace='events')), path('', include('pydis_site.apps.home.urls', namespace='home')), ) + + +if not settings.env("STATIC_BUILD"): + urlpatterns += ( + path('staff/', include('pydis_site.apps.staff.urls', namespace='staff')), + ) |