diff options
-rw-r--r-- | pydis_site/apps/api/migrations/0087_unique_constraint_filters.py | 39 | ||||
-rw-r--r-- | pydis_site/apps/api/serializers.py | 9 | ||||
-rw-r--r-- | pydis_site/apps/api/tests/test_filters.py | 12 |
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) |