Merge branch 'feature/streamfield' into feature/streamfield-frontend

This commit is contained in:
Matt Westcott 2015-01-16 14:56:03 +00:00
commit 160f38bbbb
45 changed files with 453 additions and 170 deletions

View file

@ -9,12 +9,14 @@ Changelog
* Added thousands separator for counters on dashboard
* Added contextual links to admin notification messages
* When copying pages, it is now possible to specify a place to copy to (Timo Rieber)
* FieldPanel now accepts an optional 'widget' parameter to override the field's default form widget (Alejandro Giacometti)
0.8.5 (xx.xx.20xx)
~~~~~~~~~~~~~~~~~~
* Fix: On adding a new page, the available page types are ordered by the displayed verbose name
* Fix: Active admin submenus were not properly closed when activating another
* Fix: get_sitemap_urls is now called on the specific page class so it can now be overridden (Jerel Unruh)
0.8.4 (04.12.2014)

View file

@ -38,6 +38,7 @@ Contributors
* Robert Rollins
* linibou
* Timo Rieber
* Jerel Unruh
Translators
===========

View file

@ -23,8 +23,10 @@ A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick
There are four basic types of panels:
``FieldPanel( field_name, classname=None )``
This is the panel used for basic Django field types. ``field_name`` is the name of the class property used in your model definition. ``classname`` is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields. The CSS class ``full`` can be used to format the panel so it covers the full width of the Wagtail page editor. The CSS class ``title`` can be used to mark a field as the source for auto-generated slug strings.
``FieldPanel( field_name, classname=None, widget=None )``
This is the panel used for basic Django field types. ``field_name`` is the name of the class property used in your model definition. ``classname`` is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields. The CSS class ``full`` can be used to format the panel so it covers the full width of the Wagtail page editor. The CSS class ``title`` can be used to mark a field as the source for auto-generated slug strings. The optional ``widget`` parameter allows you to specify a `django form widget`_ to use instead of the default widget for this field type.
.. _django form widget: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
``MultiFieldPanel( children, heading="", classname=None )``
This panel condenses several ``FieldPanel`` s or choosers, from a list or tuple, under a single ``heading`` string.

View file

@ -15,3 +15,4 @@ Bug fixes
* On adding a new page, the available page types are ordered by the displayed verbose name
* Active admin submenus were not properly closed when activating another
* ``get_sitemap_urls`` is now called on the specific page class so it can now be overridden

View file

@ -18,6 +18,7 @@ Minor features
* Added thousands separator for counters on dashboard
* Added contextual links to admin notification messages
* When copying pages, it is now possible to specify a place to copy to
* ``FieldPanel`` now accepts an optional ``widget`` parameter to override the field's default form widget
Bug fixes

View file

@ -40,6 +40,7 @@ install_requires = [
"Unidecode>=0.04.14",
"six>=1.7.0",
'requests>=2.0.0',
"Willow==0.1",
]

View file

@ -17,6 +17,7 @@ base =
python-dateutil==2.2
pytz==2014.7
Embedly
Willow==0.1
coverage
dj17 =

View file

@ -12,7 +12,7 @@ class Sitemap(object):
def get_urls(self):
for page in self.get_pages():
for url in page.get_sitemap_urls():
for url in page.specific.get_sitemap_urls():
yield url
def render(self):

View file

@ -2,7 +2,7 @@ from django.test import TestCase
from django.core.cache import cache
from wagtail.wagtailcore.models import Page, PageViewRestriction, Site
from wagtail.tests.models import SimplePage
from wagtail.tests.models import SimplePage, EventIndex
from .sitemap_generator import Sitemap
@ -47,6 +47,20 @@ class TestSitemapGenerator(TestCase):
self.assertIn('http://localhost/', urls) # Homepage
self.assertIn('http://localhost/hello-world/', urls) # Child page
def test_get_urls_uses_specific(self):
# Add an event page which has an extra url in the sitemap
events_page = self.home_page.add_child(instance=EventIndex(
title="Events",
slug='events',
live=True,
))
sitemap = Sitemap(self.site)
urls = [url['location'] for url in sitemap.get_urls()]
self.assertIn('http://localhost/events/', urls) # Main view
self.assertIn('http://localhost/events/past/', urls) # Sub view
def test_render(self):
sitemap = Sitemap(self.site)
xml = sitemap.render()

