diff options
| author | 2021-11-26 20:33:33 +0100 | |
|---|---|---|
| committer | 2021-11-26 20:33:33 +0100 | |
| commit | 80d355d3f3b9d4950d3bd42dbc2d79b55b1b886e (patch) | |
| tree | 2f47a75ef571a8a7fe3b2e97142810fd9358630a /pydis_site | |
| parent | Fix gitpod link in Sir Lancebot contributing guide (diff) | |
| parent | Merge pull request #612 from python-discord/redirects (diff) | |
Merge branch 'main' into patch-1
Diffstat (limited to 'pydis_site')
| -rw-r--r-- | pydis_site/apps/api/migrations/0074_reminder_failures.py | 18 | ||||
| -rw-r--r-- | pydis_site/apps/api/migrations/0075_add_redirects_filter.py | 18 | ||||
| -rw-r--r-- | pydis_site/apps/api/migrations/0075_infraction_dm_sent.py | 18 | ||||
| -rw-r--r-- | pydis_site/apps/api/migrations/0076_merge_20211125_1941.py | 14 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/filter_list.py | 1 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/infraction.py | 4 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/metricity.py | 14 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/reminder.py | 4 | ||||
| -rw-r--r-- | pydis_site/apps/api/serializers.py | 21 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_users.py | 23 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/filter_list.py | 3 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/infraction.py | 9 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/reminder.py | 13 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/user.py | 8 | ||||
| -rw-r--r-- | pydis_site/apps/redirect/redirects.yaml | 2 | ||||
| -rw-r--r-- | pydis_site/apps/redirect/urls.py | 114 | ||||
| -rw-r--r-- | pydis_site/urls.py | 16 | 
17 files changed, 259 insertions, 41 deletions
| 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/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/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 33fb7ad7..901f191a 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,12 +46,12 @@ class Metricity:          self.cursor.execute(              """              SELECT -              COUNT(*) +                COUNT(*)              FROM messages              WHERE -              author_id = '%s' -              AND NOT is_deleted -              AND NOT %s::varchar[] @> ARRAY[channel_id] +                author_id = '%s' +                AND NOT is_deleted +                AND channel_id NOT IN %s              """,              [user_id, EXCLUDE_CHANNELS]          ) @@ -79,7 +79,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/serializers.py b/pydis_site/apps/api/serializers.py index f47bedca..de2fccff 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( @@ -231,7 +240,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/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/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')), +    ) | 
