kopia lustrzana https://github.com/wagtail/wagtail
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 #7508 - see also #4945 - update image docs for same behaviour to be more readablepull/7616/head
rodzic
300163b845
commit
307d0126a2
docs/advanced_topics
wagtail/documents
static_src/wagtaildocs/js
tests
views
|
@ -6,3 +6,4 @@ Documents
|
|||
:maxdepth: 2
|
||||
|
||||
custom_document_model
|
||||
title_generation_on_upload
|
||||
|
|
|
@ -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>
|
||||
"""
|
||||
)
|
||||
```
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 }}"}
|
||||
});
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue