kopia lustrzana https://github.com/wagtail/wagtail
add ability to customise the pre-filled Image title
- leveraging a custom DOM event provides the ability to update the title before being added to the form - multiple image upload (view) needs to be able to read the title POST data - add documentationpull/7575/head
rodzic
2eb7232055
commit
e03c86f3f8
docs
advanced_topics/images
releases
wagtail
admin/views/generic
images
static_src/wagtailimages/js
tests
views
|
@ -37,6 +37,7 @@ Changelog
|
|||
* Move translations in `nl_NL` to `nl` (Loïc Teixeira, Coen van der Kamp)
|
||||
* Add documentation for how to redirect to a separate page on Form builder submissions using ``RoutablePageMixin`` (Nick Smith)
|
||||
* Refactored index listing views and made column sort-by headings more consistent (Matt Westcott)
|
||||
* Added the ability to customise the pre-filled Image title on upload and it now defaults to the filename without the file extension (LB Johnston)
|
||||
* Fix: Delete button is now correct colour on snippets and modeladmin listings (Brandon Murch)
|
||||
* Fix: Ensure that StreamBlock / ListBlock-level validation errors are counted towards error counts (Matt Westcott)
|
||||
* Fix: InlinePanel add button is now keyboard navigatable (Jesse Menn)
|
||||
|
|
|
@ -13,3 +13,4 @@ Images
|
|||
feature_detection
|
||||
image_serve_view
|
||||
focal_points
|
||||
title_generation_on_upload
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
# 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 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.
|
||||
|
||||
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.
|
||||
|
||||
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:images-upload'`. It will be dispatched on the image 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 `Image` model title field.
|
||||
- `filename` - The original filename without the extension removed.
|
||||
|
||||
To modify the generated `Image` title, access and update `event.detail.data.title`, no return value is needed.
|
||||
|
||||
For single image 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 image.
|
||||
|
||||
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
|
||||
|
||||
### Removing any url unsafe characters from 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:images-upload', function(event) {
|
||||
var newTitle = (event.detail.data.title || '').replace(/[^a-zA-Z0-9\s-]/g, "");
|
||||
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 `Image` 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:images-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:images-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>
|
||||
"""
|
||||
)
|
||||
```
|
|
@ -53,6 +53,7 @@ Other features
|
|||
* Translations in ``nl_NL`` are moved to the ``nl`` po files. ``nl_NL`` translation files are deleted. Projects that use ``LANGUAGE_CODE = 'nl-nl'`` will automatically fallback to ``nl``. (Loïc Teixeira, Coen van der Kamp)
|
||||
* Add documentation for how to redirect to a separate page on Form builder submissions using ``RoutablePageMixin`` (Nick Smith)
|
||||
* Refactored index listing views and made column sort-by headings more consistent (Matt Westcott)
|
||||
* Added the ability to customise the pre-filled Image title on upload and it now defaults to the filename without the file extension (LB Johnston)
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
|
|
@ -125,7 +125,7 @@ class AddView(PermissionCheckedMixin, TemplateView):
|
|||
# Build a form for validation
|
||||
upload_form_class = self.get_upload_form_class()
|
||||
form = upload_form_class({
|
||||
'title': request.FILES['files[]'].name,
|
||||
'title': request.POST.get('title', request.FILES['files[]'].name),
|
||||
'collection': request.POST.get('collection'),
|
||||
}, {
|
||||
'file': request.FILES['files[]'],
|
||||
|
|
|
@ -97,6 +97,35 @@ $(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.
|
||||
*
|
||||
* @example
|
||||
* document.addEventListener('wagtail:images-upload', function(event) {
|
||||
* // remove file extension
|
||||
* var newTitle = (event.detail.data.title || '').replace(/\.[^\.]+$/, "");
|
||||
* event.detail.data.title = newTitle;
|
||||
* });
|
||||
*
|
||||
* @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:images-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);
|
||||
|
|
|
@ -34,11 +34,26 @@ function ajaxifyImageUploadForm(modal) {
|
|||
fileWidget.on('change', function () {
|
||||
var titleWidget = $('#id_image-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];
|
||||
titleWidget.val(fileName);
|
||||
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:images-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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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:images-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 }}"}
|
||||
});
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
simple_upload_url: "{% url 'wagtailimages:add' %}",
|
||||
accepted_file_types: /\.({{ allowed_extensions|join:"|" }})$/i, //must be regex
|
||||
max_file_size: {{ max_filesize|stringformat:"s"|default:"null" }}, //numeric format
|
||||
max_title_length: {{ max_title_length|stringformat:"s"|default:"null" }}, //numeric format
|
||||
errormessages: {
|
||||
max_file_size: "{{ error_max_file_size|escapejs }}",
|
||||
accepted_file_types: "{{ error_accepted_file_types|escapejs }}"
|
||||
|
|
|
@ -13,6 +13,7 @@ from django.utils.safestring import mark_safe
|
|||
|
||||
from wagtail.admin.admin_url_finder import AdminURLFinder
|
||||
from wagtail.core.models import Collection, GroupCollectionPermission, get_root_collection_id
|
||||
from wagtail.images import get_image_model
|
||||
from wagtail.images.models import UploadedImage
|
||||
from wagtail.images.utils import generate_signature
|
||||
from wagtail.tests.testapp.models import CustomImage, CustomImageWithAuthor
|
||||
|
@ -1376,6 +1377,7 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils):
|
|||
This tests that a POST request to the add view saves the image and returns an edit form
|
||||
"""
|
||||
response = self.client.post(reverse('wagtailimages:add_multiple'), {
|
||||
'title': 'test title',
|
||||
'files[]': SimpleUploadedFile('test.png', get_test_image_file().file.getvalue()),
|
||||
})
|
||||
|
||||
|
@ -1386,21 +1388,59 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils):
|
|||
|
||||
# Check image
|
||||
self.assertIn('image', response.context)
|
||||
self.assertEqual(response.context['image'].title, 'test.png')
|
||||
self.assertEqual(response.context['image'].title, 'test title')
|
||||
self.assertTrue(response.context['image'].file_size)
|
||||
self.assertTrue(response.context['image'].file_hash)
|
||||
|
||||
# Check image title
|
||||
image = get_image_model().objects.get(title='test title')
|
||||
self.assertNotIn('title', image.filename)
|
||||
self.assertIn('.png', image.filename)
|
||||
|
||||
# Check form
|
||||
self.assertIn('form', response.context)
|
||||
self.assertEqual(response.context['form'].initial['title'], 'test.png')
|
||||
self.assertEqual(response.context['edit_action'], '/admin/images/multiple/%d/' % response.context['image'].id)
|
||||
self.assertEqual(response.context['delete_action'], '/admin/images/multiple/%d/delete/' % response.context['image'].id)
|
||||
self.assertEqual(response.context['form'].initial['title'], 'test title')
|
||||
|
||||
# Check JSON
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertIn('form', response_json)
|
||||
self.assertIn('image_id', response_json)
|
||||
self.assertIn('success', response_json)
|
||||
self.assertEqual(response_json['image_id'], response.context['image'].id)
|
||||
self.assertTrue(response_json['success'])
|
||||
|
||||
def test_add_post_no_title(self):
|
||||
"""
|
||||
A POST request to the add view without the title value saves the image and uses file title if needed
|
||||
"""
|
||||
response = self.client.post(reverse('wagtailimages:add_multiple'), {
|
||||
'files[]': SimpleUploadedFile('no-title.png', get_test_image_file().file.getvalue()),
|
||||
}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/json')
|
||||
|
||||
# Check image
|
||||
self.assertIn('image', response.context)
|
||||
self.assertTrue(response.context['image'].file_size)
|
||||
self.assertTrue(response.context['image'].file_hash)
|
||||
|
||||
# Check image title
|
||||
image = get_image_model().objects.get(title='no-title.png')
|
||||
self.assertEqual('no-title.png', image.filename)
|
||||
self.assertIn('.png', image.filename)
|
||||
|
||||
# Check form
|
||||
self.assertIn('form', response.context)
|
||||
self.assertEqual(response.context['form'].initial['title'], 'no-title.png')
|
||||
|
||||
# Check JSON
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertIn('form', response_json)
|
||||
self.assertIn('success', response_json)
|
||||
self.assertTrue(response_json['success'])
|
||||
|
||||
def test_add_post_nofile(self):
|
||||
"""
|
||||
|
|
|
@ -55,6 +55,7 @@ class AddView(BaseAddView):
|
|||
|
||||
context.update({
|
||||
'max_filesize': self.form.fields['file'].max_upload_size,
|
||||
'max_title_length': self.form.fields['title'].max_length,
|
||||
'allowed_extensions': ALLOWED_EXTENSIONS,
|
||||
'error_max_file_size': self.form.fields['file'].error_messages['file_too_large_unknown_size'],
|
||||
'error_accepted_file_types': self.form.fields['file'].error_messages['invalid_image_extension'],
|
||||
|
|
Ładowanie…
Reference in New Issue