kopia lustrzana https://github.com/wagtail/wagtail
Merge branch 'master' into sass
commit
f6645d4c5b
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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/"
|
|
@ -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/"
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Event: {{ self.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ self.title }}</h1>
|
||||
<h2>Event</h2>
|
||||
</body>
|
||||
</html>
|
|
@ -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)
|
|
@ -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);
|
|
@ -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)
|
|
@ -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);
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
|
@ -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)
|
|
@ -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);
|
|
@ -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)
|
|
@ -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);
|
Ładowanie…
Reference in New Issue