diff options
author | 2023-04-06 19:26:31 +0300 | |
---|---|---|
committer | 2023-04-06 19:26:31 +0300 | |
commit | 593a41e55e87fb811027e7a08be56a4097ab1fbc (patch) | |
tree | df62866382f4bdad7415b8bf3336ebd6134bc416 /pydis_site/apps/api/models | |
parent | Bump sentry-sdk from 1.19.0 to 1.19.1 (#931) (diff) | |
parent | Make additional_settings non-null with dict default (diff) |
Merge pull request #861 from python-discord/new-filter-schema
New Filtering System
Diffstat (limited to 'pydis_site/apps/api/models')
-rw-r--r-- | pydis_site/apps/api/models/__init__.py | 3 | ||||
-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 | 262 |
4 files changed, 265 insertions, 44 deletions
diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index a197e988..fee4c8d5 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,10 +1,11 @@ # flake8: noqa from .bot import ( + FilterList, + Filter, BotSetting, BumpedThread, DocumentationLink, DeletedMessage, - FilterList, Infraction, Message, MessageDeletionContext, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 013bb85e..6f09473d 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,9 +1,9 @@ # flake8: noqa +from .filters import FilterList, Filter from .bot_setting import BotSetting from .bumped_thread import BumpedThread from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink -from .filter_list import FilterList from .infraction import Infraction from .message import Message from .aoc_completionist_block import AocCompletionistBlock 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..71f8771f --- /dev/null +++ b/pydis_site/apps/api/models/bot/filters.py @@ -0,0 +1,262 @@ +from django.contrib.postgres.fields import ArrayField +from django.core.validators import MinValueValidator +from django.db import models +from django.db.models import UniqueConstraint + +# Must be imported that way to avoid circular imports +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin +from .infraction import Infraction + + +class FilterListType(models.IntegerChoices): + """Choice between allow or deny for a list type.""" + + ALLOW = 1 + DENY = 0 + + +class FilterList(ModelTimestampMixin, ModelReprMixin, 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="Whether this list is an allowlist or denylist" + ) + dm_content = models.CharField( + max_length=1000, + null=False, + blank=True, + help_text="The DM to send to a user triggering this filter." + ) + dm_embed = models.CharField( + max_length=2000, + help_text="The content of the DM embed", + null=False, + blank=True + ) + infraction_type = models.CharField( + choices=[ + (choices[0].upper(), choices[1]) + for choices in [("NONE", "None"), *Infraction.TYPE_CHOICES] + ], + max_length=10, + null=False, + 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.", + blank=True, + null=False + ) + infraction_duration = models.DurationField( + null=False, + help_text="The duration of the infraction. 0 for permanent." + ) + infraction_channel = models.BigIntegerField( + validators=( + MinValueValidator( + limit_value=0, + message="Channel IDs cannot be negative." + ), + ), + help_text="Channel in which to send the infraction.", + null=False + ) + guild_pings = ArrayField( + models.CharField(max_length=100), + help_text="Who to ping when this filter triggers.", + null=False + ) + filter_dm = models.BooleanField(help_text="Whether DMs should be filtered.", null=False) + dm_pings = ArrayField( + models.CharField(max_length=100), + help_text="Who to ping when this filter triggers on a DM.", + null=False + ) + remove_context = models.BooleanField( + help_text=( + "Whether this filter should remove the context (such as a message) " + "triggering it." + ), + null=False + ) + bypass_roles = ArrayField( + models.CharField(max_length=100), + help_text="Roles and users who can bypass this filter.", + null=False + ) + enabled = models.BooleanField( + help_text="Whether this filter is currently enabled.", + null=False + ) + send_alert = models.BooleanField( + help_text="Whether an alert should be sent.", + ) + # Where a filter should apply. + enabled_channels = ArrayField( + models.CharField(max_length=100), + help_text="Channels in which to run the filter even if it's disabled in the category." + ) + disabled_channels = ArrayField( + models.CharField(max_length=100), + help_text="Channels in which to not run the filter even if it's enabled in the category." + ) + enabled_categories = ArrayField( + models.CharField(max_length=100), + help_text="The only categories in which to run the filter." + ) + disabled_categories = ArrayField( + models.CharField(max_length=100), + help_text="Categories in which to not run the filter." + ) + + 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 {FilterListType(self.list_type).label}list {self.name!r}" + + +class FilterBase(ModelTimestampMixin, ModelReprMixin, 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.", null=True + ) + additional_settings = models.JSONField( + help_text="Additional settings which are specific to this filter.", default=dict + ) + filter_list = models.ForeignKey( + FilterList, models.CASCADE, related_name="filters", + help_text="The filter list containing this filter." + ) + dm_content = models.CharField( + max_length=1000, + null=True, + blank=True, + help_text="The DM to send to a user triggering this filter." + ) + dm_embed = models.CharField( + max_length=2000, + help_text="The content of the DM embed", + null=True, + blank=True + ) + infraction_type = models.CharField( + choices=[ + (choices[0].upper(), choices[1]) + for choices in [("NONE", "None"), *Infraction.TYPE_CHOICES] + ], + max_length=10, + 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.", + blank=True, + null=True + ) + infraction_duration = models.DurationField( + null=True, + help_text="The duration of the infraction. 0 for permanent." + ) + infraction_channel = models.BigIntegerField( + validators=( + MinValueValidator( + limit_value=0, + message="Channel IDs cannot be negative." + ), + ), + help_text="Channel in which to send the infraction.", + null=True + ) + guild_pings = ArrayField( + models.CharField(max_length=100), + help_text="Who to ping when this filter triggers.", + null=True + ) + filter_dm = models.BooleanField(help_text="Whether DMs should be filtered.", null=True) + dm_pings = ArrayField( + models.CharField(max_length=100), + help_text="Who to ping when this filter triggers on a DM.", + null=True + ) + remove_context = models.BooleanField( + help_text=( + "Whether this filter should remove the context (such as a message) " + "triggering it." + ), + null=True + ) + bypass_roles = ArrayField( + models.CharField(max_length=100), + help_text="Roles and users who can bypass this filter.", + null=True + ) + enabled = models.BooleanField( + help_text="Whether this filter is currently enabled.", + null=True + ) + send_alert = models.BooleanField( + help_text="Whether an alert should be sent.", + null=True + ) + + enabled_channels = ArrayField( + models.CharField(max_length=100), + help_text="Channels in which to run the filter even if it's disabled in the category.", + null=True + ) + disabled_channels = ArrayField( + models.CharField(max_length=100), + help_text="Channels in which to not run the filter even if it's enabled in the category.", + null=True + ) + enabled_categories = ArrayField( + models.CharField(max_length=100), + help_text="The only categories in which to run the filter.", + null=True + ) + disabled_categories = ArrayField( + models.CharField(max_length=100), + help_text="Categories in which to not run the filter.", + null=True + ) + + def __str__(self) -> str: + return f"Filter {self.content!r}" + + class Meta: + """Metaclass for FilterBase to make it abstract model.""" + + abstract = True + + +class Filter(FilterBase): + """ + The main Filter models based on `FilterBase`. + + The purpose to have this model is to have access to the Fields of the Filter model + and set the unique constraint based on those fields. + """ + + class Meta: + """Metaclass Filter to set the unique constraint.""" + + constraints = ( + UniqueConstraint( + fields=tuple( + field.name for field in FilterBase._meta.fields + if field.name not in ("id", "description", "created_at", "updated_at") + ), + name="unique_filters"), + ) |