+ if(el.html().length && !jQuery(this.options.blockElements.toString(), el).length){ + this.options.editable.execute('formatBlock', 'p'); + } + } + }); + })(jQuery); +}).call(this); \ No newline at end of file diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js b/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js index f389bf120..4f53792b9 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js @@ -6,7 +6,8 @@ var halloPlugins = { 'hallolists': {}, 'hallohr': {}, 'halloreundo': {}, - 'hallowagtaillink': {} + 'hallowagtaillink': {}, + 'hallorequireparagraphs': {} }; function registerHalloPlugin(name, opts) { diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/main-nav.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/main-nav.scss index 40e227914..181516da0 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/main-nav.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/main-nav.scss @@ -72,7 +72,9 @@ $submenu-color:darken($color-grey-1, 5%); font-size:0.95em; font-weight:300; - &:hover{ + &:hover, + &:focus{ + outline:none; background-color:rgba(100,100,100,0.15); color:white; text-shadow:-1px -1px 0px rgba(0,0,0,0.3); diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index 15a7adfc2..df4a7320f 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -1,21 +1,21 @@ -@import "variables.scss"; -@import "mixins.scss"; -@import "grid.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; +@import "wagtailadmin/scss/grid.scss"; -@import "components/explorer.scss"; -@import "components/icons.scss"; -@import "components/typography.scss"; -@import "components/tabs.scss"; -@import "components/dropdowns.scss"; -@import "components/modals.scss"; -@import "components/forms.scss"; -@import "components/listing.scss"; -@import "components/messages.scss"; -@import "components/formatters.scss"; -@import "components/header.scss"; -@import "components/progressbar.scss"; -@import "components/datetimepicker.scss"; -@import "components/main-nav.scss"; +@import "wagtailadmin/scss/components/explorer.scss"; +@import "wagtailadmin/scss/components/icons.scss"; +@import "wagtailadmin/scss/components/typography.scss"; +@import "wagtailadmin/scss/components/tabs.scss"; +@import "wagtailadmin/scss/components/dropdowns.scss"; +@import "wagtailadmin/scss/components/modals.scss"; +@import "wagtailadmin/scss/components/forms.scss"; +@import "wagtailadmin/scss/components/listing.scss"; +@import "wagtailadmin/scss/components/messages.scss"; +@import "wagtailadmin/scss/components/formatters.scss"; +@import "wagtailadmin/scss/components/header.scss"; +@import "wagtailadmin/scss/components/progressbar.scss"; +@import "wagtailadmin/scss/components/datetimepicker.scss"; +@import "wagtailadmin/scss/components/main-nav.scss"; @import "fonts.scss"; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/home.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/home.scss index f1e2ee779..3f8e08337 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/home.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/home.scss @@ -1,6 +1,6 @@ -@import "../variables.scss"; -@import "../mixins.scss"; -@import "../grid.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; +@import "wagtailadmin/scss/grid.scss"; h1{ font-weight:300; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss index 24762b4dc..d2c8db46c 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/login.scss @@ -1,6 +1,6 @@ -@import "../variables.scss"; -@import "../mixins.scss"; -@import "../grid.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; +@import "wagtailadmin/scss/grid.scss"; // overrides default nice padding defined in variables.scss $desktop-nice-padding: 100px; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss index ad89ba038..69b3d636d 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss @@ -1,6 +1,6 @@ -@import "../variables.scss"; -@import "../mixins.scss"; -@import "../grid.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; +@import "wagtailadmin/scss/grid.scss"; .page-editor { .content-wrapper{ diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/preview.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/preview.scss index c3318a7a7..545c0e7ba 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/preview.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/preview.scss @@ -1,5 +1,5 @@ -@import "../variables.scss"; -@import "../mixins.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; /* This font is just a single spinner glyph */ @font-face { diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/panels/rich-text.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/panels/rich-text.scss index dc0b63c7a..b9f7170b6 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/panels/rich-text.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/panels/rich-text.scss @@ -1,5 +1,5 @@ -@import "../variables.scss"; -@import "../mixins.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; .hallotoolbar{ position:absolute; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss index af9e20195..4fa6cb891 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/userbar.scss @@ -1,9 +1,9 @@ -@import "variables.scss"; -@import "mixins.scss"; +@import "wagtailadmin/scss/variables.scss"; +@import "wagtailadmin/scss/mixins.scss"; -@import "components/icons.scss"; +@import "wagtailadmin/scss/components/icons.scss"; -@import "fonts.scss"; +@import "wagtailadmin/scss/fonts.scss"; html, body{ background-color:transparent; diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt b/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt index b9b64d35e..892a4b93d 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt +++ b/wagtail/wagtailadmin/templates/wagtailadmin/account/password_reset/email.txt @@ -1,3 +1,3 @@ -{% load i18n %} +{% load i18n wagtailadmin_tags %}{% base_url_setting as base_url %} {% trans "Please follow the link below to reset your password" %} -{{ protocol }}://{{ domain }}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %} \ No newline at end of file +{% if base_url %}{{ base_url }}{% else %}{{ protocol }}://{{ domain }}{% endif %}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %} \ No newline at end of file diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html index a15ca2b67..ee9eff8ad 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html @@ -18,6 +18,7 @@ + diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index 0f5987d1e..79666bb3f 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -136,6 +136,11 @@ def usage_count_enabled(): return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False) +@register.assignment_tag +def base_url_setting(): + return getattr(settings, 'BASE_URL', None) + + class EscapeScriptNode(template.Node): TAG_NAME = 'escapescript' diff --git a/wagtail/wagtailadmin/tests/test_page_chooser.py b/wagtail/wagtailadmin/tests/test_page_chooser.py index 475e4f7b1..834acde8c 100644 --- a/wagtail/wagtailadmin/tests/test_page_chooser.py +++ b/wagtail/wagtailadmin/tests/test_page_chooser.py @@ -101,9 +101,24 @@ class TestChooserExternalLink(TestCase, WagtailTestUtils): self.assertEqual(self.get({'prompt_for_link_text': 'foo'}).status_code, 200) def test_create_link(self): - request = self.post({'url': 'http://www.example.com'}) - self.assertContains(request, "'url': 'http://www.example.com/',") - self.assertContains(request, "'title': 'http://www.example.com/'") + response = self.post({'url': 'http://www.example.com'}) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "'onload'") # indicates success / post back to calling page + self.assertContains(response, "'url': 'http://www.example.com/',") + self.assertContains(response, "'title': 'http://www.example.com/'") + + def test_invalid_url(self): + response = self.post({'url': 'ntp://www.example.com'}) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "'html'") # indicates failure / show error message + self.assertContains(response, "Enter a valid URL.") + + def test_allow_local_url(self): + response = self.post({'url': '/admin/'}) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "'onload'") # indicates success / post back to calling page + self.assertContains(response, "'url': '/admin/',") + self.assertContains(response, "'title': '/admin/'") class TestChooserEmailLink(TestCase, WagtailTestUtils): diff --git a/wagtail/wagtailadmin/tests/test_password_reset.py b/wagtail/wagtailadmin/tests/test_password_reset.py new file mode 100644 index 000000000..e0a1dd1b4 --- /dev/null +++ b/wagtail/wagtailadmin/tests/test_password_reset.py @@ -0,0 +1,33 @@ +from django.test import TestCase, override_settings +from django.core import mail + +from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailcore.models import Site + + +class TestUserPasswordReset(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + # need to clear urlresolver caches before/after tests, because we override ROOT_URLCONF + # in some tests here + def setUp(self): + from django.core.urlresolvers import clear_url_caches + clear_url_caches() + + def tearDown(self): + from django.core.urlresolvers import clear_url_caches + clear_url_caches() + + @override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls") + def test_email_found_default_url(self): + response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'}) + self.assertEqual(response.status_code, 302) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("testserver", mail.outbox[0].body) + + @override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls", BASE_URL='http://mysite.com') + def test_email_found_base_url(self): + response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'}) + self.assertEqual(response.status_code, 302) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("mysite.com", mail.outbox[0].body) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 250a6798d..1a4291e19 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -26,11 +26,7 @@ from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError, ImproperlyConfigured, ObjectDoesNotExist from django.utils.functional import cached_property from django.utils.encoding import python_2_unicode_compatible - -try: - from django.core import checks -except ImportError: - pass +from django.core import checks from treebeard.mp_tree import MP_Node diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index 117d3bfed..e0060e49b 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -1,5 +1,6 @@ from six import b import unittest +import mock from django.test import TestCase from django.contrib.auth import get_user_model @@ -17,9 +18,6 @@ from wagtail.wagtaildocs.models import Document from wagtail.wagtaildocs import models -# TODO: Test serve view - - class TestDocumentPermissions(TestCase): def setUp(self): # Create some user accounts for testing permissions @@ -519,3 +517,46 @@ class TestIssue613(TestCase, WagtailTestUtils): # Check self.assertEqual(len(results), 1) self.assertEqual(results[0].id, document.id) + + +class TestServeView(TestCase): + def setUp(self): + self.document = models.Document(title="Test document") + self.document.file.save('example.doc', ContentFile("A boring example document")) + + def get(self): + return self.client.get(reverse('wagtaildocs_serve', args=(self.document.id, 'example.doc'))) + + def test_response_code(self): + self.assertEqual(self.get().status_code, 200) + + @unittest.expectedFailure # Filename has a random string appended to it + def test_content_disposition_header(self): + self.assertEqual(self.get()['Content-Disposition'], 'attachment; filename=example.doc') + + def test_content_length_header(self): + self.assertEqual(self.get()['Content-Length'], '25') + + def test_is_streaming_response(self): + self.assertTrue(self.get().streaming) + + def test_content(self): + self.assertEqual(b"".join(self.get().streaming_content), b"A boring example document") + + def test_document_served_fired(self): + mock_handler = mock.MagicMock() + models.document_served.connect(mock_handler) + + self.get() + + self.assertEqual(mock_handler.call_count, 1) + self.assertEqual(mock_handler.mock_calls[0][2]['sender'], self.document) + + def test_with_nonexistent_document(self): + response = self.client.get(reverse('wagtaildocs_serve', args=(1000, 'blahblahblah', ))) + self.assertEqual(response.status_code, 404) + + @unittest.expectedFailure + def test_with_incorrect_filename(self): + response = self.client.get(reverse('wagtaildocs_serve', args=(self.document.id, 'incorrectfilename'))) + self.assertEqual(response.status_code, 404) diff --git a/wagtail/wagtaildocs/views/serve.py b/wagtail/wagtaildocs/views/serve.py index ff89c4543..c10a36989 100644 --- a/wagtail/wagtaildocs/views/serve.py +++ b/wagtail/wagtaildocs/views/serve.py @@ -1,6 +1,6 @@ from django.shortcuts import get_object_or_404 -from django.core.servers.basehttp import FileWrapper -from django.http import HttpResponse +from wsgiref.util import FileWrapper +from django.http import StreamingHttpResponse from wagtail.wagtaildocs.models import Document, document_served @@ -8,7 +8,7 @@ from wagtail.wagtaildocs.models import Document, document_served def serve(request, document_id, document_filename): doc = get_object_or_404(Document, id=document_id) wrapper = FileWrapper(doc.file) - response = HttpResponse(wrapper, content_type='application/octet-stream') + response = StreamingHttpResponse(wrapper, content_type='application/octet-stream') # TODO: strip out weird characters like semicolons from the filename # (there doesn't seem to be an official way of escaping them) diff --git a/wagtail/wagtailimages/rect.py b/wagtail/wagtailimages/rect.py index cd56f4b0d..a398df59a 100644 --- a/wagtail/wagtailimages/rect.py +++ b/wagtail/wagtailimages/rect.py @@ -57,13 +57,3 @@ class Rect(object): x + width / 2, y + height / 2, ) - - - # DELETEME - def get_key(self): - return "%(x)d-%(y)d-%(width)dx%(height)d" % { - 'x': int(self.centroid_x), - 'y': int(self.centroid_y), - 'width': int(self.width), - 'height': int(self.height), - } diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py index 6df1dba56..2701ab145 100644 --- a/wagtail/wagtailimages/tests/tests.py +++ b/wagtail/wagtailimages/tests/tests.py @@ -237,7 +237,3 @@ class TestRect(TestCase): def test_from_point(self): rect = Rect.from_point(100, 200, 50, 20) self.assertEqual(rect, Rect(75, 190, 125, 210)) - - def test_get_key(self): - rect = Rect(100, 150, 200, 250) - self.assertEqual(rect.get_key(), '150-200-100x100') diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index 43e52a73e..ca2719d54 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -16,6 +16,8 @@ from wagtail.wagtailcore.models import Page, GroupPagePermission class TestUserIndexView(TestCase, WagtailTestUtils): def setUp(self): + # create a user that should be visible in the listing + self.test_user = get_user_model().objects.create_user(username='testuser', email='testuser@email.com', password='password') self.login() def get(self, params={}): @@ -25,6 +27,15 @@ class TestUserIndexView(TestCase, WagtailTestUtils): response = self.get() self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailusers/users/index.html') + self.assertContains(response, 'testuser') + + def test_allows_negative_ids(self): + # see https://github.com/torchbox/wagtail/issues/565 + get_user_model().objects.create_user('guardian', 'guardian@example.com', 'gu@rd14n', id=-1) + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'testuser') + self.assertContains(response, 'guardian') def test_search(self): response = self.get({'q': "Hello"}) @@ -215,7 +226,7 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): new_group = Group.objects.get(name='test group') self.assertEqual(new_group.page_permissions.all().count(), 2) - @unittest.skip("currently failing on Django 1.7") + @unittest.expectedFailure def test_duplicate_page_permissions_error(self): # Try to submit duplicate page permission entries response = self.post({ diff --git a/wagtail/wagtailusers/urls/users.py b/wagtail/wagtailusers/urls/users.py index 78487fc5a..8ddd0a83d 100644 --- a/wagtail/wagtailusers/urls/users.py +++ b/wagtail/wagtailusers/urls/users.py @@ -4,5 +4,5 @@ from wagtail.wagtailusers.views import users urlpatterns = [ url(r'^$', users.index, name='wagtailusers_users_index'), url(r'^new/$', users.create, name='wagtailusers_users_create'), - url(r'^(\d+)/$', users.edit, name='wagtailusers_users_edit'), + url(r'^([^\/]+)/$', users.edit, name='wagtailusers_users_edit'), ]