View file

@ -230,6 +230,8 @@
<section id="buttons">
<h2>Buttons</h2>
<p>Buttons must have interaction possible (i.e be an input or button element) to get a suitable hover cursor</p>
<a href="#" class="button">button</a>
<a href="#" class="button button-secondary">button-secondary</a>
@ -250,8 +252,6 @@
<div class="button no bicolor icon icon-cog">button on a div</div>
<p>Buttons must have interaction possible (i.e be an input or button element) to get a suitable hover cursor</p>
<button>button</button>
<button class="button-small">small button</button>
@ -261,6 +261,14 @@
<input type="submit" class="bicolor icon icon-plus" value="bicolor only supported on button elements" />
<button class="icon icon-view">button</button>
<button class="icon text-replace yes icon-tick">A proper button</button>
<a href="#" class="button icon text-replace white icon-cog">A link button</a>
<span class="button icon text-replace no icon-bin">A non-link button</span>
<button class="button-small icon text-replace white icon-tick">A proper button</button>
<a href="#" class="button button-small icon text-replace white icon-cog">A link button</a>
<span class="button button-small icon text-replace white icon-bin">A non-link button</span>
</section>
<section id="dropdowns">

View file

@ -306,6 +306,15 @@ class EventIndex(Page):
for path in super(EventIndex, self).get_static_site_paths():
yield path
def get_sitemap_urls(self):
# Add past events url to sitemap
return super(EventIndex, self).get_sitemap_urls() + [
{
'location': self.full_url + 'past/',
'lastmod': self.latest_revision_created_at
}
]
EventIndex.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('intro', classname="full"),

View file

