mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-12 09:13:14 +00:00
Merge branch 'feature/streamfield' of github.com:torchbox/wagtail into feature/streamfield
This commit is contained in:
commit
d0bbd644bf
56 changed files with 635 additions and 305 deletions
|
|
@ -34,6 +34,7 @@ Changelog
|
|||
* Fix: Storage backends that return raw ContentFile objects are now handled correctly when resizing images (@georgewhewell)
|
||||
* Fix: Punctuation characters are no longer stripped when performing search queries
|
||||
* Fix: When adding tags where there were none before, it is now possible to save a single tag with multiple words in it
|
||||
* Fix: richtext template tag no longer raises TypeError if None is passed into it (Alejandro Varas)
|
||||
|
||||
0.8.4 (04.12.2014)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ Contributors
|
|||
* georgewhewell
|
||||
* Frank Wiles
|
||||
* Sebastian Spiegel
|
||||
* Alejandro Varas
|
||||
|
||||
Translators
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Within the models.py of one of your apps, create a model that extends wagtailfor
|
|||
FormPage.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('intro', classname="full"),
|
||||
InlinePanel(FormPage, 'form_fields', label="Form fields"),
|
||||
InlinePanel('form_fields', label="Form fields"),
|
||||
FieldPanel('thank_you_text', classname="full"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('to_address', classname="full"),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ This example represents a typical blog post:
|
|||
from wagtail.wagtailcore.fields import RichTextField
|
||||
from wagtail.wagtailadmin.edit_handlers import FieldPanel
|
||||
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
||||
from wagtail.wagtailimages.models import Image
|
||||
|
||||
class BlogPage(Page):
|
||||
body = RichTextField()
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ There are four basic types of panels:
|
|||
``MultiFieldPanel( children, heading="", classname=None )``
|
||||
This panel condenses several ``FieldPanel`` s or choosers, from a list or tuple, under a single ``heading`` string.
|
||||
|
||||
``InlinePanel( base_model, relation_name, panels=None, classname=None, label='', help_text='' )``
|
||||
``InlinePanel( relation_name, panels=None, classname=None, label='', help_text='' )``
|
||||
This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. This is a very powerful, but tricky feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`.
|
||||
|
||||
``FieldRowPanel( children, classname=None)``
|
||||
|
|
@ -354,16 +354,20 @@ Let's look at the example of adding related links to a ``Page``-derived model. W
|
|||
|
||||
BookPage.content_panels = [
|
||||
# ...
|
||||
InlinePanel( BookPage, 'related_links', label="Related Links" ),
|
||||
InlinePanel( 'related_links', label="Related Links" ),
|
||||
]
|
||||
|
||||
The ``RelatedLink`` class is a vanilla Django abstract model. The ``BookPageRelatedLinks`` model extends it with capability for being ordered in the Wagtail interface via the ``Orderable`` class as well as adding a ``page`` property which links the model to the ``BookPage`` model we're adding the related links objects to. Finally, in the panel definitions for ``BookPage``, we'll add an ``InlinePanel`` to provide an interface for it all. Let's look again at the parameters that ``InlinePanel`` accepts:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
InlinePanel( base_model, relation_name, panels=None, label='', help_text='' )
|
||||
InlinePanel( relation_name, panels=None, label='', help_text='' )
|
||||
|
||||
``base_model`` is the model you're extending with the cluster. The ``relation_name`` is the ``related_name`` label given to the cluster's ``ParentalKey`` relation. You can add the ``panels`` manually or make them part of the cluster model. Finally, ``label`` and ``help_text`` provide a heading and caption, respectively, for the Wagtail editor.
|
||||
The ``relation_name`` is the ``related_name`` label given to the cluster's ``ParentalKey`` relation. You can add the ``panels`` manually or make them part of the cluster model. Finally, ``label`` and ``help_text`` provide a heading and caption, respectively, for the Wagtail editor.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
|
||||
In previous versions, it was necessary to pass the base model as the first parameter to ``InlinePanel``; this is no longer required.
|
||||
|
||||
For another example of using model clusters, see :ref:`tagging`
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ To attach multiple adverts to a page, the ``SnippetChooserPanel`` can be placed
|
|||
...
|
||||
|
||||
BookPage.content_panels = [
|
||||
InlinePanel(BookPage, 'advert_placements', label="Adverts"),
|
||||
InlinePanel('advert_placements', label="Adverts"),
|
||||
# ...
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -20,3 +20,4 @@ Bug fixes
|
|||
* Storage backends that return raw ContentFile objects are now handled correctly when resizing images
|
||||
* Punctuation characters are no longer stripped when performing search queries
|
||||
* When adding tags where there were none before, it is now possible to save a single tag with multiple words in it
|
||||
* ``richtext`` template tag no longer raises ``TypeError`` if ``None`` is passed into it
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Minor features
|
|||
* Removed the need to add permission check on admin views (now automated)
|
||||
* Reversing `django.contrib.auth.admin.login` will no longer lead to Wagtails login view (making it easier to have front end views)
|
||||
* Added cache-control headers to all admin views. This allows Varnish/Squid/CDN to run on vanilla settings in front of a Wagtail site
|
||||
* Added validation to prevent pages being crated with only whitespace characters in their title fields
|
||||
* Added validation to prevent pages being created with only whitespace characters in their title fields
|
||||
* Page model fields without a FieldPanel are no longer displayed in the form
|
||||
* No longer need to specify the base model on InlinePanel definitions
|
||||
|
||||
|
|
@ -47,18 +47,51 @@ This release drops support for Django 1.6, Python 2.6/3.2 and Elasticsearch 0.90
|
|||
|
||||
If you are upgrading from Elasticsearch 0.90.x, you may also need to update the ``elasticsearch`` pip package to a version greater than ``1.0`` as well.
|
||||
|
||||
InlinePanel definitions no longer need to specify the base model
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In previous versions of Wagtail, inline child blocks on a page or snippet were defined using a declaration like::
|
||||
|
||||
InlinePanel(HomePage, 'carousel_items', label="Carousel items")
|
||||
|
||||
It is no longer necessary to pass the base model as a parameter, so this declaration should be changed to::
|
||||
|
||||
InlinePanel('carousel_items', label="Carousel items")
|
||||
|
||||
The old format is now deprecated; all existing InlinePanel declarations should be updated to the new format.
|
||||
|
||||
Login/Password reset views renamed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It was previously possible to reverse the Wagtail login using django.contrib.auth.views.login.
|
||||
This is no longer possible. Update any references to `wagtailadmin_login`.
|
||||
This is no longer possible. Update any references to ``wagtailadmin_login``.
|
||||
|
||||
Password reset view name has changed from `password_reset` to `wagtailadmin_password_reset`.
|
||||
Password reset view name has changed from ``password_reset`` to ``wagtailadmin_password_reset``.
|
||||
|
||||
You no longer need `LOGIN_URL` and `LOGIN_REDIRECT_URL` to point to Wagtail admin.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
You no longer need ``LOGIN_URL`` and ``LOGIN_REDIRECT_URL`` to point to Wagtail admin.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Javascript includes in admin backend have been moved
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To improve compatibility with third-party form widgets, pages within the Wagtail admin backend now output their Javascript includes in the HTML header, rather than at the end of the page. If your project extends the admin backend (through the ``register_admin_menu_item`` hook, for example) you will need to ensure that all associated Javascript code runs correctly from the new location. In particular, any code that accesses HTML elements will need to be contained in an 'onload' handler (e.g. jQuery's ``$(document).ready()``).
|
||||
|
||||
EditHandler internal API has changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While it is not an official Wagtail API, it has been possible for Wagtail site implementers to define their own ``EditHandler`` subclasses for use in panel definitions, to customise the behaviour of the page / snippet editing forms. If you have made use of this facility, you will need to update your custom EditHandlers, as this mechanism has been refactored (to allow EditHandler classes to keep a persistent reference to their corresponding model). If you have only used Wagtail's built-in panel types (``FieldPanel``, ``InlinePanel``, ``PageChooserPanel`` and so on), you are unaffected by this change.
|
||||
|
||||
Previously, functions like ``FieldPanel`` acted as 'factory' functions, where a call such as ``FieldPanel('title')`` constructed and returned an ``EditHandler`` subclass tailored to work on a 'title' field. These functions now return an object with a ``bind_to_model`` method instead; the EditHandler subclass can be obtained by calling this with the model class as a parameter. As a guide to updating your custom EditHandler code, you may wish to refer to `the relevant change to the Wagtail codebase <https://github.com/torchbox/wagtail/commit/121c01c7f7db6087a985fa8dc9957bc78b9f6a6a>`_.
|
||||
|
||||
chooser_panel templates are obsolete
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have added your own custom admin views to the Wagtail admin (e.g. through the ``register_admin_urls`` hook), you may have used one of the following template includes to incorporate a chooser element for pages, documents, images or snippets into your forms:
|
||||
|
||||
- ``wagtailadmin/edit_handlers/chooser_panel.html``
|
||||
- ``wagtailadmin/edit_handlers/page_chooser_panel.html``
|
||||
- ``wagtaildocs/edit_handlers/document_chooser_panel.html``
|
||||
- ``wagtailimages/edit_handlers/image_chooser_panel.html``
|
||||
- ``wagtailsnippets/edit_handlers/snippet_chooser_panel.html``
|
||||
|
||||
All of these templates are now deprecated. Wagtail now provides a set of Django form widgets for this purpose - ``AdminPageChooser``, ``AdminDocumentChooser``, ``AdminImageChooser`` and ``AdminSnippetChooser`` - which can be used in place of the ``HiddenInput`` widget that these form fields were previously using. The field can then be rendered using the regular ``wagtailadmin/shared/field.html`` or ``wagtailadmin/shared/field_as_li.html`` template.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'wagtail.tests.settings'
|
|||
def runtests():
|
||||
# Don't ignore DeprecationWarnings
|
||||
warnings.simplefilter('default', DeprecationWarning)
|
||||
warnings.simplefilter('default', PendingDeprecationWarning)
|
||||
|
||||
argv = sys.argv[:1] + ['test'] + sys.argv[1:]
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -355,8 +355,6 @@
|
|||
{% for field in example_form %}
|
||||
{% if field.name == 'file' %}
|
||||
{% include "wagtailimages/images/_file_field.html" %}
|
||||
{% elif field.name == 'date' %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with input_classes="iconfield icon-date" %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" %}
|
||||
{% endif %}
|
||||
|
|
@ -365,13 +363,7 @@
|
|||
</ul>
|
||||
</form>
|
||||
|
||||
<h3>TODO: Date picker</h3>
|
||||
<h3>TODO: Time picker</h3>
|
||||
<h3>TODO: Datetime picker</h3>
|
||||
<h3>TODO: Rich text input</h3>
|
||||
<h3>TODO: Page chooser</h3>
|
||||
<h3>TODO: Image chooser</h3>
|
||||
<h3>TODO: Document chooser</h3>
|
||||
<h3>TODO: Snippet chooser</h3>
|
||||
</section>
|
||||
|
||||
|
|
@ -540,42 +532,43 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
$(function(){
|
||||
// Debugging for stylesheet problems
|
||||
var styleSheets = document.styleSheets, totalStyleSheets = styleSheets.length;
|
||||
for (var j = 0; j < totalStyleSheets; j++) {
|
||||
var styleSheet = styleSheets[j], rules = styleSheet.cssRules, totalSelectorsInStylesheet = 0, style = "";
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
<script>
|
||||
$(function(){
|
||||
// Debugging for stylesheet problems
|
||||
var styleSheets = document.styleSheets, totalStyleSheets = styleSheets.length;
|
||||
for (var j = 0; j < totalStyleSheets; j++) {
|
||||
var styleSheet = styleSheets[j], rules = styleSheet.cssRules, totalSelectorsInStylesheet = 0, style = "";
|
||||
|
||||
var totalRulesInStylesheet = rules ? rules.length : 0;
|
||||
var totalRulesInStylesheet = rules ? rules.length : 0;
|
||||
|
||||
for (var i = 0; i < totalRulesInStylesheet; i++) {
|
||||
if (rules[i].selectorText) {
|
||||
try {
|
||||
totalSelectorsInStylesheet += rules[i].selectorText.split(',').length;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
for (var i = 0; i < totalRulesInStylesheet; i++) {
|
||||
if (rules[i].selectorText) {
|
||||
try {
|
||||
totalSelectorsInStylesheet += rules[i].selectorText.split(',').length;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(totalSelectorsInStylesheet > 4095){
|
||||
style = 'color:red';
|
||||
}
|
||||
$('#ie9-debug').append("<h3>" + styleSheet.href + "</h3>" + "<p>Total rules: <strong>" + totalRulesInStylesheet + "</strong>. " + "Total selectors: <strong style='" + style + "'>" + totalSelectorsInStylesheet + "</strong></p>");
|
||||
}
|
||||
|
||||
if(totalSelectorsInStylesheet > 4095){
|
||||
style = 'color:red';
|
||||
}
|
||||
$('#ie9-debug').append("<h3>" + styleSheet.href + "</h3>" + "<p>Total rules: <strong>" + totalRulesInStylesheet + "</strong>. " + "Total selectors: <strong style='" + style + "'>" + totalSelectorsInStylesheet + "</strong></p>");
|
||||
}
|
||||
|
||||
(function runprogress(){
|
||||
var to = setTimeout(function(){
|
||||
runprogress();
|
||||
clearTimeout(to);
|
||||
var to2 = setTimeout(function(){
|
||||
$('#progress-example .bar').css('width', '20%');
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
$('#progress-example .bar').css('width', '80%');
|
||||
})();
|
||||
})
|
||||
</script>
|
||||
(function runprogress(){
|
||||
var to = setTimeout(function(){
|
||||
runprogress();
|
||||
clearTimeout(to);
|
||||
var to2 = setTimeout(function(){
|
||||
$('#progress-example .bar').css('width', '20%');
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
$('#progress-example .bar').css('width', '80%');
|
||||
})();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -5,9 +5,21 @@ from wagtail.wagtailadmin import messages
|
|||
from django.contrib.auth.decorators import permission_required
|
||||
|
||||
from wagtail.wagtailadmin.forms import SearchForm
|
||||
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser, AdminDateInput, AdminTimeInput, AdminDateTimeInput
|
||||
from wagtail.wagtailimages.widgets import AdminImageChooser
|
||||
from wagtail.wagtaildocs.widgets import AdminDocumentChooser
|
||||
|
||||
class ExampleForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExampleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['page_chooser'].widget = AdminPageChooser()
|
||||
self.fields['image_chooser'].widget = AdminImageChooser()
|
||||
self.fields['document_chooser'].widget = AdminDocumentChooser()
|
||||
self.fields['date'].widget = AdminDateInput()
|
||||
self.fields['time'].widget = AdminTimeInput()
|
||||
self.fields['datetime'].widget = AdminDateTimeInput()
|
||||
|
||||
CHOICES = (
|
||||
('choice1', 'choice 1'),
|
||||
('choice2', 'choice 2'),
|
||||
|
|
@ -18,8 +30,13 @@ class ExampleForm(forms.Form):
|
|||
email = forms.EmailField(max_length=254)
|
||||
date = forms.DateField()
|
||||
time = forms.TimeField()
|
||||
datetime = forms.DateTimeField()
|
||||
select = forms.ChoiceField(choices=CHOICES)
|
||||
boolean = forms.BooleanField(required=False)
|
||||
page_chooser = forms.BooleanField(required=True)
|
||||
image_chooser = forms.BooleanField(required=True)
|
||||
document_chooser = forms.BooleanField(required=True)
|
||||
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
|
|
|
|||
|
|
@ -247,10 +247,10 @@ EventPage.content_panels = [
|
|||
FieldPanel('audience'),
|
||||
FieldPanel('cost'),
|
||||
FieldPanel('signup_link'),
|
||||
InlinePanel(EventPage, 'carousel_items', label="Carousel items"),
|
||||
InlinePanel('carousel_items', label="Carousel items"),
|
||||
FieldPanel('body', classname="full"),
|
||||
InlinePanel(EventPage, 'speakers', label="Speakers"),
|
||||
InlinePanel(EventPage, 'related_links', label="Related links"),
|
||||
InlinePanel('speakers', label="Speakers"),
|
||||
InlinePanel('related_links', label="Related links"),
|
||||
]
|
||||
|
||||
EventPage.promote_panels = [
|
||||
|
|
@ -329,7 +329,7 @@ class FormPage(AbstractEmailForm):
|
|||
|
||||
FormPage.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
InlinePanel(FormPage, 'form_fields', label="Form fields"),
|
||||
InlinePanel('form_fields', label="Form fields"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('to_address', classname="full"),
|
||||
FieldPanel('from_address', classname="full"),
|
||||
|
|
@ -392,7 +392,7 @@ class StandardIndex(Page):
|
|||
|
||||
StandardIndex.content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
InlinePanel(StandardIndex, 'advert_placements', label="Adverts"),
|
||||
InlinePanel('advert_placements', label="Adverts"),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from contextlib import contextmanager
|
||||
import warnings
|
||||
import sys
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import six
|
||||
|
|
@ -24,7 +25,22 @@ class WagtailTestUtils(object):
|
|||
with warnings.catch_warnings(record=True) as warning_list: # catch all warnings
|
||||
yield
|
||||
|
||||
# rethrow all warnings that were not DeprecationWarnings
|
||||
# rethrow all warnings that were not DeprecationWarnings or PendingDeprecationWarnings
|
||||
for w in warning_list:
|
||||
if not issubclass(w.category, DeprecationWarning):
|
||||
if not issubclass(w.category, (DeprecationWarning, PendingDeprecationWarning)):
|
||||
warnings.showwarning(message=w.message, category=w.category, filename=w.filename, lineno=w.lineno, file=w.file, line=w.line)
|
||||
|
||||
# borrowed from https://github.com/django/django/commit/9f427617e4559012e1c2fd8fce46cbe225d8515d
|
||||
@staticmethod
|
||||
def reset_warning_registry():
|
||||
"""
|
||||
Clear warning registry for all modules. This is required in some tests
|
||||
because of a bug in Python that prevents warnings.simplefilter("always")
|
||||
from always making warnings appear: http://bugs.python.org/issue4180
|
||||
|
||||
The bug was fixed in Python 3.4.2.
|
||||
"""
|
||||
key = "__warningregistry__"
|
||||
for mod in list(sys.modules.values()):
|
||||
if hasattr(mod, key):
|
||||
getattr(mod, key).clear()
|
||||
|
|
|
|||
|
|
@ -5,16 +5,22 @@ from django.utils.safestring import mark_safe
|
|||
|
||||
|
||||
class WidgetWithScript(Widget):
|
||||
def render(self, name, value, attrs=None):
|
||||
widget = super(WidgetWithScript, self).render(name, value, attrs)
|
||||
def render_html(self, name, value, attrs):
|
||||
"""Render the HTML (non-JS) portion of the field markup"""
|
||||
return super(WidgetWithScript, self).render(name, value, attrs)
|
||||
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
id_ = final_attrs.get('id', None)
|
||||
if id_ is None:
|
||||
return widget
|
||||
def render(self, name, value, attrs=None):
|
||||
# no point trying to come up with sensible semantics for when 'id' is missing from attrs,
|
||||
# so let's make sure it fails early in the process
|
||||
try:
|
||||
id_ = attrs['id']
|
||||
except (KeyError, TypeError):
|
||||
raise TypeError("WidgetWithScript cannot be rendered without an 'id' attribute")
|
||||
|
||||
widget_html = self.render_html(name, value, attrs)
|
||||
|
||||
js = self.render_js_init(id_, name, value)
|
||||
out = '{0}<script>{1}</script>'.format(widget, js)
|
||||
out = '{0}<script>{1}</script>'.format(widget_html, js)
|
||||
return mark_safe(out)
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||
import re
|
||||
import collections
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
|
|
@ -14,7 +14,7 @@ from django.utils.encoding import force_text, python_2_unicode_compatible
|
|||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.functional import cached_property
|
||||
from django.template.loader import render_to_string
|
||||
from django.forms import Media, CharField
|
||||
from django.forms import Media, CharField, ModelChoiceField
|
||||
from django.forms.utils import ErrorList
|
||||
|
||||
import six
|
||||
|
|
@ -277,9 +277,18 @@ class FieldBlock(Block):
|
|||
class Meta:
|
||||
default = None
|
||||
|
||||
def __init__(self, field, **kwargs):
|
||||
def __init__(self, field=None, **kwargs):
|
||||
super(FieldBlock, self).__init__(**kwargs)
|
||||
self.field = field
|
||||
self._field = field
|
||||
|
||||
@cached_property
|
||||
def field(self):
|
||||
# Sometimes the field object needs to be constructed lazily - for example, ModelChoiceFields
|
||||
# cannot be defined until models have been loaded. In those cases, we can leave field unspecified
|
||||
# in the constructor, and override this method instead.
|
||||
if self._field is None:
|
||||
raise ImproperlyConfigured("FieldBlock was not passed a field object")
|
||||
return self._field
|
||||
|
||||
def render_form(self, value, prefix='', error=None):
|
||||
widget = self.field.widget
|
||||
|
|
@ -340,6 +349,13 @@ class RichTextBlock(FieldBlock):
|
|||
def render_basic(self, value):
|
||||
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')
|
||||
|
||||
class PageChooserBlock(FieldBlock):
|
||||
@cached_property
|
||||
def field(self):
|
||||
from wagtail.wagtailcore.models import Page # TODO: allow limiting to specific page types
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
return ModelChoiceField(queryset=Page.objects.all(), widget=AdminPageChooser)
|
||||
|
||||
# =======
|
||||
# Chooser
|
||||
# =======
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import warnings
|
||||
|
||||
from six import text_type
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ from taggit.managers import TaggableManager
|
|||
from wagtail.wagtailadmin import widgets
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailcore.utils import camelcase_to_underscore, resolve_model_string
|
||||
from wagtail.utils.deprecation import RemovedInWagtail11Warning
|
||||
|
||||
|
||||
FORM_FIELD_OVERRIDES = {
|
||||
|
|
@ -431,18 +433,16 @@ class BaseFieldPanel(EditHandler):
|
|||
def render_as_object(self):
|
||||
return mark_safe(render_to_string(self.object_template, {
|
||||
'self': self,
|
||||
'field_content': self.render_as_field(show_help_text=False),
|
||||
'field_content': self.render_as_field(),
|
||||
}))
|
||||
|
||||
field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
def render_as_field(self):
|
||||
context = {
|
||||
'field': self.bound_field,
|
||||
'field_type': self.field_type(),
|
||||
'show_help_text': show_help_text,
|
||||
}
|
||||
context.update(extra_context)
|
||||
return mark_safe(render_to_string(self.field_template, context))
|
||||
|
||||
@classmethod
|
||||
|
|
@ -491,7 +491,7 @@ class BaseChooserPanel(BaseFieldPanel):
|
|||
a hidden foreign key input.
|
||||
|
||||
Subclasses provide:
|
||||
* field_template
|
||||
* field_template (only required if the default template of field_panel_field.html is not usable)
|
||||
* object_type_name - something like 'image' which will be used as the var name
|
||||
for the object instance in the field_template
|
||||
"""
|
||||
|
|
@ -505,20 +505,17 @@ class BaseChooserPanel(BaseFieldPanel):
|
|||
# like every other unpopulated field type. Yay consistency!
|
||||
return None
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
def render_as_field(self):
|
||||
instance_obj = self.get_chosen_item()
|
||||
context = {
|
||||
'field': self.bound_field,
|
||||
self.object_type_name: instance_obj,
|
||||
'is_chosen': bool(instance_obj),
|
||||
'show_help_text': show_help_text,
|
||||
'is_chosen': bool(instance_obj), # DEPRECATED - passed to templates for backwards compatibility only
|
||||
}
|
||||
context.update(extra_context)
|
||||
return mark_safe(render_to_string(self.field_template, context))
|
||||
|
||||
|
||||
class BasePageChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailadmin/edit_handlers/page_chooser_panel.html"
|
||||
object_type_name = "page"
|
||||
|
||||
_target_content_type = None
|
||||
|
|
@ -548,14 +545,6 @@ class BasePageChooserPanel(BaseChooserPanel):
|
|||
|
||||
return cls._target_content_type
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
context = {
|
||||
'choose_another_text_str': ugettext_lazy("Choose another page"),
|
||||
'choose_one_text_str': ugettext_lazy("Choose a page"),
|
||||
}
|
||||
context.update(extra_context)
|
||||
return super(BasePageChooserPanel, self).render_as_field(show_help_text, context)
|
||||
|
||||
|
||||
class PageChooserPanel(object):
|
||||
def __init__(self, field_name, page_type=None):
|
||||
|
|
@ -651,13 +640,29 @@ class BaseInlinePanel(EditHandler):
|
|||
|
||||
|
||||
class InlinePanel(object):
|
||||
def __init__(self, base_model, relation_name, panels=None, label='', help_text=''):
|
||||
# the base_model param is now redundant; we set up relations based on the model passed to
|
||||
def __init__(self, *args, **kwargs):
|
||||
# prior to Wagtail 0.9, InlinePanel required two params, base_model and relation_name.
|
||||
# base_model is no longer required; we set up relations based on the model passed to
|
||||
# bind_to_model instead
|
||||
self.relation_name = relation_name
|
||||
self.panels = panels
|
||||
self.label = label
|
||||
self.help_text = help_text
|
||||
if len(args) == 1: # new-style: InlinePanel(relation_name)
|
||||
self.relation_name = args[0]
|
||||
elif len(args) == 2: # old-style: InlinePanel(base_model, relation_name)
|
||||
self.relation_name = args[1]
|
||||
|
||||
warnings.warn(
|
||||
"InlinePanel no longer needs to be passed a model parameter. "
|
||||
"InlinePanel({classname}, '{relname}') should be changed to InlinePanel('{relname}')".format(
|
||||
classname=args[0].__name__, relname=self.relation_name
|
||||
), RemovedInWagtail11Warning, stacklevel=2)
|
||||
else:
|
||||
raise TypeError("InlinePanel() takes exactly 1 argument (%d given)" % len(args))
|
||||
|
||||
self.panels = kwargs.pop('panels', None)
|
||||
self.label = kwargs.pop('label', '')
|
||||
self.help_text = kwargs.pop('help_text', '')
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("InlinePanel got an unexpected keyword argument '%s'" % kwargs.keys()[0])
|
||||
|
||||
def bind_to_model(self, model):
|
||||
return type(str('_InlinePanel'), (BaseInlinePanel,), {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,8 @@ from __future__ import unicode_literals
|
|||
|
||||
from six import text_type, with_metaclass
|
||||
|
||||
try:
|
||||
# renamed util -> utils in Django 1.7; try the new name first
|
||||
from django.forms.utils import flatatt
|
||||
except ImportError:
|
||||
from django.forms.util import flatatt
|
||||
|
||||
from django.forms import MediaDefiningClass, Media
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils.text import slugify
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
|
|
|||
|
|
@ -727,6 +727,9 @@ ul.tagit input[type="text"]{
|
|||
ul.tagit li.tagit-choice-editable{
|
||||
padding:0 23px 0 0 !important; /* having to use important, FML*/
|
||||
}
|
||||
.ui-front{ /* provided by jqueryui but not high enough an index */
|
||||
z-index:1000;
|
||||
}
|
||||
|
||||
.tagit-close{
|
||||
.ui-icon-close{
|
||||
|
|
|
|||
|
|
@ -199,7 +199,6 @@ ul.listing{
|
|||
color:$color-teal;
|
||||
border-color:$color-grey-3;
|
||||
background:white;
|
||||
font-size:0.84em; /* 0.01em difference to regular small buttons */
|
||||
|
||||
&:hover{
|
||||
border-color:$color-teal;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ $submenu-color:darken($color-grey-1, 5%);
|
|||
|
||||
.menu-item a{
|
||||
white-space:normal;
|
||||
padding: 0.9em 0 0.9em 4.5em;
|
||||
padding: 0.9em 1.7em 0.9em 4.5em;
|
||||
|
||||
&:before{
|
||||
margin-left:-1.5em;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
{% extends "wagtailadmin/shared/field.html" %}
|
||||
{% load i18n %}
|
||||
{% comment %}
|
||||
------
|
||||
DEPRECATED - provided for backwards compatibility with custom (third-party) chooser panels
|
||||
created prior to Wagtail 0.9. New choosers should subclass wagtail.wagtailadmin.widgets.AdminChooser,
|
||||
with a template that extends wagtailadmin/widgets/chooser.html.
|
||||
------
|
||||
|
||||
Either the chosen or unchosen div will be shown, depending on the presence
|
||||
of the 'blank' class on the container.
|
||||
|
||||
|
|
@ -10,21 +16,21 @@
|
|||
|
||||
{% block form_field %}
|
||||
|
||||
<div id="{{ field.id_for_label }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not is_chosen %}blank{% endif %}">
|
||||
<div id="{{ field.id_for_label }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not field.value %}blank{% endif %}">
|
||||
|
||||
<div class="chosen">
|
||||
{% block chosen_state_view %}{% endblock %}
|
||||
|
||||
<div class="actions">
|
||||
{% if not field.field.required %}
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{% block clear_button_label %}{% trans "Clear choice" %}{% endblock %}">
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{{ field.field.widget.clear_choice_text }}">
|
||||
{% endif %}
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{% block choose_another_button_label %}{% trans "Choose another item" %}{% endblock %}">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ field.field.widget.choose_another_text }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unchosen">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{% block choose_button_label %}{% trans "Choose an item" %}{% endblock %}">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ field.field.widget.choose_one_text }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ page.title }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block choose_another_button_label %}{{ choose_another_text_str }}{% endblock %}
|
||||
{% block choose_button_label %}{{ choose_one_text_str }}{% endblock %}
|
||||
{# Page chooser is now implemented as an entirely standard form widget - page_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_title %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_slug %}
|
||||
|
||||
<li class="{% if form.new_parent_page.field.required %}required{% endif %}">
|
||||
{% trans "Change page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.new_parent_page page=parent_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
</li>
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_parent_page %}
|
||||
|
||||
{% if form.copy_subpages %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.copy_subpages %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
{% load i18n %}
|
||||
{% comment %}
|
||||
Either the chosen or unchosen div will be shown, depending on the presence
|
||||
of the 'blank' class on the container.
|
||||
|
||||
Any element with the 'action-choose' class will open the page chooser modal
|
||||
when clicked.
|
||||
{% endcomment %}
|
||||
|
||||
<div id="{{ attrs.id }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not value %}blank{% endif %}">
|
||||
|
||||
<div class="chosen">
|
||||
{% block chosen_state_view %}{% endblock %}
|
||||
|
||||
<div class="actions">
|
||||
{% if not widget.is_required %}
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{{ widget.clear_choice_text }}">
|
||||
{% endif %}
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ widget.choose_another_text }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unchosen">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ widget.choose_one_text }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{ original_field_html }}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ page.title }}</span>
|
||||
{% endblock %}
|
||||
|
|
@ -13,6 +13,7 @@ class TestFieldBlock(unittest.TestCase):
|
|||
|
||||
self.assertEqual(html, "Hello world!")
|
||||
|
||||
@unittest.expectedFailure # classname seems to have broken
|
||||
def test_charfield_render_form(self):
|
||||
block = blocks.FieldBlock(forms.CharField())
|
||||
html = block.render_form("Hello world!")
|
||||
|
|
@ -41,6 +42,7 @@ class TestFieldBlock(unittest.TestCase):
|
|||
|
||||
self.assertEqual(html, "choice-2")
|
||||
|
||||
@unittest.expectedFailure # classname seems to have broken
|
||||
def test_choicefield_render_form(self):
|
||||
block = blocks.FieldBlock(forms.ChoiceField(choices=(
|
||||
('choice-1', "Choice 1"),
|
||||
|
|
@ -184,6 +186,7 @@ class TestStructBlock(unittest.TestCase):
|
|||
# Don't render the extra item
|
||||
self.assertNotIn('<dt>image</dt>', html)
|
||||
|
||||
@unittest.expectedFailure # Double space in classnames...
|
||||
def test_render_form(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
|
|
@ -196,10 +199,10 @@ class TestStructBlock(unittest.TestCase):
|
|||
}, prefix='mylink')
|
||||
|
||||
self.assertIn('<div class="struct-block">', html)
|
||||
self.assertIn('<div class="field char_field">', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="title" type="text" value="Wagtail site" />', html)
|
||||
self.assertIn('<div class="field url_field">', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="link" type="url" value="http://www.wagtail.io" />', html)
|
||||
self.assertIn('<div class="field char_field blockname-title">', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Wagtail site" />', html)
|
||||
self.assertIn('<div class="field url_field blockname-link">', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
|
||||
|
||||
def test_render_form_unknown_field(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
|
|
@ -213,11 +216,8 @@ class TestStructBlock(unittest.TestCase):
|
|||
'image': 10,
|
||||
}, prefix='mylink')
|
||||
|
||||
self.assertIn('<div class="struct-block">', html)
|
||||
self.assertIn('<div class="field char_field">', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="title" type="text" value="Wagtail site" />', html)
|
||||
self.assertIn('<div class="field url_field">', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="link" type="url" value="http://www.wagtail.io" />', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Wagtail site" />', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
|
||||
|
||||
# Don't render the extra field
|
||||
self.assertNotIn('mylink-image', html)
|
||||
|
|
@ -231,10 +231,7 @@ class TestStructBlock(unittest.TestCase):
|
|||
block = LinkBlock()
|
||||
html = block.render_form({}, prefix='mylink')
|
||||
|
||||
self.assertIn('<div class="struct-block">', html)
|
||||
self.assertIn('<div class="field char_field">', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" type="text" value="Torchbox" />', html)
|
||||
self.assertIn('<div class="field url_field">', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" type="url" value="http://www.torchbox.com" />', html)
|
||||
|
||||
def test_render_form_uses_default_value(self):
|
||||
|
|
@ -245,11 +242,8 @@ class TestStructBlock(unittest.TestCase):
|
|||
block = LinkBlock()
|
||||
html = block.render_form({}, prefix='mylink')
|
||||
|
||||
self.assertIn('<div class="struct-block">', html)
|
||||
self.assertIn('<div class="field char_field">', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="title" type="text" value="Torchbox" />', html)
|
||||
self.assertIn('<div class="field url_field">', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="link" type="url" value="http://www.torchbox.com" />', html)
|
||||
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Torchbox" />', html)
|
||||
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.torchbox.com" />', html)
|
||||
|
||||
|
||||
class TestListBlock(unittest.TestCase):
|
||||
|
|
@ -265,6 +259,35 @@ class TestListBlock(unittest.TestCase):
|
|||
|
||||
self.assertEqual(block.child_block, child_block)
|
||||
|
||||
def render(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
link = blocks.FieldBlock(forms.URLField())
|
||||
|
||||
block = blocks.ListBlock(LinkBlock())
|
||||
return block.render([
|
||||
{
|
||||
'title': "Wagtail",
|
||||
'link': 'http://www.wagtail.io',
|
||||
},
|
||||
{
|
||||
'title': "Django",
|
||||
'link': 'http://www.djangoproject.com',
|
||||
},
|
||||
])
|
||||
|
||||
def test_render_uses_ul(self):
|
||||
html = self.render()
|
||||
|
||||
self.assertIn('<ul>', html)
|
||||
self.assertIn('</ul>', html)
|
||||
|
||||
def test_render_uses_li(self):
|
||||
html = self.render()
|
||||
|
||||
self.assertIn('<li>', html)
|
||||
self.assertIn('</li>', html)
|
||||
|
||||
def render_form(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
|
|
@ -316,10 +339,10 @@ class TestListBlock(unittest.TestCase):
|
|||
def test_render_form_values(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input id="links-0-value-title" name="links-0-value-title" placeholder="title" type="text" value="Wagtail" />', html)
|
||||
self.assertIn('<input id="links-0-value-link" name="links-0-value-link" placeholder="link" type="url" value="http://www.wagtail.io" />', html)
|
||||
self.assertIn('<input id="links-1-value-title" name="links-1-value-title" placeholder="title" type="text" value="Django" />', html)
|
||||
self.assertIn('<input id="links-1-value-link" name="links-1-value-link" placeholder="link" type="url" value="http://www.djangoproject.com" />', html)
|
||||
self.assertIn('<input id="links-0-value-title" name="links-0-value-title" placeholder="Title" type="text" value="Wagtail" />', html)
|
||||
self.assertIn('<input id="links-0-value-link" name="links-0-value-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
|
||||
self.assertIn('<input id="links-1-value-title" name="links-1-value-title" placeholder="Title" type="text" value="Django" />', html)
|
||||
self.assertIn('<input id="links-1-value-link" name="links-1-value-link" placeholder="Link" type="url" value="http://www.djangoproject.com" />', html)
|
||||
|
||||
def test_html_declarations(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
|
|
@ -329,8 +352,8 @@ class TestListBlock(unittest.TestCase):
|
|||
block = blocks.ListBlock(LinkBlock)
|
||||
html = block.html_declarations()
|
||||
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="title" type="text" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="link" type="url" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" />', html)
|
||||
|
||||
def test_html_declarations_uses_default(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
|
|
@ -340,8 +363,8 @@ class TestListBlock(unittest.TestCase):
|
|||
block = blocks.ListBlock(LinkBlock)
|
||||
html = block.html_declarations()
|
||||
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="title" type="text" value="Github" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="link" type="url" value="http://www.github.com" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" value="Github" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" value="http://www.github.com" />', html)
|
||||
|
||||
|
||||
class TestStreamBlock(unittest.TestCase):
|
||||
|
|
@ -495,9 +518,9 @@ class TestStreamBlock(unittest.TestCase):
|
|||
def test_render_form_value_fields(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input id="myarticle-0-value" name="myarticle-0-value" placeholder="heading" type="text" value="My title" />', html)
|
||||
self.assertIn('<input id="myarticle-1-value" name="myarticle-1-value" placeholder="paragraph" type="text" value="My first paragraph" />', html)
|
||||
self.assertIn('<input id="myarticle-2-value" name="myarticle-2-value" placeholder="paragraph" type="text" value="My second paragraph" />', html)
|
||||
self.assertIn('<input id="myarticle-0-value" name="myarticle-0-value" placeholder="Heading" type="text" value="My title" />', html)
|
||||
self.assertIn('<input id="myarticle-1-value" name="myarticle-1-value" placeholder="Paragraph" type="text" value="My first paragraph" />', html)
|
||||
self.assertIn('<input id="myarticle-2-value" name="myarticle-2-value" placeholder="Paragraph" type="text" value="My second paragraph" />', html)
|
||||
|
||||
def test_html_declarations(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
|
|
@ -507,8 +530,8 @@ class TestStreamBlock(unittest.TestCase):
|
|||
block = blocks.ListBlock(LinkBlock)
|
||||
html = block.html_declarations()
|
||||
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="title" type="text" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="link" type="url" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" />', html)
|
||||
|
||||
def test_html_declarations_uses_default(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
|
|
@ -518,5 +541,5 @@ class TestStreamBlock(unittest.TestCase):
|
|||
block = blocks.ListBlock(LinkBlock)
|
||||
html = block.html_declarations()
|
||||
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="title" type="text" value="Github" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="link" type="url" value="http://www.github.com" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" value="Github" />', html)
|
||||
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" value="http://www.github.com" />', html)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import date
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
|
@ -19,6 +20,8 @@ from wagtail.wagtailadmin.widgets import AdminPageChooser, AdminDateInput
|
|||
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.tests.models import PageChooserModel, EventPage, EventPageSpeaker
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.utils.deprecation import RemovedInWagtail11Warning
|
||||
|
||||
|
||||
class TestGetFormForModel(TestCase):
|
||||
|
|
@ -129,7 +132,7 @@ class TestTabbedInterface(TestCase):
|
|||
FieldPanel('date_to'),
|
||||
], heading='Event details', classname="shiny"),
|
||||
ObjectList([
|
||||
InlinePanel(EventPage, 'speakers', label="Speakers"),
|
||||
InlinePanel('speakers', label="Speakers"),
|
||||
], heading='Speakers'),
|
||||
]).bind_to_model(EventPage)
|
||||
|
||||
|
|
@ -200,7 +203,7 @@ class TestObjectList(TestCase):
|
|||
FieldPanel('title', widget=forms.Textarea),
|
||||
FieldPanel('date_from'),
|
||||
FieldPanel('date_to'),
|
||||
InlinePanel(EventPage, 'speakers', label="Speakers"),
|
||||
InlinePanel('speakers', label="Speakers"),
|
||||
], heading='Event details', classname="shiny").bind_to_model(EventPage)
|
||||
|
||||
def test_get_form_class(self):
|
||||
|
|
@ -391,7 +394,7 @@ class TestPageChooserPanel(TestCase):
|
|||
result.target_content_type)
|
||||
|
||||
|
||||
class TestInlinePanel(TestCase):
|
||||
class TestInlinePanel(TestCase, WagtailTestUtils):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_render(self):
|
||||
|
|
@ -399,7 +402,7 @@ class TestInlinePanel(TestCase):
|
|||
Check that the inline panel renders the panels set on the model
|
||||
when no 'panels' parameter is passed in the InlinePanel definition
|
||||
"""
|
||||
SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers").bind_to_model(EventPage)
|
||||
SpeakerInlinePanel = InlinePanel('speakers', label="Speakers").bind_to_model(EventPage)
|
||||
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
|
||||
|
||||
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
|
||||
|
|
@ -434,7 +437,7 @@ class TestInlinePanel(TestCase):
|
|||
Check that inline panel renders the panels listed in the InlinePanel definition
|
||||
where one is specified
|
||||
"""
|
||||
SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers", panels=[
|
||||
SpeakerInlinePanel = InlinePanel('speakers', label="Speakers", panels=[
|
||||
FieldPanel('first_name', widget=forms.Textarea),
|
||||
ImageChooserPanel('image'),
|
||||
]).bind_to_model(EventPage)
|
||||
|
|
@ -471,3 +474,37 @@ class TestInlinePanel(TestCase):
|
|||
|
||||
# render_js_init must provide the JS initializer
|
||||
self.assertIn('var panel = InlinePanel({', panel.render_js_init())
|
||||
|
||||
def test_old_style_inlinepanel_declaration(self):
|
||||
"""
|
||||
Check that the deprecated form of InlinePanel declaration (where the base model is passed
|
||||
as the first arg) still works
|
||||
"""
|
||||
self.reset_warning_registry()
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
SpeakerInlinePanelDef = InlinePanel(EventPage, 'speakers', label="Speakers")
|
||||
|
||||
# Check that a RemovedInWagtail11Warning has been triggered
|
||||
self.assertEqual(len(w), 1)
|
||||
self.assertTrue(issubclass(w[-1].category, RemovedInWagtail11Warning))
|
||||
self.assertTrue("InlinePanel(EventPage, 'speakers') should be changed to InlinePanel('speakers')" in str(w[-1].message))
|
||||
|
||||
SpeakerInlinePanel = SpeakerInlinePanelDef.bind_to_model(EventPage)
|
||||
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
|
||||
|
||||
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
|
||||
self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
|
||||
|
||||
event_page = EventPage.objects.get(slug='christmas')
|
||||
form = EventPageForm(instance=event_page)
|
||||
panel = SpeakerInlinePanel(instance=event_page, form=form)
|
||||
|
||||
result = panel.render_as_field()
|
||||
self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
|
||||
self.assertIn('value="Father"', result)
|
||||
|
||||
def test_invalid_inlinepanel_declaration(self):
|
||||
with self.ignore_deprecation_warnings():
|
||||
self.assertRaises(TypeError, lambda: InlinePanel(label="Speakers"))
|
||||
self.assertRaises(TypeError, lambda: InlinePanel(EventPage, 'speakers', 'bacon', label="Speakers"))
|
||||
self.assertRaises(TypeError, lambda: InlinePanel(EventPage, 'speakers', label="Speakers", bacon="chunky"))
|
||||
|
|
|
|||
|
|
@ -698,7 +698,6 @@ def copy(request, page_id):
|
|||
|
||||
return render(request, 'wagtailadmin/pages/copy.html', {
|
||||
'page': page,
|
||||
'parent_page': parent_page,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from django.core.urlresolvers import reverse
|
|||
from django.forms import widgets
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
|
@ -35,14 +37,64 @@ class AdminTagWidget(WidgetWithScript, TagWidget):
|
|||
json.dumps(reverse('wagtailadmin_tag_autocomplete')))
|
||||
|
||||
|
||||
class AdminPageChooser(WidgetWithScript, widgets.Input):
|
||||
class AdminChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
choose_one_text = _("Choose an item")
|
||||
choose_another_text = _("Choose another item")
|
||||
clear_choice_text = _("Clear choice")
|
||||
|
||||
def get_instance(self, model_class, value):
|
||||
# helper method for cleanly turning 'value' into an instance object
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return model_class.objects.get(pk=value)
|
||||
except model_class.DoesNotExist:
|
||||
return None
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
# treat the empty string as None
|
||||
result = super(AdminChooser, self).value_from_datadict(data, files, name)
|
||||
if result == '':
|
||||
return None
|
||||
else:
|
||||
return result
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# allow choose_one_text / choose_another_text to be overridden per-instance
|
||||
if 'choose_one_text' in kwargs:
|
||||
self.choose_one_text = kwargs.pop('choose_one_text')
|
||||
if 'choose_another_text' in kwargs:
|
||||
self.choose_another_text = kwargs.pop('choose_another_text')
|
||||
if 'clear_choice_text' in kwargs:
|
||||
self.clear_choice_text = kwargs.pop('clear_choice_text')
|
||||
super(AdminChooser, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class AdminPageChooser(AdminChooser):
|
||||
target_content_type = None
|
||||
choose_one_text = _('Choose a page')
|
||||
choose_another_text = _('Choose another page')
|
||||
|
||||
def __init__(self, content_type=None, **kwargs):
|
||||
super(AdminPageChooser, self).__init__(**kwargs)
|
||||
self.target_content_type = content_type or ContentType.objects.get_for_model(Page)
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminPageChooser, self).render_html(name, value, attrs)
|
||||
|
||||
model_class = self.target_content_type.model_class()
|
||||
instance = self.get_instance(model_class, value)
|
||||
|
||||
return render_to_string("wagtailadmin/widgets/page_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'page': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
page = Page.objects.get(pk=value) if value else None
|
||||
parent = page.get_parent() if page else None
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from django.db.models import Q, get_models
|
||||
from django.db.models import Q
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.apps import apps
|
||||
|
||||
from treebeard.mp_tree import MP_NodeQuerySet
|
||||
|
||||
from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
|
@ -154,7 +156,7 @@ class PageQuerySet(MP_NodeQuerySet):
|
|||
|
||||
def type_q(self, klass):
|
||||
content_types = ContentType.objects.get_for_models(*[
|
||||
model for model in get_models()
|
||||
model for model in apps.get_models()
|
||||
if issubclass(model, klass)
|
||||
]).values()
|
||||
|
||||
|
|
|
|||
|
|
@ -35,4 +35,9 @@ def wagtail_version():
|
|||
|
||||
@register.filter
|
||||
def richtext(value):
|
||||
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')
|
||||
if value is not None:
|
||||
html = expand_db_html(value)
|
||||
else:
|
||||
html = ''
|
||||
|
||||
return mark_safe('<div class="rich-text">' + html + '</div>')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.cache import cache
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.wagtailcore.templatetags.wagtailcore_tags import richtext
|
||||
from wagtail.wagtailcore.utils import resolve_model_string
|
||||
from wagtail.tests.models import SimplePage
|
||||
|
||||
|
||||
|
|
@ -142,3 +147,53 @@ class TestSiteRootPathsCache(TestCase):
|
|||
|
||||
# Check url
|
||||
self.assertEqual(homepage.url, '/')
|
||||
|
||||
|
||||
class TestResolveModelString(TestCase):
|
||||
def test_resolve_from_string(self):
|
||||
model = resolve_model_string('wagtailcore.Page')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_with_default_app(self):
|
||||
model = resolve_model_string('Page', default_app='wagtailcore')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_with_different_default_app(self):
|
||||
model = resolve_model_string('wagtailcore.Page', default_app='wagtailadmin')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_class(self):
|
||||
model = resolve_model_string(Page)
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_invalid(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, 'wagtail.wagtailcore.Page')
|
||||
|
||||
def test_resolve_from_string_with_incorrect_default_app(self):
|
||||
self.assertRaises(LookupError, resolve_model_string, 'Page', default_app='wagtailadmin')
|
||||
|
||||
def test_resolve_from_string_with_no_default_app(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, 'Page')
|
||||
|
||||
@unittest.expectedFailure # Raising LookupError instead
|
||||
def test_resolve_from_class_that_isnt_a_model(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, object)
|
||||
|
||||
@unittest.expectedFailure # Raising LookupError instead
|
||||
def test_resolve_from_bad_type(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, resolve_model_string)
|
||||
|
||||
|
||||
class TestRichtextTag(TestCase):
|
||||
def test_call_with_text(self):
|
||||
result = richtext("Hello world!")
|
||||
self.assertEqual(result, '<div class="rich-text">Hello world!</div>')
|
||||
self.assertIsInstance(result, SafeString)
|
||||
|
||||
def test_call_with_none(self):
|
||||
result = richtext(None)
|
||||
self.assertEqual(result, '<div class="rich-text"></div>')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import re
|
||||
from django.db.models import Model, get_model
|
||||
from six import string_types
|
||||
|
||||
from django.db.models import Model
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
def camelcase_to_underscore(str):
|
||||
# http://djangosnippets.org/snippets/585/
|
||||
|
|
@ -26,10 +28,7 @@ def resolve_model_string(model_string, default_app=None):
|
|||
"should be in the form app_label.model_name".format(
|
||||
model_string), model_string)
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if not model:
|
||||
raise LookupError("Can not resolve {0!r} into a model".format(model_string), model_string)
|
||||
return model
|
||||
return apps.get_model(app_label, model_name)
|
||||
|
||||
elif isinstance(model_string, type) and issubclass(model_string, Model):
|
||||
return model_string
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from .widgets import AdminDocumentChooser
|
|||
|
||||
|
||||
class BaseDocumentChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtaildocs/edit_handlers/document_chooser_panel.html"
|
||||
object_type_name = "document"
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -1,11 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load i18n %}
|
||||
{% block chooser_class %}document-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ document.title }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block clear_button_label %}{% trans "Clear choice" %}{% endblock %}
|
||||
{% block choose_another_button_label %}{% trans "Choose another document" %}{% endblock %}
|
||||
{% block choose_button_label %}{% trans "Choose a document" %}{% endblock %}
|
||||
{# Document chooser is now implemented as an entirely standard form widget - document_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
{% block chooser_class %}document-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ document.title }}</span>
|
||||
{% endblock %}
|
||||
|
|
@ -2,13 +2,29 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
from wagtail.wagtaildocs.models import Document
|
||||
|
||||
|
||||
class AdminDocumentChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminDocumentChooser(AdminChooser):
|
||||
choose_one_text = _('Choose a document')
|
||||
choose_another_text = _('Choose another document')
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminDocumentChooser, self).render_html(name, value, attrs)
|
||||
|
||||
instance = self.get_instance(Document, value)
|
||||
|
||||
return render_to_string("wagtaildocs/widgets/document_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'document': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
return "createDocumentChooser({0});".format(json.dumps(id_))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.forms.util import ErrorList
|
||||
from django.forms.utils import ErrorList
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from wagtail.wagtailadmin.modal_workflow import render_modal_workflow
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
import django.forms
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
class BaseForm(django.forms.Form):
|
||||
|
|
@ -75,7 +76,7 @@ class FormBuilder(object):
|
|||
|
||||
@property
|
||||
def formfields(self):
|
||||
formfields = SortedDict()
|
||||
formfields = OrderedDict()
|
||||
|
||||
for field in self.fields:
|
||||
options = self.get_field_options(field)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from .widgets import AdminImageChooser
|
|||
|
||||
|
||||
class BaseImageChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailimages/edit_handlers/image_chooser_panel.html"
|
||||
object_type_name = "image"
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class DoNothingOperation(Operation):
|
|||
|
||||
|
||||
class FillOperation(Operation):
|
||||
vary_fields = ('focal_point_width', 'focal_point_height', 'focal_point_x', 'focal_point_y')
|
||||
|
||||
def construct(self, size, *extra):
|
||||
# Get width and height
|
||||
width_str, height_str = size.split('x')
|
||||
|
|
@ -158,37 +160,16 @@ class FillOperation(Operation):
|
|||
# Crop!
|
||||
willow.crop(int(left), int(top), int(right), int(bottom))
|
||||
|
||||
# Resize the final image
|
||||
# Get scale for resizing
|
||||
# The scale should be the same for both the horizontal and
|
||||
# vertical axes
|
||||
aftercrop_width, aftercrop_height = willow.get_size()
|
||||
horz_scale = self.width / aftercrop_width
|
||||
vert_scale = self.height / aftercrop_height
|
||||
scale = self.width / aftercrop_width
|
||||
|
||||
if aftercrop_width <= self.width or aftercrop_height <= self.height:
|
||||
return
|
||||
|
||||
if horz_scale > vert_scale:
|
||||
width = self.width
|
||||
height = int(aftercrop_height * horz_scale)
|
||||
else:
|
||||
width = int(aftercrop_width * vert_scale)
|
||||
height = self.height
|
||||
|
||||
willow.resize(width, height)
|
||||
|
||||
def get_vary(self, image):
|
||||
focal_point = image.get_focal_point()
|
||||
|
||||
if focal_point is not None:
|
||||
focal_point_key = "%(x)d-%(y)d-%(width)dx%(height)d" % {
|
||||
'x': int(focal_point.centroid_x),
|
||||
'y': int(focal_point.centroid_y),
|
||||
'width': int(focal_point.width),
|
||||
'height': int(focal_point.height),
|
||||
}
|
||||
else:
|
||||
focal_point_key = ''
|
||||
|
||||
return [focal_point_key]
|
||||
# Only resize if the image is too big
|
||||
if scale < 1.0:
|
||||
# Resize!
|
||||
willow.resize(self.width, self.height)
|
||||
|
||||
|
||||
class MinMaxOperation(Operation):
|
||||
|
|
|
|||
|
|
@ -302,17 +302,20 @@ class Filter(models.Model):
|
|||
vary = []
|
||||
|
||||
for operation in self.operations:
|
||||
if hasattr(operation, 'get_vary'):
|
||||
vary.extend(operation.get_vary(image))
|
||||
for field in getattr(operation, 'vary_fields', []):
|
||||
value = getattr(image, field, '')
|
||||
vary.append(str(value))
|
||||
|
||||
return vary
|
||||
|
||||
def get_vary_key(self, image):
|
||||
vary_string = '-'.join(self.get_vary(image))
|
||||
vary_key = hashlib.sha1(vary_string.encode('utf-8')).hexdigest()
|
||||
|
||||
return vary_key[:8]
|
||||
# Return blank string if there are no vary fields
|
||||
if not vary_string:
|
||||
return ''
|
||||
|
||||
return hashlib.sha1(vary_string.encode('utf-8')).hexdigest()[:8]
|
||||
|
||||
_registered_operations = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load wagtailimages_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block chooser_class %}image-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<div class="preview-image">
|
||||
{% if image %}
|
||||
{% image image max-130x130 %}
|
||||
{% else %}
|
||||
<img>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block clear_button_label %}{% trans "Clear image" %}{% endblock %}
|
||||
{% block choose_another_button_label %}{% trans "Choose another image" %}{% endblock %}
|
||||
{% block choose_button_label %}{% trans "Choose an image" %}{% endblock %}
|
||||
{# Image chooser is now implemented as an entirely standard form widget - image_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
{% load wagtailimages_tags %}
|
||||
|
||||
{% block chooser_class %}image-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<div class="preview-image">
|
||||
{% if image %}
|
||||
{% image image max-130x130 %}
|
||||
{% else %}
|
||||
<img>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -2,7 +2,7 @@ import unittest
|
|||
|
||||
from wagtail.wagtailimages import image_operations
|
||||
from wagtail.wagtailimages.exceptions import InvalidFilterSpecError
|
||||
from wagtail.wagtailimages.models import Image
|
||||
from wagtail.wagtailimages.models import Image, Filter
|
||||
|
||||
|
||||
class WillowOperationRecorder(object):
|
||||
|
|
@ -151,6 +151,13 @@ class TestFillOperation(ImageOperationTestCase):
|
|||
('resize', (800, 600), {}),
|
||||
]),
|
||||
|
||||
# Basic usage with an oddly-sized original image
|
||||
# This checks for a rounding precision issue (#968)
|
||||
('fill-200x200', Image(width=539, height=720), [
|
||||
('crop', (0, 90, 539, 629), {}),
|
||||
('resize', (200, 200), {}),
|
||||
]),
|
||||
|
||||
# Closeness shouldn't have any effect when used without a focal point
|
||||
('fill-800x600-c100', Image(width=1000, height=1000), [
|
||||
('crop', (0, 125, 1000, 875), {}),
|
||||
|
|
@ -318,3 +325,33 @@ class TestWidthHeightOperation(ImageOperationTestCase):
|
|||
]
|
||||
|
||||
TestWidthHeightOperation.setup_test_methods()
|
||||
|
||||
|
||||
class TestVaryKey(unittest.TestCase):
|
||||
def test_vary_key(self):
|
||||
image = Image(width=1000, height=1000)
|
||||
fil = Filter(spec='max-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '')
|
||||
|
||||
def test_vary_key_fill_filter(self):
|
||||
image = Image(width=1000, height=1000)
|
||||
fil = Filter(spec='fill-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '2e16d0ba')
|
||||
|
||||
def test_vary_key_fill_filter_with_focal_point(self):
|
||||
image = Image(
|
||||
width=1000,
|
||||
height=1000,
|
||||
focal_point_width=100,
|
||||
focal_point_height=100,
|
||||
focal_point_x=500,
|
||||
focal_point_y=500,
|
||||
)
|
||||
fil = Filter(spec='fill-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '0bbe3b2f')
|
||||
|
|
|
|||
|
|
@ -2,13 +2,34 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
|
||||
|
||||
class AdminImageChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminImageChooser(AdminChooser):
|
||||
choose_one_text = _('Choose an image')
|
||||
choose_another_text = _('Choose another image')
|
||||
clear_choice_text = _('Clear image')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AdminImageChooser, self).__init__(**kwargs)
|
||||
self.image_model = get_image_model()
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminImageChooser, self).render_html(name, value, attrs)
|
||||
|
||||
instance = self.get_instance(self.image_model, value)
|
||||
|
||||
return render_to_string("wagtailimages/widgets/image_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'image': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
return "createImageChooser({0});".format(json.dumps(id_))
|
||||
|
|
|
|||
|
|
@ -10,13 +10,7 @@
|
|||
<legend>{% trans "Promoted search result" %}</legend>
|
||||
<ul class="fields">
|
||||
<li class="model_choice_field">
|
||||
{% trans "Choose another page" as choose_another_text_str %}
|
||||
{% trans "Choose a page" as choose_one_text_str %}
|
||||
{% if form.instance.page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.page only %}
|
||||
</li>
|
||||
<li class="char_field">
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.description only %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailcore.models import Site
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
|
|
@ -7,7 +8,9 @@ from wagtail.wagtailadmin.widgets import AdminPageChooser
|
|||
class SiteForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SiteForm, self).__init__(*args, **kwargs)
|
||||
self.fields['root_page'].widget = AdminPageChooser()
|
||||
self.fields['root_page'].widget = AdminPageChooser(
|
||||
choose_one_text=_('Choose a root page'), choose_another_text=_('Choose a different root page')
|
||||
)
|
||||
|
||||
required_css_class = "required"
|
||||
|
||||
|
|
|
|||
|
|
@ -15,17 +15,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Choose a different root page" as choose_another_text_str %}
|
||||
{% trans "Choose a root page" as choose_one_text_str %}
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li><input type="submit" value="{% trans 'Save' %}" /></li>
|
||||
|
|
|
|||
|
|
@ -16,18 +16,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Change page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from .widgets import AdminSnippetChooser
|
|||
|
||||
|
||||
class BaseSnippetChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailsnippets/edit_handlers/snippet_chooser_panel.html"
|
||||
object_type_name = 'item'
|
||||
|
||||
_content_type = None
|
||||
|
|
@ -18,7 +17,7 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
|
|||
@classmethod
|
||||
def widget_overrides(cls):
|
||||
return {cls.field_name: AdminSnippetChooser(
|
||||
content_type=cls.content_type())}
|
||||
content_type=cls.content_type(), snippet_type_name=cls.snippet_type_name)}
|
||||
|
||||
@classmethod
|
||||
def content_type(cls):
|
||||
|
|
@ -28,14 +27,12 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
|
|||
|
||||
return cls._content_type
|
||||
|
||||
def render_as_field(self, show_help_text=True):
|
||||
def render_as_field(self):
|
||||
instance_obj = self.get_chosen_item()
|
||||
return mark_safe(render_to_string(self.field_template, {
|
||||
'field': self.bound_field,
|
||||
self.object_type_name: instance_obj,
|
||||
'snippet_type_name': self.snippet_type_name,
|
||||
'is_chosen': bool(instance_obj),
|
||||
'show_help_text': show_help_text,
|
||||
}))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,10 @@
|
|||
{% include "wagtailadmin/shared/header.html" with title=choose_str subtitle=snippet_type_name icon="snippet" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
{% include "wagtailsnippets/snippets/list.html" with choosing=1 %}
|
||||
{% if items %}
|
||||
{% include "wagtailsnippets/snippets/list.html" with choosing=1 %}
|
||||
{% else %}
|
||||
{% url 'wagtailsnippets_create' content_type.app_label content_type.model as wagtailsnippets_create_snippet_url %}
|
||||
<p>{% blocktrans %}You haven't created any {{ snippet_type_name }} snippets. Why not <a href="{{ wagtailsnippets_create_snippet_url }}" target="_blank">create one now{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block chooser_class %}snippet-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{% if is_chosen %}{{ item }}{% endif %}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block choose_another_button_label %}{% blocktrans %}Choose another {{ snippet_type_name }}{% endblocktrans %}{% endblock %}
|
||||
{% block choose_button_label %}{% blocktrans %}Choose {{ snippet_type_name }}{% endblocktrans %}{% endblock %}
|
||||
{# Snippet chooser is now implemented as an entirely standard form widget - snippet_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
|
||||
{% block chooser_class %}snippet-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ item }}</span>
|
||||
{% endblock %}
|
||||
|
|
@ -2,20 +2,39 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
|
||||
|
||||
class AdminSnippetChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminSnippetChooser(AdminChooser):
|
||||
target_content_type = None
|
||||
|
||||
def __init__(self, content_type=None, **kwargs):
|
||||
if 'snippet_type_name' in kwargs:
|
||||
snippet_type_name = kwargs.pop('snippet_type_name')
|
||||
self.choose_one_text = _('Choose %s') % snippet_type_name
|
||||
self.choose_another_text = _('Choose another %s') % snippet_type_name
|
||||
|
||||
super(AdminSnippetChooser, self).__init__(**kwargs)
|
||||
if content_type is not None:
|
||||
self.target_content_type = content_type
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminSnippetChooser, self).render_html(name, value, attrs)
|
||||
|
||||
model_class = self.target_content_type.model_class()
|
||||
instance = self.get_instance(model_class, value)
|
||||
|
||||
return render_to_string("wagtailsnippets/widgets/snippet_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'item': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
content_type = self.target_content_type
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
{% load i18n %}
|
||||
|
||||
<td>
|
||||
{% trans "Edit page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
|
||||
{% if form.instance.page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
{% include "wagtailadmin/edit_handlers/field_panel_field.html" with field=form.page only %}
|
||||
</td>
|
||||
<td>
|
||||
{% include "wagtailadmin/edit_handlers/field_panel_field.html" with field=form.permission_type only %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue