Skip to content

Commit

Permalink
Output form media on add/edit/chooser image forms with custom models
Browse files Browse the repository at this point in the history
* 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...
  • Loading branch information
gasman authored and lb- committed Aug 18, 2019
1 parent f7ff6d3 commit eaad013
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/releases/2.7.rst
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions wagtail/images/templates/wagtailimages/chooser/chooser.html
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions wagtail/images/templates/wagtailimages/images/add.html
Expand Up @@ -6,6 +6,8 @@
{% block extra_js %}
{{ block.super }}

{{ form.media.js }}

{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
<script>
$(function() {
Expand All @@ -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" %}
Expand Down
4 changes: 4 additions & 0 deletions wagtail/images/templates/wagtailimages/images/edit.html
Expand Up @@ -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">
Expand All @@ -12,6 +14,8 @@
{% block extra_js %}
{{ block.super }}

{{ form.media.js }}

{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
<script>
$(function() {
Expand Down
4 changes: 4 additions & 0 deletions wagtail/images/templates/wagtailimages/multiple/add.html
Expand Up @@ -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 %}

Expand Down Expand Up @@ -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>
Expand Down
191 changes: 191 additions & 0 deletions wagtail/images/tests/test_admin_views.py
Expand Up @@ -10,6 +10,7 @@

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
Expand Down Expand Up @@ -108,6 +109,9 @@ def test_get(self):
# 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")
Expand All @@ -119,6 +123,21 @@ def test_get_with_collections(self):
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",
Expand Down Expand Up @@ -325,6 +344,11 @@ def test_simple(self):
# 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()
Expand Down Expand Up @@ -514,6 +538,34 @@ def test_no_thousand_separators_in_focal_point_editor(self):
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()
Expand Down Expand Up @@ -571,6 +623,23 @@ def test_simple(self):
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)
Expand Down Expand Up @@ -898,6 +967,11 @@ def test_add(self):
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'))
Expand Down Expand Up @@ -1095,6 +1169,123 @@ def test_delete_post_noajax(self):
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
Expand Down
1 change: 1 addition & 0 deletions wagtail/images/tests/tests.py
Expand Up @@ -569,6 +569,7 @@ def test_admin_form_fields_attribute(self):
'focal_point_width',
'focal_point_height',
'caption',
'fancy_caption',
])

def test_file_field(self):
Expand Down
19 changes: 11 additions & 8 deletions wagtail/images/views/multiple.py
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions wagtail/tests/testapp/migrations/0001_initial.py
Expand Up @@ -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')),
],
Expand Down
6 changes: 4 additions & 2 deletions wagtail/tests/testapp/models.py
Expand Up @@ -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',
)


Expand Down

0 comments on commit eaad013

Please sign in to comment.