mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-03-16 22:10:28 +00:00
Output form media on add/edit/chooser image forms with custom models
* Add tests for custom image on multiple image uploader * Output form media on image add/edit views * Output form media for 'add image' form within image chooser modal Note: this won't work reliably if the media is hosted on a CDN, because script tags inserted as part of a jQuery DOM insertion (as modals are) are loaded asynchronously and not guaranteed to complete loading before inline scripts are run. It's better than not having the includes there at all though...
This commit is contained in:
parent
f7ff6d39c4
commit
eaad013081
11 changed files with 230 additions and 12 deletions
|
|
@ -17,6 +17,7 @@ Changelog
|
|||
* Fix: Default text of page links in rich text uses the public page title rather than the admin display title (Andy Chosak)
|
||||
* Fix: Specific page permission checks are now enforced when viewing a page revision (Andy Chosak)
|
||||
* Fix: `pageurl` and `slugurl` tags no longer fail when `request.site` is `None` (Samir Shah)
|
||||
* Fix: Output form media on add/edit image forms with custom models (Matt Westcott)
|
||||
|
||||
|
||||
2.6.1 (05.08.2019)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ Bug fixes
|
|||
* Default text of page links in rich text uses the public page title rather than the admin display title (Andy Chosak)
|
||||
* Specific page permission checks are now enforced when viewing a page revision (Andy Chosak)
|
||||
* ``pageurl`` and ``slugurl`` tags no longer fail when ``request.site`` is ``None`` (Samir Shah)
|
||||
* Output form media on add/edit image forms with custom models (Matt Westcott)
|
||||
|
||||
|
||||
Upgrade considerations
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
{% trans "Choose an image" as choose_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=choose_str merged=1 tabbed=1 icon="image" %}
|
||||
|
||||
{{ uploadform.media.js }}
|
||||
{{ uploadform.media.css }}
|
||||
|
||||
{% if uploadform %}
|
||||
<ul class="tab-nav merged">
|
||||
<li class="{% if not uploadform.errors %}active{% endif %}"><a href="#search" >{% trans "Search" %}</a></li>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
|
||||
{{ form.media.js }}
|
||||
|
||||
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
|
||||
<script>
|
||||
$(function() {
|
||||
|
|
@ -16,6 +18,11 @@
|
|||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% trans "Add image" as add_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=add_str icon="image" %}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
|
||||
{{ form.media.css }}
|
||||
|
||||
<!-- Focal point chooser -->
|
||||
<link rel="stylesheet" href="{% static 'wagtailimages/css/vendor/jquery.Jcrop.min.css' %}" type="text/css">
|
||||
<link rel="stylesheet" href="{% static 'wagtailimages/css/focal-point-chooser.css' %}" type="text/css">
|
||||
|
|
@ -12,6 +14,8 @@
|
|||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
|
||||
{{ form.media.js }}
|
||||
|
||||
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
|
||||
<script>
|
||||
$(function() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
|
||||
{{ form_media.css }}
|
||||
|
||||
<link rel="stylesheet" href="{% static 'wagtailimages/css/add-multiple.css' %}" type="text/css" />
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -75,6 +77,8 @@
|
|||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
|
||||
{{ form_media.js }}
|
||||
|
||||
<!-- this exact order of plugins is vital -->
|
||||
<script src="{% static 'wagtailimages/js/vendor/load-image.min.js' %}"></script>
|
||||
<script src="{% static 'wagtailimages/js/vendor/canvas-to-blob.min.js' %}"></script>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from django.utils.http import RFC3986_SUBDELIMS, urlquote
|
|||
|
||||
from wagtail.core.models import Collection, GroupCollectionPermission
|
||||
from wagtail.images.views.serve import generate_signature
|
||||
from wagtail.tests.testapp.models import CustomImage
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
from .utils import Image, get_test_image_file
|
||||
|
|
@ -108,6 +109,9 @@ class TestImageAddView(TestCase, WagtailTestUtils):
|
|||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
# draftail should NOT be a standard JS include on this page
|
||||
self.assertNotContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
def test_get_with_collections(self):
|
||||
root_collection = Collection.get_first_root_node()
|
||||
root_collection.add_child(name="Evil plans")
|
||||
|
|
@ -119,6 +123,21 @@ class TestImageAddView(TestCase, WagtailTestUtils):
|
|||
self.assertContains(response, '<label for="id_collection">')
|
||||
self.assertContains(response, "Evil plans")
|
||||
|
||||
@override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
|
||||
def test_get_with_custom_image_model(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/add.html')
|
||||
|
||||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
# custom fields should be included
|
||||
self.assertContains(response, 'name="fancy_caption"')
|
||||
|
||||
# form media should be imported
|
||||
self.assertContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
def test_add(self):
|
||||
response = self.post({
|
||||
'title': "Test image",
|
||||
|
|
@ -325,6 +344,11 @@ class TestImageEditView(TestCase, WagtailTestUtils):
|
|||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
# draftail should NOT be a standard JS include on this page
|
||||
# (see TestImageEditViewWithCustomImageModel - this confirms that form media
|
||||
# definitions are being respected)
|
||||
self.assertNotContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
@override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
|
||||
def test_with_usage_count(self):
|
||||
response = self.get()
|
||||
|
|
@ -514,6 +538,34 @@ class TestImageEditView(TestCase, WagtailTestUtils):
|
|||
self.assertContains(response, 'data-original-width="1024"')
|
||||
|
||||
|
||||
@override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
|
||||
class TestImageEditViewWithCustomImageModel(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an image to edit
|
||||
self.image = CustomImage.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
self.storage = self.image.file.storage
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages:edit', args=(self.image.id,)), params)
|
||||
|
||||
def test_get_with_custom_image_model(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/edit.html')
|
||||
|
||||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
# form media should be imported
|
||||
self.assertContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
|
||||
class TestImageDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
|
@ -571,6 +623,23 @@ class TestImageChooserView(TestCase, WagtailTestUtils):
|
|||
self.assertEqual(response_json['step'], 'chooser')
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
|
||||
|
||||
# draftail should NOT be a standard JS include on this page
|
||||
self.assertNotIn('wagtailadmin/js/draftail.js', response_json['html'])
|
||||
|
||||
@override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
|
||||
def test_with_custom_image_model(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertEqual(response_json['step'], 'chooser')
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
|
||||
|
||||
# custom form fields should be present
|
||||
self.assertIn('name="image-chooser-upload-fancy_caption"', response_json['html'])
|
||||
|
||||
# form media imports should appear on the page
|
||||
self.assertIn('wagtailadmin/js/draftail.js', response_json['html'])
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
@ -898,6 +967,11 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/multiple/add.html')
|
||||
|
||||
# draftail should NOT be a standard JS include on this page
|
||||
# (see TestMultipleImageUploaderWithCustomImageModel - this confirms that form media
|
||||
# definitions are being respected)
|
||||
self.assertNotContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
@override_settings(WAGTAILIMAGES_MAX_UPLOAD_SIZE=1000)
|
||||
def test_add_max_file_size_context_variables(self):
|
||||
response = self.client.get(reverse('wagtailimages:add_multiple'))
|
||||
|
|
@ -1095,6 +1169,123 @@ class TestMultipleImageUploader(TestCase, WagtailTestUtils):
|
|||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
|
||||
@override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
|
||||
class TestMultipleImageUploaderWithCustomImageModel(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
This tests the multiple image upload views located in wagtailimages/views/multiple.py
|
||||
with a custom image model
|
||||
"""
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an image for running tests on
|
||||
self.image = CustomImage.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
def test_add(self):
|
||||
"""
|
||||
This tests that the add view responds correctly on a GET request
|
||||
"""
|
||||
# Send request
|
||||
response = self.client.get(reverse('wagtailimages:add_multiple'))
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/multiple/add.html')
|
||||
|
||||
# response should include form media for the image edit form
|
||||
self.assertContains(response, 'wagtailadmin/js/draftail.js')
|
||||
|
||||
def test_add_post(self):
|
||||
"""
|
||||
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'), {
|
||||
'files[]': SimpleUploadedFile('test.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')
|
||||
self.assertTemplateUsed(response, 'wagtailimages/multiple/edit_form.html')
|
||||
|
||||
# Check image
|
||||
self.assertIn('image', response.context)
|
||||
self.assertEqual(response.context['image'].title, 'test.png')
|
||||
self.assertTrue(response.context['image'].file_size)
|
||||
self.assertTrue(response.context['image'].file_hash)
|
||||
|
||||
# Check form
|
||||
self.assertIn('form', response.context)
|
||||
self.assertEqual(response.context['form'].initial['title'], 'test.png')
|
||||
self.assertIn('caption', response.context['form'].fields)
|
||||
self.assertNotIn('not_editable_field', response.context['form'].fields)
|
||||
|
||||
# Check JSON
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertIn('image_id', response_json)
|
||||
self.assertIn('form', response_json)
|
||||
self.assertIn('success', response_json)
|
||||
self.assertEqual(response_json['image_id'], response.context['image'].id)
|
||||
self.assertTrue(response_json['success'])
|
||||
|
||||
def test_edit_post(self):
|
||||
"""
|
||||
This tests that a POST request to the edit view edits the image
|
||||
"""
|
||||
# Send request
|
||||
response = self.client.post(reverse('wagtailimages:edit_multiple', args=(self.image.id, )), {
|
||||
('image-%d-title' % self.image.id): "New title!",
|
||||
('image-%d-tags' % self.image.id): "",
|
||||
('image-%d-caption' % self.image.id): "a boot stamping on a human face, forever",
|
||||
}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/json')
|
||||
|
||||
# Check JSON
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertIn('image_id', response_json)
|
||||
self.assertNotIn('form', response_json)
|
||||
self.assertIn('success', response_json)
|
||||
self.assertEqual(response_json['image_id'], self.image.id)
|
||||
self.assertTrue(response_json['success'])
|
||||
|
||||
# check that image has been updated
|
||||
new_image = CustomImage.objects.get(id=self.image.id)
|
||||
self.assertEqual(new_image.title, "New title!")
|
||||
self.assertEqual(new_image.caption, "a boot stamping on a human face, forever")
|
||||
|
||||
def test_delete_post(self):
|
||||
"""
|
||||
This tests that a POST request to the delete view deletes the image
|
||||
"""
|
||||
# Send request
|
||||
response = self.client.post(reverse(
|
||||
'wagtailimages:delete_multiple', args=(self.image.id, )
|
||||
), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/json')
|
||||
|
||||
# Make sure the image is deleted
|
||||
self.assertFalse(Image.objects.filter(id=self.image.id).exists())
|
||||
|
||||
# Check JSON
|
||||
response_json = json.loads(response.content.decode())
|
||||
self.assertIn('image_id', response_json)
|
||||
self.assertIn('success', response_json)
|
||||
self.assertEqual(response_json['image_id'], self.image.id)
|
||||
self.assertTrue(response_json['success'])
|
||||
|
||||
# check that image has been deleted
|
||||
self.assertEqual(CustomImage.objects.filter(id=self.image.id).count(), 0)
|
||||
|
||||
|
||||
class TestURLGeneratorView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Create an image for running tests on
|
||||
|
|
|
|||
|
|
@ -569,6 +569,7 @@ class TestGetImageForm(TestCase, WagtailTestUtils):
|
|||
'focal_point_width',
|
||||
'focal_point_height',
|
||||
'caption',
|
||||
'fancy_caption',
|
||||
])
|
||||
|
||||
def test_file_field(self):
|
||||
|
|
|
|||
|
|
@ -93,16 +93,19 @@ def add(request):
|
|||
'error_message': '\n'.join(['\n'.join([force_text(i) for i in v]) for k, v in form.errors.items()]),
|
||||
})
|
||||
else:
|
||||
# Instantiate a dummy copy of the form that we can retrieve validation messages and media from;
|
||||
# actual rendering of forms will happen on AJAX POST rather than here
|
||||
form = ImageForm(user=request.user)
|
||||
|
||||
return render(request, 'wagtailimages/multiple/add.html', {
|
||||
'max_filesize': form.fields['file'].max_upload_size,
|
||||
'help_text': form.fields['file'].help_text,
|
||||
'allowed_extensions': ALLOWED_EXTENSIONS,
|
||||
'error_max_file_size': form.fields['file'].error_messages['file_too_large_unknown_size'],
|
||||
'error_accepted_file_types': form.fields['file'].error_messages['invalid_image'],
|
||||
'collections': collections_to_choose,
|
||||
})
|
||||
return render(request, 'wagtailimages/multiple/add.html', {
|
||||
'max_filesize': form.fields['file'].max_upload_size,
|
||||
'help_text': form.fields['file'].help_text,
|
||||
'allowed_extensions': ALLOWED_EXTENSIONS,
|
||||
'error_max_file_size': form.fields['file'].error_messages['file_too_large_unknown_size'],
|
||||
'error_accepted_file_types': form.fields['file'].error_messages['invalid_image'],
|
||||
'collections': collections_to_choose,
|
||||
'form_media': form.media,
|
||||
})
|
||||
|
||||
|
||||
@require_POST
|
||||
|
|
|
|||
|
|
@ -135,8 +135,9 @@ class Migration(migrations.Migration):
|
|||
('focal_point_width', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('focal_point_height', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('file_size', models.PositiveIntegerField(editable=False, null=True)),
|
||||
('caption', models.CharField(max_length=255)),
|
||||
('not_editable_field', models.CharField(max_length=255)),
|
||||
('caption', models.CharField(max_length=255, blank=True)),
|
||||
('fancy_caption', wagtail.core.fields.RichTextField(blank=True)),
|
||||
('not_editable_field', models.CharField(max_length=255, blank=True)),
|
||||
('tags', taggit.managers.TaggableManager(blank=True, help_text=None, through='taggit.TaggedItem', to='taggit.Tag', verbose_name='tags')),
|
||||
('uploaded_by_user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='uploaded by user')),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -899,11 +899,13 @@ class SnippetChooserModelWithCustomPrimaryKey(models.Model):
|
|||
|
||||
|
||||
class CustomImage(AbstractImage):
|
||||
caption = models.CharField(max_length=255)
|
||||
not_editable_field = models.CharField(max_length=255)
|
||||
caption = models.CharField(max_length=255, blank=True)
|
||||
fancy_caption = RichTextField(blank=True)
|
||||
not_editable_field = models.CharField(max_length=255, blank=True)
|
||||
|
||||
admin_form_fields = Image.admin_form_fields + (
|
||||
'caption',
|
||||
'fancy_caption',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue