merging master

This commit is contained in:
Dave Cranwell 2014-05-20 10:28:03 +01:00
commit 05cbcd7546
28 changed files with 185 additions and 198 deletions

View file

@ -24,6 +24,7 @@ Changelog
* Fix: Page slugs are now validated on page edit
* Fix: Filter objects are cached to avoid a database hit every time an {% image %} tag is compiled
* Fix: Moving or changing a site root page no longer causes URLs for subpages to change to 'None'
* Fix: Eliminated raw SQL queries from wagtailcore / wagtailadmin, to ensure cross-database compatibility
0.2 (11.03.2014)
~~~~~~~~~~~~~~~~

View file

@ -25,6 +25,7 @@ Contributors
* Miguel Vieira
* Ben Emery
* David Smith
* Ben Margolis
Translators
===========

View file

@ -3,7 +3,6 @@ dj16=
Django>=1.6,<1.7
pyelasticsearch==0.6.1
elasticutils==0.8.2
unittest2
[tox]
envlist =

View file

@ -1,9 +1,20 @@
from django.contrib.auth.models import User
# We need to make sure that we're using the same unittest library that Django uses internally
# Otherwise, we get issues with the "SkipTest" and "ExpectedFailure" exceptions being recognised as errors
try:
# Firstly, try to import unittest from Django
from django.utils import unittest
except ImportError:
# Django doesn't include unittest
# We must be running on Django 1.7+ which doesn't support Python 2.6 so
# the standard unittest library should be unittest2
import unittest
def login(client):
# Create a user
User.objects.create_superuser(username='test', email='test@email.com', password='password')
# Login
client.login(username='test', password='password')
client.login(username='test', password='password')

View file

