🔧(backend) settings CONVERSION_UPLOAD_ENABLED to control docspec usage

We want to control the conversion of document at upload time. We want to
disable this feature using a settings. The new settings
CONVERSION_UPLOAD_ENABLED should be used to enable or not the conversion
at upload feature. If disabled and a file is uploaded, the reponse will
return a 400
This commit is contained in:
Manuel Raynaud
2026-03-30 15:37:48 +02:00
parent 6691167a40
commit 05423d4f04
5 changed files with 77 additions and 10 deletions
+4
View File
@@ -6,6 +6,10 @@ and this project adheres to
## [Unreleased]
### Added
- 🔧(backend) settings CONVERSION_UPLOAD_ENABLED to control usage of docspec
### Changed
- ♿(frontend) use aria-haspopup menu on DropButton triggers #2126
+5
View File
@@ -680,6 +680,11 @@ class DocumentViewSet(
# Process it if present
uploaded_file = serializer.validated_data.pop("file", None)
if uploaded_file and not settings.CONVERSION_UPLOAD_ENABLED:
raise drf.exceptions.ValidationError(
{"file": ["file upload is not allowed"]}
)
# If a file is uploaded, convert it to Yjs format and set as content
if uploaded_file:
try:
@@ -40,7 +40,7 @@ def test_api_documents_create_with_file_anonymous():
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_docx_file_success(mock_convert):
def test_api_documents_create_with_docx_file_success(mock_convert, settings):
"""
Authenticated users should be able to create documents by uploading a DOCX file.
The file should be converted to YJS format and the title should be set from filename.
@@ -49,6 +49,8 @@ def test_api_documents_create_with_docx_file_success(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion
converted_yjs = "base64encodedyjscontent"
mock_convert.return_value = converted_yjs
@@ -81,7 +83,38 @@ def test_api_documents_create_with_docx_file_success(mock_convert):
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_markdown_file_success(mock_convert):
def test_api_documents_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"
response = client.post(
"/api/v1.0/documents/",
{
"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()
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_markdown_file_success(mock_convert, settings):
"""
Authenticated users should be able to create documents by uploading a Markdown file.
"""
@@ -89,6 +122,8 @@ def test_api_documents_create_with_markdown_file_success(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion
converted_yjs = "base64encodedyjscontent"
mock_convert.return_value = converted_yjs
@@ -121,7 +156,7 @@ def test_api_documents_create_with_markdown_file_success(mock_convert):
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_and_explicit_title(mock_convert):
def test_api_documents_create_with_file_and_explicit_title(mock_convert, settings):
"""
When both file and title are provided, the filename should override the title.
"""
@@ -129,6 +164,8 @@ def test_api_documents_create_with_file_and_explicit_title(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion
converted_yjs = "base64encodedyjscontent"
mock_convert.return_value = converted_yjs
@@ -153,7 +190,7 @@ def test_api_documents_create_with_file_and_explicit_title(mock_convert):
assert document.title == "Uploaded Document.docx"
def test_api_documents_create_with_empty_file():
def test_api_documents_create_with_empty_file(settings):
"""
Creating a document with an empty file should fail with a validation error.
"""
@@ -161,6 +198,8 @@ def test_api_documents_create_with_empty_file():
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Create an empty file
file = BytesIO(b"")
file.name = "empty.docx"
@@ -179,7 +218,7 @@ def test_api_documents_create_with_empty_file():
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_conversion_error(mock_convert):
def test_api_documents_create_with_file_conversion_error(mock_convert, settings):
"""
When conversion fails, the API should return a 400 error with appropriate message.
"""
@@ -187,6 +226,8 @@ def test_api_documents_create_with_file_conversion_error(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion to raise an error
mock_convert.side_effect = ConversionError("Failed to convert document")
@@ -209,7 +250,7 @@ def test_api_documents_create_with_file_conversion_error(mock_convert):
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_service_unavailable(mock_convert):
def test_api_documents_create_with_file_service_unavailable(mock_convert, settings):
"""
When the conversion service is unavailable, appropriate error should be returned.
"""
@@ -217,6 +258,8 @@ def test_api_documents_create_with_file_service_unavailable(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion to raise ServiceUnavailableError
mock_convert.side_effect = ServiceUnavailableError(
"Failed to connect to conversion service"
@@ -264,7 +307,7 @@ def test_api_documents_create_without_file_still_works():
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_null_value(mock_convert):
def test_api_documents_create_with_file_null_value(mock_convert, settings):
"""
Passing file=null should be treated as no file upload.
"""
@@ -272,6 +315,8 @@ def test_api_documents_create_with_file_null_value(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
response = client.post(
"/api/v1.0/documents/",
{
@@ -289,7 +334,9 @@ def test_api_documents_create_with_file_null_value(mock_convert):
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_preserves_content_format(mock_convert):
def test_api_documents_create_with_file_preserves_content_format(
mock_convert, settings
):
"""
Verify that the converted content is stored correctly in the document.
"""
@@ -297,6 +344,8 @@ def test_api_documents_create_with_file_preserves_content_format(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion with realistic base64-encoded YJS data
converted_yjs = "AQMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICA="
mock_convert.return_value = converted_yjs
@@ -328,7 +377,7 @@ def test_api_documents_create_with_file_preserves_content_format(mock_convert):
@patch("core.services.converter_services.Converter.convert")
def test_api_documents_create_with_file_unicode_filename(mock_convert):
def test_api_documents_create_with_file_unicode_filename(mock_convert, settings):
"""
Test that Unicode characters in filenames are handled correctly.
"""
@@ -336,6 +385,8 @@ def test_api_documents_create_with_file_unicode_filename(mock_convert):
client = APIClient()
client.force_login(user)
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion
converted_yjs = "base64encodedyjscontent"
mock_convert.return_value = converted_yjs
@@ -363,6 +414,7 @@ def test_api_documents_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()
@@ -389,6 +441,7 @@ def test_api_documents_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()
@@ -261,7 +261,7 @@ def test_external_api_documents_create_subdocument_reader_not_allowed(
@patch("core.services.converter_services.Converter.convert")
def test_external_api_documents_create_with_markdown_file_success(
mock_convert, user_token, resource_server_backend, user_specific_sub
mock_convert, user_token, resource_server_backend, user_specific_sub, settings
):
"""
Users with an access token should be able to create documents through the resource
@@ -272,6 +272,8 @@ def test_external_api_documents_create_with_markdown_file_success(
client = APIClient()
client.credentials(HTTP_AUTHORIZATION=f"Bearer {user_token}")
settings.CONVERSION_UPLOAD_ENABLED = True
# Mock the conversion
converted_yjs = "base64encodedyjscontent"
mock_convert.return_value = converted_yjs
+3
View File
@@ -866,6 +866,9 @@ class Base(Configuration):
DOCSPEC_API_URL = values.Value(environ_name="DOCSPEC_API_URL", environ_prefix=None)
# Imported file settings
CONVERSION_UPLOAD_ENABLED = values.BooleanValue(
False, environ_name="CONVERSION_UPLOAD_ENABLED", environ_prefix=None
)
CONVERSION_FILE_MAX_SIZE = values.IntegerValue(
20 * MB,
environ_name="CONVERSION_FILE_MAX_SIZE",