mirror of
https://github.com/suitenumerique/docs.git
synced 2026-09-27 20:15:01 +02:00
✨(backend) support creating subdoc from file
The children/ endpoint was missing file upload support that the root documents endpoint already had. Added file-to-YJS conversion handling to subdocument creation. Signed-off-by: Gustavo Trott <gustavo@trott.com.br>
This commit is contained in:
@@ -8,6 +8,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(backend) support creating subdoc from file #1987
|
||||
- ⚡️(frontend) add skeleton on content loading #2254
|
||||
- ⚡️(frontend) close websocket connection when user change tab #2264
|
||||
|
||||
|
||||
@@ -676,10 +676,13 @@ class DocumentViewSet(
|
||||
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Set the current user as creator and owner of the newly created object."""
|
||||
# Remove file from validated_data as it's not a model field
|
||||
# Process it if present
|
||||
def _apply_uploaded_file_conversion(self, serializer):
|
||||
"""
|
||||
Check if a file has been uploaded with a doc or a children is created.
|
||||
If a file is present and the conversion upload enabled, the file is converted
|
||||
using the converter service and the validated_data in the serializer are filled
|
||||
with the converted file and the file name.
|
||||
"""
|
||||
uploaded_file = serializer.validated_data.pop("file", None)
|
||||
|
||||
if uploaded_file and not settings.CONVERSION_UPLOAD_ENABLED:
|
||||
@@ -707,6 +710,11 @@ class DocumentViewSet(
|
||||
{"file": ["Could not convert file content"]}
|
||||
) from err
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Set the current user as creator and owner of the newly created object."""
|
||||
|
||||
self._apply_uploaded_file_conversion(serializer)
|
||||
|
||||
obj = create_tree_node_with_retry(
|
||||
lambda: models.Document.add_root(
|
||||
creator=self.request.user,
|
||||
@@ -1016,6 +1024,8 @@ class DocumentViewSet(
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
self._apply_uploaded_file_conversion(serializer)
|
||||
|
||||
child_document = create_tree_node_with_retry(
|
||||
lambda: document.add_child(
|
||||
creator=request.user,
|
||||
|
||||
@@ -3,6 +3,8 @@ Tests for Documents API endpoint in impress's core app: children create
|
||||
"""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -10,6 +12,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.models import Document, LinkReachChoices, LinkRoleChoices
|
||||
from core.services import mime_types
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -292,3 +295,142 @@ def test_api_documents_create_document_children_race_condition():
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.numchild == 2
|
||||
|
||||
|
||||
@patch("core.services.converter_services.Converter.convert")
|
||||
def test_api_documents_children_create_with_docx_file_success(mock_convert, settings):
|
||||
"""
|
||||
Authenticated users should be able to create children document by uploading a DOCX file.
|
||||
The file should be converted to YJS format and the title should be set from filename.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
settings.CONVERSION_UPLOAD_ENABLED = True
|
||||
|
||||
# Mock the conversion
|
||||
converted_yjs = "base64encodedyjscontent"
|
||||
mock_convert.return_value = converted_yjs
|
||||
|
||||
# Create a fake DOCX file
|
||||
file_content = b"fake docx content"
|
||||
file = BytesIO(file_content)
|
||||
file.name = "My Important Document.docx"
|
||||
|
||||
parent = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{parent.id}/children/",
|
||||
{
|
||||
"file": file,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert Document.objects.count() == 2
|
||||
children = Document.objects.get(pk=response.json()["id"])
|
||||
assert children.title == "My Important Document.docx"
|
||||
assert children.content == converted_yjs
|
||||
|
||||
# Verify the converter was called correctly
|
||||
mock_convert.assert_called_once_with(
|
||||
file_content,
|
||||
content_type=mime_types.DOCX,
|
||||
accept=mime_types.YJS,
|
||||
)
|
||||
|
||||
|
||||
@patch("core.services.converter_services.Converter.convert")
|
||||
def test_api_documents_children_create_with_docx_file_disabled(mock_convert, settings):
|
||||
"""
|
||||
When conversion is not enabled, uploading a file should have no effect
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
settings.CONVERSION_UPLOAD_ENABLED = False
|
||||
|
||||
# Create a fake DOCX file
|
||||
file_content = b"fake docx content"
|
||||
file = BytesIO(file_content)
|
||||
file.name = "My Important Document.docx"
|
||||
|
||||
parent = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{parent.id}/children/",
|
||||
{
|
||||
"file": file,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"file": ["file upload is not allowed"]}
|
||||
|
||||
# Verify the converter was not called
|
||||
mock_convert.assert_not_called()
|
||||
|
||||
|
||||
def test_api_documents_children_create_with_file_max_size_exceeded(settings):
|
||||
"""
|
||||
The uploaded file should not exceed the maximum size in settings.
|
||||
"""
|
||||
settings.CONVERSION_FILE_MAX_SIZE = 1 # 1 byte for test
|
||||
settings.CONVERSION_UPLOAD_ENABLED = True
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
file = BytesIO(b"a" * (10))
|
||||
file.name = "test.docx"
|
||||
|
||||
parent = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{parent.id}/children/",
|
||||
{
|
||||
"file": file,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
assert response.json() == {"file": ["File size exceeds the maximum limit of 0 MB."]}
|
||||
|
||||
|
||||
def test_api_documents_children_create_with_file_extension_not_allowed(settings):
|
||||
"""
|
||||
The uploaded file should not have an allowed extension.
|
||||
"""
|
||||
settings.CONVERSION_FILE_EXTENSIONS_ALLOWED = [".docx"]
|
||||
settings.CONVERSION_UPLOAD_ENABLED = True
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
file = BytesIO(b"fake docx content")
|
||||
file.name = "test.md"
|
||||
|
||||
parent = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{parent.id}/children/",
|
||||
{
|
||||
"file": file,
|
||||
},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"file": [
|
||||
"File extension .md is not allowed. Allowed extensions are: ['.docx']."
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user