mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	API now supports setting metadata when POSTing documents.
This commit is contained in:
		| @@ -216,9 +216,7 @@ be instructed to consume the document from there. | |||||||
| The endpoint supports the following optional form fields: | The endpoint supports the following optional form fields: | ||||||
|  |  | ||||||
| *   ``title``: Specify a title that the consumer should use for the document. | *   ``title``: Specify a title that the consumer should use for the document. | ||||||
| *   ``correspondent``: Specify a correspondent that the consumer should use for the document. | *   ``correspondent``: Specify the ID of a correspondent that the consumer should use for the document. | ||||||
|     Case sensitive. If the specified correspondent does not exist, it will be created with this |  | ||||||
|     name and default settings. |  | ||||||
| *   ``document_type``: Similar to correspondent. | *   ``document_type``: Similar to correspondent. | ||||||
| *   ``tags``: Similar to correspondent. Specify this multiple times to have multiple tags added | *   ``tags``: Similar to correspondent. Specify this multiple times to have multiple tags added | ||||||
|     to the document. |     to the document. | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ Changelog | |||||||
| paperless-ng 0.9.5 | paperless-ng 0.9.5 | ||||||
| ################## | ################## | ||||||
|  |  | ||||||
|  | Apart from the API, this finalizes the changes I wanted to get into paperless before 1.0. The next releases will | ||||||
|  | focus on fixing bugs, minor changes to the UI, and possibly some changes to the API. | ||||||
|  |  | ||||||
| * OCR | * OCR | ||||||
|  |  | ||||||
|   * Paperless now uses `OCRmyPDF <https://github.com/jbarlow83/OCRmyPDF>`_ to perform OCR on documents. |   * Paperless now uses `OCRmyPDF <https://github.com/jbarlow83/OCRmyPDF>`_ to perform OCR on documents. | ||||||
| @@ -33,10 +36,15 @@ paperless-ng 0.9.5 | |||||||
|   * The endpoint for uploading documents now supports specifying custom titles, correspondents, tags and types. |   * The endpoint for uploading documents now supports specifying custom titles, correspondents, tags and types. | ||||||
|     This can be used by clients to override the default behavior of paperless. |     This can be used by clients to override the default behavior of paperless. | ||||||
|   * The document endpoint of API now serves document in this form: |   * The document endpoint of API now serves document in this form: | ||||||
|     * correspondents, document types and tags are referenced by their ID in the fields ``correspondent``, ``document_type`` and ``tags``. The ``*_id`` versions are gone. These fields are read/write. |  | ||||||
|     * in addition to that, ``*_object`` fields serve nested objects. Read only. Don't rely on these, they will probably get removed once I figure out how to better handle asynchronous data in the front end. |  | ||||||
|  |  | ||||||
| * Some minor improvements to the front end, such as document count in the document list, better visibility of the current view, and improvements to the filter behavior. |     * correspondents, document types and tags are referenced by their ID in the fields ``correspondent``, ``document_type`` and ``tags``. The ``*_id`` versions are gone. These fields are read/write. | ||||||
|  |     * paperless does not serve nested tags, correspondents or types anymore. | ||||||
|  |  | ||||||
|  | * Front end | ||||||
|  |  | ||||||
|  |   * Paperless does some basic caching of correspondents, tags and types and will only request them from the server when necessary or when entirely reloading the page. | ||||||
|  |   * Document lists should be somewhat faster now, especially when lots of tags/correspondents where present. | ||||||
|  |   * Some minor improvements to the front end, such as document count in the document list, better highlighting of the current page, and improvements to the filter behavior. | ||||||
|  |  | ||||||
| * Fixes: | * Fixes: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -126,22 +126,26 @@ class PostDocumentSerializer(serializers.Serializer): | |||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     correspondent = serializers.CharField( |     correspondent = serializers.PrimaryKeyRelatedField( | ||||||
|  |         queryset=Correspondent.objects.all(), | ||||||
|         label="Correspondent", |         label="Correspondent", | ||||||
|  |         allow_null=True, | ||||||
|         write_only=True, |         write_only=True, | ||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     document_type = serializers.CharField( |     document_type = serializers.PrimaryKeyRelatedField( | ||||||
|  |         queryset=DocumentType.objects.all(), | ||||||
|         label="Document type", |         label="Document type", | ||||||
|  |         allow_null=True, | ||||||
|         write_only=True, |         write_only=True, | ||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     tags = serializers.ListField( |     tags = serializers.PrimaryKeyRelatedField( | ||||||
|         child=serializers.CharField(), |         many=True, | ||||||
|  |         queryset=Tag.objects.all(), | ||||||
|         label="Tags", |         label="Tags", | ||||||
|         source="tag", |  | ||||||
|         write_only=True, |         write_only=True, | ||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
| @@ -170,24 +174,19 @@ class PostDocumentSerializer(serializers.Serializer): | |||||||
|  |  | ||||||
|         correspondent = attrs.get('correspondent') |         correspondent = attrs.get('correspondent') | ||||||
|         if correspondent: |         if correspondent: | ||||||
|             c, _ = Correspondent.objects.get_or_create(name=correspondent) |             attrs['correspondent_id'] = correspondent.id | ||||||
|             attrs['correspondent_id'] = c.id |  | ||||||
|         else: |         else: | ||||||
|             attrs['correspondent_id'] = None |             attrs['correspondent_id'] = None | ||||||
|  |  | ||||||
|         document_type = attrs.get('document_type') |         document_type = attrs.get('document_type') | ||||||
|         if document_type: |         if document_type: | ||||||
|             dt, _ = DocumentType.objects.get_or_create(name=document_type) |             attrs['document_type_id'] = document_type.id | ||||||
|             attrs['document_type_id'] = dt.id |  | ||||||
|         else: |         else: | ||||||
|             attrs['document_type_id'] = None |             attrs['document_type_id'] = None | ||||||
|  |  | ||||||
|         tags = attrs.get('tag') |         tags = attrs.get('tags') | ||||||
|         if tags: |         if tags: | ||||||
|             tag_ids = [] |             tag_ids = [tag.id for tag in tags] | ||||||
|             for tag in tags: |  | ||||||
|                 tag, _ = Tag.objects.get_or_create(name=tag) |  | ||||||
|                 tag_ids.append(tag.id) |  | ||||||
|             attrs['tag_ids'] = tag_ids |             attrs['tag_ids'] = tag_ids | ||||||
|         else: |         else: | ||||||
|             attrs['tag_ids'] = None |             attrs['tag_ids'] = None | ||||||
|   | |||||||
| @@ -410,7 +410,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): | |||||||
|     def test_upload_with_correspondent(self, async_task): |     def test_upload_with_correspondent(self, async_task): | ||||||
|         c = Correspondent.objects.create(name="test-corres") |         c = Correspondent.objects.create(name="test-corres") | ||||||
|         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|             response = self.client.post("/api/documents/post_document/", {"document": f, "correspondent": "test-corres"}) |             response = self.client.post("/api/documents/post_document/", {"document": f, "correspondent": c.id}) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|         async_task.assert_called_once() |         async_task.assert_called_once() | ||||||
| @@ -420,23 +420,18 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): | |||||||
|         self.assertEqual(kwargs['override_correspondent_id'], c.id) |         self.assertEqual(kwargs['override_correspondent_id'], c.id) | ||||||
|  |  | ||||||
|     @mock.patch("documents.views.async_task") |     @mock.patch("documents.views.async_task") | ||||||
|     def test_upload_with_new_correspondent(self, async_task): |     def test_upload_with_invalid_correspondent(self, async_task): | ||||||
|         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|             response = self.client.post("/api/documents/post_document/", {"document": f, "correspondent": "test-corres2"}) |             response = self.client.post("/api/documents/post_document/", {"document": f, "correspondent": 3456}) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 400) | ||||||
|  |  | ||||||
|         async_task.assert_called_once() |         async_task.assert_not_called() | ||||||
|  |  | ||||||
|         args, kwargs = async_task.call_args |  | ||||||
|  |  | ||||||
|         c = Correspondent.objects.get(name="test-corres2") |  | ||||||
|         self.assertEqual(kwargs['override_correspondent_id'], c.id) |  | ||||||
|  |  | ||||||
|     @mock.patch("documents.views.async_task") |     @mock.patch("documents.views.async_task") | ||||||
|     def test_upload_with_document_type(self, async_task): |     def test_upload_with_document_type(self, async_task): | ||||||
|         dt = DocumentType.objects.create(name="invoice") |         dt = DocumentType.objects.create(name="invoice") | ||||||
|         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|             response = self.client.post("/api/documents/post_document/", {"document": f, "document_type": "invoice"}) |             response = self.client.post("/api/documents/post_document/", {"document": f, "document_type": dt.id}) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|         async_task.assert_called_once() |         async_task.assert_called_once() | ||||||
| @@ -446,30 +441,37 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): | |||||||
|         self.assertEqual(kwargs['override_document_type_id'], dt.id) |         self.assertEqual(kwargs['override_document_type_id'], dt.id) | ||||||
|  |  | ||||||
|     @mock.patch("documents.views.async_task") |     @mock.patch("documents.views.async_task") | ||||||
|     def test_upload_with_new_document_type(self, async_task): |     def test_upload_with_invalid_document_type(self, async_task): | ||||||
|         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|             response = self.client.post("/api/documents/post_document/", {"document": f, "document_type": "invoice2"}) |             response = self.client.post("/api/documents/post_document/", {"document": f, "document_type": 34578}) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 400) | ||||||
|  |  | ||||||
|         async_task.assert_called_once() |         async_task.assert_not_called() | ||||||
|  |  | ||||||
|         args, kwargs = async_task.call_args |  | ||||||
|  |  | ||||||
|         dt = DocumentType.objects.get(name="invoice2") |  | ||||||
|         self.assertEqual(kwargs['override_document_type_id'], dt.id) |  | ||||||
|  |  | ||||||
|     @mock.patch("documents.views.async_task") |     @mock.patch("documents.views.async_task") | ||||||
|     def test_upload_with_tags(self, async_task): |     def test_upload_with_tags(self, async_task): | ||||||
|         t1 = Tag.objects.create(name="tag1") |         t1 = Tag.objects.create(name="tag1") | ||||||
|  |         t2 = Tag.objects.create(name="tag2") | ||||||
|         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|             response = self.client.post( |             response = self.client.post( | ||||||
|                 "/api/documents/post_document/", |                 "/api/documents/post_document/", | ||||||
|                 {"document": f, "tags": ["tag1", "tag2"]}) |                 {"document": f, "tags": [t2.id, t1.id]}) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|         async_task.assert_called_once() |         async_task.assert_called_once() | ||||||
|  |  | ||||||
|         args, kwargs = async_task.call_args |         args, kwargs = async_task.call_args | ||||||
|  |  | ||||||
|         t2 = Tag.objects.get(name="tag2") |  | ||||||
|         self.assertCountEqual(kwargs['override_tag_ids'], [t1.id, t2.id]) |         self.assertCountEqual(kwargs['override_tag_ids'], [t1.id, t2.id]) | ||||||
|  |  | ||||||
|  |     @mock.patch("documents.views.async_task") | ||||||
|  |     def test_upload_with_invalid_tags(self, async_task): | ||||||
|  |         t1 = Tag.objects.create(name="tag1") | ||||||
|  |         t2 = Tag.objects.create(name="tag2") | ||||||
|  |         with open(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), "rb") as f: | ||||||
|  |             response = self.client.post( | ||||||
|  |                 "/api/documents/post_document/", | ||||||
|  |                 {"document": f, "tags": [t2.id, t1.id, 734563]}) | ||||||
|  |         self.assertEqual(response.status_code, 400) | ||||||
|  |  | ||||||
|  |         async_task.assert_not_called() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 jonaswinkler
					jonaswinkler