@ -336,10 +336,12 @@ class BaseStructBlock(Block):
for child_rendering in child_renderings
])
# Can these be rendered with a template?
if self.label:
return format_html("<label>{0}</label> <ul>{1}</ul>", self.label, list_items)
return format_html('<div class="struct-block"><label>{0}</label> <ul>{1}</ul></div>', self.label, list_items)
else:
return format_html("<ul>{0}</ul>", list_items)
return format_html('<div class="struct-block"><ul>{0}</ul></div>', list_items)
def value_from_datadict(self, data, files, prefix):
return dict([

View file

@ -362,6 +362,15 @@ def MultiFieldPanel(children, heading="", classname=""):
class BaseFieldPanel(EditHandler):
@classmethod
def widget_overrides(cls):
"""check if a specific widget has been defined for this field"""
if hasattr(cls, 'widget'):
return {cls.field_name: cls.widget}
else:
return {}
def __init__(self, instance=None, form=None):
super(BaseFieldPanel, self).__init__(instance=instance, form=form)
self.bound_field = self.form[self.field_name]
@ -408,11 +417,16 @@ class BaseFieldPanel(EditHandler):
return [self.field_name]
def FieldPanel(field_name, classname=""):
return type(str('_FieldPanel'), (BaseFieldPanel,), {
def FieldPanel(field_name, classname="", widget=None):
base = {
'field_name': field_name,
'classname': classname,
})
}
if widget:
base['widget'] = widget
return type(str('_FieldPanel'), (BaseFieldPanel,), base)
class BaseRichTextFieldPanel(BaseFieldPanel):
@ -632,6 +646,20 @@ Page.settings_panels = [
from wagtail.wagtailadmin.blocks import StreamBlock
class BaseStreamFieldPanel(BaseFieldPanel):
def classes(self):
classes = super(BaseStreamFieldPanel, self).classes()
classes.append("stream-field")
# BaseFieldPanel is essentially for single fields, which are rendered on the front end
# with the assumption that the label (singular) will always be promoted to the full-width
# divider bar thing.
# This results in all the other labels being promoted similarly, so it's better not to
# treat this as a single field, and remove the "single-field" class.
classes.remove("single-field")
return classes
@classmethod
def widget_overrides(cls):
return {cls.field_name: widgets.StreamWidget(block_def=cls.block_def)}

View file

@ -225,13 +225,16 @@ a.tag:hover{
}
}
hr{
border:1px solid $color-grey-4;
border-width:1px 0 0;
margin:1.5em 0;
}
/* general image style */
img{
max-width:100%;
height:auto;
border: 3px solid $color-grey-4;
}
/* make a block-level element inline */

View file

@ -221,9 +221,16 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
background-color:transparent;
}
&.icon.text-replace:before{
font-size:auto;
}
/* Buttons which are only an icon */
&.icon.text-replace{
font-size:0; /* unavoidable duplication of setting in icons.scss */
width:1.8rem;
height:1.8rem;
&:before{
line-height:1.7em;
}
}
&:hover{
background-color: $color-button-hover;
@ -382,9 +389,7 @@ input[type=submit], input[type=reset], input[type=button], button{
@include border-radius(2px);
li{
background-color: $color-teal;
float:left;
cursor:pointer;
margin-right:1px;
&:last-child{
@ -392,24 +397,6 @@ input[type=submit], input[type=reset], input[type=button], button{
}
}
.icon{
padding:0.3em;
}
.icon:before{
line-height:2em;
width:2em;
}
.icon:hover{
background-color:$color-teal-darker;
&:before{
color:white;
}
}
.icon-bin:hover{
background-color:$color-red;
}
.disabled{
display:none;
}
@ -803,8 +790,7 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
.boolean_field &,
.choice_field &,
.model_choice_field &,
.image_field &,
.file_field &{
.image_field &{
padding-top:0;
}
}
@ -824,9 +810,23 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
height: 3em;
line-height:3em;
&.icon.text-replace{
width:2.2rem;
height:2.2rem;
&:before{
line-height:2.1em;
}
}
&.button-small{
height:2.3em;
line-height:2.2em;
&.icon.text-replace{
height:1.8rem;
width:1.8rem;
&:before{
line-height:1.7em;
}
}
}
&.bicolor{
@ -845,7 +845,8 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
&:before{
line-height:1.85em;
}
}
}
}
/* Special styles to counteract Firefox's completely unwarranted assumptions about button styles */

View file

@ -353,6 +353,11 @@ ul.listing{
font-size:1em;
opacity:0.7;
}
&.images img{
@include transition(border-color 0.2s ease);
border: 3px solid transparent;
}
}
ul.listing{
border-top:1px dashed $color-input-border;

View file

@ -66,6 +66,9 @@
content:"w";
margin-right:0.5em;
font-size:1.2em;
font-weight:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}

View file

@ -58,6 +58,22 @@ kbd{
padding:0.3em 0.5em;
}
dl, dt, dd{
padding:0;
margin:0;
}
dl{
margin-top:1em;
}
dt{
color:darken($color-grey-3, 5%);
text-transform:uppercase;
font-size:0.9em;
}
dd{
margin-bottom:1em;
}
/* Help text formatters */
.help-block{

View file

@ -314,6 +314,13 @@ footer, .logo{
@include column(12);
}
.divider-before{
border-left:1px solid $color-grey-4;
}
.divider-after{
border-right:1px solid $color-grey-4;
}
.row{
@include row();
}

View file

@ -1,7 +1,7 @@
{
"metadata": {
"name": "Wagtail 1",
"lastOpened": 1410881728324,
"name": "Wagtail",
"lastOpened": 1420801397108,
"created": 1405597423787
},
"iconSets": [
@ -8564,20 +8564,23 @@
"minorVersion": 0
},
"metrics": {
"emSize": 512,
"emSize": 1024,
"baseline": 6.25,
"whitespace": 50
},
"showMetrics": false,
"showMetrics": true,
"showMetadata": false,
"showVersion": false
"showVersion": false,
"includeMetadata": false
},
"imagePref": {},
"historySize": 100,
"showCodes": true,
"search": "",
"gridSize": 16,
"showGrid": true
"showGrid": true,
"showQuickUse2": true,
"showSVGs": true
},
"externalSets": []
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -110,6 +110,8 @@
margin:0;
cursor:pointer;
background-color:$color-salmon;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
@ -184,6 +186,80 @@
}
}
&.stream-field {
> fieldset{
@include column(12);
padding-left:0;
padding-right:0;
}
/* Object controls */
.stream-controls{
position:absolute;
z-index:1;
right:1em;
top:1em;
color:white;
overflow:hidden;
@include border-radius(2px);
li{
background-color: $color-teal;
float:left;
cursor:pointer;
margin-right:1px;
&:last-child{
margin-right:0;
}
}
.disabled{
display:none;
}
}
.fields > li > .field > label{
display:none;
}
.sequence{
@include clearfix;
margin:1em 0;
border:1px solid lighten($color-grey-4, 3%);
padding:0 1.5em;
}
.sequence-member{
position:relative;
padding:1em 1.5em;
border-bottom:1px solid lighten($color-grey-4, 3%);
margin:0 -1.5em;
.inner > .struct-block > label{
display:block;
width:100%;
float:none;
}
}
.sequence-inner{
@include column(10);
&:nth-of-type(1){
@include column(12);
padding:0;
}
}
.struct-block > ul > li{ /* duplicates forms.scss ln.568 */
@include clearfix();
padding-top:0.5em;
padding-bottom:1.2em;
}
}
/* special panel for the publishing fields, requires a bit more pizzazz */
&.publishing{
h2:before{

View file

@ -1,6 +1,6 @@
{% load wagtailadmin_tags %}
<div class="field {{ field|fieldtype }}">
{{ field.label_tag }}
{{ label_tag }}
<div class="field-content">
<div class="input">
{{ widget }}

View file

@ -1,4 +1,4 @@
{% extends "wagtailadmin/block_forms/sequence_member.html" %}
{% block header_controls %}
<button type="button" id="{{ prefix }}-delete">delete</button>
<button type="button" id="{{ prefix }}-delete" class="icon text-replace no icon-bin">delete</button>
{% endblock %}

View file

@ -1,11 +1,15 @@
{# Common HTML structure shared by list and stream blocks #}
{% if label %}<label>{{ label }}</label>{% endif %}
<input type="hidden" name="{{ prefix }}-count" id="{{ prefix }}-count" value="{{ list_members_html|length }}">
{% block header %}{% endblock %}
<ul id="{{ prefix }}-list">
{% for list_member_html in list_members_html %}
{{ list_member_html }}
{% endfor %}
</ul>
{% block footer %}{% endblock %}
<div class="sequence">
<input type="hidden" name="{{ prefix }}-count" id="{{ prefix }}-count" value="{{ list_members_html|length }}">
{% if label %}<label>{{ label }}</label>{% endif %}
{% block header %}{% endblock %}
<div class="sequence-inner">
<ul id="{{ prefix }}-list" class="sequence">
{% for list_member_html in list_members_html %}
{{ list_member_html }}
{% endfor %}
</ul>
{% block footer %}{% endblock %}
</div>
</div>

View file

@ -1,8 +1,14 @@
<li id="{{ prefix }}-container">
<li id="{{ prefix }}-container" class="sequence-member">
<input type="hidden" id="{{ prefix }}-deleted" name="{{ prefix }}-deleted" value="">
<input type="hidden" id="{{ prefix }}-order" name="{{ prefix }}-order" value="{{ index }}">
{% block hidden_fields %}{% endblock %}
{% block header_controls %}{% endblock %}
<div>{{ child.render_form }}</div>
<div class="stream-controls">
{% block header_controls %}{% endblock %}
</div>
<div class="inner">{{ child.render_form }}</div>
{% block footer_controls %}{% endblock %}
</li>

View file

@ -5,7 +5,7 @@
{% endblock %}
{% block header_controls %}
<button type="button" id="{{ prefix }}-delete">delete</button>
<button type="button" id="{{ prefix }}-delete" class="stream-control icon text-replace no icon-bin">delete</button>
{% endblock %}
{% block footer_controls %}

View file

@ -2,10 +2,10 @@
<li id="inline_child_{{ child.form.prefix }}">
<ul class="controls">
{% if can_order %}
<li class="icon text-replace white icon-order-up inline-child-move-up" id="{{ child.form.prefix }}-move-up">{% trans "Move up" %}</li>
<li class="icon text-replace white icon-order-down inline-child-move-down" id="{{ child.form.prefix }}-move-down">{% trans "Move down" %}</li>
<li><button class="icon text-replace white icon-order-up inline-child-move-up" id="{{ child.form.prefix }}-move-up" title="{% trans 'Move up' %}">{% trans "Move up" %}</button></li>
<li><button class="icon text-replace white icon-order-down inline-child-move-down" id="{{ child.form.prefix }}-move-down" title="{% trans 'Move down' %}">{% trans "Move down" %}</button></li>
{% endif %}
<li class="icon text-replace white icon-bin" id="{{ child.form.DELETE.id_for_label }}-button">{% trans "Delete" %}</li>
<li><button class="icon text-replace white icon-bin" id="{{ child.form.DELETE.id_for_label }}-button" title="{% trans 'Delete' %}">{% trans "Delete" %}</button></li>
</ul>
{{ child.render_form_content }}
</li>

View file

@ -3,7 +3,7 @@
{% load i18n %}
{% block titletag %}{% blocktrans with page_type=content_type.model_class.get_verbose_name %}New {{ page_type }}{% endblocktrans %}{% endblock %}
{% block bodyclass %}menu-explorer page-editor create{% endblock %}
{% block bodyclass %}menu-explorer page-editor create model-{{ content_type.model }}{% endblock %}
{% block content %}
@ -66,4 +66,10 @@
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
<script>
$(function(){
$('#page-edit-form .tab-content section.active input').first().focus();
});
</script>
{% endblock %}

View file

@ -2,8 +2,8 @@
{% load wagtailadmin_tags %}
{% load gravatar %}
{% load i18n %}
{% block titletag %}{% blocktrans with title=page.title %}Editing {{ title }}{% endblocktrans %}{% endblock %}
{% block bodyclass %}menu-explorer page-editor{% endblock %}
{% block titletag %}{% blocktrans with title=page.title page_type=content_type.model_class.get_verbose_name %}Editing {{ page_type }}: {{ title }}{% endblocktrans %}{% endblock %}
{% block bodyclass %}menu-explorer page-editor model-{{ content_type.model }}{% endblock %}
{% block content %}
{% page_permissions page as page_perms %}
@ -12,7 +12,7 @@
<div class="row row-flush">
<div class="left col9">
<h1 class="icon icon-doc-empty-inverse">{% blocktrans with title=page.title %}Editing <span>{{ title }}</span>{% endblocktrans %}</h1>
<h1 class="icon icon-doc-empty-inverse">{% blocktrans with title=page.title page_type=content_type.model_class.get_verbose_name %}Editing {{ page_type }} <span>{{ title }}</span>{% endblocktrans %}</h1>
</div>
<div class="right col3">
{% trans "Status" %}

View file

@ -1,5 +1,5 @@
{% load wagtailadmin_tags %}
<div class="field {{ field|fieldtype }} {{ field_classes }}">
<div class="field {{ field|fieldtype }} {{ field|widgettype }} {{ field_classes }}">
{{ field.label_tag }}
<div class="field-content">
<div class="input {{ input_classes }} ">

View file

@ -65,6 +65,14 @@ def fieldtype(bound_field):
return ""
@register.filter
def widgettype(bound_field):
try:
return camelcase_to_underscore(bound_field.field.widget.__class__.__name__)
except AttributeError:
return ""
@register.filter
def meta_description(model):
try:

View file

@ -255,6 +255,8 @@ def edit(request, page_id):
page = get_object_or_404(Page, id=page_id).get_latest_revision_as_page()
parent = page.get_parent()
content_type = ContentType.objects.get_for_model(page)
page_perms = page.permissions_for_user(request.user)
if not page_perms.can_edit():
raise PermissionDenied
@ -373,6 +375,7 @@ def edit(request, page_id):
return render(request, 'wagtailadmin/pages/edit.html', {
'page': page,
'content_type': content_type,
'edit_handler': edit_handler,
'errors_debug': errors_debug,
'preview_modes': page.preview_modes,

View file

@ -1,7 +1,7 @@
{% extends "wagtailadmin/shared/field_as_li.html" %}
{% extends "wagtailadmin/shared/field.html" %}
{% load i18n %}
{% block form_field %}
<a href="{{ document.url }}" class="icon icon-doc-full-inverse">{{ document.file }}</a><br /><br />
<a href="{{ document.url }}" class="icon icon-doc-full-inverse">{{ document.filename }}</a><br /><br />
{% trans "Change document:" %}
{{ field }}
{% endblock %}

View file

@ -0,0 +1,4 @@
{% load wagtailadmin_tags %}
<li class="{% if field.field.required %}required{% endif %} {{ wrapper_classes }} {{ li_classes }} {% if field.errors %}error{% endif %}">
{% include "wagtaildocs/documents/_file_field.html" %}
</li>

View file

@ -15,20 +15,32 @@
{% trans "Editing" as editing_str %}
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=document.title icon="doc-full-inverse" usage_object=document %}
<div class="nice-padding">
<form action="{% url 'wagtaildocs_edit_document' document.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% if field.name == 'file' %}
{% include "wagtaildocs/documents/_file_field.html" %}
{% else %}
{% include "wagtailadmin/shared/field_as_li.html" %}
{% endif %}
{% endfor %}
<li><input type="submit" value="{% trans 'Save' %}" /> <a href="{% url 'wagtaildocs_delete_document' document.id %}" class="button button-secondary no">{% trans "Delete document" %}</a></li>
</ul>
</form>
<div class="row row-flush nice-padding">
<div class="col10 divider-after">
<form action="{% url 'wagtaildocs_edit_document' document.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% if field.name == 'file' %}
{% include "wagtaildocs/documents/_file_field_as_li.html" %}
{% else %}
{% include "wagtailadmin/shared/field_as_li.html" %}
{% endif %}
{% endfor %}
<li><input type="submit" value="{% trans 'Save' %}" /> <a href="{% url 'wagtaildocs_delete_document' document.id %}" class="button button-secondary no">{% trans "Delete document" %}</a></li>
</ul>
</form>
</div>
<div class="col2">
<dl>
{% if document.file %}
<dt>{% trans "Filesize" %}</dt>
<dd>{{ document.file.size|filesizeformat }}</dd>
{% endif %}
</dl>
</div>
</div>
</div>

View file

@ -4,6 +4,7 @@ import re
from six import BytesIO, text_type
from taggit.managers import TaggableManager
from willow.image import Image as WillowImage
from django.core.files import File
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
@ -80,6 +81,19 @@ class AbstractImage(models.Model, TagSearchable):
def __str__(self):
return self.title
def get_willow_image(self):
try:
image_file = self.file.file # triggers a call to self.storage.open, so IOErrors from missing files will be raised at this point
except IOError as e:
# re-throw this as a SourceImageIOError so that calling code can distinguish
# these from IOErrors elsewhere in the process
raise SourceImageIOError(text_type(e))
image_file.open('rb')
image_file.seek(0)
return WillowImage.open(image_file)
def get_rect(self):
return Rect(0, 0, self.width, self.height)

View file

@ -1,8 +1,10 @@
{% extends "wagtailadmin/shared/field_as_li.html" %}
{% load i18n %}
{% extends "wagtailadmin/shared/field.html" %}
{% load i18n wagtailimages_tags %}
{% block form_field %}
<span class="icon icon-image">{{ image.filename }}</span><br /><br />
{% image image original as original_image %}
{% trans "Change image:" %}
<a href="{{ original_image.url }}" class="icon icon-image">{{ image.filename }}</a> ({{ original_image.width }}x{{ original_image.height}})<br /><br />
{% trans "Change image file:" %}
{{ field }}
{% endblock %}

View file

@ -0,0 +1,4 @@
{% load wagtailadmin_tags %}
<li class="{% if field.field.required %}required{% endif %} {{ wrapper_classes }} {{ li_classes }} {% if field.errors %}error{% endif %}">
{% include "wagtailimages/images/_file_field.html" %}
</li>

View file

@ -28,14 +28,14 @@
<div class="row row-flush nice-padding">
<div class="col7">
<div class="col5">
<form action="{% url 'wagtailimages_edit_image' image.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% if field.name == 'file' %}
{% include "wagtailimages/images/_file_field.html" %}
{% include "wagtailimages/images/_file_field_as_li.html" %}
{% elif field.is_hidden %}
{{ field }}
{% else %}
@ -47,7 +47,7 @@
</ul>
</form>
</div>
<div class="col5">
<div class="col5 divider-after">
<h2 class="label">{% trans "Focal point (optional)" %}</h2>
<p>{% trans "To define this image's most important region, drag a box over the image below." %} {% if image.focal_point %}({% trans "Current focal point shown" %}){% endif %}</p>
@ -64,10 +64,22 @@
<div class="current-focal-point-indicator{% if not image.focal_point %} hidden{% endif %}"></div>
</div>
</div>
<div class="col2 ">
{% if url_generator_enabled %}
<a href="{% url 'wagtailimages_url_generator' image.id %}" class="button bicolor icon icon-link">{% trans "URL Generator" %}</a>
<hr />
{% endif %}
{% image image original as original_image %}
<dl>
<dt>{% trans "Max dimensions" %}</dt>
<dd>{{ original_image.width }}x{{ original_image.height }}</dd>
<dt>{% trans "Filesize" %}</dt>
<dd>{{ image.file.size|filesizeformat }}</dd>
</dl>
</div>
</div>
{% endblock %}

View file

@ -1,4 +1,5 @@
import unittest
from willow.image import Image as WillowImage
from django.test import TestCase
from django.core.urlresolvers import reverse
@ -13,7 +14,7 @@ from django.db import connection
from wagtail.tests.utils import WagtailTestUtils, test_concurrently
from wagtail.wagtailcore.models import Page
from wagtail.tests.models import EventPage, EventPageCarouselItem
from wagtail.wagtailimages.models import Rendition, Filter
from wagtail.wagtailimages.models import Rendition, Filter, SourceImageIOError
from wagtail.wagtailimages.backends import get_image_backend
from wagtail.wagtailimages.backends.pillow import PillowBackend
from wagtail.wagtailimages.rect import Rect
@ -268,6 +269,29 @@ class TestGetUsage(TestCase):
self.assertTrue(issubclass(Page, type(self.image.get_usage()[0])))
class TestGetWillowImage(TestCase):
fixtures = ['test.json']
def setUp(self):
self.image = Image.objects.create(
title="Test image",
file=get_test_image_file(),
)
def test_willow_image_object_returned(self):
willow_image = self.image.get_willow_image()
self.assertIsInstance(willow_image, WillowImage)
def test_with_missing_image(self):
# Image id=1 in test fixtures has a missing image file
bad_image = Image.objects.get(id=1)
# Attempting to get the Willow image for images without files
# should raise a SourceImageIOError
self.assertRaises(SourceImageIOError, bad_image.get_willow_image)
class TestIssue573(TestCase):
"""
This tests for a bug which causes filename limit on Renditions to be reached

View file

@ -1,9 +1,9 @@
{% load i18n %}
<li id="inline_child_{{ form.prefix }}"{% if form.DELETE.value %} style="display: none;"{% endif %}>
<ul class="controls">
<li class="icon text-replace icon-order-up inline-child-move-up" id="{{ form.prefix }}-move-up">{% trans "Move up" %}</li>
<li class="icon text-replace icon-order-down inline-child-move-down" id="{{ form.prefix }}-move-down">{% trans "Move down" %}</li>
<li class="icon text-replace icon-bin" id="{{ form.DELETE.id_for_label }}-button">{% trans "Delete" %}</li>
<li><button class="icon text-replace icon-order-up inline-child-move-up" id="{{ form.prefix }}-move-up">{% trans "Move up" %}</button></li>
<li><button class="icon text-replace icon-order-down inline-child-move-down" id="{{ form.prefix }}-move-down">{% trans "Move down" %}</button></li>
<li><button class="icon text-replace icon-bin" id="{{ form.DELETE.id_for_label }}-button">{% trans "Delete" %}</button></li>
</ul>
<fieldset>