Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] Adding a custom field with the same name of an existing field leads to internal server error
### Description

If you try to add a custom field with the name of an already existing custom field name, an internal error occours.

### Steps to reproduce

1. Create custom field "ABC" with type integer
1. Create another custom field "ABC" with type string

### Webserver logs

```bash
{"headers":{"normalizedNames":{},"lazyUpdate":null},"status":500,"statusText":"Internal Server Error","url":"http://paperless.local/api/custom_fields/","ok":false,"name":"HttpErrorResponse","message":"Http failure response for http://paperless.local/api/custom_fields/: 500 Internal Server Error","error":"\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <title>Server Error (500)</title>\n</head>\n<body>\n  <h1>Server Error (500)</h1><p></p>\n</body>\n</html>\n"}
```


### Browser logs

_No response_

### Paperless-ngx version

2.4.2

### Host OS

GNU/Linux synology_braswell_216

### Installation method

Docker - official image

### Browser

Firefox

### Configuration changes

_No response_

### Other

_No response_

### Please confirm the following

- [X] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
- [X] I have already searched for relevant existing issues and discussions before opening this report.
- [X] I have updated the title field above with a concise description.


Discussion:
I get an IntegrityError including the field name. Seems clear enough?

```
IntegrityError at /api/custom_fields/ UNIQUE
constraint failed:
documents_customfield.name
```

<img width="363" alt="Screenshot 2024-02-03 at 7 13 55 AM" src="https://github.com/paperless-ngx/paperless-ngx/assets/4887959/59dfb77e-6a22-4f06-a82e-f2b22bcd0eb6">


We could probably check and return a bad request instead.  Slightly nicer.
Sure
It's this issue, again: https://github.com/encode/django-rest-framework/issues/7173

Maybe someday they will release again
Oh right, forgot about that. Insane to me its been 2y since a DRF release.

You want to take care of that? Im happy to if not!

Resolving patch:
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -81,7 +81,7 @@ def get_slug(self, obj):
     slug = SerializerMethodField()
 
     def validate(self, data):
-        # see https://github.com/encode/django-rest-framework/issues/7173
+        # TODO: remove pending https://github.com/encode/django-rest-framework/issues/7173
         name = data["name"] if "name" in data else self.instance.name
         owner = (
             data["owner"]
@@ -441,6 +441,17 @@ class Meta:
             "data_type",
         ]
 
+    def validate(self, attrs):
+        # TODO: remove pending https://github.com/encode/django-rest-framework/issues/7173
+        name = attrs["name"] if "name" in attrs else self.instance.name
+        if ("name" in attrs) and self.Meta.model.objects.filter(
+            name=name,
+        ).exists():
+            raise serializers.ValidationError(
+                {"error": "Object violates name unique constraint"},
+            )
+        return super().validate(attrs)
+
 
 class ReadWriteSerializerMethodField(serializers.SerializerMethodField):
     """


Test patch:
diff --git a/src/documents/tests/test_api_custom_fields.py b/src/documents/tests/test_api_custom_fields.py
--- a/src/documents/tests/test_api_custom_fields.py
+++ b/src/documents/tests/test_api_custom_fields.py
@@ -53,6 +53,29 @@ def test_create_custom_field(self):
             self.assertEqual(data["name"], name)
             self.assertEqual(data["data_type"], field_type)
 
+    def test_create_custom_field_nonunique_name(self):
+        """
+        GIVEN:
+            - Custom field exists
+        WHEN:
+            - API request to create custom field with the same name
+        THEN:
+            - HTTP 400 is returned
+        """
+        CustomField.objects.create(
+            name="Test Custom Field",
+            data_type=CustomField.FieldDataType.STRING,
+        )
+
+        resp = self.client.post(
+            self.ENDPOINT,
+            data={
+                "data_type": "string",
+                "name": "Test Custom Field",
+            },
+        )
+        self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
+
     def test_create_custom_field_instance(self):
         """
         GIVEN: