We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] duplicate key value violates unique constraint "documents_customfieldinstance_unique_document_field"
### Description

I have 3 custom fields of type "Document Link" in a Consumption template. Since the current version, the following error message appears and the document is not imported.

### Steps to reproduce

1. Creating a custom attribute with the type "Document Link"
2. Creating a template with the custom attribute
3. Importing a new file via the consum directory 
4. Template match the new file
5. Import fails

### Webserver logs

```bash
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 477, in trace_task
    R = retval = fun(*args, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 760, in __protected_call__
    return self.run(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/paperless/src/documents/tasks.py", line 167, in consume_file
    document = Consumer().try_consume_file(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/paperless/src/documents/consumer.py", line 577, in try_consume_file
    self._fail(
  File "/usr/src/paperless/src/documents/consumer.py", line 115, in _fail
    raise ConsumerError(f"{self.filename}: {log_message or message}") from exception

documents.consumer.ConsumerError: ASN231228001_[ar].pdf: The following error occurred while storing document ASN231228001_[ar].pdf after parsing: duplicate key value violates unique constraint "documents_customfieldinstance_unique_document_field"

DETAIL:  Key (document_id, field_id)=(1, 1) already exists.
```


### Browser logs

```bash
[2023-12-28 15:37:48,629] [INFO] [paperless.management.consumer] Using inotify to watch directory for changes: /usr/src/paperless/consume
[2023-12-28 16:05:00,040] [DEBUG] [paperless.classifier] Document classification model does not exist (yet), not performing automatic matching.
[2023-12-28 16:05:00,043] [WARNING] [paperless.tasks] Classifier error: No training data available.
[2023-12-28 16:16:58,574] [INFO] [paperless.management.consumer] Adding /usr/src/paperless/consume/ASN231228001_[ar].pdf to the task queue.
[2023-12-28 16:16:58,681] [DEBUG] [paperless.barcodes] Scanning for barcodes using ZXING
[2023-12-28 16:17:02,426] [DEBUG] [paperless.barcodes] Upscaling image by 2.0 for better barcode detection
[2023-12-28 16:17:07,894] [DEBUG] [paperless.barcodes] Barcode of type BarcodeFormat.QRCode found: ASN231228001
[2023-12-28 16:17:07,923] [DEBUG] [paperless.barcodes] Upscaling image by 2.0 for better barcode detection
[2023-12-28 16:17:19,043] [DEBUG] [paperless.barcodes] Found ASN Barcode: ASN231228001
[2023-12-28 16:17:19,043] [DEBUG] [paperless.barcodes] Found ASN Barcode: ASN231228001
[2023-12-28 16:17:19,043] [INFO] [paperless.tasks] Found ASN in barcode: 231228001
[2023-12-28 16:17:19,087] [INFO] [paperless.matching] Document matched template Arztrechnung
[2023-12-28 16:17:19,100] [INFO] [paperless.matching] Document matched template Rechnung
[2023-12-28 16:17:19,107] [INFO] [paperless.matching] Document did not match template Zahlungserinnerung
[2023-12-28 16:17:19,107] [DEBUG] [paperless.matching] Document filename ASN231228001_[ar].pdf does not match *[ze]*
[2023-12-28 16:17:19,107] [INFO] [paperless.matching] Document matched template Mahnung
[2023-12-28 16:17:19,150] [INFO] [paperless.consumer] Consuming ASN231228001_[ar].pdf
[2023-12-28 16:17:19,164] [DEBUG] [paperless.consumer] Detected mime type: application/pdf
[2023-12-28 16:17:19,167] [DEBUG] [paperless.consumer] Parser: RasterisedDocumentParser
[2023-12-28 16:17:19,169] [DEBUG] [paperless.consumer] Parsing ASN231228001_[ar].pdf...
[2023-12-28 16:17:19,307] [DEBUG] [paperless.parsing.tesseract] Calling OCRmyPDF with args: {'input_file': PosixPath('/tmp/paperless/paperless-ngx4ifi0ip2/ASN231228001_[ar].pdf'), 'output_file': PosixPath('/tmp/paperless/paperless-6q98pv8s/archive.pdf'), 'use_threads': True, 'jobs': 4, 'language': 'deu', 'output_type': 'pdfa', 'progress_bar': False, 'color_conversion_strategy': 'RGB', 'skip_text': True, 'clean': True, 'deskew': True, 'rotate_pages': True, 'rotate_pages_threshold': 12.0, 'sidecar': PosixPath('/tmp/paperless/paperless-6q98pv8s/sidecar.txt')}
[2023-12-28 16:17:55,539] [DEBUG] [paperless.parsing.tesseract] Using text from sidecar file
[2023-12-28 16:17:55,540] [DEBUG] [paperless.consumer] Generating thumbnail for ASN231228001_[ar].pdf...
[2023-12-28 16:17:55,543] [DEBUG] [paperless.parsing] Execute: convert -density 300 -scale 500x5000> -alpha remove -strip -auto-orient /tmp/paperless/paperless-6q98pv8s/archive.pdf[0] /tmp/paperless/paperless-6q98pv8s/convert.webp
[2023-12-28 16:17:59,548] [DEBUG] [paperless.classifier] Document classification model does not exist (yet), not performing automatic matching.
[2023-12-28 16:17:59,551] [DEBUG] [paperless.consumer] Saving record to database
[2023-12-28 16:17:59,551] [DEBUG] [paperless.consumer] Creation date from parse_date: 2014-10-14 00:00:00+00:00
[2023-12-28 16:17:59,685] [ERROR] [paperless.consumer] The following error occurred while storing document ASN231228001_[ar].pdf after parsing: duplicate key value violates unique constraint "documents_customfieldinstance_unique_document_field"
```


### Paperless-ngx version

2.2.0

### Host OS

Ubuntu 22.04

### Installation method

Docker - official image

### Browser

_No response_

### Configuration changes

_No response_

### Other

I have performed a new installation of Paperless-NGX.

### 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.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/documents/data_models.py`
Content:
```
1 import dataclasses
2 import datetime
3 from enum import IntEnum
4 from pathlib import Path
5 from typing import Optional
6 
7 import magic
8 
9 
10 @dataclasses.dataclass
11 class DocumentMetadataOverrides:
12     """
13     Manages overrides for document fields which normally would
14     be set from content or matching.  All fields default to None,
15     meaning no override is happening
16     """
17 
18     filename: Optional[str] = None
19     title: Optional[str] = None
20     correspondent_id: Optional[int] = None
21     document_type_id: Optional[int] = None
22     tag_ids: Optional[list[int]] = None
23     storage_path_id: Optional[int] = None
24     created: Optional[datetime.datetime] = None
25     asn: Optional[int] = None
26     owner_id: Optional[int] = None
27     view_users: Optional[list[int]] = None
28     view_groups: Optional[list[int]] = None
29     change_users: Optional[list[int]] = None
30     change_groups: Optional[list[int]] = None
31     custom_field_ids: Optional[list[int]] = None
32 
33     def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
34         """
35         Merges two DocumentMetadataOverrides objects such that object B's overrides
36         are only applied if the property is empty in object A or merged if multiple
37         are accepted.
38 
39         The update is an in-place modification of self
40         """
41         # only if empty
42         if self.title is None:
43             self.title = other.title
44         if self.correspondent_id is None:
45             self.correspondent_id = other.correspondent_id
46         if self.document_type_id is None:
47             self.document_type_id = other.document_type_id
48         if self.storage_path_id is None:
49             self.storage_path_id = other.storage_path_id
50         if self.owner_id is None:
51             self.owner_id = other.owner_id
52 
53         # merge
54         if self.tag_ids is None:
55             self.tag_ids = other.tag_ids
56         elif other.tag_ids is not None:
57             self.tag_ids.extend(other.tag_ids)
58 
59         if self.view_users is None:
60             self.view_users = other.view_users
61         elif other.view_users is not None:
62             self.view_users.extend(other.view_users)
63 
64         if self.view_groups is None:
65             self.view_groups = other.view_groups
66         elif other.view_groups is not None:
67             self.view_groups.extend(other.view_groups)
68 
69         if self.change_users is None:
70             self.change_users = other.change_users
71         elif other.change_users is not None:
72             self.change_users.extend(other.change_users)
73 
74         if self.change_groups is None:
75             self.change_groups = other.change_groups
76         elif other.change_groups is not None:
77             self.change_groups.extend(other.change_groups)
78 
79         if self.custom_field_ids is None:
80             self.custom_field_ids = other.custom_field_ids
81         elif other.custom_field_ids is not None:
82             self.custom_field_ids.extend(other.custom_field_ids)
83 
84         return self
85 
86 
87 class DocumentSource(IntEnum):
88     """
89     The source of an incoming document.  May have other uses in the future
90     """
91 
92     ConsumeFolder = 1
93     ApiUpload = 2
94     MailFetch = 3
95 
96 
97 @dataclasses.dataclass
98 class ConsumableDocument:
99     """
100     Encapsulates an incoming document, either from consume folder, API upload
101     or mail fetching and certain useful operations on it.
102     """
103 
104     source: DocumentSource
105     original_file: Path
106     mailrule_id: Optional[int] = None
107     mime_type: str = dataclasses.field(init=False, default=None)
108 
109     def __post_init__(self):
110         """
111         After a dataclass is initialized, this is called to finalize some data
112         1. Make sure the original path is an absolute, fully qualified path
113         2. Get the mime type of the file
114         """
115         # Always fully qualify the path first thing
116         # Just in case, convert to a path if it's a str
117         self.original_file = Path(self.original_file).resolve()
118 
119         # Get the file type once at init
120         # Note this function isn't called when the object is unpickled
121         self.mime_type = magic.from_file(self.original_file, mime=True)
122
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/documents/data_models.py b/src/documents/data_models.py
--- a/src/documents/data_models.py
+++ b/src/documents/data_models.py
@@ -55,31 +55,37 @@
             self.tag_ids = other.tag_ids
         elif other.tag_ids is not None:
             self.tag_ids.extend(other.tag_ids)
+            self.tag_ids = list(set(self.tag_ids))
 
         if self.view_users is None:
             self.view_users = other.view_users
         elif other.view_users is not None:
             self.view_users.extend(other.view_users)
+            self.view_users = list(set(self.view_users))
 
         if self.view_groups is None:
             self.view_groups = other.view_groups
         elif other.view_groups is not None:
             self.view_groups.extend(other.view_groups)
+            self.view_groups = list(set(self.view_groups))
 
         if self.change_users is None:
             self.change_users = other.change_users
         elif other.change_users is not None:
             self.change_users.extend(other.change_users)
+            self.change_users = list(set(self.change_users))
 
         if self.change_groups is None:
             self.change_groups = other.change_groups
         elif other.change_groups is not None:
             self.change_groups.extend(other.change_groups)
+            self.change_groups = list(set(self.change_groups))
 
         if self.custom_field_ids is None:
             self.custom_field_ids = other.custom_field_ids
         elif other.custom_field_ids is not None:
             self.custom_field_ids.extend(other.custom_field_ids)
+            self.custom_field_ids = list(set(self.custom_field_ids))
 
         return self

{"golden_diff": "diff --git a/src/documents/data_models.py b/src/documents/data_models.py\n--- a/src/documents/data_models.py\n+++ b/src/documents/data_models.py\n@@ -55,31 +55,37 @@\n             self.tag_ids = other.tag_ids\n         elif other.tag_ids is not None:\n             self.tag_ids.extend(other.tag_ids)\n+            self.tag_ids = list(set(self.tag_ids))\n \n         if self.view_users is None:\n             self.view_users = other.view_users\n         elif other.view_users is not None:\n             self.view_users.extend(other.view_users)\n+            self.view_users = list(set(self.view_users))\n \n         if self.view_groups is None:\n             self.view_groups = other.view_groups\n         elif other.view_groups is not None:\n             self.view_groups.extend(other.view_groups)\n+            self.view_groups = list(set(self.view_groups))\n \n         if self.change_users is None:\n             self.change_users = other.change_users\n         elif other.change_users is not None:\n             self.change_users.extend(other.change_users)\n+            self.change_users = list(set(self.change_users))\n \n         if self.change_groups is None:\n             self.change_groups = other.change_groups\n         elif other.change_groups is not None:\n             self.change_groups.extend(other.change_groups)\n+            self.change_groups = list(set(self.change_groups))\n \n         if self.custom_field_ids is None:\n             self.custom_field_ids = other.custom_field_ids\n         elif other.custom_field_ids is not None:\n             self.custom_field_ids.extend(other.custom_field_ids)\n+            self.custom_field_ids = list(set(self.custom_field_ids))\n \n         return self\n", "issue": "[BUG] duplicate key value violates unique constraint \"documents_customfieldinstance_unique_document_field\"\n### Description\n\nI have 3 custom fields of type \"Document Link\" in a Consumption template. Since the current version, the following error message appears and the document is not imported.\n\n### Steps to reproduce\n\n1. Creating a custom attribute with the type \"Document Link\"\r\n2. Creating a template with the custom attribute\r\n3. Importing a new file via the consum directory \r\n4. Template match the new file\r\n5. Import fails\n\n### Webserver logs\n\n```bash\nThe above exception was the direct cause of the following exception:\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.11/site-packages/celery/app/trace.py\", line 477, in trace_task\r\n    R = retval = fun(*args, **kwargs)\r\n                 ^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/celery/app/trace.py\", line 760, in __protected_call__\r\n    return self.run(*args, **kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/src/paperless/src/documents/tasks.py\", line 167, in consume_file\r\n    document = Consumer().try_consume_file(\r\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/src/paperless/src/documents/consumer.py\", line 577, in try_consume_file\r\n    self._fail(\r\n  File \"/usr/src/paperless/src/documents/consumer.py\", line 115, in _fail\r\n    raise ConsumerError(f\"{self.filename}: {log_message or message}\") from exception\r\n\r\ndocuments.consumer.ConsumerError: ASN231228001_[ar].pdf: The following error occurred while storing document ASN231228001_[ar].pdf after parsing: duplicate key value violates unique constraint \"documents_customfieldinstance_unique_document_field\"\r\n\r\nDETAIL:  Key (document_id, field_id)=(1, 1) already exists.\n```\n\n\n### Browser logs\n\n```bash\n[2023-12-28 15:37:48,629] [INFO] [paperless.management.consumer] Using inotify to watch directory for changes: /usr/src/paperless/consume\r\n[2023-12-28 16:05:00,040] [DEBUG] [paperless.classifier] Document classification model does not exist (yet), not performing automatic matching.\r\n[2023-12-28 16:05:00,043] [WARNING] [paperless.tasks] Classifier error: No training data available.\r\n[2023-12-28 16:16:58,574] [INFO] [paperless.management.consumer] Adding /usr/src/paperless/consume/ASN231228001_[ar].pdf to the task queue.\r\n[2023-12-28 16:16:58,681] [DEBUG] [paperless.barcodes] Scanning for barcodes using ZXING\r\n[2023-12-28 16:17:02,426] [DEBUG] [paperless.barcodes] Upscaling image by 2.0 for better barcode detection\r\n[2023-12-28 16:17:07,894] [DEBUG] [paperless.barcodes] Barcode of type BarcodeFormat.QRCode found: ASN231228001\r\n[2023-12-28 16:17:07,923] [DEBUG] [paperless.barcodes] Upscaling image by 2.0 for better barcode detection\r\n[2023-12-28 16:17:19,043] [DEBUG] [paperless.barcodes] Found ASN Barcode: ASN231228001\r\n[2023-12-28 16:17:19,043] [DEBUG] [paperless.barcodes] Found ASN Barcode: ASN231228001\r\n[2023-12-28 16:17:19,043] [INFO] [paperless.tasks] Found ASN in barcode: 231228001\r\n[2023-12-28 16:17:19,087] [INFO] [paperless.matching] Document matched template Arztrechnung\r\n[2023-12-28 16:17:19,100] [INFO] [paperless.matching] Document matched template Rechnung\r\n[2023-12-28 16:17:19,107] [INFO] [paperless.matching] Document did not match template Zahlungserinnerung\r\n[2023-12-28 16:17:19,107] [DEBUG] [paperless.matching] Document filename ASN231228001_[ar].pdf does not match *[ze]*\r\n[2023-12-28 16:17:19,107] [INFO] [paperless.matching] Document matched template Mahnung\r\n[2023-12-28 16:17:19,150] [INFO] [paperless.consumer] Consuming ASN231228001_[ar].pdf\r\n[2023-12-28 16:17:19,164] [DEBUG] [paperless.consumer] Detected mime type: application/pdf\r\n[2023-12-28 16:17:19,167] [DEBUG] [paperless.consumer] Parser: RasterisedDocumentParser\r\n[2023-12-28 16:17:19,169] [DEBUG] [paperless.consumer] Parsing ASN231228001_[ar].pdf...\r\n[2023-12-28 16:17:19,307] [DEBUG] [paperless.parsing.tesseract] Calling OCRmyPDF with args: {'input_file': PosixPath('/tmp/paperless/paperless-ngx4ifi0ip2/ASN231228001_[ar].pdf'), 'output_file': PosixPath('/tmp/paperless/paperless-6q98pv8s/archive.pdf'), 'use_threads': True, 'jobs': 4, 'language': 'deu', 'output_type': 'pdfa', 'progress_bar': False, 'color_conversion_strategy': 'RGB', 'skip_text': True, 'clean': True, 'deskew': True, 'rotate_pages': True, 'rotate_pages_threshold': 12.0, 'sidecar': PosixPath('/tmp/paperless/paperless-6q98pv8s/sidecar.txt')}\r\n[2023-12-28 16:17:55,539] [DEBUG] [paperless.parsing.tesseract] Using text from sidecar file\r\n[2023-12-28 16:17:55,540] [DEBUG] [paperless.consumer] Generating thumbnail for ASN231228001_[ar].pdf...\r\n[2023-12-28 16:17:55,543] [DEBUG] [paperless.parsing] Execute: convert -density 300 -scale 500x5000> -alpha remove -strip -auto-orient /tmp/paperless/paperless-6q98pv8s/archive.pdf[0] /tmp/paperless/paperless-6q98pv8s/convert.webp\r\n[2023-12-28 16:17:59,548] [DEBUG] [paperless.classifier] Document classification model does not exist (yet), not performing automatic matching.\r\n[2023-12-28 16:17:59,551] [DEBUG] [paperless.consumer] Saving record to database\r\n[2023-12-28 16:17:59,551] [DEBUG] [paperless.consumer] Creation date from parse_date: 2014-10-14 00:00:00+00:00\r\n[2023-12-28 16:17:59,685] [ERROR] [paperless.consumer] The following error occurred while storing document ASN231228001_[ar].pdf after parsing: duplicate key value violates unique constraint \"documents_customfieldinstance_unique_document_field\"\n```\n\n\n### Paperless-ngx version\n\n2.2.0\n\n### Host OS\n\nUbuntu 22.04\n\n### Installation method\n\nDocker - official image\n\n### Browser\n\n_No response_\n\n### Configuration changes\n\n_No response_\n\n### Other\n\nI have performed a new installation of Paperless-NGX.\n\n### Please confirm the following\n\n- [X] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.\n- [X] I have already searched for relevant existing issues and discussions before opening this report.\n- [X] I have updated the title field above with a concise description.\n", "before_files": [{"content": "import dataclasses\nimport datetime\nfrom enum import IntEnum\nfrom pathlib import Path\nfrom typing import Optional\n\nimport magic\n\n\n@dataclasses.dataclass\nclass DocumentMetadataOverrides:\n    \"\"\"\n    Manages overrides for document fields which normally would\n    be set from content or matching.  All fields default to None,\n    meaning no override is happening\n    \"\"\"\n\n    filename: Optional[str] = None\n    title: Optional[str] = None\n    correspondent_id: Optional[int] = None\n    document_type_id: Optional[int] = None\n    tag_ids: Optional[list[int]] = None\n    storage_path_id: Optional[int] = None\n    created: Optional[datetime.datetime] = None\n    asn: Optional[int] = None\n    owner_id: Optional[int] = None\n    view_users: Optional[list[int]] = None\n    view_groups: Optional[list[int]] = None\n    change_users: Optional[list[int]] = None\n    change_groups: Optional[list[int]] = None\n    custom_field_ids: Optional[list[int]] = None\n\n    def update(self, other: \"DocumentMetadataOverrides\") -> \"DocumentMetadataOverrides\":\n        \"\"\"\n        Merges two DocumentMetadataOverrides objects such that object B's overrides\n        are only applied if the property is empty in object A or merged if multiple\n        are accepted.\n\n        The update is an in-place modification of self\n        \"\"\"\n        # only if empty\n        if self.title is None:\n            self.title = other.title\n        if self.correspondent_id is None:\n            self.correspondent_id = other.correspondent_id\n        if self.document_type_id is None:\n            self.document_type_id = other.document_type_id\n        if self.storage_path_id is None:\n            self.storage_path_id = other.storage_path_id\n        if self.owner_id is None:\n            self.owner_id = other.owner_id\n\n        # merge\n        if self.tag_ids is None:\n            self.tag_ids = other.tag_ids\n        elif other.tag_ids is not None:\n            self.tag_ids.extend(other.tag_ids)\n\n        if self.view_users is None:\n            self.view_users = other.view_users\n        elif other.view_users is not None:\n            self.view_users.extend(other.view_users)\n\n        if self.view_groups is None:\n            self.view_groups = other.view_groups\n        elif other.view_groups is not None:\n            self.view_groups.extend(other.view_groups)\n\n        if self.change_users is None:\n            self.change_users = other.change_users\n        elif other.change_users is not None:\n            self.change_users.extend(other.change_users)\n\n        if self.change_groups is None:\n            self.change_groups = other.change_groups\n        elif other.change_groups is not None:\n            self.change_groups.extend(other.change_groups)\n\n        if self.custom_field_ids is None:\n            self.custom_field_ids = other.custom_field_ids\n        elif other.custom_field_ids is not None:\n            self.custom_field_ids.extend(other.custom_field_ids)\n\n        return self\n\n\nclass DocumentSource(IntEnum):\n    \"\"\"\n    The source of an incoming document.  May have other uses in the future\n    \"\"\"\n\n    ConsumeFolder = 1\n    ApiUpload = 2\n    MailFetch = 3\n\n\n@dataclasses.dataclass\nclass ConsumableDocument:\n    \"\"\"\n    Encapsulates an incoming document, either from consume folder, API upload\n    or mail fetching and certain useful operations on it.\n    \"\"\"\n\n    source: DocumentSource\n    original_file: Path\n    mailrule_id: Optional[int] = None\n    mime_type: str = dataclasses.field(init=False, default=None)\n\n    def __post_init__(self):\n        \"\"\"\n        After a dataclass is initialized, this is called to finalize some data\n        1. Make sure the original path is an absolute, fully qualified path\n        2. Get the mime type of the file\n        \"\"\"\n        # Always fully qualify the path first thing\n        # Just in case, convert to a path if it's a str\n        self.original_file = Path(self.original_file).resolve()\n\n        # Get the file type once at init\n        # Note this function isn't called when the object is unpickled\n        self.mime_type = magic.from_file(self.original_file, mime=True)\n", "path": "src/documents/data_models.py"}], "after_files": [{"content": "import dataclasses\nimport datetime\nfrom enum import IntEnum\nfrom pathlib import Path\nfrom typing import Optional\n\nimport magic\n\n\n@dataclasses.dataclass\nclass DocumentMetadataOverrides:\n    \"\"\"\n    Manages overrides for document fields which normally would\n    be set from content or matching.  All fields default to None,\n    meaning no override is happening\n    \"\"\"\n\n    filename: Optional[str] = None\n    title: Optional[str] = None\n    correspondent_id: Optional[int] = None\n    document_type_id: Optional[int] = None\n    tag_ids: Optional[list[int]] = None\n    storage_path_id: Optional[int] = None\n    created: Optional[datetime.datetime] = None\n    asn: Optional[int] = None\n    owner_id: Optional[int] = None\n    view_users: Optional[list[int]] = None\n    view_groups: Optional[list[int]] = None\n    change_users: Optional[list[int]] = None\n    change_groups: Optional[list[int]] = None\n    custom_field_ids: Optional[list[int]] = None\n\n    def update(self, other: \"DocumentMetadataOverrides\") -> \"DocumentMetadataOverrides\":\n        \"\"\"\n        Merges two DocumentMetadataOverrides objects such that object B's overrides\n        are only applied if the property is empty in object A or merged if multiple\n        are accepted.\n\n        The update is an in-place modification of self\n        \"\"\"\n        # only if empty\n        if self.title is None:\n            self.title = other.title\n        if self.correspondent_id is None:\n            self.correspondent_id = other.correspondent_id\n        if self.document_type_id is None:\n            self.document_type_id = other.document_type_id\n        if self.storage_path_id is None:\n            self.storage_path_id = other.storage_path_id\n        if self.owner_id is None:\n            self.owner_id = other.owner_id\n\n        # merge\n        if self.tag_ids is None:\n            self.tag_ids = other.tag_ids\n        elif other.tag_ids is not None:\n            self.tag_ids.extend(other.tag_ids)\n            self.tag_ids = list(set(self.tag_ids))\n\n        if self.view_users is None:\n            self.view_users = other.view_users\n        elif other.view_users is not None:\n            self.view_users.extend(other.view_users)\n            self.view_users = list(set(self.view_users))\n\n        if self.view_groups is None:\n            self.view_groups = other.view_groups\n        elif other.view_groups is not None:\n            self.view_groups.extend(other.view_groups)\n            self.view_groups = list(set(self.view_groups))\n\n        if self.change_users is None:\n            self.change_users = other.change_users\n        elif other.change_users is not None:\n            self.change_users.extend(other.change_users)\n            self.change_users = list(set(self.change_users))\n\n        if self.change_groups is None:\n            self.change_groups = other.change_groups\n        elif other.change_groups is not None:\n            self.change_groups.extend(other.change_groups)\n            self.change_groups = list(set(self.change_groups))\n\n        if self.custom_field_ids is None:\n            self.custom_field_ids = other.custom_field_ids\n        elif other.custom_field_ids is not None:\n            self.custom_field_ids.extend(other.custom_field_ids)\n            self.custom_field_ids = list(set(self.custom_field_ids))\n\n        return self\n\n\nclass DocumentSource(IntEnum):\n    \"\"\"\n    The source of an incoming document.  May have other uses in the future\n    \"\"\"\n\n    ConsumeFolder = 1\n    ApiUpload = 2\n    MailFetch = 3\n\n\n@dataclasses.dataclass\nclass ConsumableDocument:\n    \"\"\"\n    Encapsulates an incoming document, either from consume folder, API upload\n    or mail fetching and certain useful operations on it.\n    \"\"\"\n\n    source: DocumentSource\n    original_file: Path\n    mailrule_id: Optional[int] = None\n    mime_type: str = dataclasses.field(init=False, default=None)\n\n    def __post_init__(self):\n        \"\"\"\n        After a dataclass is initialized, this is called to finalize some data\n        1. Make sure the original path is an absolute, fully qualified path\n        2. Get the mime type of the file\n        \"\"\"\n        # Always fully qualify the path first thing\n        # Just in case, convert to a path if it's a str\n        self.original_file = Path(self.original_file).resolve()\n\n        # Get the file type once at init\n        # Note this function isn't called when the object is unpickled\n        self.mime_type = magic.from_file(self.original_file, mime=True)\n", "path": "src/documents/data_models.py"}]}