@ -1,3 +1,15 @@
var halloPlugins = {
'halloformat': {},
'halloheadings': {formatBlocks: ["p", "h2", "h3", "h4", "h5"]},
'hallolists': {},
'hallohr': {},
'halloreundo': {},
'hallowagtaillink': {},
};
function registerHalloPlugin(name, opts) {
halloPlugins[name] = (opts || {});
}
function makeRichTextEditable(id) {
var input = $('#' + id);
var richText = $('<div class="richtext"></div>').html(input.val());
@ -19,17 +31,7 @@ function makeRichTextEditable(id) {
richText.hallo({
toolbar: 'halloToolbarFixed',
toolbarcssClass: 'testy',
plugins: {
'halloformat': {},
'halloheadings': {formatBlocks: ["p", "h2", "h3", "h4", "h5"]},
'hallolists': {},
'hallohr': {},
'halloreundo': {},
'hallowagtailimage': {},
'hallowagtailembeds': {},
'hallowagtaillink': {},
'hallowagtaildoclink': {}
}
plugins: halloPlugins
}).bind('hallomodified', function(event, data) {
input.val(data.content);
if (!removeStylingPending) {

View file

@ -116,7 +116,7 @@ img{
}
.nav-wrapper{
@include box-shadow(inset -2px 0px 10px 0px rgba(0, 0, 0, 0.5));
@include box-shadow(inset -5px 0px 5px -3px rgba(0, 0, 0, 0.3));
position:relative;
background: $color-grey-1;
margin-left: -100%;
@ -777,13 +777,8 @@ footer, .logo{
.wrapper{
max-width:$breakpoint-desktop-larger;
}
.nav-wrapper{
@include box-shadow(inset -6px 0px 4px 0px rgba(0, 0, 0, 0.2));
.inner{
background:$color-grey-1;
@include box-shadow(inset -6px 0px 4px 0px rgba(0, 0, 0, 0.2));
}
.nav-wrapper .inner{
background:$color-grey-1;
}
footer{

View file

@ -179,6 +179,7 @@ form{
left: $desktop-nice-padding;
margin-top: -1em;
top: 50%;
font-size:1.5em;
}
.full{

View file

@ -4,11 +4,6 @@
CSS declarations to be included on the 'create page' and 'edit page' views
{% endcomment %}
{% comment %}
TODO: have a mechanism for sub-apps to specify their own declarations -
ideally wagtailadmin shouldn't have to know anything at all about wagtailimages and friends
{% endcomment %}
{% compress css %}
<link rel="stylesheet" href="{{ STATIC_URL }}wagtailadmin/scss/layouts/page-editor.scss" type="text/x-scss" />
<link rel="stylesheet" href="{{ STATIC_URL }}wagtailadmin/scss/panels/rich-text.scss" type="text/x-scss" />

View file

@ -4,6 +4,11 @@
Javascript declarations to be included on the 'create page' and 'edit page' views
{% endcomment %}
<script>
window.chooserUrls = {
'pageChooser': '{% url "wagtailadmin_choose_page" %}'
};
</script>
{% compress js %}
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/rangy-core.js"></script>
@ -14,21 +19,9 @@
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtail-toolbar.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-hr.js"></script>
<script src="{{ STATIC_URL }}wagtailimages/js/hallo-plugins/hallo-wagtailimage.js"></script>
<script src="{{ STATIC_URL }}wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js"></script>
<script src="{{ STATIC_URL }}wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/page-editor.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/page-chooser.js"></script>
{% comment %}
TODO: use the insert_editor_js hook to inject things like image-chooser.js and hallo-wagtailimage.js
from their respective apps such as wagtailimages -
ideally wagtailadmin shouldn't have to know anything at all about wagtailimages.
TODO: a method of injecting these sorts of things on demand when the modal is spawned.
{% endcomment %}
<script src="{{ STATIC_URL }}wagtailimages/js/image-chooser.js"></script>
<script src="{{ STATIC_URL }}wagtaildocs/js/document-chooser.js"></script>
<script src="{{ STATIC_URL }}wagtailsnippets/js/snippet-chooser.js"></script>
<script src="{{ STATIC_URL }}admin/js/urlify.js"></script>
{% hook_output 'insert_editor_js' %}
@ -41,19 +34,10 @@
{% comment %}
Additional js from widgets media. Allows for custom widgets in admin panel.
Can be used for TODO above (including image-choser.js at wagtailimages)
{% endcomment %}
{{ edit_handler.form.media.js }}
<script>
window.chooserUrls = {
'documentChooser': '{% url "wagtaildocs_chooser" %}',
'imageChooser': '{% url "wagtailimages_chooser" %}',
'embedsChooser': '{% url "wagtailembeds_chooser" %}',
'pageChooser': '{% url "wagtailadmin_choose_page" %}',
'snippetChooser': '{% url "wagtailsnippets_choose_generic" %}'
};
{% get_date_format_override as format_override %}
window.overrideDateInputFormat ='{{ format_override }}';

View file

@ -1,13 +0,0 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% blocktrans with page_type=content_type.model_class.get_verbose_name %}Where do you want to create a {{ page_type }}{% endblocktrans %}{% endblock %}
{% block content %}
{% trans "Where do you want to create this" as where_str %}
{% include "wagtailadmin/shared/header.html" with title=where_str subtitle=content_type.model_class.get_verbose_name icon="doc-empty-inverse" %}
<ul>
{% for page in parent_pages %}
<li><a href="{% url 'wagtailadmin_pages_create' content_type.app_label content_type.model page.id %}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -1,20 +0,0 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% trans "Create a new page" %}{% endblock %}
{% block bodyclass %}menu-explorer{% endblock %}
{% block content %}
{% trans "Create a new page" as create_str %}
{% include "wagtailadmin/shared/header.html" with title=create_str icon="doc-empty-inverse" %}
<div class="nice-padding">
<p>{% trans "Your new page will be saved in the <em>top level</em> of your website. You can move it after saving." %}</p>
{% if all_page_types %}
<ul class="listing">
{% for content_type in all_page_types %}
<li><a href="{% url 'wagtailadmin_pages_select_location' content_type.app_label content_type.model %}" class="icon icon-plus-inverse">{{ content_type.model_class.get_verbose_name }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

View file

@ -6,10 +6,6 @@
{{ menu_item.render_html }}
{% endfor %}
{% comment %}
{# TODO: make this work #}
<li><a href="{% url 'wagtailadmin_pages_select_type' %}" class="icon teal icon-plus-inverse">{% trans 'New page' %}</a></li>
{% endcomment %}
<li class="footer">
<div class="avatar icon icon-user"><a href="{% url 'wagtailadmin_account' %}" title="{% trans 'Account settings' %}">{% if request.user.email %}<img src="{% gravatar_url request.user.email %}" />{% endif %}</a></div>
<a href="{% url 'wagtailadmin_logout' %}">{% trans "Log out" %}</a>

View file

@ -1,7 +1,6 @@
from django.test import TestCase
import unittest2 as unittest
from wagtail.tests.models import SimplePage, EventPage
from wagtail.tests.utils import login
from wagtail.tests.utils import login, unittest
from wagtail.wagtailcore.models import Page
from django.core.urlresolvers import reverse
@ -37,32 +36,6 @@ class TestPageExplorer(TestCase):
self.assertTrue(response.context['pages'].filter(id=self.child_page.id).exists())
class TestPageSelectTypeLocation(TestCase):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# Login
login(self.client)
def test_select_type(self):
response = self.client.get(reverse('wagtailadmin_pages_select_type'))
self.assertEqual(response.status_code, 200)
@unittest.expectedFailure # For some reason, this returns a 302...
def test_select_location_testpage(self):
response = self.client.get(reverse('wagtailadmin_pages_select_location', args=('tests', 'eventpage')))
self.assertEqual(response.status_code, 200)
def test_select_location_nonexistanttype(self):
response = self.client.get(reverse('wagtailadmin_pages_select_location', args=('notanapp', 'notamodel')))
self.assertEqual(response.status_code, 404)
def test_select_location_nonpagetype(self):
response = self.client.get(reverse('wagtailadmin_pages_select_location', args=('wagtailimages', 'image')))
self.assertEqual(response.status_code, 404)
class TestPageCreation(TestCase):
def setUp(self):
# Find root page

View file

@ -49,8 +49,6 @@ urlpatterns += [
url(r'^pages/$', pages.index, name='wagtailadmin_explore_root'),
url(r'^pages/(\d+)/$', pages.index, name='wagtailadmin_explore'),
url(r'^pages/new/$', pages.select_type, name='wagtailadmin_pages_select_type'),
url(r'^pages/new/(\w+)/(\w+)/$', pages.select_location, name='wagtailadmin_pages_select_location'),
url(r'^pages/new/(\w+)/(\w+)/(\d+)/$', pages.create, name='wagtailadmin_pages_create'),
url(r'^pages/new/(\w+)/(\w+)/(\d+)/preview/$', pages.preview_on_create, name='wagtailadmin_pages_preview_on_create'),
url(r'^pages/usage/(\w+)/(\w+)/$', pages.content_type_use, name='wagtailadmin_pages_type_use'),

View file

@ -40,28 +40,6 @@ def index(request, parent_page_id=None):
})
@permission_required('wagtailadmin.access_admin')
def select_type(request):
# Get the list of page types that can be created within the pages that currently exist
existing_page_types = ContentType.objects.raw("""
SELECT DISTINCT content_type_id AS id FROM wagtailcore_page
""")
all_page_types = sorted(get_page_types(), key=lambda pagetype: pagetype.name.lower())
page_types = set()
for content_type in existing_page_types:
allowed_subpage_types = content_type.model_class().clean_subpage_types()
for subpage_type in allowed_subpage_types:
subpage_content_type = ContentType.objects.get_for_model(subpage_type)
page_types.add(subpage_content_type)
return render(request, 'wagtailadmin/pages/select_type.html', {
'page_types': page_types,
'all_page_types': all_page_types
})
@permission_required('wagtailadmin.access_admin')
def add_subpage(request, parent_page_id):
parent_page = get_object_or_404(Page, id=parent_page_id).specific
@ -78,38 +56,6 @@ def add_subpage(request, parent_page_id):
})
@permission_required('wagtailadmin.access_admin')
def select_location(request, content_type_app_name, content_type_model_name):
try:
content_type = ContentType.objects.get_by_natural_key(content_type_app_name, content_type_model_name)
except ContentType.DoesNotExist:
raise Http404
page_class = content_type.model_class()
# page_class must be a Page type and not some other random model
if not issubclass(page_class, Page):
raise Http404
# find all the valid locations (parent pages) where a page of the chosen type can be added
parent_pages = page_class.allowed_parent_pages()
if len(parent_pages) == 0:
# user cannot create a page of this type anywhere - fail with an error
messages.error(request, _("Sorry, you do not have access to create a page of type <em>'{0}'</em>.").format(content_type.name))
return redirect('wagtailadmin_pages_select_type')
elif len(parent_pages) == 1:
# only one possible location - redirect them straight there
messages.warning(request, _("Pages of this type can only be created as children of <em>'{0}'</em>. This new page will be saved there.").format(parent_pages[0].title))
return redirect('wagtailadmin_pages_create', content_type_app_name, content_type_model_name, parent_pages[0].id)
else:
# prompt them to select a location
return render(request, 'wagtailadmin/pages/select_location.html', {
'content_type': content_type,
'page_class': page_class,
'parent_pages': parent_pages,
})
@permission_required('wagtailadmin.access_admin')
def content_type_use(request, content_type_app_name, content_type_model_name):
try:

View file

@ -638,17 +638,9 @@ def get_navigation_menu_items():
# or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu)
navigable_content_type_ids = get_navigable_page_content_type_ids()
if navigable_content_type_ids:
pages = Page.objects.raw("""
SELECT * FROM wagtailcore_page
WHERE numchild > 0 OR content_type_id IN %s OR depth = 2
ORDER BY path
""", [tuple(navigable_content_type_ids)])
pages = Page.objects.filter(Q(content_type__in=navigable_content_type_ids)|Q(depth=2)|Q(numchild__gt=0)).order_by('path')
else:
pages = Page.objects.raw("""
SELECT * FROM wagtailcore_page
WHERE numchild > 0 OR depth = 2
ORDER BY path
""")
pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
# Turn this into a tree structure:
# tree_node = (page, children)

View file

@ -1,5 +1,7 @@
from django.conf import settings
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin import hooks
@ -21,3 +23,23 @@ def construct_main_menu(request, menu_items):
MenuItem(_('Documents'), urlresolvers.reverse('wagtaildocs_index'), classnames='icon icon-doc-full-inverse', order=400)
)
hooks.register('construct_main_menu', construct_main_menu)
def editor_js():
js_files = [
'wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js',
'wagtaildocs/js/document-chooser.js',
]
js_includes = format_html_join('\n', '<script src="{0}{1}"></script>',
((settings.STATIC_URL, filename) for filename in js_files)
)
return js_includes + format_html(
"""
<script>
window.chooserUrls.documentChooser = '{0}';
registerHalloPlugin('hallowagtaildoclink');
</script>
""",
urlresolvers.reverse('wagtaildocs_chooser')
)
hooks.register('insert_editor_js', editor_js)

View file

@ -152,6 +152,17 @@ def get_embed(url, max_width=None, finder=None):
finder = get_default_finder()
embed_dict = finder(url, max_width)
# Make sure width and height are valid integers before inserting into database
try:
embed_dict['width'] = int(embed_dict['width'])
except (TypeError, ValueError):
embed_dict['width'] = None
try:
embed_dict['height'] = int(embed_dict['height'])
except (TypeError, ValueError):
embed_dict['height'] = None
# Create database record
embed, created = Embed.objects.get_or_create(
url=url,

View file

@ -8,6 +8,20 @@ class TestEmbeds(TestCase):
def setUp(self):
self.hit_count = 0
def dummy_finder(self, url, max_width=None):
# Up hit count
self.hit_count += 1
# Return a pretend record
return {
'title': "Test: " + url,
'type': 'video',
'thumbnail_url': '',
'width': max_width if max_width else 640,
'height': 480,
'html': "<p>Blah blah blah</p>",
}
def test_get_embed(self):
embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder)
@ -31,20 +45,23 @@ class TestEmbeds(TestCase):
embed = get_embed('www.test.com/4321', finder=self.dummy_finder)
self.assertEqual(self.hit_count, 3)
def dummy_finder(self, url, max_width=None):
# Up hit count
self.hit_count += 1
# Return a pretend record
def dummy_finder_invalid_width(self, url, max_width=None):
# Return a record with an invalid width
return {
'title': "Test: " + url,
'type': 'video',
'thumbnail_url': '',
'width': max_width if max_width else 640,
'width': '100%',
'height': 480,
'html': "<p>Blah blah blah</p>",
}
def test_invalid_width(self):
embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder_invalid_width)
# Width must be set to None
self.assertEqual(embed.width, None)
class TestChooser(TestCase):
def setUp(self):

View file

@ -1,4 +1,7 @@
from django.conf import settings
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html
from wagtail.wagtailadmin import hooks
from wagtail.wagtailembeds import urls
@ -9,3 +12,18 @@ def register_admin_urls():
url(r'^embeds/', include(urls)),
]
hooks.register('register_admin_urls', register_admin_urls)
def editor_js():
return format_html("""
<script src="{0}{1}"></script>
<script>
window.chooserUrls.embedsChooser = '{2}';
registerHalloPlugin('hallowagtailembeds');
</script>
""",
settings.STATIC_URL,
'wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js',
urlresolvers.reverse('wagtailembeds_chooser')
)
hooks.register('insert_editor_js', editor_js)

View file

@ -4,7 +4,7 @@ import os.path
from taggit.managers import TaggableManager
from django.core.files import File
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, ValidationError
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db import models
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
@ -17,6 +17,8 @@ from unidecode import unidecode
from wagtail.wagtailadmin.taggable import TagSearchable
from wagtail.wagtailimages.backends import get_image_backend
from .utils import validate_image_format
class AbstractImage(models.Model, TagSearchable):
title = models.CharField(max_length=255, verbose_name=_('Title') )
@ -34,12 +36,7 @@ class AbstractImage(models.Model, TagSearchable):
filename = prefix[:-1] + dot + extension
return os.path.join(folder_name, filename)
def file_extension_validator(ffile):
extension = ffile.name.split(".")[-1].lower()
if extension not in ["gif", "jpg", "jpeg", "png"]:
raise ValidationError(_("Not a valid image format. Please use a gif, jpeg or png file instead."))
file = models.ImageField(verbose_name=_('File'), upload_to=get_upload_to, width_field='width', height_field='height', validators=[file_extension_validator])
file = models.ImageField(verbose_name=_('File'), upload_to=get_upload_to, width_field='width', height_field='height', validators=[validate_image_format])
width = models.IntegerField(editable=False)
height = models.IntegerField(editable=False)
created_at = models.DateTimeField(auto_now_add=True)

View file

@ -4,9 +4,7 @@ from django.contrib.auth.models import User, Group, Permission
from django.core.urlresolvers import reverse
from django.core.files.uploadedfile import SimpleUploadedFile
import unittest2 as unittest
from wagtail.tests.utils import login
from wagtail.tests.utils import login, unittest
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.templatetags import image_tags

View file

@ -0,0 +1,28 @@
import os
from PIL import Image
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
def validate_image_format(f):
# Check file extension
extension = os.path.splitext(f.name)[1].lower()[1:]
if extension == 'jpg':
extension = 'jpeg'
if extension not in ['gif', 'jpeg', 'png']:
raise ValidationError(_("Not a valid image. Please use a gif, jpeg or png file with the correct file extension."))
if not f.closed:
# Open image file
file_position = f.tell()
f.seek(0)
image = Image.open(f)
f.seek(file_position)
# Check that the internal format matches the extension
if image.format.upper() != extension.upper():
raise ValidationError(_("Not a valid %s image. Please use a gif, jpeg or png file with the correct file extension.") % (extension.upper()))

View file

@ -1,5 +1,7 @@
from django.conf import settings
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin import hooks
@ -21,3 +23,23 @@ def construct_main_menu(request, menu_items):
MenuItem(_('Images'), urlresolvers.reverse('wagtailimages_index'), classnames='icon icon-image', order=300)
)
hooks.register('construct_main_menu', construct_main_menu)
def editor_js():
js_files = [
'wagtailimages/js/hallo-plugins/hallo-wagtailimage.js',
'wagtailimages/js/image-chooser.js',
]
js_includes = format_html_join('\n', '<script src="{0}{1}"></script>',
((settings.STATIC_URL, filename) for filename in js_files)
)
return js_includes + format_html(
"""
<script>
window.chooserUrls.imageChooser = '{0}';
registerHalloPlugin('hallowagtailimage');
</script>
""",
urlresolvers.reverse('wagtailimages_chooser')
)
hooks.register('insert_editor_js', editor_js)

View file

@ -2,7 +2,7 @@ from django.test import TestCase
from django.test.utils import override_settings
from django.conf import settings
from django.core import management
import unittest2 as unittest
from wagtail.tests.utils import unittest
from wagtail.wagtailsearch import models, get_search_backend
from wagtail.wagtailsearch.backends.db import DBSearch
from wagtail.wagtailsearch.backends import InvalidSearchBackendError

View file

@ -1,9 +1,8 @@
from django.test import TestCase
from django.core import management
from wagtail.wagtailsearch import models
from wagtail.tests.utils import login
from wagtail.tests.utils import login, unittest
from StringIO import StringIO
import unittest2 as unittest
class TestHitCounter(TestCase):

View file

@ -5,7 +5,7 @@ when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
import unittest2 as unittest
from wagtail.tests.utils import unittest
from django.test import TestCase

View file

@ -1,5 +1,7 @@
from django.conf import settings
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin import hooks
@ -22,3 +24,15 @@ def construct_main_menu(request, menu_items):
MenuItem(_('Snippets'), urlresolvers.reverse('wagtailsnippets_index'), classnames='icon icon-snippet', order=500)
)
hooks.register('construct_main_menu', construct_main_menu)
def editor_js():
return format_html("""
<script src="{0}{1}"></script>
<script>window.chooserUrls.snippetChooser = '{2}';</script>
""",
settings.STATIC_URL,
'wagtailsnippets/js/snippet-chooser.js',
urlresolvers.reverse('wagtailsnippets_choose_generic')
)
hooks.register('insert_editor_js', editor_js)