aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pydis_site/apps/api/migrations/0087_unique_constraint_filters.py39
-rw-r--r--pydis_site/apps/api/serializers.py9
-rw-r--r--pydis_site/apps/api/tests/test_filters.py12
3 files changed, 34 insertions, 26 deletions
diff --git a/pydis_site/apps/api/migrations/0087_unique_constraint_filters.py b/pydis_site/apps/api/migrations/0087_unique_constraint_filters.py
index b2ff91f1..910e7b1b 100644
--- a/pydis_site/apps/api/migrations/0087_unique_constraint_filters.py
+++ b/pydis_site/apps/api/migrations/0087_unique_constraint_filters.py
@@ -1,5 +1,3 @@
-# Generated by Django 3.1.14 on 2022-03-22 16:31
-
from django.db import migrations, models
@@ -10,29 +8,18 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.AddConstraint(
- model_name='filter',
- constraint=models.UniqueConstraint(fields=(
- 'content',
- 'additional_field',
- 'filter_list',
- 'dm_content',
- 'dm_embed',
- 'infraction_type',
- 'infraction_reason',
- 'infraction_duration',
- 'infraction_channel',
- 'guild_pings',
- 'filter_dm',
- 'dm_pings',
- 'remove_context',
- 'bypass_roles',
- 'enabled',
- 'send_alert',
- 'enabled_channels',
- 'disabled_channels',
- 'enabled_categories',
- 'disabled_categories'
- ), name='unique_filters'),
+ migrations.RunSQL(
+ "ALTER TABLE api_filter "
+ "ADD CONSTRAINT unique_filters UNIQUE NULLS NOT DISTINCT "
+ "(content, additional_field, filter_list_id, dm_content, dm_embed, infraction_type, infraction_reason, infraction_duration, infraction_channel, guild_pings, filter_dm, dm_pings, remove_context, bypass_roles, enabled, send_alert, enabled_channels, disabled_channels, enabled_categories, disabled_categories)",
+ state_operations=[
+ migrations.AddConstraint(
+ model_name='filter',
+ constraint=models.UniqueConstraint(
+ fields=('content', 'additional_field', 'filter_list', 'dm_content', 'dm_embed', 'infraction_type', 'infraction_reason', 'infraction_duration', 'infraction_channel', 'guild_pings', 'filter_dm', 'dm_pings', 'remove_context', 'bypass_roles', 'enabled', 'send_alert', 'enabled_channels', 'disabled_channels', 'enabled_categories', 'disabled_categories'),
+ name='unique_filters'
+ ),
+ ),
+ ],
),
]
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 7f9461ec..8da47802 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -257,6 +257,15 @@ class FilterSerializer(ModelSerializer):
) + SETTINGS_FIELDS
extra_kwargs = _create_filter_meta_extra_kwargs()
+ def create(self, validated_data: dict) -> User:
+ """Override the create method to catch violations of the custom uniqueness constraint."""
+ try:
+ return super().create(validated_data)
+ except IntegrityError:
+ raise ValidationError(
+ "Check if a filter with this combination of content and settings already exists in this filter list."
+ )
+
def to_representation(self, instance: Filter) -> dict:
"""
Provides a custom JSON representation to the Filter Serializers.
diff --git a/pydis_site/apps/api/tests/test_filters.py b/pydis_site/apps/api/tests/test_filters.py
index cae78cd6..73c8e0d9 100644
--- a/pydis_site/apps/api/tests/test_filters.py
+++ b/pydis_site/apps/api/tests/test_filters.py
@@ -327,3 +327,15 @@ class FilterValidationTests(AuthenticatedAPITestCase):
f"{base_filter_list.url()}/{case_fl.id}", data=clean_test_json(filter_list_settings)
)
self.assertEqual(response.status_code, response_code)
+
+ def test_filter_unique_constraint(self) -> None:
+ test_filter = get_test_sequences()["filter"]
+ test_filter.model.objects.all().delete()
+ test_filter_object = test_filter.model(**test_filter.object)
+ save_nested_objects(test_filter_object, False)
+
+ response = self.client.post(test_filter.url(), data=clean_test_json(test_filter.object))
+ self.assertEqual(response.status_code, 201)
+
+ response = self.client.post(test_filter.url(), data=clean_test_json(test_filter.object))
+ self.assertEqual(response.status_code, 400)