add ability to customise the pre-filled document title

- leveraging a custom DOM event provides the ability to update the title before being added to the form
- add documentation
- fixes 
- see also 
- update image docs for same behaviour to be more readable
pull/7616/head
LB Johnston 2021-10-06 20:22:02 +10:00 zatwierdzone przez LB (Ben Johnston)
rodzic 300163b845
commit 307d0126a2
9 zmienionych plików z 272 dodań i 2 usunięć
wagtail/documents
templates/wagtaildocs
documents
multiple

Wyświetl plik

@ -6,3 +6,4 @@ Documents
:maxdepth: 2
custom_document_model
title_generation_on_upload

Wyświetl plik

@ -0,0 +1,111 @@
# Title generation on upload
When uploading a file (document), Wagtail takes the filename, removes the file extension, and populates the title field. This section is about how to customise this filename to title conversion.
The filename to title conversion is used on the single file widget, multiple upload widget, and within chooser modals.
You can also customise this [same behaviour for images](../images/title_generation_on_upload).
You can customise the resolved value of this title using a JavaScript [event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) which will listen to the `'wagtail:documents-upload'` event.
The simplest way to add JavaScript to the editor is via the [`insert_global_admin_js` hook](../../reference/hooks.html#insert-global-admin-js), however any JavaScript that adds the event listener will work.
## DOM Event
The event name to listen for is `'wagtail:documents-upload'`. It will be dispatched on the document upload `form`. The event's `detail` attribute will contain:
- `data` - An object which includes the `title` to be used. It is the filename with the extension removed.
- `maxTitleLength` - An integer (or `null`) which is the maximum length of the `Document` model title field.
- `filename` - The original filename without the extension removed.
To modify the generated `Document` title, access and update `event.detail.data.title`, no return value is needed.
For single document uploads, the custom event will only run if the title does not already have a value so that we do not overwrite whatever the user has typed.
You can prevent the default behaviour by calling `event.preventDefault()`. For the single upload page or modals, this will not pre-fill any value into the title. For multiple upload, this will avoid any title submission and use the filename title only (with file extension) as a title is required to save the document.
The event will 'bubble' up so that you can simply add a global `document` listener to capture all of these events, or you can scope your listener or handler logic as needed to ensure you only adjust titles in some specific scenarios.
See MDN for more information about [custom JavasScript events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events).
## Code Examples
### Adding the file extension to the start of the title
```python
# wagtail_hooks.py
from django.utils.safestring import mark_safe
from wagtail.core import hooks
@hooks.register("insert_global_admin_js")
def get_global_admin_js():
return mark_safe(
"""
<script>
window.addEventListener('DOMContentLoaded', function () {
document.addEventListener('wagtail:documents-upload', function(event) {
var extension = (event.detail.filename.match(/\.([^.]*?)(?=\?|#|$)/) || [''])[1];
var newTitle = '(' + extension.toUpperCase() + ') ' + (event.detail.data.title || '');
event.detail.data.title = newTitle;
});
});
</script>
"""
)
```
### Changing generated titles on the page editor only to remove dashes/underscores
Using the [`insert_editor_js` hook](../../reference/hooks.html#insert-editor-js) instead so that this script will not run on the `Document` upload page, only on page editors.
```python
# wagtail_hooks.py
from django.utils.safestring import mark_safe
from wagtail.core import hooks
@hooks.register("insert_editor_js")
def get_global_admin_js():
return mark_safe(
"""
<script>
window.addEventListener('DOMContentLoaded', function () {
document.addEventListener('wagtail:documents-upload', function(event) {
// replace dashes/underscores with a space
var newTitle = (event.detail.data.title || '').replace(/(\s|_|-)/g, " ");
event.detail.data.title = newTitle;
});
});
</script>
"""
)
```
### Stopping pre-filling of title based on filename
```python
# wagtail_hooks.py
from django.utils.safestring import mark_safe
from wagtail.core import hooks
@hooks.register("insert_global_admin_js")
def get_global_admin_js():
return mark_safe(
"""
<script>
window.addEventListener('DOMContentLoaded', function () {
document.addEventListener('wagtail:documents-upload', function(event) {
// will stop title pre-fill on single file uploads
// will set the multiple upload title to the filename (with extension)
event.preventDefault();
});
});
</script>
"""
)
```

Wyświetl plik

@ -1,8 +1,10 @@
# Title generation on upload
Override how the title is set when adding a single image, multiple images or uploading an image within a chooser modal.
When uploading an image, Wagtail takes the filename, removes the file extension, and populates the title field. This section is about how to customise this filename to title conversion.
When a file is dropped into the multi-upload page or selected on the single file selection form a title will be automatically populated into the Title field. The default behaviour is to use the image's filename excluding the extension.
The filename to title conversion is used on the single file widget, multiple upload widget, and within chooser modals.
You can also customise this [same behaviour for documents](../documents/title_generation_on_upload).
You can customise the resolved value of this title using a JavaScript [event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) which will listen to the `'wagtail:images-upload'` event.

Wyświetl plik

@ -81,6 +81,28 @@ $(function() {
}
},
/**
* Allow a custom title to be defined by an event handler for this form.
* If event.preventDefault is called, the original behaviour of using the raw
* filename (with extension) as the title is preserved.
*
* @param {HtmlElement[]} form
* @returns {{name: 'string', value: *}[]}
*/
formData: function(form) {
var filename = this.files[0].name;
var data = { title: filename.replace(/\.[^\.]+$/, '') };
var maxTitleLength = window.fileupload_opts.max_title_length;
var event = form.get(0).dispatchEvent(new CustomEvent(
'wagtail:documents-upload',
{ bubbles: true, cancelable: true, detail: { data: data, filename: filename, maxTitleLength: maxTitleLength } }
));
// default behaviour (title is just file name)
return event ? form.serializeArray().concat({ name:'title', value: data.title }) : form.serializeArray();
},
done: function(e, data) {
var itemElement = $(data.context);
var response = JSON.parse(data.result);

Wyświetl plik

@ -20,6 +20,33 @@ function ajaxifyDocumentUploadForm(modal) {
return false;
});
var fileWidget = $('#id_document-chooser-upload-file', modal.body);
fileWidget.on('change', function () {
var titleWidget = $('#id_document-chooser-upload-title', modal.body);
var title = titleWidget.val();
// do not override a title that already exists (from manual editing or previous upload)
if (title === '') {
// The file widget value example: `C:\fakepath\image.jpg`
var parts = fileWidget.val().split('\\');
var filename = parts[parts.length - 1];
// allow event handler to override filename (used for title) & provide maxLength as int to event
var maxTitleLength = parseInt(titleWidget.attr('maxLength') || '0', 10) || null;
var data = { title: filename.replace(/\.[^\.]+$/, '') };
// allow an event handler to customise data or call event.preventDefault to stop any title pre-filling
var form = fileWidget.closest('form').get(0);
var event = form.dispatchEvent(new CustomEvent(
'wagtail:documents-upload',
{ bubbles: true, cancelable: true, detail: { data: data, filename: filename, maxTitleLength: maxTitleLength } }
));
if (!event) return; // do not set a title if event.preventDefault(); is called by handler
titleWidget.val(data.title);
}
});
}
DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS = {

Wyświetl plik

@ -11,6 +11,31 @@
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
<script>
$(function() {
$('#id_file').on(
'change',
function() {
var $titleField = $('#id_title');
// do not override a title that already exists (from manual editing or previous upload)
if ($titleField.val()) return;
// file widget value example: `C:\fakepath\image.jpg` - convert to just the filename part
var filename = $(this).val().split('\\').slice(-1)[0];
var data = { title: filename.replace(/\.[^\.]+$/, '') };
var maxTitleLength = parseInt($titleField.attr('maxLength') || '0', 10) || null;
// allow an event handler to customise data or call event.preventDefault to stop any title pre-filling
var form = $(this).closest('form').get(0);
var event = form.dispatchEvent(new CustomEvent(
'wagtail:documents-upload',
{ bubbles: true, cancelable: true, detail: { data: data, filename: filename, maxTitleLength: maxTitleLength } }
));
if (!event) return; // do not set a title if event.preventDefault(); is called by handler
$titleField.val(data.title);
}
);
$('#id_tags').tagit({
autocomplete: {source: "{{ autocomplete_url|addslashes }}"}
});

Wyświetl plik

@ -93,6 +93,7 @@
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
<script>
window.fileupload_opts = {
max_title_length: {{ max_title_length|stringformat:"s"|default:"null" }}, //numeric format
simple_upload_url: "{% url 'wagtaildocs:add' %}"
}
window.tagit_opts = {

Wyświetl plik

@ -687,6 +687,53 @@ class TestMultipleDocumentUploader(TestCase, WagtailTestUtils):
# form should not contain a collection chooser
self.assertNotIn('Collection', response_json['form'])
def test_add_post_with_title(self):
"""
This tests that a POST request to the add view saves the document with a suplied title and returns an edit form
"""
response = self.client.post(reverse('wagtaildocs:add_multiple'), {
'title': '(TXT) test title',
'files[]': SimpleUploadedFile('test.txt', b"Simple text document"),
})
# Check response
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/json')
self.assertTemplateUsed(response, 'wagtailadmin/generic/multiple_upload/edit_form.html')
# Check document
self.assertIn('doc', response.context)
self.assertEqual(response.context['doc'].title, '(TXT) test title')
self.assertIn('.txt', response.context['doc'].filename)
self.assertTrue(response.context['doc'].file_size)
self.assertTrue(response.context['doc'].file_hash)
self.assertEqual(response.context['edit_action'], '/admin/documents/multiple/%d/' % response.context['doc'].id)
self.assertEqual(response.context['delete_action'], '/admin/documents/multiple/%d/delete/' % response.context['doc'].id)
# check that it is in the root collection
doc = get_document_model().objects.get(title='(TXT) test title')
root_collection = Collection.get_first_root_node()
self.assertEqual(doc.collection, root_collection)
# Check form
self.assertIn('form', response.context)
self.assertEqual(
set(response.context['form'].fields),
set(get_document_model().admin_form_fields) - {'file', 'collection'},
)
self.assertEqual(response.context['form'].initial['title'], '(TXT) test title')
# Check JSON
response_json = json.loads(response.content.decode())
self.assertIn('doc_id', response_json)
self.assertIn('form', response_json)
self.assertIn('success', response_json)
self.assertEqual(response_json['doc_id'], response.context['doc'].id)
self.assertTrue(response_json['success'])
# form should not contain a collection chooser
self.assertNotIn('Collection', response_json['form'])
def test_add_post_with_collections(self):
"""
This tests that a POST request to the add view saves the document
@ -924,6 +971,31 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
# form should not contain a collection chooser
self.assertNotIn('Collection', response_json['form'])
def test_add_post_with_title(self):
"""
This tests that a POST request to the add view saves the document with a suplied title and returns an edit form
"""
response = self.client.post(reverse('wagtaildocs:add_multiple'), {
'title': '(TXT) test title',
'files[]': SimpleUploadedFile('test.txt', b"Simple text document"),
})
# Check response
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/json')
self.assertTemplateUsed(response, 'wagtailadmin/generic/multiple_upload/edit_form.html')
# Check document
self.assertIn('uploaded_document', response.context)
self.assertIn('.txt', response.context['uploaded_document'].file.name)
# Check JSON
response_json = json.loads(response.content.decode())
self.assertIn('uploaded_document_id', response_json)
self.assertIn('form', response_json)
self.assertEqual(response_json['uploaded_document_id'], response.context['uploaded_document'].id)
self.assertTrue(response_json['success'])
def test_add_post_with_collections(self):
"""
This tests that a POST request to the add view saves the document

Wyświetl plik

@ -54,6 +54,15 @@ class AddView(BaseAddView):
return doc
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'max_title_length': self.form.fields['title'].max_length,
})
return context
class EditView(BaseEditView):
permission_policy = permission_policy