Merge branch 'master' into sass

pull/137/head
Matt Westcott 2014-03-05 19:53:27 +00:00
commit f6645d4c5b
20 zmienionych plików z 1094 dodań i 230 usunięć

Wyświetl plik

@ -82,6 +82,9 @@ if not settings.configured:
'wagtail.wagtailredirects',
'wagtail.tests',
],
PASSWORD_HASHERS=(
'django.contrib.auth.hashers.MD5PasswordHasher', # don't use the intentionally slow default password hasher
),
WAGTAILSEARCH_BACKENDS=WAGTAILSEARCH_BACKENDS,
WAGTAIL_SITE_NAME='Test Site'
)

Wyświetl plik

@ -0,0 +1,129 @@
# Production-configured Wagtail installation
# (secure services/account for full production use).
# Tested on Debian 7.0.
# Tom Dyson and Neal Todd
# NB: Ensure the system locale is okay before running (dpkg-reconfigure locales).
PROJECT=mywagtail
PROJECT_ROOT=/usr/local/django
echo "This script overwrites key files, and should only be run on a new box."
read -p "Type 'yes' to confirm: " CONFIRM
[$CONFIRM== “yes” ] || exit
read -p "Enter a name for your project [$PROJECT]: " U_PROJECT
if [ ! -z "$U_PROJECT" ]; then
PROJECT=$U_PROJECT
fi
read -p "Enter the root of your project, without trailing slash [$PROJECT_ROOT]: " U_PROJECT_ROOT
if [ ! -z "$U_PROJECT_ROOT" ]; then
PROJECT_ROOT=$U_PROJECT_ROOT
fi
if [ ! -z "$PROJECT_ROOT" ]; then
mkdir -p $PROJECT_ROOT || exit
fi
echo -e "\nPlease come back in a few minutes, when we'll need you to create an admin account."
sleep 5
SERVER_IP=`ifconfig eth0 |grep "inet addr" | cut -d: -f2 | cut -d" " -f1`
aptitude update
aptitude -y install git python-pip nginx postgresql redis-server
aptitude -y install postgresql-server-dev-all python-dev libxml2-dev libxslt-dev libjpeg62-dev
wget -nv http://nodejs.org/dist/v0.10.20/node-v0.10.20.tar.gz
tar xzf node-v0.10.20.tar.gz
cd node-v0.10.20
./configure && make -s && make -s install
cd ..
rm -r node-v0.10.20 node-v0.10.20.tar.gz
npm install -g less
perl -pi -e "s/^(local\s+all\s+postgres\s+)peer$/\1trust/" /etc/postgresql/9.1/main/pg_hba.conf
service postgresql reload
aptitude -y install openjdk-7-jre-headless
curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb
dpkg -i elasticsearch-1.0.0.deb
rm elasticsearch-1.0.0.deb
update-rc.d elasticsearch defaults 95 10
service elasticsearch start
cd $PROJECT_ROOT
git clone https://github.com/torchbox/wagtaildemo.git $PROJECT
cd $PROJECT
mv wagtaildemo $PROJECT
perl -pi -e"s/wagtaildemo/$PROJECT/" manage.py $PROJECT/wsgi.py $PROJECT/settings/*.py
rm -r etc README.md Vagrantfile* .git .gitignore
dd if=/dev/zero of=/tmpswap bs=1024 count=524288
mkswap /tmpswap
swapon /tmpswap
pip install -r requirements/production.txt
swapoff -v /tmpswap
rm /tmpswap
echo SECRET_KEY = \"`python -c 'import random; print "".join([random.SystemRandom().choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)") for i in range(50)])'`\" > $PROJECT/settings.local.py
echo ALLOWED_HOSTS = [\'$SERVER_IP\',] >> $PROJECT/settings/local.py
createdb -Upostgres $PROJECT
./manage.py syncdb --settings=$PROJECT.settings.production
./manage.py migrate --settings=$PROJECT.settings.production
./manage.py update_index --settings=$PROJECT.settings.production
./manage.py collectstatic --settings=$PROJECT.settings.production --noinput
pip install uwsgi
cp $PROJECT/wsgi.py $PROJECT/wsgi_production.py
perl -pi -e"s/($PROJECT.settings)/\1.production/" $PROJECT/wsgi_production.py
curl -O https://raw2.github.com/nginx/nginx/master/conf/uwsgi_params
cat << EOF > /etc/nginx/sites-enabled/default
upstream django {
server unix://$PROJECT_ROOT/$PROJECT/uwsgi.sock;
}
server {
listen 80;
charset utf-8;
client_max_body_size 75M; # max upload size
location /media {
alias $PROJECT_ROOT/$PROJECT/media;
}
location /static {
alias $PROJECT_ROOT/$PROJECT/static;
}
location / {
uwsgi_pass django;
include $PROJECT_ROOT/$PROJECT/uwsgi_params;
}
}
EOF
cat << EOF > $PROJECT_ROOT/$PROJECT/uwsgi_conf.ini
[uwsgi]
chdir = $PROJECT_ROOT/$PROJECT
module = $PROJECT.wsgi_production
master = true
processes = 10
socket = $PROJECT_ROOT/$PROJECT/uwsgi.sock
chmod-socket = 666
vacuum = true
EOF
mkdir -p /etc/uwsgi/vassals/
ln -s $PROJECT_ROOT/$PROJECT/uwsgi_conf.ini /etc/uwsgi/vassals/
curl -o /etc/init.d/uwsgi https://raw.github.com/torchbox/wagtail/master/scripts/install/uwsgi-init.d
mkdir /var/log/uwsgi
chmod 755 /etc/init.d/uwsgi
update-rc.d uwsgi defaults
service uwsgi start
service nginx restart
URL="http://$SERVER_IP"
echo -e "\n\nWagtail lives!\n\n"
echo "The public site is at $URL/"
echo "and the admin interface is at $URL/admin/"

Wyświetl plik

@ -0,0 +1,125 @@
# Production-configured Wagtail installation
# (secure services/account for full production use).
# Tested on Ubuntu 13.10.
# Tom Dyson and Neal Todd
PROJECT=mywagtail
PROJECT_ROOT=/usr/local/django
echo "This script overwrites key files, and should only be run on a new box."
read -p "Type 'yes' to confirm: " CONFIRM
[$CONFIRM== “yes” ] || exit
read -p "Enter a name for your project [$PROJECT]: " U_PROJECT
if [ ! -z "$U_PROJECT" ]; then
PROJECT=$U_PROJECT
fi
read -p "Enter the root of your project, without trailing slash [$PROJECT_ROOT]: " U_PROJECT_ROOT
if [ ! -z "$U_PROJECT_ROOT" ]; then
PROJECT_ROOT=$U_PROJECT_ROOT
fi
if [ ! -z "$PROJECT_ROOT" ]; then
mkdir -p $PROJECT_ROOT || exit
fi
echo -e "\nPlease come back in a few minutes, when we'll need you to create an admin account."
sleep 5
SERVER_IP=`ifconfig eth0 |grep "inet addr" | cut -d: -f2 | cut -d" " -f1`
aptitude update
aptitude -y install git python-pip nginx postgresql redis-server
aptitude -y install postgresql-server-dev-all python-dev libxml2-dev libxslt-dev libjpeg62-dev
aptitude -y install npm
ln -s /usr/bin/nodejs /usr/bin/node
npm install -g less
perl -pi -e "s/^(local\s+all\s+postgres\s+)peer$/\1trust/" /etc/postgresql/9.1/main/pg_hba.conf
service postgresql reload
aptitude -y install openjdk-7-jre-headless
curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.deb
dpkg -i elasticsearch-1.0.0.deb
rm elasticsearch-1.0.0.deb
update-rc.d elasticsearch defaults 95 10
service elasticsearch start
cd $PROJECT_ROOT
git clone https://github.com/torchbox/wagtaildemo.git $PROJECT
cd $PROJECT
mv wagtaildemo $PROJECT
perl -pi -e"s/wagtaildemo/$PROJECT/" manage.py $PROJECT/wsgi.py $PROJECT/settings/*.py
rm -r etc README.md Vagrantfile* .git .gitignore
dd if=/dev/zero of=/tmpswap bs=1024 count=524288
mkswap /tmpswap
swapon /tmpswap
pip install -r requirements/production.txt
swapoff -v /tmpswap
rm /tmpswap
echo SECRET_KEY = \"`python -c 'import random; print "".join([random.SystemRandom().choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)") for i in range(50)])'`\" > $PROJECT/settings.local.py
echo ALLOWED_HOSTS = [\'$SERVER_IP\',] >> $PROJECT/settings/local.py
createdb -Upostgres $PROJECT
./manage.py syncdb --settings=$PROJECT.settings.production
./manage.py migrate --settings=$PROJECT.settings.production
./manage.py update_index --settings=$PROJECT.settings.production
./manage.py collectstatic --settings=$PROJECT.settings.production --noinput
pip install uwsgi
cp $PROJECT/wsgi.py $PROJECT/wsgi_production.py
perl -pi -e"s/($PROJECT.settings)/\1.production/" $PROJECT/wsgi_production.py
curl -O https://raw2.github.com/nginx/nginx/master/conf/uwsgi_params
cat << EOF > /etc/nginx/sites-enabled/default
upstream django {
server unix://$PROJECT_ROOT/$PROJECT/uwsgi.sock;
}
server {
listen 80;
charset utf-8;
client_max_body_size 75M; # max upload size
location /media {
alias $PROJECT_ROOT/$PROJECT/media;
}
location /static {
alias $PROJECT_ROOT/$PROJECT/static;
}
location / {
uwsgi_pass django;
include $PROJECT_ROOT/$PROJECT/uwsgi_params;
}
}
EOF
cat << EOF > $PROJECT_ROOT/$PROJECT/uwsgi_conf.ini
[uwsgi]
chdir = $PROJECT_ROOT/$PROJECT
module = $PROJECT.wsgi_production
master = true
processes = 10
socket = $PROJECT_ROOT/$PROJECT/uwsgi.sock
chmod-socket = 666
vacuum = true
EOF
mkdir -p /etc/uwsgi/vassals/
ln -s $PROJECT_ROOT/$PROJECT/uwsgi_conf.ini /etc/uwsgi/vassals/
cat << EOF > /etc/init/uwsgi.conf
description "uwsgi for wagtail"
start on runlevel [2345]
stop on runlevel [06]
exec uwsgi --emperor /etc/uwsgi/vassals
EOF
service uwsgi start
service nginx restart
URL="http://$SERVER_IP"
echo -e "\n\nWagtail lives!\n\n"
echo "The public site is at $URL/"
echo "and the admin interface is at $URL/admin/"

Wyświetl plik

@ -0,0 +1,113 @@
#!/usr/bin/env bash
### BEGIN INIT INFO
# Provides: emperor
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: uwsgi for wagtail
# Description: uwsgi for wagtail
### END INIT INFO
set -e
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
DAEMON=/usr/local/bin/uwsgi
RUN=/var/run/uwsgi
CONFIG_DIR=/etc/uwsgi/vassals
NAME=uwsgi
DESC=emperor
OWNER=root
GROUP=root
OP=$1
[[ -x $DAEMON ]] || exit 0
[[ -d $RUN ]] || mkdir $RUN && chown $OWNER.$GROUP $RUN
do_pid_check()
{
local PIDFILE=$1
[[ -f $PIDFILE ]] || return 0
local PID=$(cat $PIDFILE)
for p in $(pgrep $NAME); do
[[ $p == $PID ]] && return 1
done
return 0
}
do_start()
{
local PIDFILE=$RUN/$NAME.pid
local START_OPTS=" \
--emperor $CONFIG_DIR \
--pidfile $PIDFILE \
--uid $OWNER --gid $GROUP \
--daemonize /var/log/$NAME/uwsgi-emperor.log"
if do_pid_check $PIDFILE; then
$NAME $START_OPTS
else
echo "Already running!"
fi
}
send_sig()
{
local PIDFILE=$RUN/$NAME.pid
set +e
[[ -f $PIDFILE ]] && kill $1 $(cat $PIDFILE) > /dev/null 2>&1
set -e
}
wait_and_clean_pidfile()
{
local PIDFILE=$RUN/uwsgi.pid
until do_pid_check $PIDFILE; do
echo -n "";
done
rm -f $PIDFILE
}
do_stop()
{
send_sig -3
wait_and_clean_pidfile
}
do_reload()
{
send_sig -1
}
case "$OP" in
start)
echo "Starting $DESC: "
do_start
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
do_stop
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC: "
do_reload
echo "$NAME."
;;
restart)
echo "Restarting $DESC: "
do_stop
sleep 1
do_start
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|reload}">&2
exit 1
;;
esac
exit 0

Wyświetl plik

@ -39,7 +39,7 @@
"model": "wagtailcore.page",
"fields": {
"title": "Events",
"numchild": 1,
"numchild": 2,
"show_in_menus": true,
"live": true,
"depth": 3,
@ -69,7 +69,8 @@
"content_type": ["tests", "eventpage"],
"path": "0001000100010001",
"url_path": "/home/events/christmas/",
"slug": "christmas"
"slug": "christmas",
"owner": 2
}
},
{
@ -84,6 +85,62 @@
}
},
{
"pk": 5,
"model": "wagtailcore.page",
"fields": {
"title": "Tentative Unpublished Event",
"numchild": 1,
"show_in_menus": true,
"live": false,
"depth": 4,
"content_type": ["tests", "eventpage"],
"path": "0001000100010002",
"url_path": "/home/events/tentative-unpublished-event/",
"slug": "tentative-unpublished-event",
"owner": 2
}
},
{
"pk": 5,
"model": "tests.eventpage",
"fields": {
"date_from": "2015-07-04",
"audience": "public",
"location": "The moon",
"body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>",
"cost": "Free"
}
},
{
"pk": 6,
"model": "wagtailcore.page",
"fields": {
"title": "Someone Else's Event",
"numchild": 1,
"show_in_menus": true,
"live": false,
"depth": 4,
"content_type": ["tests", "eventpage"],
"path": "0001000100010003",
"url_path": "/home/events/someone-elses-event/",
"slug": "someone-elses-event",
"owner": 3
}
},
{
"pk": 6,
"model": "tests.eventpage",
"fields": {
"date_from": "2015-07-04",
"audience": "private",
"location": "The moon",
"body": "<p>your name's not down, you're not coming in</p>",
"cost": "Free (but not for you)"
}
},
{
"pk": 1,
"model": "wagtailcore.site",
@ -93,5 +150,140 @@
"port": 80,
"is_default_site": true
}
},
{
"pk": 3,
"model": "auth.group",
"fields": {
"name": "Event editors",
"permissions": [
["access_admin", "wagtailadmin", "admin"],
["add_image", "wagtailimages", "image"],
["change_image", "wagtailimages", "image"],
["delete_image", "wagtailimages", "image"]
]
}
},
{
"pk": 4,
"model": "auth.group",
"fields": {
"name": "Event moderators",
"permissions": [
["access_admin", "wagtailadmin", "admin"],
["add_image", "wagtailimages", "image"],
["change_image", "wagtailimages", "image"],
["delete_image", "wagtailimages", "image"]
]
}
},
{
"pk": 1,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event editors"],
"page": 3,
"permission_type": "add"
}
},
{
"pk": 2,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event moderators"],
"page": 3,
"permission_type": "add"
}
},
{
"pk": 3,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event moderators"],
"page": 3,
"permission_type": "edit"
}
},
{
"pk": 4,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event moderators"],
"page": 3,
"permission_type": "publish"
}
},
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "superuser",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [
],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superuser@example.com"
}
},
{
"pk": 2,
"model": "auth.user",
"fields": {
"username": "eventeditor",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [
["Event editors"]
],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventeditor@example.com"
}
},
{
"pk": 3,
"model": "auth.user",
"fields": {
"username": "eventmoderator",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [
["Event moderators"]
],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventmoderator@example.com"
}
},
{
"pk": 4,
"model": "auth.user",
"fields": {
"username": "inactiveuser",
"first_name": "",
"last_name": "",
"is_active": false,
"is_superuser": false,
"is_staff": false,
"groups": [
["Event moderators"]
],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "inactiveuser@example.com"
}
}
]

Wyświetl plik

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Event: {{ self.title }}</title>
</head>
<body>
<h1>{{ self.title }}</h1>
<h2>Event</h2>
</body>
</html>

Wyświetl plik

@ -1,28 +0,0 @@
# Hallo - a rich text editing jQuery UI widget
# (c) 2011 Henri Bergius, IKS Consortium
# Hallo may be freely distributed under the MIT license
((jQuery) ->
jQuery.widget "IKS.hallohr",
options:
editable: null
toolbar: null
uuid: ''
buttonCssClass: null
populateToolbar: (toolbar) ->
buttonset = jQuery "<span class=\"#{@widgetName}\"></span>"
buttonElement = jQuery '<span></span>'
buttonElement.hallobutton
uuid: @options.uuid
editable: @options.editable
label: "Horizontal rule"
command: "insertHorizontalRule"
icon: "icon-horizontalrule"
cssClass: @options.buttonCssClass
buttonset.append buttonElement
buttonset.hallobuttonset()
toolbar.append buttonset
)(jQuery)

Wyświetl plik

@ -0,0 +1,31 @@
// Generated by CoffeeScript 1.6.2
(function() {
(function(jQuery) {
return jQuery.widget("IKS.hallohr", {
options: {
editable: null,
toolbar: null,
uuid: '',
buttonCssClass: null
},
populateToolbar: function(toolbar) {
var buttonElement, buttonset;
buttonset = jQuery("<span class=\"" + this.widgetName + "\"></span>");
buttonElement = jQuery('<span></span>');
buttonElement.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: "Horizontal rule",
command: "insertHorizontalRule",
icon: "icon-horizontalrule",
cssClass: this.options.buttonCssClass
});
buttonset.append(buttonElement);
buttonset.hallobuttonset();
return toolbar.append(buttonset);
}
});
})(jQuery);
}).call(this);

Wyświetl plik

@ -1,68 +0,0 @@
# plugin for hallo.js to allow inserting links using Wagtail's page chooser
(($) ->
$.widget "IKS.hallowagtaillink",
options:
uuid: ''
editable: null
populateToolbar: (toolbar) ->
widget = this
getEnclosingLink = () ->
# if cursor is currently within a link element, return it, otherwise return null
node = widget.options.editable.getSelection().commonAncestorContainer
return $(node).parents('a').get(0)
# Create an element for holding the button
button = $('<span></span>')
button.hallobutton
uuid: @options.uuid
editable: @options.editable
label: 'Links'
icon: 'icon-link'
command: null
queryState: (event) ->
button.hallobutton('checked', !!getEnclosingLink())
# Append the button to toolbar
toolbar.append button
button.on "click", (event) ->
enclosingLink = getEnclosingLink()
if enclosingLink
# remove existing link
$(enclosingLink).replaceWith(enclosingLink.innerHTML)
button.hallobutton('checked', false)
widget.options.editable.element.trigger('change')
else
# commence workflow to add a link
lastSelection = widget.options.editable.getSelection()
if lastSelection.collapsed
# TODO: don't hard-code this, as it may be changed in urls.py
url = window.chooserUrls.pageChooser + '?allow_external_link=true&allow_email_link=true&prompt_for_link_text=true'
else
url = window.chooserUrls.pageChooser + '?allow_external_link=true&allow_email_link=true'
ModalWorkflow
url: url
responses:
pageChosen: (pageData) ->
a = document.createElement('a')
a.setAttribute('href', pageData.url)
if pageData.id
a.setAttribute('data-id', pageData.id)
a.setAttribute('data-linktype', 'page')
if (not lastSelection.collapsed) and lastSelection.canSurroundContents()
# use the selected content as the link text
lastSelection.surroundContents(a)
else
# no text is selected, so use the page title as link text
a.appendChild(document.createTextNode pageData.title)
lastSelection.insertNode(a)
widget.options.editable.element.trigger('change')
)(jQuery)

Wyświetl plik

@ -0,0 +1,74 @@
// Generated by CoffeeScript 1.6.2
(function() {
(function($) {
return $.widget("IKS.hallowagtaillink", {
options: {
uuid: '',
editable: null
},
populateToolbar: function(toolbar) {
var button, getEnclosingLink, widget;
widget = this;
getEnclosingLink = function() {
var node;
node = widget.options.editable.getSelection().commonAncestorContainer;
return $(node).parents('a').get(0);
};
button = $('<span></span>');
button.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: 'Links',
icon: 'icon-link',
command: null,
queryState: function(event) {
return button.hallobutton('checked', !!getEnclosingLink());
}
});
toolbar.append(button);
return button.on("click", function(event) {
var enclosingLink, lastSelection, url;
enclosingLink = getEnclosingLink();
if (enclosingLink) {
$(enclosingLink).replaceWith(enclosingLink.innerHTML);
button.hallobutton('checked', false);
return widget.options.editable.element.trigger('change');
} else {
lastSelection = widget.options.editable.getSelection();
if (lastSelection.collapsed) {
url = window.chooserUrls.pageChooser + '?allow_external_link=true&allow_email_link=true&prompt_for_link_text=true';
} else {
url = window.chooserUrls.pageChooser + '?allow_external_link=true&allow_email_link=true';
}
return ModalWorkflow({
url: url,
responses: {
pageChosen: function(pageData) {
var a;
a = document.createElement('a');
a.setAttribute('href', pageData.url);
if (pageData.id) {
a.setAttribute('data-id', pageData.id);
a.setAttribute('data-linktype', 'page');
}
if ((!lastSelection.collapsed) && lastSelection.canSurroundContents()) {
lastSelection.surroundContents(a);
} else {
a.appendChild(document.createTextNode(pageData.title));
lastSelection.insertNode(a);
}
return widget.options.editable.element.trigger('change');
}
}
});
}
});
}
});
})(jQuery);
}).call(this);

Wyświetl plik

@ -13,16 +13,16 @@
<script src="{{ STATIC_URL }}wagtailadmin/js/expanding_formset.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/modal-workflow.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtail-toolbar.js"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-wagtaillink.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-hr.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailimages/js/hallo-plugins/hallo-wagtailimage.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.coffee"></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: have a mechanism to specify image-chooser.js (and hallo-wagtailimage.coffee)
TODO: have a mechanism to specify image-chooser.js (and hallo-wagtailimage.js)
within the wagtailimages app -
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.

Wyświetl plik

@ -75,7 +75,7 @@
</td>
<td class="type">{{ parent_page.content_type.model_class.get_verbose_name }}</td>
<td class="status">
{% if not choosing and parent_page.live and not parent_page.is_root and 'view_live' not in hide_actions|default:'' %}
{% if not choosing and not moving and parent_page.live and not parent_page.is_root and 'view_live' not in hide_actions|default:'' %}
<a href="{{ parent_page.url }}" target="_blank" class="status-tag {% if parent_page.status_string != "draft" %}primary{% endif %}">{{ parent_page.status_string|capfirst }}</a>
{% else %}
<span class="status-tag {% if parent_page.status_string != "draft" %}primary{% endif %}">{{ parent_page.status_string|capfirst }}</span>
@ -208,7 +208,7 @@
{% endif %}
<td class="type">{{ page.content_type.model_class.get_verbose_name }}</td>
<td class="status">
{% if not choosing and page.live and 'view_live' not in hide_actions|default:'' %}
{% if not choosing and not moving and page.live and 'view_live' not in hide_actions|default:'' %}
<a href="{{ page.url }}" target="_blank" class="status-tag {% if page.status_string != "draft" %}primary{% endif %}">{{ page.status_string }}</a>
{% else %}
<span class="status-tag {% if page.status_string != "draft" %}primary{% endif %}">{{ page.status_string }}</span>

Wyświetl plik

@ -6,11 +6,11 @@ from modelcluster.models import ClusterableModel
from django.db import models, connection, transaction
from django.db.models import get_model, Q
from django.http import Http404
from django.shortcuts import render
from django.core.cache import cache
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Group
from django.conf import settings
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailcore.util import camelcase_to_underscore
@ -326,7 +326,7 @@ class Page(MP_Node, ClusterableModel, Indexed):
return revision.as_page_object()
def serve(self, request):
return render(request, self.template, {
return TemplateResponse(request, self.template, {
'self': self
})
@ -742,7 +742,7 @@ class PagePermissionTester(object):
def can_move_to(self, destination):
# reject the logically impossible cases first
if self.page == destination or destination.is_child_of(self.page):
if self.page == destination or destination.is_descendant_of(self.page):
return False
# and shortcut the trivial 'everything' / 'nothing' permissions

Wyświetl plik

@ -1,10 +1,37 @@
from django.test import TestCase
from django.test import TestCase, Client
from django.http import HttpRequest, Http404
from django.contrib.auth.models import User
from wagtail.wagtailcore.models import Page, Site
from wagtail.tests.models import EventPage
class TestRouting(TestCase):
fixtures = ['test.json']
def test_find_site_for_request(self):
default_site = Site.objects.get(is_default_site=True)
events_page = Page.objects.get(url_path='/home/events/')
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
# requests without a Host: header should be directed to the default site
request = HttpRequest()
request.path = '/'
self.assertEqual(Site.find_for_request(request), default_site)
# requests with a known Host: header should be directed to the specific site
request = HttpRequest()
request.path = '/'
request.META['HTTP_HOST'] = 'events.example.com'
self.assertEqual(Site.find_for_request(request), events_site)
# requests with an unrecognised Host: header should be directed to the default site
request = HttpRequest()
request.path = '/'
request.META['HTTP_HOST'] = 'unknown.example.com'
self.assertEqual(Site.find_for_request(request), default_site)
def test_urls(self):
default_site = Site.objects.get(is_default_site=True)
homepage = Page.objects.get(url_path='/home/')
@ -38,3 +65,234 @@ class TestRouting(TestCase):
self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
def test_request_routing(self):
homepage = Page.objects.get(url_path='/home/')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
request = HttpRequest()
request.path = '/events/christmas/'
response = homepage.route(request, ['events', 'christmas'])
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context_data['self'], christmas_page)
used_template = response.resolve_template(response.template_name)
self.assertEqual(used_template.name, 'tests/event_page.html')
def test_route_to_unknown_page_returns_404(self):
homepage = Page.objects.get(url_path='/home/')
request = HttpRequest()
request.path = '/events/quinquagesima/'
with self.assertRaises(Http404):
homepage.route(request, ['events', 'quinquagesima'])
def test_route_to_unpublished_page_returns_404(self):
homepage = Page.objects.get(url_path='/home/')
request = HttpRequest()
request.path = '/events/tentative-unpublished-event/'
with self.assertRaises(Http404):
homepage.route(request, ['events', 'tentative-unpublished-event'])
class TestServeView(TestCase):
fixtures = ['test.json']
def test_serve(self):
response = self.client.get('/events/christmas/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
self.assertEqual(response.context['self'], christmas_page)
self.assertContains(response, '<h1>Christmas</h1>')
self.assertContains(response, '<h2>Event</h2>')
def test_serve_unknown_page_returns_404(self):
response = self.client.get('/events/quinquagesima/')
self.assertEqual(response.status_code, 404)
def test_serve_unpublished_page_returns_404(self):
response = self.client.get('/events/tentative-unpublished-event/')
self.assertEqual(response.status_code, 404)
def test_serve_with_multiple_sites(self):
events_page = Page.objects.get(url_path='/home/events/')
Site.objects.create(hostname='events.example.com', root_page=events_page)
response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
self.assertEqual(response.context['self'], christmas_page)
self.assertContains(response, '<h1>Christmas</h1>')
self.assertContains(response, '<h2>Event</h2>')
# same request to the default host should return a 404
c = Client()
response = c.get('/christmas/', HTTP_HOST='localhost')
self.assertEqual(response.status_code, 404)
class TestPagePermission(TestCase):
fixtures = ['test.json']
def test_nonpublisher_page_permissions(self):
event_editor = User.objects.get(username='eventeditor')
homepage = Page.objects.get(url_path='/home/')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
homepage_perms = homepage.permissions_for_user(event_editor)
christmas_page_perms = christmas_page.permissions_for_user(event_editor)
unpub_perms = unpublished_event_page.permissions_for_user(event_editor)
someone_elses_event_perms = someone_elses_event_page.permissions_for_user(event_editor)
self.assertFalse(homepage_perms.can_add_subpage())
self.assertTrue(christmas_page_perms.can_add_subpage())
self.assertTrue(unpub_perms.can_add_subpage())
self.assertTrue(someone_elses_event_perms.can_add_subpage())
self.assertFalse(homepage_perms.can_edit())
self.assertTrue(christmas_page_perms.can_edit())
self.assertTrue(unpub_perms.can_edit())
self.assertFalse(someone_elses_event_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else
self.assertFalse(homepage_perms.can_delete())
self.assertFalse(christmas_page_perms.can_delete()) # cannot delete because it is published
self.assertTrue(unpub_perms.can_delete())
self.assertFalse(someone_elses_event_perms.can_delete())
self.assertFalse(homepage_perms.can_publish())
self.assertFalse(christmas_page_perms.can_publish())
self.assertFalse(unpub_perms.can_publish())
self.assertFalse(homepage_perms.can_unpublish())
self.assertFalse(christmas_page_perms.can_unpublish())
self.assertFalse(unpub_perms.can_unpublish())
self.assertFalse(homepage_perms.can_publish_subpage())
self.assertFalse(christmas_page_perms.can_publish_subpage())
self.assertFalse(unpub_perms.can_publish_subpage())
self.assertFalse(homepage_perms.can_reorder_children())
self.assertFalse(christmas_page_perms.can_reorder_children())
self.assertFalse(unpub_perms.can_reorder_children())
self.assertFalse(homepage_perms.can_move())
self.assertFalse(christmas_page_perms.can_move()) # cannot move because this would involve unpublishing from its current location
self.assertTrue(unpub_perms.can_move())
self.assertFalse(someone_elses_event_perms.can_move())
self.assertFalse(christmas_page_perms.can_move_to(unpublished_event_page)) # cannot move because this would involve unpublishing from its current location
self.assertTrue(unpub_perms.can_move_to(christmas_page))
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
def test_publisher_page_permissions(self):
event_moderator = User.objects.get(username='eventmoderator')
homepage = Page.objects.get(url_path='/home/')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
homepage_perms = homepage.permissions_for_user(event_moderator)
christmas_page_perms = christmas_page.permissions_for_user(event_moderator)
unpub_perms = unpublished_event_page.permissions_for_user(event_moderator)
self.assertFalse(homepage_perms.can_add_subpage())
self.assertTrue(christmas_page_perms.can_add_subpage())
self.assertTrue(unpub_perms.can_add_subpage())
self.assertFalse(homepage_perms.can_edit())
self.assertTrue(christmas_page_perms.can_edit())
self.assertTrue(unpub_perms.can_edit())
self.assertFalse(homepage_perms.can_delete())
self.assertTrue(christmas_page_perms.can_delete()) # cannot delete because it is published
self.assertTrue(unpub_perms.can_delete())
self.assertFalse(homepage_perms.can_publish())
self.assertTrue(christmas_page_perms.can_publish())
self.assertTrue(unpub_perms.can_publish())
self.assertFalse(homepage_perms.can_unpublish())
self.assertTrue(christmas_page_perms.can_unpublish())
self.assertFalse(unpub_perms.can_unpublish()) # cannot unpublish a page that isn't published
self.assertFalse(homepage_perms.can_publish_subpage())
self.assertTrue(christmas_page_perms.can_publish_subpage())
self.assertTrue(unpub_perms.can_publish_subpage())
self.assertFalse(homepage_perms.can_reorder_children())
self.assertTrue(christmas_page_perms.can_reorder_children())
self.assertTrue(unpub_perms.can_reorder_children())
self.assertFalse(homepage_perms.can_move())
self.assertTrue(christmas_page_perms.can_move())
self.assertTrue(unpub_perms.can_move())
self.assertTrue(christmas_page_perms.can_move_to(unpublished_event_page))
self.assertTrue(unpub_perms.can_move_to(christmas_page))
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
def test_inactive_user_has_no_permissions(self):
user = User.objects.get(username='inactiveuser')
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
christmas_page_perms = christmas_page.permissions_for_user(user)
unpub_perms = unpublished_event_page.permissions_for_user(user)
self.assertFalse(unpub_perms.can_add_subpage())
self.assertFalse(unpub_perms.can_edit())
self.assertFalse(unpub_perms.can_delete())
self.assertFalse(unpub_perms.can_publish())
self.assertFalse(christmas_page_perms.can_unpublish())
self.assertFalse(unpub_perms.can_publish_subpage())
self.assertFalse(unpub_perms.can_reorder_children())
self.assertFalse(unpub_perms.can_move())
self.assertFalse(unpub_perms.can_move_to(christmas_page))
def test_superuser_has_full_permissions(self):
user = User.objects.get(username='superuser')
homepage = Page.objects.get(url_path='/home/')
root = Page.objects.get(url_path='/')
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
homepage_perms = homepage.permissions_for_user(user)
root_perms = root.permissions_for_user(user)
unpub_perms = unpublished_event_page.permissions_for_user(user)
self.assertTrue(homepage_perms.can_add_subpage())
self.assertTrue(root_perms.can_add_subpage())
self.assertTrue(homepage_perms.can_edit())
self.assertFalse(root_perms.can_edit()) # root is not a real editable page, even to superusers
self.assertTrue(homepage_perms.can_delete())
self.assertFalse(root_perms.can_delete())
self.assertTrue(homepage_perms.can_publish())
self.assertFalse(root_perms.can_publish())
self.assertTrue(homepage_perms.can_unpublish())
self.assertFalse(root_perms.can_unpublish())
self.assertFalse(unpub_perms.can_unpublish())
self.assertTrue(homepage_perms.can_publish_subpage())
self.assertTrue(root_perms.can_publish_subpage())
self.assertTrue(homepage_perms.can_reorder_children())
self.assertTrue(root_perms.can_reorder_children())
self.assertTrue(homepage_perms.can_move())
self.assertFalse(root_perms.can_move())
self.assertTrue(homepage_perms.can_move_to(root))
self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))

Wyświetl plik

@ -1,45 +0,0 @@
# plugin for hallo.js to allow inserting links using Wagtail's page chooser
(($) ->
$.widget "IKS.hallowagtaildoclink",
options:
uuid: ''
editable: null
populateToolbar: (toolbar) ->
widget = this
# Create an element for holding the button
button = $('<span></span>')
button.hallobutton
uuid: @options.uuid
editable: @options.editable
label: 'Documents'
icon: 'icon-file-text-alt'
command: null
# Append the button to toolbar
toolbar.append button
button.on "click", (event) ->
lastSelection = widget.options.editable.getSelection()
ModalWorkflow
url: window.chooserUrls.documentChooser
responses:
documentChosen: (docData) ->
a = document.createElement('a')
a.setAttribute('href', docData.url)
a.setAttribute('data-id', docData.id)
a.setAttribute('data-linktype', 'document')
if (not lastSelection.collapsed) and lastSelection.canSurroundContents()
# use the selected content as the link text
lastSelection.surroundContents(a)
else
# no text is selected, so use the doc title as link text
a.appendChild(document.createTextNode docData.title)
lastSelection.insertNode(a)
widget.options.editable.element.trigger('change')
)(jQuery)

Wyświetl plik

@ -0,0 +1,51 @@
// Generated by CoffeeScript 1.6.2
(function() {
(function($) {
return $.widget("IKS.hallowagtaildoclink", {
options: {
uuid: '',
editable: null
},
populateToolbar: function(toolbar) {
var button, widget;
widget = this;
button = $('<span></span>');
button.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: 'Documents',
icon: 'icon-file-text-alt',
command: null
});
toolbar.append(button);
return button.on("click", function(event) {
var lastSelection;
lastSelection = widget.options.editable.getSelection();
return ModalWorkflow({
url: window.chooserUrls.documentChooser,
responses: {
documentChosen: function(docData) {
var a;
a = document.createElement('a');
a.setAttribute('href', docData.url);
a.setAttribute('data-id', docData.id);
a.setAttribute('data-linktype', 'document');
if ((!lastSelection.collapsed) && lastSelection.canSurroundContents()) {
lastSelection.surroundContents(a);
} else {
a.appendChild(document.createTextNode(docData.title));
lastSelection.insertNode(a);
}
return widget.options.editable.element.trigger('change');
}
}
});
});
}
});
})(jQuery);
}).call(this);

Wyświetl plik

@ -1,36 +0,0 @@
# plugin for hallo.js to allow inserting embeds
(($) ->
$.widget "IKS.hallowagtailembeds",
options:
uuid: ''
editable: null
populateToolbar: (toolbar) ->
widget = this
# Create an element for holding the button
button = $('<span></span>')
button.hallobutton
uuid: @options.uuid
editable: @options.editable
label: 'Embed'
icon: 'icon-media'
command: null
# Append the button to toolbar
toolbar.append button
button.on "click", (event) ->
lastSelection = widget.options.editable.getSelection()
insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last()
ModalWorkflow
url: window.chooserUrls.embedsChooser
responses:
embedChosen: (embedData) ->
elem = $(embedData).get(0)
lastSelection.insertNode(elem)
if elem.getAttribute('contenteditable') == 'false'
insertRichTextDeleteControl(elem)
widget.options.editable.element.trigger('change')
)(jQuery)

Wyświetl plik

@ -0,0 +1,47 @@
// Generated by CoffeeScript 1.6.2
(function() {
(function($) {
return $.widget("IKS.hallowagtailembeds", {
options: {
uuid: '',
editable: null
},
populateToolbar: function(toolbar) {
var button, widget;
widget = this;
button = $('<span></span>');
button.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: 'Embed',
icon: 'icon-media',
command: null
});
toolbar.append(button);
return button.on("click", function(event) {
var insertionPoint, lastSelection;
lastSelection = widget.options.editable.getSelection();
insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last();
return ModalWorkflow({
url: window.chooserUrls.embedsChooser,
responses: {
embedChosen: function(embedData) {
var elem;
elem = $(embedData).get(0);
lastSelection.insertNode(elem);
if (elem.getAttribute('contenteditable') === 'false') {
insertRichTextDeleteControl(elem);
}
return widget.options.editable.element.trigger('change');
}
}
});
});
}
});
})(jQuery);
}).call(this);

Wyświetl plik

@ -1,39 +0,0 @@
# plugin for hallo.js to allow inserting images from the Wagtail image library
(($) ->
$.widget "IKS.hallowagtailimage",
options:
uuid: ''
editable: null
populateToolbar: (toolbar) ->
widget = this
# Create an element for holding the button
button = $('<span></span>')
button.hallobutton
uuid: @options.uuid
editable: @options.editable
label: 'Images'
icon: 'icon-picture'
command: null
# Append the button to toolbar
toolbar.append button
button.on "click", (event) ->
lastSelection = widget.options.editable.getSelection()
insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last()
ModalWorkflow
url: window.chooserUrls.imageChooser + '?select_format=true'
responses:
imageChosen: (imageData) ->
elem = $(imageData.html).get(0)
lastSelection.insertNode(elem)
if elem.getAttribute('contenteditable') == 'false'
insertRichTextDeleteControl(elem)
widget.options.editable.element.trigger('change')
)(jQuery)

Wyświetl plik

@ -0,0 +1,47 @@
// Generated by CoffeeScript 1.6.2
(function() {
(function($) {
return $.widget("IKS.hallowagtailimage", {
options: {
uuid: '',
editable: null
},
populateToolbar: function(toolbar) {
var button, widget;
widget = this;
button = $('<span></span>');
button.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: 'Images',
icon: 'icon-picture',
command: null
});
toolbar.append(button);
return button.on("click", function(event) {
var insertionPoint, lastSelection;
lastSelection = widget.options.editable.getSelection();
insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last();
return ModalWorkflow({
url: window.chooserUrls.imageChooser + '?select_format=true',
responses: {
imageChosen: function(imageData) {
var elem;
elem = $(imageData.html).get(0);
lastSelection.insertNode(elem);
if (elem.getAttribute('contenteditable') === 'false') {
insertRichTextDeleteControl(elem);
}
return widget.options.editable.element.trigger('change');
}
}
});
});
}
});
})(jQuery);
}).call(this);