diff options
| author | 2021-04-26 17:17:06 +0200 | |
|---|---|---|
| committer | 2021-12-18 17:59:36 +0100 | |
| commit | 2f5effe075287dab4965f3278031bcd433a83f7c (patch) | |
| tree | 4e38de386c93c4e8d68354627edbd895310d5fe0 /pydis_site | |
| parent | Merge pull request #635 from python-discord/revert-metricity-change (diff) | |
Filter: new schema
This commit adds new filter schema as described in #479
Diffstat (limited to 'pydis_site')
| -rw-r--r-- | pydis_site/apps/api/models/__init__.py | 5 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/__init__.py | 2 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/filter_list.py | 42 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/filters.py | 187 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/filter_list.py | 2 | 
5 files changed, 194 insertions, 44 deletions
diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index fd5bf220..72f59b57 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,11 @@  # flake8: noqa  from .bot import (      FilterList, +    FilterSettings, +    FilterAction, +    ChannelRange, +    Filter, +    FilterOverride,      BotSetting,      DocumentationLink,      DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index ac864de3..1bfe0063 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,5 @@  # flake8: noqa -from .filter_list import FilterList +from .filters import FilterList, FilterSettings, FilterAction, ChannelRange, Filter, FilterOverride  from .bot_setting import BotSetting  from .deleted_message import DeletedMessage  from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py deleted file mode 100644 index d30f7213..00000000 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin - - -class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): -    """An item that is either allowed or denied.""" - -    FilterListType = models.TextChoices( -        'FilterListType', -        'GUILD_INVITE ' -        'FILE_FORMAT ' -        'DOMAIN_NAME ' -        'FILTER_TOKEN ' -        'REDIRECT ' -    ) -    type = models.CharField( -        max_length=50, -        help_text="The type of allowlist this is on.", -        choices=FilterListType.choices, -    ) -    allowed = models.BooleanField( -        help_text="Whether this item is on the allowlist or the denylist." -    ) -    content = models.TextField( -        help_text="The data to add to the allow or denylist." -    ) -    comment = models.TextField( -        help_text="Optional comment on this entry.", -        null=True -    ) - -    class Meta: -        """Metaconfig for this model.""" - -        # This constraint ensures only one filterlist with the -        # same content can exist. This means that we cannot have both an allow -        # and a deny for the same item, and we cannot have duplicates of the -        # same item. -        constraints = [ -            models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'), -        ] diff --git a/pydis_site/apps/api/models/bot/filters.py b/pydis_site/apps/api/models/bot/filters.py new file mode 100644 index 00000000..dfc38e82 --- /dev/null +++ b/pydis_site/apps/api/models/bot/filters.py @@ -0,0 +1,187 @@ +from typing import List + +from django.contrib.postgres.fields import ArrayField +from django.core.exceptions import ValidationError +from django.db import models +from django.db.models import UniqueConstraint + + +class FilterListType(models.IntegerChoices): +    """Choice between allow or deny for a list type.""" + +    ALLOW: 1 +    DENY: 0 + + +class InfractionType(models.TextChoices): +    """Possible type of infractions.""" + +    NOTE = "Note" +    WARN = "Warn" +    MUTE = "Mute" +    KICK = "Kick" +    BAN = "Ban" + + +# Valid special values in ping related fields +VALID_PINGS = ("everyone", "here", "moderators", "onduty", "admins") + + +def validate_ping_field(value_list: List[str]) -> None: +    """Validate that the values are either a special value or a UID.""" +    for value in value_list: +        # Check if it is a special value +        if value in VALID_PINGS: +            continue +        # Check if it is a UID +        if value.isnumeric(): +            continue + +        raise ValidationError(f"{value!r} isn't a valid ping type.") + + +class FilterList(models.Model): +    """Represent a list in its allow or deny form.""" + +    name = models.CharField(max_length=50, help_text="The unique name of this list.") +    list_type = models.IntegerField( +        choices=FilterListType.choices, +        help_text="Whenever this list is an allowlist or denylist" +    ) + +    filters = models.ManyToManyField("Filter", help_text="The content of this list.") +    default_settings = models.ForeignKey( +        "FilterSettings", +        models.CASCADE, +        help_text="Default parameters of this list." +    ) + +    class Meta: +        """Constrain name and list_type unique.""" + +        constraints = ( +            UniqueConstraint(fields=("name", "list_type"), name="unique_name_type"), +        ) + +    def __str__(self) -> str: +        return f"Filter {'allow' if self.list_type == 1 else 'deny'}list {self.name!r}" + + +class FilterSettings(models.Model): +    """Persistent settings of a filter list.""" + +    ping_type = ArrayField( +        models.CharField(max_length=20), +        validators=(validate_ping_field,), +        help_text="Who to ping when this filter triggers." +    ) +    filter_dm = models.BooleanField(help_text="Whenever DMs should be filtered.") +    dm_ping_type = ArrayField( +        models.CharField(max_length=20), +        validators=(validate_ping_field,), +        help_text="Who to ping when this filter triggers on a DM." +    ) +    delete_messages = models.BooleanField( +        help_text="Whenever this filter should delete messages triggering it." +    ) +    bypass_roles = ArrayField( +        models.BigIntegerField(), +        help_text="Roles and users who can bypass this filter." +    ) +    enabled = models.BooleanField( +        help_text="Whenever ths filter is currently enabled." +    ) +    default_action = models.ForeignKey( +        "FilterAction", +        models.CASCADE, +        help_text="The default action to perform." +    ) +    default_range = models.ForeignKey( +        "ChannelRange", +        models.CASCADE, +        help_text="Where does this filter apply." +    ) + + +class FilterAction(models.Model): +    """The action to take when a filter is triggered.""" + +    user_dm = models.CharField( +        max_length=1000, +        null=True, +        help_text="The DM to send to a user triggering this filter." +    ) +    infraction_type = models.CharField( +        choices=InfractionType.choices, +        max_length=4, +        null=True, +        help_text="The infraction to apply to this user." +    ) +    infraction_reason = models.CharField( +        max_length=1000, +        help_text="The reason to give for the infraction." +    ) +    infraction_duration = models.DurationField( +        null=True, +        help_text="The duration of the infraction. Null if permanent." +    ) + + +class ChannelRange(models.Model): +    """ +    Where a filter should apply. + +    The resolution is done in the following order: +    - disallowed channels +    - disallowed categories +    - allowed categories +    - allowed channels +    - default +    """ + +    disallowed_channels = ArrayField(models.IntegerField()) +    disallowed_categories = ArrayField(models.IntegerField()) +    allowed_channels = ArrayField(models.IntegerField()) +    allowed_category = ArrayField(models.IntegerField()) +    default = models.BooleanField() + + +class Filter(models.Model): +    """One specific trigger of a list.""" + +    content = models.CharField(max_length=100, help_text="The definition of this filter.") +    description = models.CharField(max_length=200, help_text="Why this filter has been added.") +    additional_field = models.BooleanField(null=True, help_text="Implementation specific field.") +    override = models.ForeignKey( +        "FilterOverride", +        models.SET_NULL, +        null=True, +        help_text="Override the default settings." +    ) + +    def __str__(self) -> str: +        return f"Filter {self.content!r}" + + +class FilterOverride(models.Model): +    """ +    Setting overrides of a specific filter. + +    Any non-null value will override the default ones. +    """ + +    ping_type = ArrayField( +        models.CharField(max_length=20), +        validators=(validate_ping_field,), null=True +    ) +    filter_dm = models.BooleanField(null=True) +    dm_ping_type = ArrayField( +        models.CharField(max_length=20), +        validators=(validate_ping_field,), +        null=True +    ) +    delete_messages = models.BooleanField(null=True) +    bypass_roles = ArrayField(models.IntegerField(), null=True) +    enabled = models.BooleanField(null=True) +    default_action = models.ForeignKey("FilterAction", models.CASCADE, null=True) +    default_range = models.ForeignKey("ChannelRange", models.CASCADE, null=True) diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py index 4b05acee..3eacdaaa 100644 --- a/pydis_site/apps/api/viewsets/bot/filter_list.py +++ b/pydis_site/apps/api/viewsets/bot/filter_list.py @@ -3,7 +3,7 @@ from rest_framework.request import Request  from rest_framework.response import Response  from rest_framework.viewsets import ModelViewSet -from pydis_site.apps.api.models.bot.filter_list import FilterList +from pydis_site.apps.api.models.bot.filters import FilterList  from pydis_site.apps.api.serializers import FilterListSerializer  |