From c397a62d49d564b36b79650fe85a20e0451d3d86 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Thu, 13 Feb 2014 15:33:53 +0000 Subject: [PATCH 01/11] Update README.rst with link to @spapas's tutorial --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 49231a78c..a55c3f011 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ Find out more at `wagtail.io `_. Getting started ~~~~~~~~~~~~~~~ -To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo `_; see the `README `_ for installation instructions. +To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo `_; see the `README `_ for installation instructions. `Serafeim Papastefanos `_ has kindly written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. Contributing ~~~~~~~~~~~~ From 8daf6b3407aebff2d7ea3fcfc6e0bc6b259353e5 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 13 Feb 2014 15:47:19 +0000 Subject: [PATCH 02/11] added user searching --- .../static/wagtailadmin/css/core.less | 1 + .../wagtailadmin/css/layouts/login.less | 4 +- .../wagtaildocs/documents/index.html | 5 -- .../templates/wagtailusers/index.html | 75 ++++++++----------- .../templates/wagtailusers/list.html | 40 ++++++++++ .../templates/wagtailusers/results.html | 15 ++++ wagtail/wagtailusers/views/users.py | 40 ++++++++-- 7 files changed, 123 insertions(+), 57 deletions(-) create mode 100644 wagtail/wagtailusers/templates/wagtailusers/list.html create mode 100644 wagtail/wagtailusers/templates/wagtailusers/results.html diff --git a/wagtail/wagtailadmin/static/wagtailadmin/css/core.less b/wagtail/wagtailadmin/static/wagtailadmin/css/core.less index 7980d678b..5e599cc4a 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/css/core.less +++ b/wagtail/wagtailadmin/static/wagtailadmin/css/core.less @@ -63,6 +63,7 @@ h2{ } } a{ + outline:none; color:@color-link; text-decoration:none; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/css/layouts/login.less b/wagtail/wagtailadmin/static/wagtailadmin/css/layouts/login.less index 3c523631c..cae9fcb24 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/css/layouts/login.less +++ b/wagtail/wagtailadmin/static/wagtailadmin/css/layouts/login.less @@ -136,7 +136,6 @@ form{ } } - @media screen and (min-width: @breakpoint-mobile){ .content-wrapper{ float:none; @@ -154,10 +153,11 @@ form{ &:before { content: ''; + width:0px; display: inline-block; height: 100%; vertical-align: middle; - margin-left:-0.3em; + margin-left:-0.4em; } } diff --git a/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html b/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html index 41668caee..c640db94b 100644 --- a/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html +++ b/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html @@ -8,11 +8,6 @@ var wait = setTimeout(search, 200); $(this).data('timer', wait); }); - // $('a.suggested-tag').click(function() { - // $('#id_q').val($(this).text()); - // search(); - // return false; - // }) var search_current_index = 0; var search_next_index = 0; diff --git a/wagtail/wagtailusers/templates/wagtailusers/index.html b/wagtail/wagtailusers/templates/wagtailusers/index.html index 12e7f193d..63acea8e3 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/index.html +++ b/wagtail/wagtailusers/templates/wagtailusers/index.html @@ -2,51 +2,40 @@ {% load gravatar %} {% block titletag %}Users{% endblock %} {% block bodyclass %}menu-users{% endblock %} -{% block content %} +{% block extra_js %} + +{% endblock %} + +{% block content %} + {% include "wagtailadmin/shared/header.html" with title="Users" add_link="wagtailusers_create" add_text="Add a user" search_url="wagtailusers_index" %}
- - - - - - - - - - - {% for user in users %} - - - - - - - {% endfor %} - -
- Name - {% if ordering == "name" %} - - {% else %} - - {% endif %} - - Username - {% if ordering == "username" %} - - {% else %} - - {% endif %} - LevelStatus
-

- - {{ user.get_full_name|default:user.username }} -

-
{{ user.username }}{% if user.is_superuser %}Admin{% endif %}
{% if user.is_active %}Active{% else %}Inactive{% endif %}
- - {% include "wagtailadmin/shared/pagination_nav.html" with items=users linkurl="wagtailusers_index" %} +
+ {% include "wagtailusers/results.html" %} +
{% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/list.html b/wagtail/wagtailusers/templates/wagtailusers/list.html new file mode 100644 index 000000000..32733b195 --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/list.html @@ -0,0 +1,40 @@ +{% load gravatar %} + + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
+ Name + {% if ordering == "name" %} + + {% else %} + + {% endif %} + + Username + {% if ordering == "username" %} + + {% else %} + + {% endif %} + LevelStatus
+

+ + {{ user.get_full_name|default:user.username }} +

+
{{ user.username }}{% if user.is_superuser %}Admin{% endif %}
{% if user.is_active %}Active{% else %}Inactive{% endif %}
\ No newline at end of file diff --git a/wagtail/wagtailusers/templates/wagtailusers/results.html b/wagtail/wagtailusers/templates/wagtailusers/results.html new file mode 100644 index 000000000..dbd367aca --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/results.html @@ -0,0 +1,15 @@ +{% if users %} + {% if is_searching %} +

{{ users|length }} match{{ users|pluralize:"es" }}

+ {% endif %} + + {% include "wagtailusers/list.html" %} + + {% include "wagtailadmin/shared/pagination_nav.html" with items=users is_searching=is_searching linkurl="wagtailusers_index" %} +{% else %} + {% if is_searching %} +

Sorry, no users match "{{ search_query }}" + {% else %} +

There are no users configured. Why not add some?

+ {% endif %} +{% endif %} diff --git a/wagtail/wagtailusers/views/users.py b/wagtail/wagtailusers/views/users.py index cc35d249c..b871b14e3 100644 --- a/wagtail/wagtailusers/views/users.py +++ b/wagtail/wagtailusers/views/users.py @@ -3,15 +3,31 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import permission_required from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages +from django.db.models import Q +from wagtail.wagtailadmin.forms import SearchForm from wagtail.wagtailusers.forms import UserCreationForm, UserEditForm - @permission_required('auth.change_user') def index(request): + q = None p = request.GET.get("p", 1) + is_searching = False - users = User.objects.order_by('last_name', 'first_name') + if 'q' in request.GET: + form = SearchForm(request.GET, placeholder_suffix="users") + if form.is_valid(): + q = form.cleaned_data['q'] + + is_searching = True + users = User.objects.filter(Q(username__icontains=q) | Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(email__icontains=q)) + else: + form = SearchForm(placeholder_suffix="users") + + if not is_searching: + users = User.objects + + users = users.order_by('last_name', 'first_name') if 'ordering' in request.GET: ordering = request.GET['ordering'] @@ -31,11 +47,21 @@ def index(request): except EmptyPage: users = paginator.page(paginator.num_pages) - return render(request, 'wagtailusers/index.html', { - 'users': users, - 'ordering': ordering, - }) - + if request.is_ajax(): + return render(request, "wagtailusers/results.html", { + 'users': users, + 'is_searching': is_searching, + 'search_query': q, + 'ordering': ordering, + }) + else: + return render(request, "wagtailusers/index.html", { + 'search_form': form, + 'users': users, + 'is_searching': is_searching, + 'ordering': ordering, + 'search_query': q, + }) @permission_required('auth.change_user') def create(request): From 1e0d63ee729dfbfb71b85ec4011a1aa3ae7aec9d Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 13 Feb 2014 16:00:38 +0000 Subject: [PATCH 03/11] password inputs should reflect errors too --- .../wagtailadmin/static/wagtailadmin/css/components/forms.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less b/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less index 5f51fd00d..19d9d2b16 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less +++ b/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less @@ -378,7 +378,8 @@ li.focused > .help{ color:@color-red; clear:both; } -.error input[type=text], .error input[type=email], .error input[type=tel], .error textarea, .error select, .error .tagit{ + +.error input, .error textarea, .error select, .error .tagit{ border-color:@color-red; background-color:@color-input-error-bg; } From d08ef04dcba6e7d6e1590af50b1e7ad198afaa91 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 13 Feb 2014 16:51:11 +0000 Subject: [PATCH 04/11] unified search in header bar. still todo: searches in modals --- .../wagtailadmin/css/components/forms.less | 2 +- .../static/wagtailadmin/js/core.js | 25 ++++++++++++++ .../wagtailadmin/chooser/_search_behaviour.js | 2 ++ .../wagtailadmin/pages/_editor_js.html | 4 +-- .../templates/wagtailadmin/pages/search.html | 34 +++---------------- .../wagtaildocs/documents/index.html | 28 +++------------ .../templates/wagtailimages/images/index.html | 34 +++---------------- .../templates/wagtailredirects/index.html | 29 +++------------- .../wagtailsearch/editorspicks/index.html | 29 +++------------- .../templates/wagtailusers/index.html | 28 +++------------ 10 files changed, 59 insertions(+), 156 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less b/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less index 19d9d2b16..7c59e234f 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less +++ b/wagtail/wagtailadmin/static/wagtailadmin/css/components/forms.less @@ -563,7 +563,7 @@ ul.inline li:first-child, li.inline:first-child{ } -/* search bars (search integrated into header area) */ +/* search bars (search integrated into header area) */ .search-bar{ margin-top:-2em; padding-top:1em; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js index 49551abd7..5c35629ca 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js @@ -106,4 +106,29 @@ $(function(){ $('#menu-search').bind('focus click', function(){ $(this).addClass('focussed'); }); + + /* Header search behaviour */ + var search_current_index = 0; + var search_next_index = 0; + + $(window.headerSearch.termInput).on('input', function() { + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 200); + $(this).data('timer', wait); + }); + + function search () { + search_next_index++; + var index = search_next_index; + $.ajax({ + url: window.headerSearch.url, + data: {q: $(window.headerSearch.termInput).val()}, + success: function(data, status) { + if (index > search_current_index) { + search_current_index = index; + $(window.headerSearch.targetOutput).html(data); + } + }, + }); + }; }); diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js b/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js index 252f015b3..9c3f77154 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js +++ b/wagtail/wagtailadmin/templates/wagtailadmin/chooser/_search_behaviour.js @@ -1,6 +1,7 @@ modal.ajaxifyForm($('form.search-bar', modal.body)); var searchUrl = $('form.search-bar', modal.body).attr('action'); + function search() { $.ajax({ url: searchUrl, @@ -12,6 +13,7 @@ function search() { }); return false; } + $('#id_q', modal.body).on('input', function() { clearTimeout($.data(this, 'timer')); var wait = setTimeout(search, 200); diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html index a88f6143d..68c7ebdd9 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html @@ -31,7 +31,7 @@ {% endcompress %} - - {% endblock %} diff --git a/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html b/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html index c640db94b..84dc5a86b 100644 --- a/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html +++ b/wagtail/wagtaildocs/templates/wagtaildocs/documents/index.html @@ -3,29 +3,11 @@ {% block bodyclass %}menu-documents{% endblock %} {% block extra_js %} {% endblock %} diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/index.html b/wagtail/wagtailimages/templates/wagtailimages/images/index.html index c972620c7..7515e5852 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/images/index.html +++ b/wagtail/wagtailimages/templates/wagtailimages/images/index.html @@ -5,35 +5,11 @@ {% block bodyclass %}menu-images{% endblock %} {% block extra_js %} {% endblock %} diff --git a/wagtail/wagtailredirects/templates/wagtailredirects/index.html b/wagtail/wagtailredirects/templates/wagtailredirects/index.html index 76c25a930..706969957 100644 --- a/wagtail/wagtailredirects/templates/wagtailredirects/index.html +++ b/wagtail/wagtailredirects/templates/wagtailredirects/index.html @@ -4,30 +4,11 @@ {% block extra_js %} {% endblock %} diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/index.html b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/index.html index 1628b60f9..2c0948e0a 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/index.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/index.html @@ -4,30 +4,11 @@ {% block extra_js %} {% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/index.html b/wagtail/wagtailusers/templates/wagtailusers/index.html index 63acea8e3..c10e6c207 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/index.html +++ b/wagtail/wagtailusers/templates/wagtailusers/index.html @@ -4,29 +4,11 @@ {% block bodyclass %}menu-users{% endblock %} {% block extra_js %} {% endblock %} From a819527cab8391c1f3e5380ebb027d50570ce3de Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 13 Feb 2014 16:56:38 +0000 Subject: [PATCH 05/11] Update README.rst --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a55c3f011..4720af8e1 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,9 @@ Wagtail is a Django content management system built originally for the `Royal Co * Fast out of the box. `Varnish `_-friendly if you need it * Tests! But not enough; we're working hard to improve this -Find out more at `wagtail.io `_. +Find out more at `wagtail.io `_. + +Got a question? Ask it on our `Google Group `_. Getting started ~~~~~~~~~~~~~~~ From 05e4e5240872f24a36c72ecf944dde3574e1ee99 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Thu, 13 Feb 2014 22:54:11 +0000 Subject: [PATCH 06/11] Change tutorial link to Serafeim's blog --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4720af8e1..0664ff04e 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Got a question? Ask it on our `Google Group `_; see the `README `_ for installation instructions. `Serafeim Papastefanos `_ has kindly written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. +To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo `_; see the `README `_ for installation instructions. `Serafeim Papastefanos `_ has written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. Contributing ~~~~~~~~~~~~ From f2ca7426ec79dfcf199acdcde6ccec3dd3709e6f Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 13 Feb 2014 23:15:07 +0000 Subject: [PATCH 07/11] Added 'embed finders'. Cleaned up wagtail embeds --- wagtail/wagtailembeds/__init__.py | 2 +- wagtail/wagtailembeds/embeds.py | 181 +++++++++++++----- wagtail/wagtailembeds/embeds/__init__.py | 0 wagtail/wagtailembeds/embeds/embed.py | 80 -------- wagtail/wagtailembeds/embeds/oembed_api.py | 52 ----- wagtail/wagtailembeds/embeds/unittests.py | 66 ------- wagtail/wagtailembeds/endpoints.json | 114 ----------- wagtail/wagtailembeds/format.py | 6 +- .../endpoints.json => oembed_providers.py} | 30 ++- .../templatetags/embed_filters.py | 2 +- wagtail/wagtailembeds/tests.py | 50 ++++- wagtail/wagtailembeds/views/chooser.py | 28 ++- 12 files changed, 215 insertions(+), 396 deletions(-) delete mode 100644 wagtail/wagtailembeds/embeds/__init__.py delete mode 100644 wagtail/wagtailembeds/embeds/embed.py delete mode 100644 wagtail/wagtailembeds/embeds/oembed_api.py delete mode 100644 wagtail/wagtailembeds/embeds/unittests.py delete mode 100644 wagtail/wagtailembeds/endpoints.json rename wagtail/wagtailembeds/{embeds/endpoints.json => oembed_providers.py} (94%) diff --git a/wagtail/wagtailembeds/__init__.py b/wagtail/wagtailembeds/__init__.py index 8e3708ca9..b75cbc491 100644 --- a/wagtail/wagtailembeds/__init__.py +++ b/wagtail/wagtailembeds/__init__.py @@ -1,2 +1,2 @@ from .models import Embed -from .embeds.embed import get_embed +from .embeds import get_embed diff --git a/wagtail/wagtailembeds/embeds.py b/wagtail/wagtailembeds/embeds.py index a2cff4d62..d6be04279 100644 --- a/wagtail/wagtailembeds/embeds.py +++ b/wagtail/wagtailembeds/embeds.py @@ -1,29 +1,53 @@ -from datetime import datetime - - +import sys +from importlib import import_module +import requests from django.conf import settings - -from .models import Embed - -import os -module_dir = os.path.dirname(__file__) # get current directory -file_path = os.path.join(module_dir, 'endpoints.json') -print file_path -print open(file_path).read() +from datetime import datetime +from django.utils import six +from wagtail.wagtailembeds.oembed_providers import get_oembed_provider +from wagtail.wagtailembeds.models import Embed -def get_embed_embedly(url, max_width=None): - # Check database +class EmbedNotFoundException(Exception): pass + +class EmbedlyException(Exception): pass +class AccessDeniedEmbedlyException(EmbedlyException): pass + + +# Pinched from django 1.7 source code. +# TODO: Replace this with "from django.utils.module_loading import import_string" when django 1.7 is released +def import_string(dotted_path): + """ + Import a dotted module path and return the attribute/class designated by the + last name in the path. Raise ImportError if the import failed. + """ try: - return Embed.objects.get(url=url, max_width=max_width) - except Embed.DoesNotExist: - pass + module_path, class_name = dotted_path.rsplit('.', 1) + except ValueError: + msg = "%s doesn't look like a module path" % dotted_path + six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + + module = import_module(module_path) try: - # Call embedly API - client = Embedly(key=settings.EMBEDLY_KEY) + return getattr(module, class_name) except AttributeError: - return None + msg = 'Module "%s" does not define a "%s" attribute/class' % ( + dotted_path, class_name) + six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + + +def embedly(url, max_width=None, key=None): + from embedly import Embedly + + # Get embedly key + if key is None: + key = settings.EMBEDLY_KEY + + # Get embedly client + client = Embedly(key=settings.EMBEDLY_KEY) + + # Call embedly if max_width is not None: oembed = client.oembed(url, maxwidth=max_width, better=False) else: @@ -31,45 +55,98 @@ def get_embed_embedly(url, max_width=None): # Check for error if oembed.get('error'): - return None - - # Save result to database - row, created = Embed.objects.get_or_create( - url=url, - max_width=max_width, - defaults={ - 'type': oembed['type'], - 'title': oembed['title'], - 'thumbnail_url': oembed.get('thumbnail_url'), - 'width': oembed.get('width'), - 'height': oembed.get('height') - } - ) + if oembed['error_code'] in [401, 403]: + raise AccessDeniedEmbedlyException + elif oembed['error_code'] == 404: + raise EmbedNotFoundException + else: + raise EmbedlyException + # Convert photos into HTML if oembed['type'] == 'photo': html = '' % (oembed['url'], ) else: html = oembed.get('html') - if html: - row.html = html - row.last_updated = datetime.now() - row.save() + # Return embed as a dict + return { + 'title': oembed['title'], + 'type': oembed['type'], + 'thumbnail_url': oembed.get('thumbnail_url'), + 'width': oembed.get('width'), + 'height': oembed.get('height'), + 'html': html, + } - # Return new embed - return row -def get_embed_oembed(url, max_width=None): - pass - -get_embed = get_embed_oembed -try: - from embedly import Embedly - if hasattr(settings,'EMBEDLY_KEY'): - get_embed = get_embed_embedly -except: - pass - -print get_embed +def oembed(url, max_width=None): + # Find provider + provider = get_oembed_provider(url) + if provider is None: + raise EmbedNotFoundException - \ No newline at end of file + # Work out params + params = {'url': url, 'format': 'json', } + if max_width: + params['maxwidth'] = max_width + + # Perform request + r = requests.get(provider, params=params) + if r.status_code != 200: + raise EmbedNotFoundException + oembed = r.json() + + # Convert photos into HTML + if oembed['type'] == 'photo': + html = '' % (oembed['url'], ) + else: + html = oembed.get('html') + + # Return embed as a dict + return { + 'title': oembed['title'], + 'type': oembed['type'], + 'thumbnail_url': oembed.get('thumbnail_url'), + 'width': oembed.get('width'), + 'height': oembed.get('height'), + 'html': html, + } + + +def get_default_finder(): + # Check if the user has set the embed finder manually + if hasattr(settings, 'WAGTAILEMBEDS_EMBED_FINDER'): + return import_string(settings.WAGTAILEMBEDS_EMBED_FINDER) + + # Use embedly if the embedly key is set + if hasattr(settings, 'EMBEDLY_KEY'): + return embedly + + # Fall back to oembed + return oembed + + +def get_embed(url, max_width=None, finder=None): + # Check database + try: + return Embed.objects.get(url=url, max_width=max_width) + except Embed.DoesNotExist: + pass + + # Get/Call finder + if not finder: + finder = get_default_finder() + embed_dict = finder(url, max_width) + + # Create database record + embed, created = Embed.objects.get_or_create( + url=url, + max_width=max_width, + defaults=embed_dict, + ) + + # Save + embed.last_updated = datetime.now() + embed.save() + + return embed diff --git a/wagtail/wagtailembeds/embeds/__init__.py b/wagtail/wagtailembeds/embeds/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/wagtail/wagtailembeds/embeds/embed.py b/wagtail/wagtailembeds/embeds/embed.py deleted file mode 100644 index 3d91a7fae..000000000 --- a/wagtail/wagtailembeds/embeds/embed.py +++ /dev/null @@ -1,80 +0,0 @@ -from datetime import datetime -from django.conf import settings -from ..models import Embed -import oembed_api - -class EmbedlyException(Exception): pass -class AccessDeniedEmbedlyException(Exception): pass -class NotFoundEmbedlyException(Exception): pass - -def get_embed_embedly(url, max_width=None): - # Check database - try: - return Embed.objects.get(url=url, max_width=max_width) - except Embed.DoesNotExist: - pass - - client = Embedly(key=settings.EMBEDLY_KEY) - - if max_width is not None: - oembed = client.oembed(url, maxwidth=max_width, better=False) - else: - oembed = client.oembed(url, better=False) - - # Check for error - if oembed.get('error'): - if oembed['error_code'] in [401,403]: - raise AccessDeniedEmbedlyException - elif oembed['error_code'] == 404: - raise NotFoundEmbedlyException - else: - raise EmbedlyException - - return save_embed(url, max_width, oembed) - - -def get_embed_oembed(url, max_width=None): - # Check database - try: - return Embed.objects.get(url=url, max_width=max_width) - except Embed.DoesNotExist: - pass - - oembed = oembed_api.get_embed_oembed(url, max_width) - return save_embed(url, max_width, oembed) - - -def save_embed(url, max_width, oembed): - row, created = Embed.objects.get_or_create( - url=url, - max_width=max_width, - defaults={ - 'type': oembed['type'], - 'title': oembed['title'], - 'thumbnail_url': oembed.get('thumbnail_url'), - 'width': oembed.get('width'), - 'height': oembed.get('height') - } - ) - - if oembed['type'] == 'photo': - html = '' % (oembed['url'], ) - else: - html = oembed.get('html') - - if html: - row.html = html - row.last_updated = datetime.now() - row.save() - - return row - -# As a default use oembed -get_embed = get_embed_oembed -try: - from embedly import Embedly - # if EMBEDLY_KEY is set and embedly library found the use embedly - if hasattr(settings,'EMBEDLY_KEY'): - get_embed = get_embed_embedly -except: - pass diff --git a/wagtail/wagtailembeds/embeds/oembed_api.py b/wagtail/wagtailembeds/embeds/oembed_api.py deleted file mode 100644 index 8f7945577..000000000 --- a/wagtail/wagtailembeds/embeds/oembed_api.py +++ /dev/null @@ -1,52 +0,0 @@ -import os, re -import urllib2, urllib -from datetime import datetime -import json - -class NotImplementedOembedException(Exception): - pass - -ENDPOINTS = {} - -def get_embed_oembed(url, max_width=None): - provider = None - for endpoint in ENDPOINTS.keys(): - for pattern in ENDPOINTS[endpoint]: - if re.match(pattern, url): - provider = endpoint - break - if not provider: - raise NotImplementedOembedException - params = {'url': url, 'format': 'json', } - if max_width: - params['maxwidth'] = max_width - req = provider+'?' +urllib.urlencode(params) - request = urllib2.Request(req) - opener = urllib2.build_opener() - # Some provicers were not working without a user agent - request.add_header('User-Agent','Mozilla/5.0') - return json.loads(opener.open(request).read()) - - -# Uses the public domain collection of oembed endpoints by Mathias Panzenbpeck (panzi) -# at https://github.com/panzi/oembedendpoints/blob/master/endpoints-regexp.json - -def load_oembed_endpoints(): - module_dir = os.path.dirname(__file__) - endpoints_path = os.path.join(module_dir, 'endpoints.json') - with open( endpoints_path) as f: - endpoints = json.loads(f.read()) - - for endpoint in endpoints.keys(): - endpoint_key = endpoint.replace('{format}', 'json') - - ENDPOINTS[endpoint_key]=[] - for pattern in endpoints[endpoint]: - ENDPOINTS[endpoint_key].append(re.compile(pattern)) - - - -load_oembed_endpoints() - - - \ No newline at end of file diff --git a/wagtail/wagtailembeds/embeds/unittests.py b/wagtail/wagtailembeds/embeds/unittests.py deleted file mode 100644 index 4c7653b99..000000000 --- a/wagtail/wagtailembeds/embeds/unittests.py +++ /dev/null @@ -1,66 +0,0 @@ -import unittest -import oembed - -# Test that a bunch of oembed examples is working -# If any of these is removed or changed then the unit test will fail -# This is a unittest TestCase (and not a django.test one) since django -# database is not actually needed for these tests - -TEST_DATA = [ - { - 'url':'http://www.youtube.com/watch?v=S3xAeTmsJfg', - 'title':'Animation: Ferret dance (A series of tubes)' - }, - { - 'url':'http://vimeo.com/86036070', - 'title':'Wagtail: A new Django CMS' - }, - { - 'url':'https://speakerdeck.com/harmstyler/an-introduction-to-django', - 'title':'An Introduction to Django' - }, - { - 'url':'https://ifttt.com/recipes/144705-new-twitter-followers-in-a-google-spreadsheet', - 'title':'New Twitter followers in a Google spreadsheet' - }, - { - 'url':'http://www.hulu.com/watch/20807/late-night-with-conan-obrien-wed-may-21-2008', - 'title':'Wed, May 21, 2008 (Late Night With Conan O\'Brien)' - }, - { - 'url':'http://www.flickr.com/photos/dfluke/5995957175/', - 'title':'Django pony!?' - }, - { - 'url':'http://www.slideshare.net/simon/the-django-web-application-framework', - 'title':'The Django Web Application Framework' - }, - { - 'url':'http://www.rdio.com/artist/The_Black_Keys/album/Brothers/', - 'title':'Brothers' - }, - { - 'url':'http://instagram.com/p/kFKCcEKmBq/', - 'title':'Family holidays in #Greece!' - }, - { - 'url':'https://www.kickstarter.com/projects/noujaimfilms/the-square-a-film-about-the-egyptian-revolution', - 'title':'Sundance Award Winning Film on the Egyptian Revolution' - }, - { - 'url':'http://www.dailymotion.com/video/xoxulz_babysitter_animals', - 'title':'Babysitter!' - } -] - -class TestEmbeds(unittest.TestCase): - def test_get_embed_oembed(self): - for td in TEST_DATA: - embed = oembed.get_embed_oembed_low(td['url']) - self.assertEqual(embed['title'], td['title'] ) - self.assertIsNotNone(embed['type'] ) - self.assertIsNotNone(embed['width'] ) - self.assertIsNotNone(embed['height'] ) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/wagtail/wagtailembeds/endpoints.json b/wagtail/wagtailembeds/endpoints.json deleted file mode 100644 index 7cee0cec7..000000000 --- a/wagtail/wagtailembeds/endpoints.json +++ /dev/null @@ -1,114 +0,0 @@ -[ -{ -"url": "http://*.blip.tv/*", -"url_re": "blip\\.tv/.+", -"example_url": "http://pycon.blip.tv/file/2058801/", -"endpoint_url": "http://blip.tv/oembed/", -"title": "blip.tv" -}, -{ -"url": "http://*.dailymotion.com/*", -"url_re": "dailymotion\\.com/.+", -"example_url": "http://www.dailymotion.com/video/x5ioet_phoenix-mars-lander_tech", -"endpoint_url": "http://www.dailymotion.com/api/oembed/", -"title": "Dailymotion" -}, -{ -"url": "http://*.flickr.com/photos/*", -"url_re": "flickr\\.com/photos/[-.\\w@]+/\\d+/?", -"example_url": "http://www.flickr.com/photos/fuffer2005/2435339994/", -"endpoint_url": "http://www.flickr.com/services/oembed/", -"title": "Flickr Photos" -}, -{ -"url": "http://www.hulu.com/watch/*", -"url_re": "hulu\\.com/watch/.*", -"example_url": "http://www.hulu.com/watch/20807/late-night-with-conan", -"endpoint_url": "http://www.hulu.com/api/oembed.json", -"title": "Hulu" -}, -{ -"url": "http://*.nfb.ca/film/*", -"url_re": "nfb\\.ca/film/[-\\w]+/?", -"example_url": "http://www.nfb.ca/film/blackfly/", -"endpoint_url": "http://www.nfb.ca/remote/services/oembed/", -"title": "National Film Board of Canada" -}, -{ -"url": "http://qik.com/*", -"url_re": "qik\\.com/\\w+", -"example_url": "http://qik.com/video/86776", -"endpoint_url": "http://qik.com/api/oembed.json", -"title": "Qik Video" -}, -{ -"url": "http://*.revision3.com/*", -"url_re": "revision3\\.com/.+", -"example_url": "http://revision3.com/diggnation/2008-04-17xsanned/", -"endpoint_url": "http://revision3.com/api/oembed/", -"title": "Revision3" -}, -{ -"url": "http://*.scribd.com/*", -"url_re": "scribd\\.com/.+", -"example_url": "http://www.scribd.com/doc/17896323/Indian-Automobile-industryPEST", -"endpoint_url": "http://www.scribd.com/services/oembed", -"title": "Scribd" -}, -{ -"url": "http://*.viddler.com/explore/*", -"url_re": "viddler\\.com/explore/.*/videos/\\w+/?", -"example_url": "http://www.viddler.com/explore/engadget/videos/14/", -"endpoint_url": "http://lab.viddler.com/services/oembed/", -"title": "Viddler Video" -}, -{ -"url": "http://www.vimeo.com/* and http://www.vimeo.com/groups/*/videos/*", -"url_re": "vimeo\\.com/.*", -"example_url": "http://www.vimeo.com/1211060", -"endpoint_url": "http://www.vimeo.com/api/oembed.json", -"title": "Vimeo" -}, -{ -"url": "http://*.youtube.com/watch*", -"url_re": "youtube\\.com/watch.+v=[\\w-]+&?", -"example_url": "http://www.youtube.com/watch?v=vk1HvP7NO5w", -"endpoint_url": "http://www.youtube.com/oembed", -"title": "YouTube" -}, -{ -"url": "http://dotsub.com/view/*", -"url_re": "dotsub\\.com/view/[-\\da-zA-Z]+$", -"example_url": "http://dotsub.com/view/10e3cb5e-96c7-4cfb-bcea-8ab11e04e090", -"endpoint_url": "http://dotsub.com/services/oembed", -"title": "dotSUB.com" -}, -{ -"url": "http://yfrog.(com|ru|com.tr|it|fr|co.il|co.uk|com.pl|pl|eu|us)/*", -"url_re": "yfrog\\.(com|ru|com\\.tr|it|fr|co\\.il|co\\.uk|com\\.pl|pl|eu|us)/[a-zA-Z0-9]+$", -"example_url": "http://yfrog.com/0wgvcpj", -"endpoint_url": "http://www.yfrog.com/api/oembed", -"title": "YFrog" -}, -{ -"url": "http://*.clikthrough.com/theater/video/*", -"url_re": "clikthrough\\.com/theater/video/\\d+$", -"example_url": "http://www.clikthrough.com/theater/video/55", -"endpoint_url": "http://clikthrough.com/services/oembed", -"title": "Clikthrough" -}, -{ -"url": "http://*.kinomap.com/*", -"url_re": "kinomap\\.com/.+", -"example_url": "http://www.kinomap.com/kms-vzkpc7", -"endpoint_url": "http://www.kinomap.com/oembed", -"title": "Kinomap" -}, -{ -"url": "http://*.photobucket.com/albums/*|http://*.photobucket.com/groups/*", -"url_re": "photobucket\\.com/(albums|groups)/.+$", -"example_url": "http://img.photobucket.com/albums/v211/JAV123/Michael%20Holland%20Candle%20Burning/_MG_5661.jpg", -"endpoint_url": "http://photobucket.com/oembed", -"title": "Photobucket" -} -] diff --git a/wagtail/wagtailembeds/format.py b/wagtail/wagtailembeds/format.py index 6fac83bb3..b08955c2d 100644 --- a/wagtail/wagtailembeds/format.py +++ b/wagtail/wagtailembeds/format.py @@ -2,7 +2,7 @@ from __future__ import division # Use true division from django.utils.html import escape -from .embeds.embed import get_embed +from wagtail.wagtailembeds import get_embed def embed_to_frontend_html(url): @@ -24,8 +24,8 @@ def embed_to_frontend_html(url): def embed_to_editor_html(url): - # Check that the embed exists embed = get_embed(url) if embed is None: - return '' + return + return '

%s

%s

' % (url, escape(embed.title), url, embed.thumbnail_url) diff --git a/wagtail/wagtailembeds/embeds/endpoints.json b/wagtail/wagtailembeds/oembed_providers.py similarity index 94% rename from wagtail/wagtailembeds/embeds/endpoints.json rename to wagtail/wagtailembeds/oembed_providers.py index c289b7183..e60c3e5d7 100644 --- a/wagtail/wagtailembeds/embeds/endpoints.json +++ b/wagtail/wagtailembeds/oembed_providers.py @@ -1,4 +1,4 @@ -{ +OEMBED_ENDPOINTS = { "https://speakerdeck.com/oembed.{format}": [ "^http(?:s)?://speakerdeck\\.com/.+$" ], @@ -292,4 +292,30 @@ "http://www.ifttt.com/oembed/": [ "^http(?:s)?://ifttt\\.com/recipes/.+$" ] -} \ No newline at end of file +} + + +# Compile endpoints into regular expression objects +import re + +def compile_endpoints(): + endpoints = {} + for endpoint in OEMBED_ENDPOINTS.keys(): + endpoint_key = endpoint.replace('{format}', 'json') + + endpoints[endpoint_key] = [] + for pattern in OEMBED_ENDPOINTS[endpoint]: + endpoints[endpoint_key].append(re.compile(pattern)) + + return endpoints + +OEMBED_ENDPOINTS_COMPILED = compile_endpoints() + + +def get_oembed_provider(url): + for endpoint in OEMBED_ENDPOINTS_COMPILED.keys(): + for pattern in OEMBED_ENDPOINTS_COMPILED[endpoint]: + if re.match(pattern, url): + return endpoint + + return diff --git a/wagtail/wagtailembeds/templatetags/embed_filters.py b/wagtail/wagtailembeds/templatetags/embed_filters.py index 5ca14a7cf..d916c0ffb 100644 --- a/wagtail/wagtailembeds/templatetags/embed_filters.py +++ b/wagtail/wagtailembeds/templatetags/embed_filters.py @@ -1,7 +1,7 @@ from django import template from django.utils.safestring import mark_safe -from wagtail.wagtailembeds.embeds.embed import get_embed +from wagtail.wagtailembeds import get_embed register = template.Library() diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py index 233156ea2..a57469295 100644 --- a/wagtail/wagtailembeds/tests.py +++ b/wagtail/wagtailembeds/tests.py @@ -1,13 +1,45 @@ from django.test import TestCase - -#from .embeds import get_embed +from unittest import skip +from wagtail.wagtailembeds import get_embed class TestEmbeds(TestCase): - # FIXME: test currently depends on a valid EMBEDLY_KEY being set - we don't particularly - # want to put one in runtests.py. See https://github.com/torchbox/wagtail/issues/26 for - # progress on eliminating Embedly as a dependency - def DISABLEDtest_get_embed(self): - # This test will fail if the video is removed or the title is changed - embed = get_embed('http://www.youtube.com/watch?v=S3xAeTmsJfg') - self.assertEqual(embed.title, 'Animation: Ferret dance (A series of tubes)') + def setUp(self): + self.hit_count = 0 + + def test_get_embed(self): + embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder) + + # Check that the embed is correct + self.assertEqual(embed.title, "Test: www.test.com/1234") + self.assertEqual(embed.type, 'video') + self.assertEqual(embed.width, 400) + + # Check that there has only been one hit to the backend + self.assertEqual(self.hit_count, 1) + + # Look for the same embed again and check the hit count hasn't increased + embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder) + self.assertEqual(self.hit_count, 1) + + # Look for a different embed, hit count should increase + embed = get_embed('www.test.com/4321', max_width=400, finder=self.dummy_finder) + self.assertEqual(self.hit_count, 2) + + # Look for the same embed with a different width, this should also increase hit count + 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 + return { + 'title': "Test: " + url, + 'type': 'video', + 'thumbnail_url': '', + 'width': max_width if max_width else 640, + 'height': 480, + 'html': "

Blah blah blah

", + } \ No newline at end of file diff --git a/wagtail/wagtailembeds/views/chooser.py b/wagtail/wagtailembeds/views/chooser.py index 94695f1a3..8a1f940c6 100644 --- a/wagtail/wagtailembeds/views/chooser.py +++ b/wagtail/wagtailembeds/views/chooser.py @@ -5,8 +5,7 @@ from wagtail.wagtailadmin.modal_workflow import render_modal_workflow from wagtail.wagtailembeds.forms import EmbedForm from wagtail.wagtailembeds.format import embed_to_editor_html -from wagtail.wagtailembeds.embeds.oembed_api import NotImplementedOembedException -from wagtail.wagtailembeds.embeds.embed import EmbedlyException, AccessDeniedEmbedlyException, NotFoundEmbedlyException +from wagtail.wagtailembeds.embeds import EmbedNotFoundException, EmbedlyException, AccessDeniedEmbedlyException @@ -23,27 +22,24 @@ def chooser_upload(request): form = EmbedForm(request.POST, request.FILES) if form.is_valid(): + error = None try: embed_html = embed_to_editor_html(form.cleaned_data['url']) + print embed_html return render_modal_workflow( request, None, 'wagtailembeds/chooser/embed_chosen.js', {'embed_html': embed_html} ) - except Exception as e : - #print e - #import traceback - #traceback.print_exc() + except AccessDeniedEmbedlyException: + error = "There seems to be a problem with your embedly API key. Please check your settings." + except EmbedNotFoundException: + error = "Cannot find an embed for this URL." + except EmbedlyException: + error = "There seems to be an error with Embedly while trying to embed this URL. Please try again later." + + if error: errors = form._errors.setdefault('url', ErrorList()) - if type(e) == NotImplementedOembedException: - errors.append("This URL is not supported by an oembed provider. You may try embedding it using Embedly by setting a propery EMBEDLY_KEY in your settings.") - elif type(e) == AccessDeniedEmbedlyException: - errors.append("There seems to be a problem with your embedly API key. Please check your settings.") - elif type(e) == NotFoundEmbedlyException: - errors.append("The URL you are trying to embed cannot be found.") - elif type(e) == EmbedlyException: - errors.append("There seems to be an error with Embedly while trying to embed this URL. Please try again later.") - else: - errors.append(str(e) ) + errors.append(error) return render_modal_workflow(request, 'wagtailembeds/chooser/chooser.html', 'wagtailembeds/chooser/chooser.js', { 'form': form, }) From 4f914fdc57a835eb692ebfce5ba22d9f4ed7af20 Mon Sep 17 00:00:00 2001 From: Peter Arnott Date: Fri, 14 Feb 2014 18:51:18 +1100 Subject: [PATCH 08/11] Create login_wrapper view Redirect the user if they are already logged in. --- wagtail/wagtailadmin/views/account.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py index b352d06b3..7188f37a7 100644 --- a/wagtail/wagtailadmin/views/account.py +++ b/wagtail/wagtailadmin/views/account.py @@ -2,6 +2,7 @@ from django.conf import settings from django.shortcuts import render, redirect from django.contrib import messages from django.contrib.auth.forms import SetPasswordForm +from django.contrib.auth.views import login def account(request): @@ -31,3 +32,11 @@ def change_password(request): 'form': form, 'can_change_password': can_change_password, }) + +# Wrap login view to prevent logged in users accessing the page +def login_wrapper(request, **kwargs): + if request.user.is_authenticated(): + return redirect(settings.LOGIN_REDIRECT_URL) + else: + return login(request, **kwargs) + From 193b75e1526ebd5bbc96a4a9639129664c579672 Mon Sep 17 00:00:00 2001 From: Peter Arnott Date: Fri, 14 Feb 2014 18:59:08 +1100 Subject: [PATCH 09/11] Change login view to the login_wrapper view --- wagtail/wagtailadmin/urls.py | 16 ++++++++-------- wagtail/wagtailadmin/views/account.py | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/wagtail/wagtailadmin/urls.py b/wagtail/wagtailadmin/urls.py index b26591905..93b78bed3 100644 --- a/wagtail/wagtailadmin/urls.py +++ b/wagtail/wagtailadmin/urls.py @@ -6,13 +6,6 @@ from wagtail.wagtailadmin.forms import LoginForm, PasswordResetForm urlpatterns = patterns( 'django.contrib.auth.views', - url( - r'^login/$', 'login', { - 'template_name': 'wagtailadmin/login.html', - 'authentication_form': LoginForm, - 'extra_context': {'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)}, - } , name='wagtailadmin_login' - ), url(r'^logout/$', 'logout', {'next_page': 'wagtailadmin_login'}), # Password reset @@ -79,4 +72,11 @@ urlpatterns += patterns( url(r'^account/$', 'account.account', name='wagtailadmin_account'), url(r'^account/change_password/$', 'account.change_password', name='wagtailadmin_account_change_password'), -) + url( + r'^login/$', 'account.login_wrapper', { + 'template_name': 'wagtailadmin/login.html', + 'authentication_form': LoginForm, + 'extra_context': {'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)}, + } , name='wagtailadmin_login' + ), +) \ No newline at end of file diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py index 7188f37a7..2bac17ac6 100644 --- a/wagtail/wagtailadmin/views/account.py +++ b/wagtail/wagtailadmin/views/account.py @@ -38,5 +38,4 @@ def login_wrapper(request, **kwargs): if request.user.is_authenticated(): return redirect(settings.LOGIN_REDIRECT_URL) else: - return login(request, **kwargs) - + return login(request, **kwargs) \ No newline at end of file From 7b2eb66e2c2a9307e2dffb45e55338540ac5cbbc Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 14 Feb 2014 11:28:05 +0000 Subject: [PATCH 10/11] Added requests to requirements --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e92d9df23..7f030492f 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ setup( "beautifulsoup4>=4.3.2", "lxml>=3.3.0", "BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed + "requests==2.2.1", ], zip_safe=False, ) From 4e5800265f5cc33aed0fcf5d65795335405d84a4 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 14 Feb 2014 11:53:44 +0000 Subject: [PATCH 11/11] removed 'account' text which was only ever present in an impossible situation --- .../wagtailadmin/templates/wagtailadmin/shared/main_nav.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html index fb2ca74fe..52eb1c89c 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html @@ -11,7 +11,7 @@
  • New page
  • {% endcomment %} {% if request.user.is_superuser %} {# for now, 'More' links will be superuser-only #}