initial package
This commit is contained in:
parent
ed6c299cab
commit
ddbd35be26
32
MANIFEST.in
32
MANIFEST.in
|
@ -1,32 +0,0 @@
|
||||||
recursive-include portail_citoyen2/apps/login_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/login_plugin/static *.css *.gif *.png *.js
|
|
||||||
recursive-include portail_citoyen2/apps/login_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/data_source_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/data_source_plugin/static *.css *.gif *.png *.js
|
|
||||||
recursive-include portail_citoyen2/apps/data_source_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/a2_service_list_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/a2_service_list_plugin/static *.css *.gif *.png *.js
|
|
||||||
recursive-include portail_citoyen2/apps/a2_service_list_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/passerelle_register_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/passerelle_register_plugin/static *.css *.gif *.png *.js
|
|
||||||
recursive-include portail_citoyen2/apps/passerelle_register_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/auquotidien_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/auquotidien_plugin/static *.js
|
|
||||||
recursive-include portail_citoyen2/apps/auquotidien_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/federation_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/federation_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/feed_plugin/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/feed_plugin/locale *.po *.mo
|
|
||||||
recursive-include portail_citoyen2/apps/msp/templates *.html
|
|
||||||
recursive-include portail_citoyen2/apps/msp/static *.css *.gif *.png *.js *.jpg
|
|
||||||
recursive-include portail_citoyen2/apps/msp/locale *.po *.mo
|
|
||||||
include portail_citoyen2/apps/msp/README.txt
|
|
||||||
recursive-include portail_citoyen2/fixtures *.json
|
|
||||||
recursive-include portail_citoyen2/templates *.html *.txt
|
|
||||||
recursive-include portail_citoyen2/static *.css *.gif *.png *.js
|
|
||||||
recursive-include portail_citoyen2/locale *.po *.mo
|
|
||||||
recursive-include help *.page
|
|
||||||
include local_settings.py.example
|
|
||||||
include requirements.txt
|
|
||||||
include MANIFEST.in
|
|
||||||
include VERSION
|
|
67
README
67
README
|
@ -1,67 +0,0 @@
|
||||||
How to start
|
|
||||||
============
|
|
||||||
|
|
||||||
To work on portail-citoyen just execute the following lines (command
|
|
||||||
to launch start with $, other lines are expected output)::
|
|
||||||
|
|
||||||
$ pip install -r ./requirements
|
|
||||||
$ cp local_settings.py.example local_settings.py
|
|
||||||
$ ./portail-citoyen syncdb --all # you will be asked to create a new admin user
|
|
||||||
Syncing...
|
|
||||||
Creating tables ...
|
|
||||||
[ snipped ]
|
|
||||||
Creating table cmsplugin_a2servicelistplugin
|
|
||||||
Creating table registration_registrationprofile
|
|
||||||
|
|
||||||
You just installed Django's auth system, which means you don't have any superusers defined.
|
|
||||||
Would you like to create one now? (yes/no): yes
|
|
||||||
Username: admin
|
|
||||||
First name: admin
|
|
||||||
Last name: admin
|
|
||||||
E-mail address: admin@coin.org
|
|
||||||
Password:
|
|
||||||
Password (again):
|
|
||||||
Superuser created successfully.
|
|
||||||
Installing custom SQL ...
|
|
||||||
Installing indexes ...
|
|
||||||
Installed 2 object(s) from 1 fixture(s)
|
|
||||||
|
|
||||||
Synced:
|
|
||||||
> django.contrib.auth
|
|
||||||
[ snipped ]
|
|
||||||
> registration
|
|
||||||
|
|
||||||
Not synced (use migrations):
|
|
||||||
-
|
|
||||||
(use ./manage.py migrate to migrate these)
|
|
||||||
$ ./portail-citoyen migrate --fake
|
|
||||||
[ lots of migrations running ]
|
|
||||||
$ ./portail-citoyen runserver
|
|
||||||
Validating models...
|
|
||||||
|
|
||||||
0 errors found
|
|
||||||
April 12, 2013 - 16:04:50
|
|
||||||
Django version 1.5.1, using settings 'compte_agglo_montpellier.settings'
|
|
||||||
Development server is running at http://127.0.0.1:8000/
|
|
||||||
Quit the server with CONTROL-C.
|
|
||||||
|
|
||||||
The application is now usable at http://localhost:8000/
|
|
||||||
|
|
||||||
Settings
|
|
||||||
--------
|
|
||||||
|
|
||||||
Settings can be passed using a local_settings.py file or the shell
|
|
||||||
environment. Dictionaries are passed by flattening the variable name and the
|
|
||||||
dictionnary key separated by an underscore character. For example to passe the
|
|
||||||
variable MAIN_SITE_URL in templates, define the following environment
|
|
||||||
variable::
|
|
||||||
|
|
||||||
export PORTAIL_CITOYEN_TEMPLATE_VARS_MAIN_SITE_URL=https://main-site.com/
|
|
||||||
|
|
||||||
Those variables must be prefixed with PORTAIL_CITOYEN_ when set in a
|
|
||||||
local_settings.py file or in the environment.
|
|
||||||
|
|
||||||
Name Description
|
|
||||||
================================ ============================================
|
|
||||||
TEMPLATE_VARS dictionnary of variables passed to templates
|
|
||||||
PORTAIL_ADMIN_URL URL of the global administration portal
|
|
3
TODO
3
TODO
|
@ -1,3 +0,0 @@
|
||||||
- Authentication is handled by authsaml2
|
|
||||||
- First user created must be superadmin
|
|
||||||
- User admin must be read-only
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
portail-citoyen2 (1.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
|
||||||
|
-- Jérôme Schneider <jschneider@entrouvert.com> Tue, 01 Apr 2014 15:17:34 +0200
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
7
|
|
@ -0,0 +1,5 @@
|
||||||
|
export DATABASE_ENGINE='django.db.backends.postgresql_psycopg2'
|
||||||
|
export DATABASE_NAME='_DBC_DBNAME_'
|
||||||
|
export DATABASE_USER='_DBC_DBUSER_'
|
||||||
|
export DATABASE_PASSWORD='_DBC_DBPASS_'
|
||||||
|
export DATABASE_HOST='localhost'
|
|
@ -0,0 +1,41 @@
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
server_name citoyen.example.fr;
|
||||||
|
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||||
|
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/citoyen.example.fr-access.log combined;
|
||||||
|
error_log /var/log/nginx/citoyen.example.fr-error.log;
|
||||||
|
|
||||||
|
location /static {
|
||||||
|
alias /var/lib/portail-citoyen/static;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://unix:/var/run/portail-citoyen/portail-citoyen.sock;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-SSL on;
|
||||||
|
proxy_set_header X-Forwarded-Protocol ssl;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name citoyen.example.fr;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/citoyen.example.fr-access.log combined;
|
||||||
|
error_log /var/log/nginx/citoyen.example.fr-error.log;
|
||||||
|
|
||||||
|
location /static {
|
||||||
|
alias /var/lib/portail-citoyen/static;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://unix:/var/run/portail-citoyen/portail-citoyen.sock;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# do not remove this line, it imports db configuration from dbconfig-common
|
||||||
|
[ -f /etc/portail-citoyen/db.conf ] && . /etc/portail-citoyen/db.conf
|
||||||
|
# do not remove this line, it imports secret which is automatically generated
|
||||||
|
[ -f /etc/portail-citoyen/secret ] && . /etc/portail-citoyen/secret
|
||||||
|
# import saml certificates (automatically genereted)
|
||||||
|
[ -f /etc/portail-citoyen/saml-certs.conf ] && . /etc/portail-citoyen/saml-certs.conf
|
||||||
|
|
||||||
|
# Static root directory
|
||||||
|
export STATIC_ROOT='/var/lib/portail-citoyen/static'
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
#export DEBUG=yes
|
||||||
|
|
||||||
|
# Define administrators / managers
|
||||||
|
export ADMINS='admin eo;admin@entrouvert.com'
|
||||||
|
|
||||||
|
# Database configuration (please use dpkg-reconfigure portail-citoyen)
|
||||||
|
#export DATABASE_ENGINE='django.db.backends.sqlite3'
|
||||||
|
#export DATABASE_NAME='/var/lib/portail-citoyen/portail-citoyen.db'
|
||||||
|
|
||||||
|
# Sentry / Raven configuration
|
||||||
|
#export export RAVEN_CONFIG_DSN='' # require package python-raven
|
||||||
|
|
||||||
|
# Log root directory
|
||||||
|
export LOG_ROOT='/var/log/portail-citoyen/portail-citoyen.log'
|
||||||
|
|
||||||
|
# We are behind a reverse proxy so we accept every hosts
|
||||||
|
export ALLOWED_HOSTS='*'
|
||||||
|
|
||||||
|
# Email configuration
|
||||||
|
#export EMAIL_HOST='localhost'
|
||||||
|
#export EMAIL_PORT='25'
|
||||||
|
#export EMAIL_SUBJECT_PREFIX='[Portail Citoyen]'
|
||||||
|
#export SERVER_EMAIL='root@test.com'
|
||||||
|
#export DEFAULT_FROM_EMAIL='portail-citoyen@test.com'
|
||||||
|
|
||||||
|
# Enables some features
|
||||||
|
export IDP_SAML2='yes'
|
||||||
|
#export IDP_OPENID='yes'
|
||||||
|
#export IDP_CAS='yes'
|
||||||
|
#export AUTH_SAML2='yes'
|
||||||
|
#export AUTH_OPENID='yes'
|
||||||
|
#export AUTH_SSL='yes'
|
||||||
|
#export AUTH_OATH='yes'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
. /etc/portail-citoyen/portail-citoyen.conf
|
||||||
|
|
||||||
|
python /usr/lib/portail-citoyen/manage.py syncdb --all --noinput
|
||||||
|
python /usr/lib/portail-citoyen/manage.py migrate --fake --noinput
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
Source: portail-citoyen2
|
||||||
|
Maintainer: Jérôme Schneider <jschneider@entrouvert.com>
|
||||||
|
Section: python
|
||||||
|
Priority: optional
|
||||||
|
Build-Depends: python-setuptools (>= 0.6b3),
|
||||||
|
python-all (>= 2.6.6-3),
|
||||||
|
debhelper (>= 8.0),
|
||||||
|
openssl,
|
||||||
|
python-django (>= 1.5)
|
||||||
|
Standards-Version: 3.9.1
|
||||||
|
X-Python-Version: >= 2.6
|
||||||
|
|
||||||
|
Package: python-portail-citoyen2
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, python (>= 2.6),
|
||||||
|
python-imaging,
|
||||||
|
python-feedparser,
|
||||||
|
python-django (>= 1.5), python-django (<< 1.6),
|
||||||
|
python-authentic2 (>= 2.1.2),
|
||||||
|
python-django-cms (>= 3.0rc1),
|
||||||
|
python-django-admin-tools (>= 0.5),
|
||||||
|
python-django-admin-tools (<< 0.6),
|
||||||
|
python-requests (>= 1.0.0),
|
||||||
|
python-django-allauth (>= 0.14)
|
||||||
|
Description: Portail citoyen v2 python module
|
||||||
|
Conflicts: python-portail-citoyen
|
||||||
|
|
||||||
|
Package: portail-citoyen2
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, adduser,
|
||||||
|
python-portail-citoyen2 (>= 1.0),
|
||||||
|
python-entrouvert (>= 6.0),
|
||||||
|
python-django-south (>= 0.8.4),
|
||||||
|
python-psycopg2,
|
||||||
|
gunicorn, dbconfig-common,
|
||||||
|
debconf | debconf-2.0, ucf
|
||||||
|
Recommends: postgresql-client
|
||||||
|
Suggests: nginx, postgresql
|
||||||
|
Description: Portail citoyen v2 daemon
|
||||||
|
Conflicts: portail-citoyen
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
README
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$(whoami)" != "portail-citoyen" ]; then
|
||||||
|
if which sudo; then
|
||||||
|
if sudo -v -u portail-citoyen; then
|
||||||
|
sudo -u portail-citoyen portail-citoyen-manage "$@"
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
echo "You must run this script with portail-citoyen user"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/portail-citoyen/portail-citoyen.conf ]; then
|
||||||
|
. /etc/portail-citoyen/portail-citoyen.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
/usr/lib/portail-citoyen/manage.py "$@"
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# config maintainer script for foo-pgsql
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# source debconf stuff
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
# source dbconfig-common shell library, and call the hook function
|
||||||
|
if [ -f /usr/share/dbconfig-common/dpkg/config.pgsql ]; then
|
||||||
|
. /usr/share/dbconfig-common/dpkg/config.pgsql
|
||||||
|
dbc_go portail-citoyen $@
|
||||||
|
fi
|
||||||
|
|
||||||
|
#DEBHELPER#
|
|
@ -0,0 +1,8 @@
|
||||||
|
etc/portail-citoyen
|
||||||
|
usr/lib/portail-citoyen
|
||||||
|
usr/share/dbconfig-common/scripts/portail-citoyen/install
|
||||||
|
var/lib/portail-citoyen/media
|
||||||
|
var/lib/portail-citoyen/static
|
||||||
|
var/lib/portail-citoyen/extra-static
|
||||||
|
var/run/portail-citoyen
|
||||||
|
var/log/portail-citoyen
|
|
@ -0,0 +1 @@
|
||||||
|
README
|
|
@ -0,0 +1,178 @@
|
||||||
|
#!/bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: portail-citoyen
|
||||||
|
# Required-Start: $network $local_fs
|
||||||
|
# Required-Stop:
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Portail citoyen
|
||||||
|
# Description: Portail citoyen
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
# Author: Jérôme Schneider <jschneider@entrouvert.com>
|
||||||
|
|
||||||
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
DESC=portail-citoyen
|
||||||
|
NAME=portail-citoyen
|
||||||
|
DAEMON=/usr/bin/gunicorn
|
||||||
|
PID_DIR=/var/run/$NAME
|
||||||
|
LOG_DIR=/var/log/$NAME
|
||||||
|
PIDFILE=$PID_DIR/$NAME.pid
|
||||||
|
SCRIPTNAME=/etc/init.d/$NAME
|
||||||
|
|
||||||
|
USER=portail-citoyen
|
||||||
|
GROUP=portail-citoyen
|
||||||
|
|
||||||
|
DAEMON_ARGS="--pid $PIDFILE \
|
||||||
|
--user $USER --group $GROUP \
|
||||||
|
--daemon \
|
||||||
|
--access-logfile $LOG_DIR/gunicorn-access.log \
|
||||||
|
--log-file $LOG_DIR/gunicorn-error.log \
|
||||||
|
--bind=unix:$PID_DIR/$NAME.sock \
|
||||||
|
--workers=10 \
|
||||||
|
--worker-class=sync \
|
||||||
|
--timeout=60 \
|
||||||
|
portail_citoyen.wsgi:application"
|
||||||
|
|
||||||
|
# Exit if the package is not installed
|
||||||
|
[ -x $DAEMON ] || exit 0
|
||||||
|
|
||||||
|
# Read configuration variable file if it is present
|
||||||
|
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||||
|
|
||||||
|
# Load confg
|
||||||
|
. /etc/portail-citoyen/portail-citoyen.conf
|
||||||
|
|
||||||
|
# Load the VERBOSE setting and other rcS variables
|
||||||
|
. /lib/init/vars.sh
|
||||||
|
|
||||||
|
# Define LSB log_* functions.
|
||||||
|
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
# Create pid directory
|
||||||
|
if [ ! -d $PID_DIR ]; then
|
||||||
|
install -d -m 755 -o $USER -g $GROUP $PID_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that starts the daemon/service
|
||||||
|
#
|
||||||
|
do_start()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been started
|
||||||
|
# 1 if daemon was already running
|
||||||
|
# 2 if daemon could not be started
|
||||||
|
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||||
|
|| return 1
|
||||||
|
start-stop-daemon --start --quiet --exec $DAEMON -- \
|
||||||
|
$DAEMON_ARGS \
|
||||||
|
|| return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that stops the daemon/service
|
||||||
|
#
|
||||||
|
do_stop()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been stopped
|
||||||
|
# 1 if daemon was already stopped
|
||||||
|
# 2 if daemon could not be stopped
|
||||||
|
# other if a failure occurred
|
||||||
|
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||||
|
RETVAL="$?"
|
||||||
|
[ "$RETVAL" = 2 ] && return 2
|
||||||
|
# Wait for children to finish too if this is a daemon that forks
|
||||||
|
# and if the daemon is only ever run from this initscript.
|
||||||
|
# If the above conditions are not satisfied then add some other code
|
||||||
|
# that waits for the process to drop all resources that could be
|
||||||
|
# needed by services started subsequently. A last resort is to
|
||||||
|
# sleep for some time.
|
||||||
|
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||||
|
[ "$?" = 2 ] && return 2
|
||||||
|
# Many daemons don't delete their pidfiles when they exit.
|
||||||
|
rm -f $PIDFILE
|
||||||
|
return "$RETVAL"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that sends a SIGHUP to the daemon/service
|
||||||
|
#
|
||||||
|
do_reload() {
|
||||||
|
#
|
||||||
|
# If the daemon can reload its configuration without
|
||||||
|
# restarting (for example, when it is sent a SIGHUP),
|
||||||
|
# then implement that here.
|
||||||
|
#
|
||||||
|
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
do_migrate() {
|
||||||
|
log_action_msg "Applying new migrations .."
|
||||||
|
su $USER -p -c "/usr/bin/portail-citoyen-manage syncdb --migrate --noinput"
|
||||||
|
log_action_msg ".. done"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
do_migrate
|
||||||
|
log_daemon_msg "Starting $DESC " "$NAME"
|
||||||
|
do_start
|
||||||
|
case "$?" in
|
||||||
|
0|1) log_end_msg 0 ;;
|
||||||
|
2) log_end_msg 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
case "$?" in
|
||||||
|
0|1) log_end_msg 0 ;;
|
||||||
|
2) log_end_msg 1 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
#reload|force-reload)
|
||||||
|
#
|
||||||
|
# If do_reload() is not implemented then leave this commented out
|
||||||
|
# and leave 'force-reload' as an alias for 'restart'.
|
||||||
|
#
|
||||||
|
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||||
|
#do_reload
|
||||||
|
#log_end_msg $?
|
||||||
|
#;;
|
||||||
|
restart|force-reload)
|
||||||
|
#
|
||||||
|
# If the "reload" option is implemented then remove the
|
||||||
|
# 'force-reload' alias
|
||||||
|
#
|
||||||
|
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
do_migrate
|
||||||
|
case "$?" in
|
||||||
|
0|1)
|
||||||
|
do_start
|
||||||
|
case "$?" in
|
||||||
|
0) log_end_msg 0 ;;
|
||||||
|
1) log_end_msg 1 ;; # Old process is still running
|
||||||
|
*) log_end_msg 1 ;; # Failed to start
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Failed to stop
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
debian/conf/portail-citoyen.conf /etc/portail-citoyen
|
||||||
|
debian/conf/nginx-example.conf /etc/portail-citoyen
|
||||||
|
debian/conf/db.conf /usr/share/portail-citoyen/templates
|
||||||
|
debian/portail-citoyen-manage /usr/bin
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Postinst script for portail-citoyen
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
NAME=portail-citoyen
|
||||||
|
USER=portail-citoyen
|
||||||
|
GROUP=portail-citoyen
|
||||||
|
HOME=/var/lib/$NAME
|
||||||
|
|
||||||
|
# source debconf stuff
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
if ! getent group $GROUP > /dev/null 2>&1; then
|
||||||
|
echo -n "Adding group $GROUP.." >&2
|
||||||
|
addgroup --quiet --system $GROUP
|
||||||
|
echo "..done" >&2
|
||||||
|
fi
|
||||||
|
if ! getent passwd $USER > /dev/null 2>&1; then
|
||||||
|
echo -n "Adding user $USER.." >&2
|
||||||
|
adduser --quiet --system --gecos "$NAME daemon" \
|
||||||
|
--ingroup $GROUP \
|
||||||
|
--no-create-home --home $HOME \
|
||||||
|
$USER
|
||||||
|
echo "..done" >&2
|
||||||
|
fi
|
||||||
|
if [ ! -f "/etc/$NAME/secret" ]; then
|
||||||
|
echo -n "Generating Django secret.." >&2
|
||||||
|
echo "export SECRET_KEY='`</dev/urandom tr -dc [:alnum:]-_\!\%\^:\; | head -c70`'" > /etc/$NAME/secret
|
||||||
|
chmod 0640 /etc/$NAME/secret
|
||||||
|
chown root:$GROUP /etc/$NAME/secret
|
||||||
|
echo "..done" >&2
|
||||||
|
fi
|
||||||
|
if [ ! -f "/etc/$NAME/saml-certs.conf" ]; then
|
||||||
|
echo -n "Generating SAML certificates.." >&2
|
||||||
|
openssl genrsa -out /tmp/saml.key 2048 >&2
|
||||||
|
openssl rsa -in /tmp/saml.key -pubout -out /tmp/saml.pub >&2
|
||||||
|
echo SAML_SIGNATURE_PRIVATE_KEY=\"`cat /tmp/saml.key`\" > /etc/$NAME/saml-certs.conf
|
||||||
|
echo SAML_SIGNATURE_PUBLIC_KEY=\"`cat /tmp/saml.pub`\" >> /etc/$NAME/saml-certs.conf
|
||||||
|
rm /tmp/saml.key /tmp/saml.pub
|
||||||
|
echo "..done" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown $USER:$GROUP /var/lib/portail-citoyen \
|
||||||
|
/var/lib/portail-citoyen/static \
|
||||||
|
/var/lib/portail-citoyen/extra-static \
|
||||||
|
/var/lib/portail-citoyen/media \
|
||||||
|
/var/run/portail-citoyen \
|
||||||
|
/var/log/portail-citoyen
|
||||||
|
|
||||||
|
# source dbconfig-common shell library, and call the hook function
|
||||||
|
if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then
|
||||||
|
. /usr/share/dbconfig-common/dpkg/postinst.pgsql
|
||||||
|
dbc_generate_include="template:/etc/portail-citoyen/db.conf"
|
||||||
|
dbc_generate_include_args="-o template_infile=/usr/share/portail-citoyen/templates/db.conf -U"
|
||||||
|
dbc_generate_include_owner="root:portail-citoyen"
|
||||||
|
dbc_generate_include_perms="640"
|
||||||
|
dbc_pgsql_createdb_encoding="UTF8"
|
||||||
|
dbc_go portail-citoyen $@
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Generating static files.." >&2
|
||||||
|
su $USER -p -c "/usr/bin/portail-citoyen-manage collectstatic --noinput --link" >&2
|
||||||
|
echo "..done" >&2
|
||||||
|
;;
|
||||||
|
|
||||||
|
reconfigure|abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
db_stop
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# postrm script for portail-citoyen
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$1" in purge)
|
||||||
|
deluser --quiet --system portail-citoyen > /dev/null || true
|
||||||
|
rm -f /etc/portail-citoyen/secret
|
||||||
|
rm -rf /var/lib/portail-citoyen/static/*
|
||||||
|
# source debconf stuff
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
# source dbconfig-common shell library, and call the hook function
|
||||||
|
if [ -f /usr/share/dbconfig-common/dpkg/postrm.pgsql ]; then
|
||||||
|
. /usr/share/dbconfig-common/dpkg/postrm.pgsql
|
||||||
|
dbc_go portail-citoyen $@
|
||||||
|
fi
|
||||||
|
|
||||||
|
DBCONF=/etc/portail-citoyen/db.conf
|
||||||
|
if [ "$1" = "purge" ]; then
|
||||||
|
rm -f $DBCONF
|
||||||
|
if which ucf >/dev/null 2>&1; then
|
||||||
|
ucf --purge $DBCONF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
usr/
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
d=$(CURDIR)/debian/tmp
|
||||||
|
portailcitoyen=$(CURDIR)/debian/portail-citoyen
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --with python2
|
||||||
|
|
||||||
|
override_dh_install:
|
||||||
|
install -m 755 -o root -g root debian/conf/syncdb.sh $(portailcitoyen)/usr/share/dbconfig-common/scripts/portail-citoyen/install/pgsql
|
||||||
|
mv $(d)/usr/bin/portail-citoyen2 $(portailcitoyen)/usr/lib/portail-citoyen/manage.py
|
||||||
|
rm -rf $(d)/usr/bin
|
||||||
|
dh_install
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
|
@ -1,310 +0,0 @@
|
||||||
<page xmlns="http://projectmallard.org/1.0/"
|
|
||||||
type="guide" id="api-services" xml:lang="fr">
|
|
||||||
|
|
||||||
<info>
|
|
||||||
<link type="guide" xref="index#api" />
|
|
||||||
<revision docversion="0.1" date="2013-04-05" status="draft"/>
|
|
||||||
<revision docversion="0.3" date="2013-05-01" status="draft"/>
|
|
||||||
<credit type="author">
|
|
||||||
<name>Frédéric Péters</name>
|
|
||||||
<email>fpeters@entrouvert.com</email>
|
|
||||||
</credit>
|
|
||||||
<credit type="author">
|
|
||||||
<name>Benjamin Dauvergne</name>
|
|
||||||
<email>bdauvergne@entrouvert.com</email>
|
|
||||||
</credit>
|
|
||||||
<credit type="author">
|
|
||||||
<name>Pierre Cros</name>
|
|
||||||
<email>pcros@entrouvert.com</email>
|
|
||||||
</credit>
|
|
||||||
<desc>Remontée d'informations de services vers le portail citoyen</desc>
|
|
||||||
</info>
|
|
||||||
|
|
||||||
<title>Remontée d'informations vers le portail citoyen</title>
|
|
||||||
|
|
||||||
<section id="intro">
|
|
||||||
<title>Introduction</title>
|
|
||||||
|
|
||||||
<p>Le portail citoyen est une plate-forme dans laquelle sont agrégées des
|
|
||||||
informations provenant de sources web diverses.</p>
|
|
||||||
|
|
||||||
<p>Afin de permettre cette remontée d'information pour les services ne
|
|
||||||
disposant pas nativement d'une API exposée sur le Web, nous proposons
|
|
||||||
d'utiliser celle qui est décrite dans ce document.</p>
|
|
||||||
|
|
||||||
</section> <!-- #intro -->
|
|
||||||
|
|
||||||
|
|
||||||
<section id="requete">
|
|
||||||
<title>Requête</title>
|
|
||||||
|
|
||||||
<p>Le portail effectue des requêtes HTTP GET vers les différents services. Les
|
|
||||||
échanges atendus sont conformes au style d'architecture REST.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<section id="req-ident">
|
|
||||||
<title>Requête identifiante</title>
|
|
||||||
|
|
||||||
<p>L'URI utilisée pour ces requêtes peut contenir des informations identifiant
|
|
||||||
l'usager concerné.</p>
|
|
||||||
|
|
||||||
<section id="saml">
|
|
||||||
<title>Identifiant SAML</title>
|
|
||||||
|
|
||||||
<p>Lorsque le service intègre le protocole de fédération d'identité SAML, la
|
|
||||||
requête identifiante utilisera un identifiant SAML, le NameID :</p>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
https://www.example.com/eservices/pending?NameID=<var>nameid</var>
|
|
||||||
</code>
|
|
||||||
</section> <!-- #saml -->
|
|
||||||
|
|
||||||
<section id="adel">
|
|
||||||
<title>Adresse électronique</title>
|
|
||||||
|
|
||||||
<p>Lorsque le service n'intègre pas le protocole de fédération d'identité SAML,
|
|
||||||
la requête identifiante utilisera l'adresse électronique de l'utilisateur :</p>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
https://www.example.com/eservices/pending?email=<var>email</var>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<note>
|
|
||||||
<title>Adresses électroniques identiques</title>
|
|
||||||
<p>L'utilisation de cette méthode pour identifier l'utilisateur ne fonctionne
|
|
||||||
que si ce dernier a employé la même adresse électronique pour la création du
|
|
||||||
compte citoyen et la création du compte sur le service.</p>
|
|
||||||
</note>
|
|
||||||
</section> <!-- #adel -->
|
|
||||||
</section> <!-- #req-ident -->
|
|
||||||
|
|
||||||
<section id="autres">
|
|
||||||
<title>Autres requêtes</title>
|
|
||||||
|
|
||||||
<p>Il est possible pour le service d'exposer davantage de données via des
|
|
||||||
URL.</p>
|
|
||||||
|
|
||||||
<section id="donnees">
|
|
||||||
<title>Données génériques (qui ne concernent pas un utilisateur particulier)</title>
|
|
||||||
|
|
||||||
<p>L'applications peut exposer des données génériques (horaires, informations,
|
|
||||||
actualités). Elle doit le faire via une URL du type :</p>
|
|
||||||
|
|
||||||
<code>https://www.example.com/eservices/horaires/</code>
|
|
||||||
</section> <!-- #donnees -->
|
|
||||||
|
|
||||||
<section id="filtres">
|
|
||||||
<title>Filtres</title>
|
|
||||||
|
|
||||||
<p>Il peut être utile pour le portail citoyen de filtrer les données récupérées
|
|
||||||
en utilisant des critères divers (catégories, localisations...) via les URL.
|
|
||||||
Les filtres doivent être compris dans les URL sous la forme (pour exposer les
|
|
||||||
horaires de Montpellier par exemple) :</p>
|
|
||||||
|
|
||||||
<code>https://www.example.com/eservices/montpellier/horaires/</code>
|
|
||||||
|
|
||||||
</section> <!-- #filtres -->
|
|
||||||
</section> <!-- #autres -->
|
|
||||||
</section> <!-- #requete -->
|
|
||||||
|
|
||||||
<section id="structure">
|
|
||||||
<title>Structure de l'information fournie par les services</title>
|
|
||||||
|
|
||||||
<p>En réponse à la requête du portail citoyen, le service doit fournir une
|
|
||||||
information structurée. Cette réponse peut prendre plusieurs formes.</p>
|
|
||||||
|
|
||||||
<section id="fils">
|
|
||||||
<title>Fils d'info</title>
|
|
||||||
|
|
||||||
<p>Le portail peut intégrer, sans que cela ne nécessite la moindre
|
|
||||||
modification, les fils d'information au format <link
|
|
||||||
href="http://en.wikipedia.org/wiki/RSS">RSS</link> et <link
|
|
||||||
href="http://en.wikipedia.org/wiki/Atom_(standard)">Atom</link> qui sont
|
|
||||||
offerts par les services.</p>
|
|
||||||
|
|
||||||
</section> <!-- #fils -->
|
|
||||||
|
|
||||||
<section id="html">
|
|
||||||
<title>HTML</title>
|
|
||||||
|
|
||||||
<p>Comme pour les fils d'infos, des blocs de code HTML fournis par les services
|
|
||||||
peuvent être intégrés directement dans le portail. À noter toutefois que ce
|
|
||||||
HTML doit être "brut" parce qu'il est filtré. Il ne peut pas contenir de
|
|
||||||
Javascript par exemple.</p>
|
|
||||||
|
|
||||||
</section> <!-- #html -->
|
|
||||||
|
|
||||||
<section id="json">
|
|
||||||
<title>JSON</title>
|
|
||||||
|
|
||||||
<p>La réponse du service à la requête du portail peut aussi être faite en
|
|
||||||
JSON.</p>
|
|
||||||
|
|
||||||
<section id="liste">
|
|
||||||
<title>Liste</title>
|
|
||||||
|
|
||||||
<p>Les données exposées au format JSON doivent être organisées sous forme d'un
|
|
||||||
tableau associatif. Les clés (variables) de ce tableau sont les suivantes :</p>
|
|
||||||
|
|
||||||
<list>
|
|
||||||
<item><p><code>title</code> : titre (obligatoire)</p></item>
|
|
||||||
<item><p><code>url</code> : URL (obligatoire)</p></item>
|
|
||||||
<item><p><code>description</code> : description (facultatif)</p></item>
|
|
||||||
</list>
|
|
||||||
|
|
||||||
<p>La liste des données doit être attachées à une clé nommée <code>data</code>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Exemple :</p>
|
|
||||||
|
|
||||||
<code mime="application/json">
|
|
||||||
{ "data": [
|
|
||||||
{"title": "Demande de bac pour ordures",
|
|
||||||
"url": "https://eservices.example.com/dechets/demande-bac"},
|
|
||||||
{"title": "Demande d'acte de naissance",
|
|
||||||
"url": "https://eservices.example.com/actes/naissance",
|
|
||||||
"Description": "Faites vos démarches sans vous déplacer"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</code>
|
|
||||||
|
|
||||||
</section> <!-- #liste -->
|
|
||||||
|
|
||||||
<section id="tableau">
|
|
||||||
<title>Tableau</title>
|
|
||||||
|
|
||||||
<p>Le portail citoyen peut aussi recevoir des données qu'il affichera sous
|
|
||||||
forme de tableau.</p>
|
|
||||||
|
|
||||||
<p>La liste des données doit être attachées à une clé nommée <code>data</code>
|
|
||||||
et une clé nommée <code>columns</code> peut exister pour fournir les libellés
|
|
||||||
des colonnes.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Exemple : </p>
|
|
||||||
|
|
||||||
<code mime="application/json">
|
|
||||||
{"data": [
|
|
||||||
{"day": "Lundi", "open": "8h", "close": "17h"},
|
|
||||||
{"day": "Mardi", "open": "8h", "close": "19h"},
|
|
||||||
{"day": "Mercredi", "open": "9h", "close": "17h"}
|
|
||||||
],
|
|
||||||
"columns": {
|
|
||||||
"day": "Jour",
|
|
||||||
"open": "Horaire d'ouverture",
|
|
||||||
"close": "Horaire de fermeture"}
|
|
||||||
}
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<p>Le résultat sur le portail citoyen sera l'affichage du tableau suivant :</p>
|
|
||||||
|
|
||||||
<table frame="none" rules="none" shade="rows">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td><p>Jour</p></td>
|
|
||||||
<td><p>Horaire d'ouverture</p></td>
|
|
||||||
<td><p>Horaire de fermeture</p></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><p>Lundi</p></td><td><p>8h</p></td><td><p>17h</p></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><p>Mardi</p></td><td><p>8h</p></td><td><p>19h</p></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><p>Mercredi</p></td><td><p>9h</p></td><td><p>17h</p></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</section> <!-- #tableau -->
|
|
||||||
|
|
||||||
</section> <!-- #json -->
|
|
||||||
|
|
||||||
</section> <!-- #structure -->
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<title>Signature de l'URL</title>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
L'URL doit être signée via une clé partagée à configurer des deux cotés de la
|
|
||||||
liaison, la signature est du type HMAC; l'algorithme de hash à employer est
|
|
||||||
passé en paramètre.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<note><p>Si le service doit être utilisé par différents requêteurs il est
|
|
||||||
recommandé de ne pas utiliser une clé unique; il est ainsi suggéré que l'URL
|
|
||||||
attende également un paramètre précisant l'appelant, à travers, par exemple,
|
|
||||||
un paramètre supplémentaire <code>apikey</code>.
|
|
||||||
</p></note>
|
|
||||||
|
|
||||||
<note><p>En ce qui concerne l'algorithme de hash, il est préconisé d'utiliser
|
|
||||||
SHA-256 par respect du <link
|
|
||||||
href="http://references.modernisation.gouv.fr/rgs-securite">Référentiel Général
|
|
||||||
de Sécurité (RGS)</link>.</p></note>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
La signature est à calculer sur la query string encodée complète, en
|
|
||||||
enlevant les paramètres terminaux <code>algo</code>, <code>timestamp</code>,
|
|
||||||
<code>nonce</code> et <code>signature</code>. La formule de calcul de la
|
|
||||||
signature est la suivante :
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
BASE64(HMAC-HASH(query_string+'algo=HASH&timestamp=' + timestamp + '&nonce=" + nonce, clé))
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<list>
|
|
||||||
|
|
||||||
<item><p><code>timestamp</code> est la date dans la zone GMT au format ISO8601
|
|
||||||
en se limitant à la précision des secondes (ex : 2012-04-04T12:34:00Z),
|
|
||||||
</p></item>
|
|
||||||
|
|
||||||
<item><p>nonce est une chaîne aléatoire contenant au moins 128 bits d'entropie
|
|
||||||
(16 octets aléatoires),</p></item>
|
|
||||||
|
|
||||||
<item><p>algo est une chaîne représentant l'algorithme de hachage utilisé, sont
|
|
||||||
définis : sha1, sha256, sha512 pour les trois algorithmes correspondant.
|
|
||||||
L'utilisation d'une valeur différente n'est pas définie.</p></item>
|
|
||||||
|
|
||||||
</list>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
La query string définitive est ainsi :
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
<var>qs_initial</var>&algo=<var>algo</var>&timestamp=<var>ts</var>&nonce=<var>nonce</var>&signature=<var>signature</var>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Pour la validation il faut contrôler :
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<list>
|
|
||||||
|
|
||||||
<item><p>que la signature regénérée est identique,</p></item>
|
|
||||||
|
|
||||||
<item><p>que le timestamp est dans une fenêtre étroite par rapport à la date
|
|
||||||
courante, il est recommandé d'accepter un écart de trente secondes
|
|
||||||
maximum.</p></item>
|
|
||||||
|
|
||||||
<item><p>que le nonce n'a encore jamais été vu (associé au timestamp il suffit
|
|
||||||
de conserver les nonces pour une durée de 3 ou 4 fois la fenêtre de temps, par
|
|
||||||
exemple cinq minutes).</p></item>
|
|
||||||
|
|
||||||
</list>
|
|
||||||
|
|
||||||
<note><p>
|
|
||||||
Lors de l'utilisation de ces signatures il est important d'utiliser HTTPS pour
|
|
||||||
éviter les interceptions et de stocker les nonces pour éviter les rejeux et à
|
|
||||||
maintenir une bonne synchronisation des horloges, en utilisant par exemple le
|
|
||||||
protocole NTP.
|
|
||||||
</p></note>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</page>
|
|
13
jenkins.sh
13
jenkins.sh
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/bash -e
|
|
||||||
pip install --upgrade setuptools
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install --upgrade pylint
|
|
||||||
pip install --upgrade pyOpenSSL==0.13 ndg-httpsclient requests pyasn1
|
|
||||||
sed -i 's/^MAX = 64/MAX = 200/' $VIRTUAL_ENV/lib/python*/site-packages/ndg/httpsclient/subj_alt_name.py
|
|
||||||
|
|
||||||
pip install --upgrade -r requirements.txt
|
|
||||||
|
|
||||||
./portail-citoyen syncdb --migrate --noinput --no-initial-data
|
|
||||||
./portail-citoyen loaddata initial_data
|
|
||||||
./portail-citoyen validate
|
|
||||||
(pylint -f parseable --rcfile /var/lib/jenkins/pylint.django.rc portail_citoyen2/ | tee pylint.out) || /bin/true
|
|
|
@ -1,5 +0,0 @@
|
||||||
BASE=`dirname $0`
|
|
||||||
|
|
||||||
ENV=${ENV:-dev}
|
|
||||||
|
|
||||||
$BASE/run.sh loaddata --traceback initial_data
|
|
|
@ -1,22 +0,0 @@
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
DEBUG_TOOLBAR = True
|
|
||||||
TEMPLATE_DEBUG = True
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
|
||||||
'INTERCEPT_REDIRECTS': False,
|
|
||||||
}
|
|
||||||
INTERNAL_IPS = ('127.0.0.1',)
|
|
||||||
|
|
||||||
SECRET_KEY = 'coin'
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': os.path.join(os.path.dirname(__file__), 'portail_citoyen.sqlite'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "portail_citoyen2.settings")
|
|
||||||
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
|
|
||||||
execute_from_command_line(sys.argv)
|
|
|
@ -1,8 +0,0 @@
|
||||||
import os.path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
__version__ = '0.1'
|
|
||||||
|
|
||||||
apps_dir = os.path.join(os.path.dirname(__file__), 'apps')
|
|
||||||
if apps_dir not in sys.path:
|
|
||||||
sys.path.append(apps_dir)
|
|
|
@ -1,14 +0,0 @@
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.utils.http import urlencode
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
@never_cache
|
|
||||||
def login(request, extra_context=None):
|
|
||||||
query = urlencode({REDIRECT_FIELD_NAME: request.build_absolute_uri()})
|
|
||||||
url = '{0}?{1}'.format(settings.LOGIN_URL, query)
|
|
||||||
return HttpResponseRedirect(url)
|
|
||||||
|
|
||||||
admin.site.login = login
|
|
|
@ -1 +0,0 @@
|
||||||
# Create your models here.
|
|
|
@ -1,33 +0,0 @@
|
||||||
from allauth.socialaccount import providers
|
|
||||||
from allauth.socialaccount.providers.base import ProviderAccount
|
|
||||||
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
|
|
||||||
from allauth.account.models import EmailAddress
|
|
||||||
|
|
||||||
class Authentic2Account(ProviderAccount):
|
|
||||||
def to_str(self):
|
|
||||||
return self.account.uid
|
|
||||||
|
|
||||||
|
|
||||||
class Authentic2Provider(OAuth2Provider):
|
|
||||||
id = 'authentic2'
|
|
||||||
name = 'Authentic2'
|
|
||||||
package = 'portail_citoyen2.allauth_authentic2'
|
|
||||||
account_class = Authentic2Account
|
|
||||||
|
|
||||||
def extract_uid(self, data):
|
|
||||||
return str(data['username'])
|
|
||||||
|
|
||||||
def extract_common_fields(self, data):
|
|
||||||
return dict(email=data.get('email'),
|
|
||||||
username=data.get('username'),
|
|
||||||
name=data.get('displayname'))
|
|
||||||
|
|
||||||
def extract_email_addresses(self, data):
|
|
||||||
ret = [EmailAddress(email=data['email'],
|
|
||||||
verified=True,
|
|
||||||
primary=True)]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
providers.registry.register(Authentic2Provider)
|
|
|
@ -1,5 +0,0 @@
|
||||||
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
|
|
||||||
from .provider import Authentic2Provider
|
|
||||||
|
|
||||||
urlpatterns = default_urlpatterns(Authentic2Provider)
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import urlparse
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
|
|
||||||
OAuth2LoginView,
|
|
||||||
OAuth2CallbackView)
|
|
||||||
from .provider import Authentic2Provider
|
|
||||||
|
|
||||||
|
|
||||||
class Authentic2OAuth2Adapter(OAuth2Adapter):
|
|
||||||
provider_id = Authentic2Provider.id
|
|
||||||
|
|
||||||
def get_url(self):
|
|
||||||
provider = self.get_provider()
|
|
||||||
try:
|
|
||||||
return provider.get_settings()['URL']
|
|
||||||
except IndexError:
|
|
||||||
raise ImproperlyConfigured('The authentic2 provider needs an URL defined in settings')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def access_token_url(self):
|
|
||||||
return urlparse.urljoin(self.get_url(), 'access_token')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def authorize_url(self):
|
|
||||||
return urlparse.urljoin(self.get_url(), 'authorize')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def profile_url(self):
|
|
||||||
return urlparse.urljoin(self.get_url(), 'user-info')
|
|
||||||
|
|
||||||
def complete_login(self, request, app, token, **kwargs):
|
|
||||||
resp = requests.get(self.profile_url,
|
|
||||||
headers={'authorization': 'Bearer %s' % token.token})
|
|
||||||
extra_data = resp.json()
|
|
||||||
return self.get_provider().sociallogin_from_response(request,
|
|
||||||
extra_data)
|
|
||||||
|
|
||||||
|
|
||||||
oauth2_login = OAuth2LoginView.adapter_view(Authentic2OAuth2Adapter)
|
|
||||||
oauth2_callback = OAuth2CallbackView.adapter_view(Authentic2OAuth2Adapter)
|
|
|
@ -1,38 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
class AppSettings(object):
|
|
||||||
__defaults = {
|
|
||||||
'PORTAIL_ADMIN_URL': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, prefix):
|
|
||||||
self.prefix = prefix
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name in self.__defaults:
|
|
||||||
return self._settings(name, self.__defaults[name])
|
|
||||||
else:
|
|
||||||
return self._settings(name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def TEMPLATE_VARS(self):
|
|
||||||
v = self._settings('TEMPLATE_VARS', {})
|
|
||||||
try:
|
|
||||||
from django.conf import settings
|
|
||||||
v['LOGOUT_URL'] = settings.LOGOUT_URL
|
|
||||||
except AttributeError:
|
|
||||||
raise ImproperlyConfigured('LOGOUT_URL is mandatory')
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _settings(self, name, default=Ellipsis):
|
|
||||||
from django.conf import settings
|
|
||||||
if default is Ellipsis:
|
|
||||||
return getattr(settings, self.prefix + name)
|
|
||||||
else:
|
|
||||||
return getattr(settings, self.prefix + name, default)
|
|
||||||
|
|
||||||
app_settings = AppSettings('PORTAIL_CITOYEN_')
|
|
||||||
app_settings.__name__ = __name__
|
|
||||||
sys.modules[__name__] = app_settings
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from models import AuQuotidienAPI
|
|
||||||
|
|
||||||
class AuQuotidienAPIAdmin(admin.ModelAdmin):
|
|
||||||
list_display = [ 'name', 'order', 'active', 'service_provider', 'orig', 'hash_algo', 'verify_certificate', 'allow_redirects', 'timeout' ]
|
|
||||||
|
|
||||||
admin.site.register(AuQuotidienAPI, AuQuotidienAPIAdmin)
|
|
|
@ -1,78 +0,0 @@
|
||||||
import logging
|
|
||||||
import urlparse
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
from cms.plugin_pool import plugin_pool
|
|
||||||
|
|
||||||
|
|
||||||
from data_source_plugin.cms_plugins import DataSourcePlugin, Data
|
|
||||||
from data_source_plugin.models import DataSource
|
|
||||||
|
|
||||||
from .models import (
|
|
||||||
AuQuotidienActiveFormsPlugin as AuQuotidienActiveFormsPluginModel,
|
|
||||||
AuQuotidienCategoryPlugin as AuQuotidienCategoryPluginModel)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class FakeSource(object):
|
|
||||||
def __init__(self, url, api):
|
|
||||||
self.url = url
|
|
||||||
self.mime_type = DataSource.JSON
|
|
||||||
self.api = api
|
|
||||||
|
|
||||||
def __getattr__(self, attribute):
|
|
||||||
return getattr(self.api, attribute)
|
|
||||||
|
|
||||||
class AuQuotidienBasePlugin(DataSourcePlugin):
|
|
||||||
model = AuQuotidienActiveFormsPluginModel
|
|
||||||
render_template = None
|
|
||||||
text_enabled = True
|
|
||||||
inlines = []
|
|
||||||
url_template = None
|
|
||||||
|
|
||||||
def get_apis(self, context, instance):
|
|
||||||
user = context['request'].user
|
|
||||||
return instance.apis.filter(service_provider__libertyfederation__user=user, active=True)
|
|
||||||
|
|
||||||
def get_sources(self, context, instance):
|
|
||||||
for api in self.get_apis(context, instance):
|
|
||||||
url = api.base_url
|
|
||||||
url_suffix = self.url_template
|
|
||||||
orig = api.orig
|
|
||||||
url_suffix = url_suffix.format(provider_id=api.provider_id,
|
|
||||||
orig=orig)
|
|
||||||
url = urlparse.urljoin(url, url_suffix)
|
|
||||||
source = FakeSource(url=url, api=api)
|
|
||||||
yield Data(source, context, 0, instance.refresh)
|
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
|
||||||
ctx = super(AuQuotidienBasePlugin, self).render(context, instance,
|
|
||||||
placeholder)
|
|
||||||
ctx['instance'] = instance
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
class AuQuotidienActiveFormsPlugin(AuQuotidienBasePlugin):
|
|
||||||
model = AuQuotidienActiveFormsPluginModel
|
|
||||||
name = _('au-quotidien active forms')
|
|
||||||
render_template = 'auquotidien_plugin/active_forms.html'
|
|
||||||
text_enabled = True
|
|
||||||
inlines = []
|
|
||||||
url_template = '/myspace/json/forms?format=json&NameID={{{{federations.service_{provider_id}.links.0}}}}&orig={orig}'
|
|
||||||
|
|
||||||
|
|
||||||
plugin_pool.register_plugin(AuQuotidienActiveFormsPlugin)
|
|
||||||
|
|
||||||
|
|
||||||
class AuQuotidienCategoryPlugin(AuQuotidienBasePlugin):
|
|
||||||
model = AuQuotidienCategoryPluginModel
|
|
||||||
name = _('au-quotidien categories')
|
|
||||||
render_template = 'auquotidien_plugin/categories.html'
|
|
||||||
text_enabled = True
|
|
||||||
inlines = []
|
|
||||||
url_template = '/categories?format=json&NameID={{{{federations.service_{provider_id}.links.0}}}}&orig={orig}'
|
|
||||||
|
|
||||||
|
|
||||||
plugin_pool.register_plugin(AuQuotidienCategoryPlugin)
|
|
|
@ -1,105 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2014-02-10 16:31+0100\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
|
|
||||||
#: cms_plugins.py:59 models.py:67 models.py:68
|
|
||||||
msgid "au-quotidien active forms"
|
|
||||||
msgstr "affichage des démarches en cours"
|
|
||||||
|
|
||||||
#: cms_plugins.py:71 models.py:82
|
|
||||||
msgid "au-quotidien categories"
|
|
||||||
msgstr "affichage des catégories de démarches"
|
|
||||||
|
|
||||||
#: models.py:14
|
|
||||||
msgid "name"
|
|
||||||
msgstr "nom"
|
|
||||||
|
|
||||||
#: models.py:16
|
|
||||||
msgid "active"
|
|
||||||
msgstr "actif"
|
|
||||||
|
|
||||||
#: models.py:17
|
|
||||||
msgid "order"
|
|
||||||
msgstr "ordre"
|
|
||||||
|
|
||||||
#: models.py:19
|
|
||||||
msgid "liberty service provider for the au-quotidien service"
|
|
||||||
msgstr "fournisseur de service liberty associé au service au-quotidien"
|
|
||||||
|
|
||||||
#: models.py:21
|
|
||||||
msgid "origin"
|
|
||||||
msgstr "origine"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "hashing algorithm"
|
|
||||||
msgstr "algorithme de hachage"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "signature key"
|
|
||||||
msgstr "clé de signature"
|
|
||||||
|
|
||||||
#: models.py:26
|
|
||||||
msgid "verify certificate"
|
|
||||||
msgstr "vérifier le certificat SSL"
|
|
||||||
|
|
||||||
#: models.py:28
|
|
||||||
msgid "allows HTTP redirections"
|
|
||||||
msgstr "permettre les redirections HTTP"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "it can improve latencies to forbid redirection follow"
|
|
||||||
msgstr ""
|
|
||||||
"cela peut améliorer les performances en cas d'absence de service d'interdire "
|
|
||||||
"les redirections"
|
|
||||||
|
|
||||||
#: models.py:31
|
|
||||||
msgid "timeout"
|
|
||||||
msgstr "temps d'expiration pour la requête HTTP"
|
|
||||||
|
|
||||||
#: models.py:33
|
|
||||||
msgid "time in second to wait before failing to download a datasource"
|
|
||||||
msgstr " "
|
|
||||||
|
|
||||||
#: models.py:50
|
|
||||||
msgid "au-quotidien API endpoint"
|
|
||||||
msgstr "terminaison de l'API au-quotidien"
|
|
||||||
|
|
||||||
#: models.py:51
|
|
||||||
msgid "au-quotidien API endpoints"
|
|
||||||
msgstr "terminaisons de l'API au-quotidien"
|
|
||||||
|
|
||||||
#: models.py:56 models.py:71
|
|
||||||
msgid "refresh timeout"
|
|
||||||
msgstr "durée de rafraîchissement"
|
|
||||||
|
|
||||||
#: models.py:57 models.py:72
|
|
||||||
msgid "Number of seconds between two web service calls"
|
|
||||||
msgstr "nombre de secondes entre deux appels au web-service"
|
|
||||||
|
|
||||||
#: models.py:61 models.py:76
|
|
||||||
msgid "refresh {0}"
|
|
||||||
msgstr "durée de rafraîchissement {0}s"
|
|
||||||
|
|
||||||
#: templates/auquotidien_plugin/categories.html:2
|
|
||||||
msgid "Other procedures.."
|
|
||||||
msgstr "Autres démarches..."
|
|
||||||
|
|
||||||
#: templates/auquotidien_plugin/categories.html:20
|
|
||||||
msgid "All categories"
|
|
||||||
msgstr "Toutes les démarches"
|
|
|
@ -1,215 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.utils import datetime_utils as datetime
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
# Adding model 'AuQuotidienAPI'
|
|
||||||
db.create_table(u'auquotidien_plugin_auquotidienapi', (
|
|
||||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
|
||||||
('name', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True)),
|
|
||||||
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
|
||||||
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
|
||||||
('service_provider', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['saml.LibertyServiceProvider'])),
|
|
||||||
('orig', self.gf('django.db.models.fields.CharField')(max_length=64)),
|
|
||||||
('hash_algo', self.gf('django.db.models.fields.CharField')(default='', max_length=16, blank=True)),
|
|
||||||
('signature_key', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True)),
|
|
||||||
('verify_certificate', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
|
||||||
('allow_redirects', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
|
||||||
('timeout', self.gf('django.db.models.fields.IntegerField')(default=10)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'auquotidien_plugin', ['AuQuotidienAPI'])
|
|
||||||
|
|
||||||
# Adding model 'AuQuotidienActiveFormsPlugin'
|
|
||||||
db.create_table(u'auquotidien_plugin_auquotidienactiveformsplugin', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('refresh', self.gf('django.db.models.fields.IntegerField')(default=60)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'auquotidien_plugin', ['AuQuotidienActiveFormsPlugin'])
|
|
||||||
|
|
||||||
# Adding M2M table for field apis on 'AuQuotidienActiveFormsPlugin'
|
|
||||||
m2m_table_name = db.shorten_name(u'auquotidien_plugin_auquotidienactiveformsplugin_apis')
|
|
||||||
db.create_table(m2m_table_name, (
|
|
||||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
|
||||||
('auquotidienactiveformsplugin', models.ForeignKey(orm[u'auquotidien_plugin.auquotidienactiveformsplugin'], null=False)),
|
|
||||||
('auquotidienapi', models.ForeignKey(orm[u'auquotidien_plugin.auquotidienapi'], null=False))
|
|
||||||
))
|
|
||||||
db.create_unique(m2m_table_name, ['auquotidienactiveformsplugin_id', 'auquotidienapi_id'])
|
|
||||||
|
|
||||||
# Adding model 'AuQuotidienCategoryPlugin'
|
|
||||||
db.create_table(u'auquotidien_plugin_auquotidiencategoryplugin', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('refresh', self.gf('django.db.models.fields.IntegerField')(default=60)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'auquotidien_plugin', ['AuQuotidienCategoryPlugin'])
|
|
||||||
|
|
||||||
# Adding M2M table for field apis on 'AuQuotidienCategoryPlugin'
|
|
||||||
m2m_table_name = db.shorten_name(u'auquotidien_plugin_auquotidiencategoryplugin_apis')
|
|
||||||
db.create_table(m2m_table_name, (
|
|
||||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
|
||||||
('auquotidiencategoryplugin', models.ForeignKey(orm[u'auquotidien_plugin.auquotidiencategoryplugin'], null=False)),
|
|
||||||
('auquotidienapi', models.ForeignKey(orm[u'auquotidien_plugin.auquotidienapi'], null=False))
|
|
||||||
))
|
|
||||||
db.create_unique(m2m_table_name, ['auquotidiencategoryplugin_id', 'auquotidienapi_id'])
|
|
||||||
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
# Deleting model 'AuQuotidienAPI'
|
|
||||||
db.delete_table(u'auquotidien_plugin_auquotidienapi')
|
|
||||||
|
|
||||||
# Deleting model 'AuQuotidienActiveFormsPlugin'
|
|
||||||
db.delete_table(u'auquotidien_plugin_auquotidienactiveformsplugin')
|
|
||||||
|
|
||||||
# Removing M2M table for field apis on 'AuQuotidienActiveFormsPlugin'
|
|
||||||
db.delete_table(db.shorten_name(u'auquotidien_plugin_auquotidienactiveformsplugin_apis'))
|
|
||||||
|
|
||||||
# Deleting model 'AuQuotidienCategoryPlugin'
|
|
||||||
db.delete_table(u'auquotidien_plugin_auquotidiencategoryplugin')
|
|
||||||
|
|
||||||
# Removing M2M table for field apis on 'AuQuotidienCategoryPlugin'
|
|
||||||
db.delete_table(db.shorten_name(u'auquotidien_plugin_auquotidiencategoryplugin_apis'))
|
|
||||||
|
|
||||||
|
|
||||||
models = {
|
|
||||||
u'attribute_aggregator.attributeitem': {
|
|
||||||
'Meta': {'object_name': 'AttributeItem'},
|
|
||||||
'attribute_name': ('django.db.models.fields.CharField', [], {'default': "('OpenLDAProotDSE', 'OpenLDAProotDSE')", 'max_length': '100'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'output_name_format': ('django.db.models.fields.CharField', [], {'default': "('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI')", 'max_length': '100'}),
|
|
||||||
'output_namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'}),
|
|
||||||
'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['attribute_aggregator.AttributeSource']", 'null': 'True', 'blank': 'True'})
|
|
||||||
},
|
|
||||||
u'attribute_aggregator.attributelist': {
|
|
||||||
'Meta': {'object_name': 'AttributeList'},
|
|
||||||
'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'attributes of the list'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['attribute_aggregator.AttributeItem']"}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
|
|
||||||
},
|
|
||||||
u'attribute_aggregator.attributesource': {
|
|
||||||
'Meta': {'object_name': 'AttributeSource'},
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
|
|
||||||
'namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'})
|
|
||||||
},
|
|
||||||
u'auquotidien_plugin.auquotidienactiveformsplugin': {
|
|
||||||
'Meta': {'object_name': 'AuQuotidienActiveFormsPlugin', '_ormbases': ['cms.CMSPlugin']},
|
|
||||||
'apis': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auquotidien_plugin.AuQuotidienAPI']", 'symmetrical': 'False'}),
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'refresh': ('django.db.models.fields.IntegerField', [], {'default': '60'})
|
|
||||||
},
|
|
||||||
u'auquotidien_plugin.auquotidienapi': {
|
|
||||||
'Meta': {'ordering': "('order', 'name')", 'object_name': 'AuQuotidienAPI'},
|
|
||||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'allow_redirects': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'hash_algo': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
|
|
||||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
|
||||||
'orig': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
|
||||||
'service_provider': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyServiceProvider']"}),
|
|
||||||
'signature_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
|
|
||||||
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
|
||||||
'verify_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
|
||||||
},
|
|
||||||
u'auquotidien_plugin.auquotidiencategoryplugin': {
|
|
||||||
'Meta': {'object_name': 'AuQuotidienCategoryPlugin', '_ormbases': ['cms.CMSPlugin']},
|
|
||||||
'apis': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auquotidien_plugin.AuQuotidienAPI']", 'symmetrical': 'False'}),
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'refresh': ('django.db.models.fields.IntegerField', [], {'default': '60'})
|
|
||||||
},
|
|
||||||
'cms.cmsplugin': {
|
|
||||||
'Meta': {'object_name': 'CMSPlugin'},
|
|
||||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
|
||||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
|
||||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
|
|
||||||
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
|
|
||||||
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
|
||||||
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
|
||||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
|
|
||||||
},
|
|
||||||
'cms.placeholder': {
|
|
||||||
'Meta': {'object_name': 'Placeholder'},
|
|
||||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
|
|
||||||
},
|
|
||||||
u'idp.attributepolicy': {
|
|
||||||
'Meta': {'object_name': 'AttributePolicy'},
|
|
||||||
'allow_attributes_selection': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'ask_consent_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'attribute_filter_for_sso_from_push_sources': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'filter attributes of push sources with list'", 'null': 'True', 'to': u"orm['attribute_aggregator.AttributeList']"}),
|
|
||||||
'attribute_list_for_sso_from_pull_sources': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes from pull sources'", 'null': 'True', 'to': u"orm['attribute_aggregator.AttributeList']"}),
|
|
||||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'filter_source_of_filtered_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'forward_attributes_from_push_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'map_attributes_from_push_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'map_attributes_of_filtered_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
|
||||||
'output_name_format': ('django.db.models.fields.CharField', [], {'default': "('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI')", 'max_length': '100'}),
|
|
||||||
'output_namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'}),
|
|
||||||
'send_error_and_no_attrs_if_missing_required_attrs': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'source_filter_for_sso_from_push_sources': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'filter attributes of push sources with sources'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['attribute_aggregator.AttributeSource']"})
|
|
||||||
},
|
|
||||||
u'saml.libertyprovider': {
|
|
||||||
'Meta': {'ordering': "('name',)", 'object_name': 'LibertyProvider'},
|
|
||||||
'ca_cert_chain': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
|
||||||
'entity_id': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
|
|
||||||
'entity_id_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
|
|
||||||
'federation_source': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'metadata': ('django.db.models.fields.TextField', [], {}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
|
|
||||||
'protocol_conformance': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
|
|
||||||
'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
|
||||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '140'}),
|
|
||||||
'ssl_certificate': ('django.db.models.fields.TextField', [], {'blank': 'True'})
|
|
||||||
},
|
|
||||||
u'saml.libertyproviderpolicy': {
|
|
||||||
'Meta': {'object_name': 'LibertyProviderPolicy'},
|
|
||||||
'authn_request_signature_check_hint': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
|
|
||||||
},
|
|
||||||
u'saml.libertyserviceprovider': {
|
|
||||||
'Meta': {'object_name': 'LibertyServiceProvider'},
|
|
||||||
'attribute_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_policy'", 'null': 'True', 'to': u"orm['idp.AttributePolicy']"}),
|
|
||||||
'enable_following_attribute_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'enable_following_sp_options_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'liberty_provider': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'service_provider'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['saml.LibertyProvider']"}),
|
|
||||||
'policy': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['saml.LibertyProviderPolicy']", 'null': 'True'}),
|
|
||||||
'sp_options_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sp_options_policy'", 'null': 'True', 'to': u"orm['saml.SPOptionsIdPPolicy']"})
|
|
||||||
},
|
|
||||||
u'saml.spoptionsidppolicy': {
|
|
||||||
'Meta': {'object_name': 'SPOptionsIdPPolicy'},
|
|
||||||
'accept_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'accepted_name_id_format': ('authentic2.saml.fields.MultiSelectField', [], {'max_length': '1024', 'blank': 'True'}),
|
|
||||||
'ask_user_consent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'authn_request_signed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'default_name_id_format': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '256'}),
|
|
||||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'encrypt_assertion': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'encrypt_nameid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'federation_mode': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
|
||||||
'forward_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'idp_initiated_sso': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'iframe_logout_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '300'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
|
||||||
'needs_iframe_logout': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'prefered_assertion_consumer_binding': ('django.db.models.fields.CharField', [], {'default': "'meta'", 'max_length': '4'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['auquotidien_plugin']
|
|
|
@ -1,82 +0,0 @@
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
from cms.models import CMSPlugin
|
|
||||||
|
|
||||||
|
|
||||||
from data_source_plugin.models import DataSource
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [ 'AuQuotidienAPI', 'AuQuotidienActiveFormsPlugin' ]
|
|
||||||
|
|
||||||
class AuQuotidienAPI(models.Model):
|
|
||||||
name = models.CharField(verbose_name=_('name'), max_length=128, blank=True,
|
|
||||||
default='')
|
|
||||||
active = models.BooleanField(verbose_name=_('active'), default=True)
|
|
||||||
order = models.IntegerField(verbose_name=_('order'), default=0)
|
|
||||||
service_provider = models.ForeignKey('saml.LibertyServiceProvider',
|
|
||||||
verbose_name=_('liberty service provider for the au-quotidien '
|
|
||||||
'service'))
|
|
||||||
orig = models.CharField(max_length=64, verbose_name=_('origin'))
|
|
||||||
hash_algo = models.CharField(verbose_name=_('hashing algorithm'),
|
|
||||||
max_length=16, choices=DataSource.HASHES, default='', blank=True)
|
|
||||||
signature_key = models.CharField(verbose_name=_('signature key'),
|
|
||||||
max_length=128, default='', blank=True)
|
|
||||||
verify_certificate = models.BooleanField(verbose_name=_('verify '
|
|
||||||
'certificate'), default=True, blank=True)
|
|
||||||
allow_redirects = models.BooleanField(verbose_name=_('allows HTTP redirections'),
|
|
||||||
help_text=_('it can improve latencies to forbid redirection follow'),
|
|
||||||
default=True)
|
|
||||||
timeout = models.IntegerField(verbose_name=_('timeout'),
|
|
||||||
default=10,
|
|
||||||
help_text=_('time in second to wait before '
|
|
||||||
'failing to download a datasource'))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def base_url(self):
|
|
||||||
'''Base URL of the Au-Quotidien service'''
|
|
||||||
url = self.service_provider.liberty_provider.entity_id
|
|
||||||
return url.split('/saml/')[0]
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def provider_id(self):
|
|
||||||
return self.service_provider.liberty_provider.pk
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('au-quotidien API endpoint')
|
|
||||||
verbose_name_plural = _('au-quotidien API endpoints')
|
|
||||||
ordering = ('order', 'name')
|
|
||||||
|
|
||||||
|
|
||||||
class AuQuotidienActiveFormsPlugin(CMSPlugin):
|
|
||||||
refresh = models.IntegerField(verbose_name=_('refresh timeout'), default=60,
|
|
||||||
help_text=_('Number of seconds between two web service calls'))
|
|
||||||
apis = models.ManyToManyField(AuQuotidienAPI, verbose_name=('au-quotidien API endpoints'))
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return _('refresh {0}').format(self.refresh)
|
|
||||||
|
|
||||||
def copy_relations(self, old_instance):
|
|
||||||
self.apis = old_instance.apis.all()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('au-quotidien active forms')
|
|
||||||
verbose_name_plural = _('au-quotidien active forms')
|
|
||||||
|
|
||||||
class AuQuotidienCategoryPlugin(CMSPlugin):
|
|
||||||
refresh = models.IntegerField(verbose_name=_('refresh timeout'), default=60,
|
|
||||||
help_text=_('Number of seconds between two web service calls'))
|
|
||||||
apis = models.ManyToManyField(AuQuotidienAPI, verbose_name=('au-quotidien API endpoints'))
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return _('refresh {0}').format(self.refresh)
|
|
||||||
|
|
||||||
def copy_relations(self, old_instance):
|
|
||||||
self.apis = old_instance.apis.all()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('au-quotidien categories')
|
|
|
@ -1,54 +0,0 @@
|
||||||
$(document).ready(function () {
|
|
||||||
$('.aq-catgr-plg').each(function (i, elt) {
|
|
||||||
var $plugin = $(elt);
|
|
||||||
var $titles = $('.aq-catgr-plg-title', elt);
|
|
||||||
var $categories = $('.aq-catgr-plg-body', elt);
|
|
||||||
var $empty = $plugin.attr('data-empty');
|
|
||||||
|
|
||||||
if ($plugin.data('initialized')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$plugin.data('initialized', 1);
|
|
||||||
|
|
||||||
$titles.hide();
|
|
||||||
|
|
||||||
if ($categories.length > 1) {
|
|
||||||
if ($empty) {
|
|
||||||
$categories.hide();
|
|
||||||
} else {
|
|
||||||
$categories.slice(1).hide();
|
|
||||||
}
|
|
||||||
// Create a selector from titles
|
|
||||||
var $p = $('<p/>');
|
|
||||||
var $select = $('<select/>');
|
|
||||||
if ($empty) {
|
|
||||||
var $option = $('<option/>');
|
|
||||||
$option.text($empty);
|
|
||||||
$select.append($option);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < $titles.length; i++) {
|
|
||||||
var $body = $($categories[i]);
|
|
||||||
var name = $body.attr('data-name');
|
|
||||||
var $option = $('<option/>');
|
|
||||||
$option.text(name);
|
|
||||||
$option.data('body', $body);
|
|
||||||
$option.val($body[0].id);
|
|
||||||
$select.append($option);
|
|
||||||
}
|
|
||||||
$p.append($select);
|
|
||||||
$('.aq-catgr-plg-selector-container', elt).append($p);
|
|
||||||
|
|
||||||
// Show selected categories
|
|
||||||
var on_select_change = function(event) {
|
|
||||||
console.log(event.target);
|
|
||||||
var $option = $('option:selected', event.target);
|
|
||||||
console.log($option);
|
|
||||||
var id = '#' + $option.val();
|
|
||||||
console.log(id);
|
|
||||||
$categories.not(id).hide();
|
|
||||||
$(id).show();
|
|
||||||
}
|
|
||||||
$select.bind('change', on_select_change);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
<ul class="aq-forms-plg-listing">
|
|
||||||
{% for data_source in data_sources %}
|
|
||||||
{% for data in data_source.content reversed %}
|
|
||||||
<li class="aq-forms-plg-item">
|
|
||||||
<a class="aq-forms-plg-link"
|
|
||||||
href="{{data.url}}">{{data.title}}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
|
@ -1,27 +0,0 @@
|
||||||
{% load i18n sekizai_tags staticfiles %}
|
|
||||||
<div class="aq-catgr-plg" data-empty="{% trans "Other procedures.." %}">
|
|
||||||
<div class="aq-catgr-plg-selector-container"></div>
|
|
||||||
{% for data_source in data_sources %}
|
|
||||||
{% with base_url=data_source.data_source.api.base_url name=data_source.data_source.name %}
|
|
||||||
<div id="aq-catgr-plg-body-{{instance.pk}}-{{data_source.data_source.api.pk}}" data-name="{{name}}" class="aq-catgr-plg-body">
|
|
||||||
<h3 class="aq-catgr-plg-title">{{name}}</h3>
|
|
||||||
<ul class="aq-catgr-plg-listing">
|
|
||||||
{% for category in data_source.content.data|dictsort:"title" %}
|
|
||||||
<li class="aq-catgr-plg-item">
|
|
||||||
<a class="aq-catgr-plg-link"
|
|
||||||
href="{{base_url}}/login/?ReturnUrl={{ category.url }}">
|
|
||||||
{{ category.title }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<a class="aq-catgr-plg-all-catgrs-link"
|
|
||||||
href="{{base_url}}/login/">{% trans "All categories" %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% addtoblock "js" %}<script type="text/javascript" src="{% static "auquotidien_plugin/js/categories.js" %}"></script>{% endaddtoblock %}
|
|
|
@ -1,16 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
from cms_plugins import DataSourcePlugin as DataSourcePluginAdmin
|
|
||||||
from models import PluginDataSource, DataSource
|
|
||||||
|
|
||||||
class PluginDataSourceInlineAdmin(admin.TabularInline):
|
|
||||||
model = PluginDataSource
|
|
||||||
|
|
||||||
class DataSourceAdmin(admin.ModelAdmin):
|
|
||||||
list_display = [ 'name', 'mime_type', 'url' ]
|
|
||||||
list_filter = ['mime_type']
|
|
||||||
|
|
||||||
DataSourcePluginAdmin.inlines = copy(DataSourcePluginAdmin.inlines)
|
|
||||||
DataSourcePluginAdmin.inlines.append(PluginDataSourceInlineAdmin)
|
|
||||||
admin.site.register(DataSource, DataSourceAdmin)
|
|
|
@ -1,175 +0,0 @@
|
||||||
import logging
|
|
||||||
import hashlib
|
|
||||||
from xml.etree import ElementTree as ET
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.template import Template
|
|
||||||
|
|
||||||
import feedparser
|
|
||||||
import requests
|
|
||||||
from requests.exceptions import RequestException, HTTPError, Timeout
|
|
||||||
|
|
||||||
from cms.plugin_base import CMSPluginBase
|
|
||||||
from cms.plugin_pool import plugin_pool
|
|
||||||
from models import DataSourcePlugin as DataSourcePluginModel, DataSource, RawInlineTemplatePlugin as RawInlineTemplatePluginModel
|
|
||||||
import signature
|
|
||||||
|
|
||||||
from allauth.socialaccount.models import SocialToken
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CACHE_SENTINEL = object()
|
|
||||||
|
|
||||||
class Data(object):
|
|
||||||
'''Encapsulate data from a source'''
|
|
||||||
MAPPING = {
|
|
||||||
DataSource.JSON: 'json',
|
|
||||||
DataSource.RSS: 'rss',
|
|
||||||
DataSource.HTML: 'html',
|
|
||||||
DataSource.XML: 'xml',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, data_source, context, limit, refresh, request=None):
|
|
||||||
self.data_source = data_source
|
|
||||||
self.kind = self.MAPPING.get(data_source.mime_type)
|
|
||||||
self.context = context
|
|
||||||
self.url = Template(self.data_source.url).render(self.context)
|
|
||||||
self.limit = limit
|
|
||||||
self.refresh = refresh
|
|
||||||
self.key = hashlib.md5('datasource-{self.data_source.id}-{self.url}-{self.limit}-{self.refresh}'.format(self=self)).hexdigest()
|
|
||||||
self.now = time.time()
|
|
||||||
self.__content = CACHE_SENTINEL
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
def get_access_token(self):
|
|
||||||
user = self.request.user
|
|
||||||
try:
|
|
||||||
token = SocialToken.objects.get(account__provider='authentic2',
|
|
||||||
account__user=user)
|
|
||||||
logger.debug('retrieved access token: %r', token)
|
|
||||||
return token.token
|
|
||||||
except SocialToken.DoesNotExist:
|
|
||||||
logger.warning('unable to find a social token for user: %r', user)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def update_content(self):
|
|
||||||
content = None
|
|
||||||
try:
|
|
||||||
self.final_url = self.url
|
|
||||||
if self.data_source.signature_key:
|
|
||||||
# remove the hmac- prefix
|
|
||||||
hash_algo = self.data_source.auth_mech[:5]
|
|
||||||
self.final_url = signature.sign_url(self.final_url.encode('ascii'),
|
|
||||||
self.data_source.signature_key.encode('utf-8'),
|
|
||||||
algo=hash_algo)
|
|
||||||
logger.debug('getting data source %r from url %r',
|
|
||||||
self.data_source.name, self.final_url)
|
|
||||||
headers = {
|
|
||||||
'Accept': self.data_source.mime_type,
|
|
||||||
}
|
|
||||||
if self.data_source.auth_mech == 'oauth2':
|
|
||||||
headers['Authorization'] = 'Bearer %s' % self.get_access_token()
|
|
||||||
request = requests.get(self.final_url, headers=headers,
|
|
||||||
verify=self.data_source.verify_certificate,
|
|
||||||
allow_redirects=self.data_source.allow_redirects,
|
|
||||||
timeout=self.data_source.timeout)
|
|
||||||
request.raise_for_status()
|
|
||||||
except HTTPError:
|
|
||||||
logger.warning('HTTP Error %s when loading datasource %s from'
|
|
||||||
' URL %s', request.status_code, self.data_source.id, self.final_url)
|
|
||||||
except Timeout:
|
|
||||||
logger.warning('HTTP Request timeout(%s s) when loading datasource'
|
|
||||||
' %s from URL %s', self.data_source.timeout,
|
|
||||||
self.data_source.id, self.final_url)
|
|
||||||
except RequestException:
|
|
||||||
logger.warning('HTTP Request failed when loading datasource'
|
|
||||||
' %s from URL %s', self.data_source.id, self.final_url)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
content = getattr(self, 'get_content_'+self.kind)(request)
|
|
||||||
except Exception:
|
|
||||||
logger.exception('decoding of content from %s failed', self.final_url)
|
|
||||||
else:
|
|
||||||
logger.debug('getting data source %r from url %r finished',
|
|
||||||
self.data_source.id, self.final_url)
|
|
||||||
if self.refresh and content is not None:
|
|
||||||
cache.set(self.key, (content, self.now+self.refresh), 3600)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def get_content(self):
|
|
||||||
if self.__content is not CACHE_SENTINEL:
|
|
||||||
return self.__content
|
|
||||||
self.__content, until = cache.get(self.key, (CACHE_SENTINEL, None))
|
|
||||||
use_cache = self.__content is not CACHE_SENTINEL
|
|
||||||
# do not use cache if refresh timeout is 0
|
|
||||||
use_cache = use_cache and self.refresh > 0
|
|
||||||
# do not use cache if updatecache is present in the query string
|
|
||||||
use_cache = use_cache and 'updatecache' not in self.context['request'].GET
|
|
||||||
|
|
||||||
if use_cache:
|
|
||||||
if until < self.now:
|
|
||||||
# reload cache content asynchronously in a thread
|
|
||||||
# and return the current content
|
|
||||||
logger.debug('content from %r is stale launching reloading', self.url)
|
|
||||||
threading.Thread(target=self.update_content).start()
|
|
||||||
else:
|
|
||||||
self.__content = self.update_content()
|
|
||||||
return self.__content
|
|
||||||
content = property(get_content)
|
|
||||||
|
|
||||||
|
|
||||||
def get_content_json(self, request):
|
|
||||||
try:
|
|
||||||
return request.json()
|
|
||||||
except ValueError:
|
|
||||||
logger.warning('unable to decode json content from %s: %r', self.final_url, request.content[:20])
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_content_rss(self, request):
|
|
||||||
result = feedparser.parse(request.content)
|
|
||||||
result.entries = sorted(result.entries, key=lambda e: e['updated_parsed'])[:self.limit]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_content_html(self, request):
|
|
||||||
return request.text
|
|
||||||
|
|
||||||
def get_content_xml(self, request):
|
|
||||||
try:
|
|
||||||
return ET.fromstring(request.content)
|
|
||||||
except ET.ParseError:
|
|
||||||
logger.error('unable to parse the XML content %r', request.content)
|
|
||||||
return None
|
|
||||||
|
|
||||||
class RawInlineTemplatePlugin(CMSPluginBase):
|
|
||||||
model = RawInlineTemplatePluginModel
|
|
||||||
name = _('Raw Inline Template Plugin')
|
|
||||||
render_template = "data_source_plugin/plugin.html"
|
|
||||||
text_enabled = True
|
|
||||||
|
|
||||||
def icon_src(self, instance):
|
|
||||||
return settings.STATIC_URL + u"cms/images/plugins/link.png"
|
|
||||||
|
|
||||||
class DataSourcePlugin(CMSPluginBase):
|
|
||||||
model = DataSourcePluginModel
|
|
||||||
name = _('Data Source Plugin')
|
|
||||||
render_template = None
|
|
||||||
text_enabled = True
|
|
||||||
|
|
||||||
def get_sources(self, context, instance):
|
|
||||||
request = context['request']
|
|
||||||
for source in instance.sources.all():
|
|
||||||
yield Data(source.source, context, instance.limit, instance.refresh, request=request)
|
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
|
||||||
logger.debug('getting context of data source plugin %s', instance.id)
|
|
||||||
context['data_sources'] = list(self.get_sources(context, instance))
|
|
||||||
logger.debug('finished getting context of data source plugin %s', instance.id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
plugin_pool.register_plugin(RawInlineTemplatePlugin)
|
|
||||||
plugin_pool.register_plugin(DataSourcePlugin)
|
|
|
@ -1,110 +0,0 @@
|
||||||
# Translation of portail-citoyen strings
|
|
||||||
# Copyright (C) 2013 Entr'ouvert
|
|
||||||
# This file is distributed under the same license as the portail-citoyen package.
|
|
||||||
# Benjamin Dauvergne <bdauvergne@entrouvert.com>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: portail_citoyen2 0.1.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-11-13 16:06+0100\n"
|
|
||||||
"PO-Revision-Date: 2013-11-13 16:07+0100\n"
|
|
||||||
"Last-Translator: Benjamin Dauvergne <bdauvergne@entrouvert.com>\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
|
|
||||||
#: cms_plugins.py:124
|
|
||||||
msgid "Raw Inline Template Plugin"
|
|
||||||
msgstr "Plugin de contenu brut"
|
|
||||||
|
|
||||||
#: cms_plugins.py:133
|
|
||||||
msgid "Data Source Plugin"
|
|
||||||
msgstr "Plugin source de donnée"
|
|
||||||
|
|
||||||
#: models.py:25
|
|
||||||
msgid "JSON"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:26
|
|
||||||
msgid "RSS"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "HTML"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:28
|
|
||||||
msgid "XML"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "Name"
|
|
||||||
msgstr "Nom"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "MIME Type"
|
|
||||||
msgstr "Type MIME"
|
|
||||||
|
|
||||||
#: models.py:40
|
|
||||||
msgid "URL"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:42
|
|
||||||
msgid "Hashing algorithm"
|
|
||||||
msgstr "Algorithme de condensat"
|
|
||||||
|
|
||||||
#: models.py:44
|
|
||||||
msgid "Signature key"
|
|
||||||
msgstr "Clé de signature"
|
|
||||||
|
|
||||||
#: models.py:46
|
|
||||||
msgid "verify certificate"
|
|
||||||
msgstr "Vérifier les certificats"
|
|
||||||
|
|
||||||
#: models.py:51
|
|
||||||
msgid "You must choose a hashing algorithm if you set a signature key"
|
|
||||||
msgstr "Vous devez choisir une algorithme de condensat si vous définissez une clé de signature"
|
|
||||||
|
|
||||||
#: models.py:55
|
|
||||||
msgid "Data source {name}, url: {url} mime-type: {mime_type}"
|
|
||||||
msgstr "Source de donnée {name}, url: {url}, mime-type: {mime_type}"
|
|
||||||
|
|
||||||
#: models.py:59
|
|
||||||
msgid "Data source"
|
|
||||||
msgstr "Source de données"
|
|
||||||
|
|
||||||
#: models.py:60
|
|
||||||
msgid "Data sources"
|
|
||||||
msgstr "Sources de données"
|
|
||||||
|
|
||||||
#: models.py:76
|
|
||||||
msgid "Overloaded template"
|
|
||||||
msgstr "Template surchargé"
|
|
||||||
|
|
||||||
#: models.py:91 models.py:95
|
|
||||||
msgid "Raw inline template plugin"
|
|
||||||
msgstr "Plugin de contenu brut"
|
|
||||||
|
|
||||||
#: models.py:92
|
|
||||||
msgid "Raw inline template plugins"
|
|
||||||
msgstr "Plugins de contenu brut"
|
|
||||||
|
|
||||||
#: models.py:99
|
|
||||||
msgid "Maximum entries"
|
|
||||||
msgstr "Nombre maximum de lignes"
|
|
||||||
|
|
||||||
#: models.py:100
|
|
||||||
msgid "Refresh timeout"
|
|
||||||
msgstr "Temps entre les rafraichissements des données"
|
|
||||||
|
|
||||||
#: models.py:101
|
|
||||||
msgid "Number of seconds between two web service calls"
|
|
||||||
msgstr "Nombre de seconds entre deux appels à un web-service"
|
|
||||||
|
|
||||||
#: models.py:108
|
|
||||||
#, python-format
|
|
||||||
msgid "DataSource with %d sources"
|
|
||||||
msgstr "Plugin contenant %d sources"
|
|
|
@ -1,120 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.utils import datetime_utils as datetime
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
# Adding model 'DataSource'
|
|
||||||
db.create_table(u'data_source_plugin_datasource', (
|
|
||||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
|
||||||
('name', self.gf('django.db.models.fields.CharField')(max_length=32)),
|
|
||||||
('mime_type', self.gf('django.db.models.fields.CharField')(max_length=256)),
|
|
||||||
('url', self.gf('django.db.models.fields.URLField')(max_length=1024)),
|
|
||||||
('hash_algo', self.gf('django.db.models.fields.CharField')(default='', max_length=16, blank=True)),
|
|
||||||
('signature_key', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True)),
|
|
||||||
('verify_certificate', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
|
||||||
('allow_redirects', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
|
||||||
('timeout', self.gf('django.db.models.fields.IntegerField')(default=10)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'data_source_plugin', ['DataSource'])
|
|
||||||
|
|
||||||
# Adding model 'PluginDataSource'
|
|
||||||
db.create_table(u'data_source_plugin_plugindatasource', (
|
|
||||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
|
||||||
('source', self.gf('django.db.models.fields.related.ForeignKey')(related_name='plugins', to=orm['data_source_plugin.DataSource'])),
|
|
||||||
('plugin', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sources', to=orm['data_source_plugin.DataSourcePlugin'])),
|
|
||||||
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'data_source_plugin', ['PluginDataSource'])
|
|
||||||
|
|
||||||
# Adding model 'RawInlineTemplatePlugin'
|
|
||||||
db.create_table(u'data_source_plugin_rawinlinetemplateplugin', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('template_source', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'data_source_plugin', ['RawInlineTemplatePlugin'])
|
|
||||||
|
|
||||||
# Adding model 'DataSourcePlugin'
|
|
||||||
db.create_table(u'data_source_plugin_datasourceplugin', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('template_source', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
|
|
||||||
('limit', self.gf('django.db.models.fields.IntegerField')(default=10)),
|
|
||||||
('refresh', self.gf('django.db.models.fields.IntegerField')(default=60)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'data_source_plugin', ['DataSourcePlugin'])
|
|
||||||
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
# Deleting model 'DataSource'
|
|
||||||
db.delete_table(u'data_source_plugin_datasource')
|
|
||||||
|
|
||||||
# Deleting model 'PluginDataSource'
|
|
||||||
db.delete_table(u'data_source_plugin_plugindatasource')
|
|
||||||
|
|
||||||
# Deleting model 'RawInlineTemplatePlugin'
|
|
||||||
db.delete_table(u'data_source_plugin_rawinlinetemplateplugin')
|
|
||||||
|
|
||||||
# Deleting model 'DataSourcePlugin'
|
|
||||||
db.delete_table(u'data_source_plugin_datasourceplugin')
|
|
||||||
|
|
||||||
|
|
||||||
models = {
|
|
||||||
'cms.cmsplugin': {
|
|
||||||
'Meta': {'object_name': 'CMSPlugin'},
|
|
||||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
|
||||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
|
||||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
|
|
||||||
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
|
|
||||||
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
|
||||||
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
|
||||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
|
|
||||||
},
|
|
||||||
'cms.placeholder': {
|
|
||||||
'Meta': {'object_name': 'Placeholder'},
|
|
||||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.datasource': {
|
|
||||||
'Meta': {'ordering': "('name',)", 'object_name': 'DataSource'},
|
|
||||||
'allow_redirects': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'hash_algo': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
|
||||||
'signature_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
|
|
||||||
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
|
||||||
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
|
|
||||||
'verify_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.datasourceplugin': {
|
|
||||||
'Meta': {'object_name': 'DataSourcePlugin'},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
|
||||||
'refresh': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
|
|
||||||
'template_source': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.plugindatasource': {
|
|
||||||
'Meta': {'ordering': "('order', 'id')", 'object_name': 'PluginDataSource'},
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
|
||||||
'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sources'", 'to': u"orm['data_source_plugin.DataSourcePlugin']"}),
|
|
||||||
'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'plugins'", 'to': u"orm['data_source_plugin.DataSource']"})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.rawinlinetemplateplugin': {
|
|
||||||
'Meta': {'object_name': 'RawInlineTemplatePlugin'},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'template_source': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['data_source_plugin']
|
|
|
@ -1,80 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
no_dry_run = True
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
db.rename_column('data_source_plugin_datasource', 'hash_algo', 'auth_mech')
|
|
||||||
for ds in orm.DataSource.objects.all():
|
|
||||||
if ds.auth_mech.startswith('sha'):
|
|
||||||
ds.auth_mech = 'hmac-' + ds.auth_mech
|
|
||||||
ds.save()
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
db.rename_column('data_source_plugin_datasource', 'auth_mech', 'hash_algo')
|
|
||||||
for ds in orm.DataSource.objects.all():
|
|
||||||
if ds.auth_mech.startswith('hmac-'):
|
|
||||||
ds.auth_mech = ds.auth_mech[5:]
|
|
||||||
ds.save()
|
|
||||||
if ds.auth_mech == 'oauth2':
|
|
||||||
ds.delete()
|
|
||||||
|
|
||||||
models = {
|
|
||||||
'cms.cmsplugin': {
|
|
||||||
'Meta': {'object_name': 'CMSPlugin'},
|
|
||||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
|
||||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
|
||||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
|
|
||||||
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
|
|
||||||
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
|
||||||
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
|
||||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
|
|
||||||
},
|
|
||||||
'cms.placeholder': {
|
|
||||||
'Meta': {'object_name': 'Placeholder'},
|
|
||||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.datasource': {
|
|
||||||
'Meta': {'ordering': "('name',)", 'object_name': 'DataSource'},
|
|
||||||
'allow_redirects': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'auth_mech': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
|
||||||
'signature_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
|
|
||||||
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
|
||||||
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
|
|
||||||
'verify_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.datasourceplugin': {
|
|
||||||
'Meta': {'object_name': 'DataSourcePlugin'},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
|
||||||
'refresh': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
|
|
||||||
'template_source': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.plugindatasource': {
|
|
||||||
'Meta': {'ordering': "('order', 'id')", 'object_name': 'PluginDataSource'},
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
|
||||||
'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sources'", 'to': u"orm['data_source_plugin.DataSourcePlugin']"}),
|
|
||||||
'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'plugins'", 'to': u"orm['data_source_plugin.DataSource']"})
|
|
||||||
},
|
|
||||||
u'data_source_plugin.rawinlinetemplateplugin': {
|
|
||||||
'Meta': {'object_name': 'RawInlineTemplatePlugin'},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'template_source': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['data_source_plugin']
|
|
|
@ -1,115 +0,0 @@
|
||||||
from django.template import Template
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.template.base import TemplateSyntaxError
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from cms.models import CMSPlugin
|
|
||||||
|
|
||||||
__all__ = [ 'DataSource', 'PluginDataSource', 'DataSourcePlugin' ]
|
|
||||||
|
|
||||||
def validate_template(value):
|
|
||||||
try:
|
|
||||||
Template(value)
|
|
||||||
except TemplateSyntaxError, e:
|
|
||||||
raise ValidationError(*e.args)
|
|
||||||
|
|
||||||
|
|
||||||
class DataSource(models.Model):
|
|
||||||
JSON = 'application/json'
|
|
||||||
RSS = 'application/rss+xml'
|
|
||||||
HTML = 'text/html'
|
|
||||||
XML = 'text/xml'
|
|
||||||
|
|
||||||
CHOICES = (
|
|
||||||
(JSON, _('JSON')),
|
|
||||||
(RSS, _('RSS')),
|
|
||||||
(HTML, _('HTML')),
|
|
||||||
(XML, _('XML')),
|
|
||||||
)
|
|
||||||
|
|
||||||
HASHES = (
|
|
||||||
('', 'None'),
|
|
||||||
('hmac-sha256', 'HMAC-SHA-256'),
|
|
||||||
('hmac-sha1', 'HMAC-SHA-1'),
|
|
||||||
('oauth2', 'OAuth2'),
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.CharField(verbose_name=_('Name'), max_length=32)
|
|
||||||
mime_type = models.CharField(max_length=256, verbose_name=_('MIME Type'),
|
|
||||||
choices=CHOICES)
|
|
||||||
url = models.URLField(verbose_name=_('URL'), max_length=1024,
|
|
||||||
validators=[validate_template])
|
|
||||||
auth_mech = models.CharField(verbose_name=_('Authentication mechanism'),
|
|
||||||
max_length=16, choices=HASHES, default='', blank=True)
|
|
||||||
signature_key = models.CharField(verbose_name=_('Signature key'),
|
|
||||||
max_length=128, default='', blank=True)
|
|
||||||
verify_certificate = models.BooleanField(verbose_name=_('verify '
|
|
||||||
'certificate'), default=True, blank=True)
|
|
||||||
allow_redirects = models.BooleanField(verbose_name=_('allows HTTP redirections'),
|
|
||||||
help_text=_('it can improve latencies to forbid redirection follow'),
|
|
||||||
default=True)
|
|
||||||
timeout = models.IntegerField(verbose_name=_('timeout'),
|
|
||||||
default=10,
|
|
||||||
help_text=_('time in second to wait before '
|
|
||||||
'failing to download a datasource'))
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if self.signature_key and (not self.auth_mech or not self.auth_mech.startswith('hmac-')):
|
|
||||||
raise ValidationError(_('You must choose a hashing algorithm if '
|
|
||||||
'you set a signature key'))
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return _('Data source {name}, url: {url} mime-type: {mime_type}').format(
|
|
||||||
name=self.name, mime_type=self.mime_type, url=self.url)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Data source')
|
|
||||||
verbose_name_plural = _('Data sources')
|
|
||||||
ordering = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
class PluginDataSource(models.Model):
|
|
||||||
source = models.ForeignKey('DataSource', related_name='plugins')
|
|
||||||
plugin = models.ForeignKey('DataSourcePlugin', related_name='sources')
|
|
||||||
order = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return unicode(self.source)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('order', 'id')
|
|
||||||
|
|
||||||
class InlineTemplatePlugin(CMSPlugin):
|
|
||||||
template_source = models.TextField(verbose_name=_('Overloaded template'),
|
|
||||||
blank=True, default='', validators=[validate_template])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def render_template(self):
|
|
||||||
return Template(self.template_source)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class RawInlineTemplatePlugin(InlineTemplatePlugin):
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Raw inline template plugin')
|
|
||||||
verbose_name_plural = _('Raw inline template plugins')
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return _('Raw inline template plugin')
|
|
||||||
|
|
||||||
|
|
||||||
class DataSourcePlugin(InlineTemplatePlugin):
|
|
||||||
limit = models.IntegerField(verbose_name=_('Maximum entries'), default=10)
|
|
||||||
refresh = models.IntegerField(verbose_name=_('Refresh timeout'), default=60,
|
|
||||||
help_text=_('Number of seconds between two web service calls'))
|
|
||||||
|
|
||||||
def copy_relations(self, old_instance):
|
|
||||||
for source in old_instance.sources.all():
|
|
||||||
self.sources.create(source=source.source, order=source.order)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
s = _('DataSource with %d sources') % self.sources.count()
|
|
||||||
return s
|
|
|
@ -1,71 +0,0 @@
|
||||||
import datetime
|
|
||||||
import base64
|
|
||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
import urllib
|
|
||||||
import random
|
|
||||||
import urlparse
|
|
||||||
|
|
||||||
'''Simple signature scheme for query strings'''
|
|
||||||
|
|
||||||
def sign_url(url, key, algo='sha256', timestamp=None, nonce=None):
|
|
||||||
parsed = urlparse.urlparse(url)
|
|
||||||
new_query = sign_query(parsed.query, key, algo, timestamp, nonce)
|
|
||||||
return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
|
|
||||||
|
|
||||||
def sign_query(query, key, algo='sha256', timestamp=None, nonce=None):
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = datetime.datetime.utcnow()
|
|
||||||
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
if nonce is None:
|
|
||||||
nonce = hex(random.getrandbits(128))[2:]
|
|
||||||
new_query = query
|
|
||||||
if new_query:
|
|
||||||
new_query += '&'
|
|
||||||
new_query += urllib.urlencode((
|
|
||||||
('algo', algo),
|
|
||||||
('timestamp', timestamp),
|
|
||||||
('nonce', nonce)))
|
|
||||||
signature = base64.b64encode(sign_string(new_query, key, algo=algo))
|
|
||||||
new_query += '&signature=' + urllib.quote(signature)
|
|
||||||
return new_query
|
|
||||||
|
|
||||||
def sign_string(s, key, algo='sha256', timedelta=30):
|
|
||||||
digestmod = getattr(hashlib, algo)
|
|
||||||
hash = hmac.HMAC(key, digestmod=digestmod, msg=s)
|
|
||||||
return hash.digest()
|
|
||||||
|
|
||||||
def check_url(url, key, known_nonce=None, timedelta=30):
|
|
||||||
parsed = urlparse.urlparse(url, 'https')
|
|
||||||
return check_query(parsed.query, key)
|
|
||||||
|
|
||||||
def check_query(query, key, known_nonce=None, timedelta=30):
|
|
||||||
parsed = urlparse.parse_qs(query)
|
|
||||||
signature = base64.b64decode(parsed['signature'][0])
|
|
||||||
algo = parsed['algo'][0]
|
|
||||||
timestamp = parsed['timestamp'][0]
|
|
||||||
timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
nonce = parsed['nonce']
|
|
||||||
unsigned_query = query.split('&signature=')[0]
|
|
||||||
if known_nonce is not None and known_nonce(nonce):
|
|
||||||
return False
|
|
||||||
print 'timedelta', datetime.datetime.utcnow() - timestamp
|
|
||||||
if abs(datetime.datetime.utcnow() - timestamp) > datetime.timedelta(seconds=timedelta):
|
|
||||||
return False
|
|
||||||
return check_string(unsigned_query, signature, key, algo=algo)
|
|
||||||
|
|
||||||
def check_string(s, signature, key, algo='sha256'):
|
|
||||||
# constant time compare
|
|
||||||
signature2 = sign_string(s, key, algo=algo)
|
|
||||||
if len(signature2) != len(signature):
|
|
||||||
return False
|
|
||||||
res = 0
|
|
||||||
for a, b in zip(signature, signature2):
|
|
||||||
res |= ord(a) ^ ord(b)
|
|
||||||
return res == 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test_key = '12345'
|
|
||||||
signed_query = sign_query('NameId=_12345&orig=montpellier', test_key)
|
|
||||||
assert check_query(signed_query, test_key, timedelta=0) is False
|
|
||||||
assert check_query(signed_query, test_key) is True
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% for source in data_sources %}
|
|
||||||
{% if plugin.debug %}
|
|
||||||
<pre style="white-space: pre-wrap">
|
|
||||||
Url: {{ source.url }}
|
|
||||||
Content:
|
|
||||||
|
|
||||||
{{ source.content|pprint }}
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
{% elif source.kind == 'rss' %}
|
|
||||||
{% for entry in source.content.entries|dictsort:"updated_parsed" %}
|
|
||||||
<p><a href="{{ entry.link }}">{{ entry.title }}</a></p>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
|
@ -1,16 +0,0 @@
|
||||||
"""
|
|
||||||
This file demonstrates writing tests using the unittest module. These will pass
|
|
||||||
when you run "manage.py test".
|
|
||||||
|
|
||||||
Replace this with more appropriate tests for your application.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
|
||||||
def test_basic_addition(self):
|
|
||||||
"""
|
|
||||||
Tests that 1 + 1 always equals 2.
|
|
||||||
"""
|
|
||||||
self.assertEqual(1 + 1, 2)
|
|
|
@ -1 +0,0 @@
|
||||||
# Create your views here.
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from . import models
|
|
||||||
|
|
||||||
class FeedAdmin(admin.ModelAdmin):
|
|
||||||
list_display = [ 'name', 'url', 'color_hex', 'css_classes' ]
|
|
||||||
|
|
||||||
admin.site.register(models.Feed, FeedAdmin)
|
|
|
@ -1,75 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
from cms.plugin_base import CMSPluginBase
|
|
||||||
from cms.plugin_pool import plugin_pool
|
|
||||||
|
|
||||||
from . import models
|
|
||||||
from . import forms
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SelectUserFeedPlugin(CMSPluginBase):
|
|
||||||
model = models.SelectUserFeed
|
|
||||||
name = _('select user feeds')
|
|
||||||
render_template = 'feed_plugin/select_user_feed.html'
|
|
||||||
text_enabled = True
|
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
|
||||||
request = context['request']
|
|
||||||
user = request.user
|
|
||||||
submit = 'select-user-feed-plugin-%s' % instance.id
|
|
||||||
if request.method == 'POST' and submit in request.POST:
|
|
||||||
form = forms.FeedForm(data=request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
models.FeedPreference.objects.filter(user=user).delete()
|
|
||||||
for feed in form.cleaned_data['feeds']:
|
|
||||||
models.FeedPreference.objects.get_or_create(
|
|
||||||
user=user, feed=feed)
|
|
||||||
else:
|
|
||||||
initial = dict(feeds=models.FeedPreference.objects.filter(user=user)
|
|
||||||
.values_list('feed_id', flat=True))
|
|
||||||
form = forms.FeedForm(initial=initial)
|
|
||||||
context.update(dict(form=form, submit=submit))
|
|
||||||
return context
|
|
||||||
|
|
||||||
class ShowUserFeedPlugin(CMSPluginBase):
|
|
||||||
model = models.ShowUserFeed
|
|
||||||
name = _('show user feeds')
|
|
||||||
render_template = 'feed_plugin/show_user_feed.html'
|
|
||||||
text_enabled = True
|
|
||||||
|
|
||||||
def get_feeds(self, instance, user):
|
|
||||||
entries = []
|
|
||||||
feeds = models.Feed.objects.filter(feedpreference__user=user) \
|
|
||||||
.order_by('id')
|
|
||||||
for feed in feeds:
|
|
||||||
feed_entries = feed.get_feed_entries(instance.limit,
|
|
||||||
instance.timeout)
|
|
||||||
if feed_entries:
|
|
||||||
for date, title, link in feed_entries:
|
|
||||||
entries.append((date, title, link, feed))
|
|
||||||
entries.sort(reverse=True)
|
|
||||||
entries = entries[:instance.limit]
|
|
||||||
entries = [{
|
|
||||||
'title': title,
|
|
||||||
'link': link,
|
|
||||||
'feed_name': feed.name,
|
|
||||||
'color_hex': feed.color_hex,
|
|
||||||
'css_classes': feed.css_classes
|
|
||||||
} for date, title, link, feed in entries]
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
|
||||||
request = context['request']
|
|
||||||
entries = self.get_feeds(instance, request.user)
|
|
||||||
context.update({'entries': entries})
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
plugin_pool.register_plugin(SelectUserFeedPlugin)
|
|
||||||
plugin_pool.register_plugin(ShowUserFeedPlugin)
|
|
|
@ -1,10 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from . import models
|
|
||||||
from . import widgets
|
|
||||||
|
|
||||||
class FeedForm(forms.Form):
|
|
||||||
feeds = forms.ModelMultipleChoiceField(queryset=models.Feed.objects.all(),
|
|
||||||
label=_('Your feeds'), widget=widgets.CheckboxMultipleSelect,
|
|
||||||
required=False)
|
|
|
@ -1,59 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-07-24 23:01+0200\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
|
|
||||||
#: cms_plugins.py:18
|
|
||||||
msgid "select user feeds"
|
|
||||||
msgstr "Choisir ses flux"
|
|
||||||
|
|
||||||
#: cms_plugins.py:42
|
|
||||||
msgid "show user feeds"
|
|
||||||
msgstr "Afficher les flux de l'utilisateur"
|
|
||||||
|
|
||||||
#: forms.py:8
|
|
||||||
msgid "Your feeds"
|
|
||||||
msgstr "Vos flux"
|
|
||||||
|
|
||||||
#: models.py:11
|
|
||||||
msgid "user feed subscription"
|
|
||||||
msgstr "abonnement à un flux RSS"
|
|
||||||
|
|
||||||
#: models.py:12
|
|
||||||
msgid "user feed subscriptions"
|
|
||||||
msgstr "abonnements aux flux RSS"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "nom"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "CSS classes"
|
|
||||||
msgstr "classe CSS"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "feed"
|
|
||||||
msgstr "flux"
|
|
||||||
|
|
||||||
#: models.py:28
|
|
||||||
msgid "feeds"
|
|
||||||
msgstr "flux"
|
|
||||||
|
|
||||||
#: templates/feed_plugin/select_user_feed.html:5
|
|
||||||
msgid "Validate"
|
|
||||||
msgstr "Valider"
|
|
|
@ -1,142 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.utils import datetime_utils as datetime
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
# Adding model 'FeedPreference'
|
|
||||||
db.create_table(u'feed_plugin_feedpreference', (
|
|
||||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
|
||||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
|
||||||
('feed', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['feed_plugin.Feed'])),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'feed_plugin', ['FeedPreference'])
|
|
||||||
|
|
||||||
# Adding model 'SelectUserFeed'
|
|
||||||
db.create_table(u'feed_plugin_selectuserfeed', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'feed_plugin', ['SelectUserFeed'])
|
|
||||||
|
|
||||||
# Adding model 'ShowUserFeed'
|
|
||||||
db.create_table(u'feed_plugin_showuserfeed', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('limit', self.gf('django.db.models.fields.PositiveIntegerField')(default=10)),
|
|
||||||
('timeout', self.gf('django.db.models.fields.PositiveIntegerField')(default=60)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'feed_plugin', ['ShowUserFeed'])
|
|
||||||
|
|
||||||
# Adding model 'Feed'
|
|
||||||
db.create_table(u'feed_plugin_feed', (
|
|
||||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
|
||||||
('name', self.gf('django.db.models.fields.CharField')(max_length=32)),
|
|
||||||
('url', self.gf('django.db.models.fields.URLField')(max_length=200)),
|
|
||||||
('color_hex', self.gf('django.db.models.fields.CharField')(max_length=6, blank=True)),
|
|
||||||
('css_classes', self.gf('django.db.models.fields.CharField')(max_length=128, blank=True)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'feed_plugin', ['Feed'])
|
|
||||||
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
# Deleting model 'FeedPreference'
|
|
||||||
db.delete_table(u'feed_plugin_feedpreference')
|
|
||||||
|
|
||||||
# Deleting model 'SelectUserFeed'
|
|
||||||
db.delete_table(u'feed_plugin_selectuserfeed')
|
|
||||||
|
|
||||||
# Deleting model 'ShowUserFeed'
|
|
||||||
db.delete_table(u'feed_plugin_showuserfeed')
|
|
||||||
|
|
||||||
# Deleting model 'Feed'
|
|
||||||
db.delete_table(u'feed_plugin_feed')
|
|
||||||
|
|
||||||
|
|
||||||
models = {
|
|
||||||
u'auth.group': {
|
|
||||||
'Meta': {'object_name': 'Group'},
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
|
||||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
|
||||||
},
|
|
||||||
u'auth.permission': {
|
|
||||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
|
||||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
|
||||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
|
||||||
},
|
|
||||||
u'auth.user': {
|
|
||||||
'Meta': {'object_name': 'User'},
|
|
||||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
|
||||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
|
||||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
|
||||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
|
||||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
|
||||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
|
||||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
|
||||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
|
||||||
},
|
|
||||||
'cms.cmsplugin': {
|
|
||||||
'Meta': {'object_name': 'CMSPlugin'},
|
|
||||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
|
||||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
|
||||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
|
|
||||||
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
|
|
||||||
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
|
||||||
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
|
||||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
|
|
||||||
},
|
|
||||||
'cms.placeholder': {
|
|
||||||
'Meta': {'object_name': 'Placeholder'},
|
|
||||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
|
|
||||||
},
|
|
||||||
u'contenttypes.contenttype': {
|
|
||||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
|
||||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
|
||||||
},
|
|
||||||
u'feed_plugin.feed': {
|
|
||||||
'Meta': {'object_name': 'Feed'},
|
|
||||||
'color_hex': ('django.db.models.fields.CharField', [], {'max_length': '6', 'blank': 'True'}),
|
|
||||||
'css_classes': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
|
||||||
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
|
|
||||||
},
|
|
||||||
u'feed_plugin.feedpreference': {
|
|
||||||
'Meta': {'object_name': 'FeedPreference'},
|
|
||||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['feed_plugin.Feed']"}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
|
|
||||||
},
|
|
||||||
u'feed_plugin.selectuserfeed': {
|
|
||||||
'Meta': {'object_name': 'SelectUserFeed', '_ormbases': ['cms.CMSPlugin']},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
|
|
||||||
},
|
|
||||||
u'feed_plugin.showuserfeed': {
|
|
||||||
'Meta': {'object_name': 'ShowUserFeed', '_ormbases': ['cms.CMSPlugin']},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'limit': ('django.db.models.fields.PositiveIntegerField', [], {'default': '10'}),
|
|
||||||
'timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '60'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['feed_plugin']
|
|
|
@ -1,90 +0,0 @@
|
||||||
import hashlib
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.html import strip_tags
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
import feedparser
|
|
||||||
|
|
||||||
from cms.models import CMSPlugin
|
|
||||||
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class FeedPreference(models.Model):
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
|
||||||
feed = models.ForeignKey('Feed')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('user feed subscription')
|
|
||||||
verbose_name_plural = _('user feed subscriptions')
|
|
||||||
|
|
||||||
class SelectUserFeed(CMSPlugin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ShowUserFeed(CMSPlugin):
|
|
||||||
limit = models.PositiveIntegerField(default=10)
|
|
||||||
timeout = models.PositiveIntegerField(default=60)
|
|
||||||
|
|
||||||
class Feed(models.Model):
|
|
||||||
name = models.CharField(max_length=32, verbose_name=_('name'))
|
|
||||||
url = models.URLField()
|
|
||||||
color_hex = models.CharField(max_length=6,
|
|
||||||
verbose_name=_('Color'),
|
|
||||||
help_text=_('as an hexadecimal number'),
|
|
||||||
blank=True)
|
|
||||||
css_classes = models.CharField(max_length=128,
|
|
||||||
verbose_name=_('CSS classes'),
|
|
||||||
blank=True)
|
|
||||||
|
|
||||||
def load(self, now=None, limit=9999):
|
|
||||||
if now is None:
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
feed = feedparser.parse(self.url)
|
|
||||||
key = self.cache_key()
|
|
||||||
entries = []
|
|
||||||
for entry in feed.entries:
|
|
||||||
for attribute in ('published_parsed', 'updated_parsed',
|
|
||||||
'created_parsed', 'expired_parsed'):
|
|
||||||
date = getattr(entry, attribute, None)
|
|
||||||
if date is not None:
|
|
||||||
break
|
|
||||||
if date is None:
|
|
||||||
continue
|
|
||||||
title = strip_tags(entry.title.strip())
|
|
||||||
if not title:
|
|
||||||
title = strip_tags(entry.description.strip())
|
|
||||||
title = ' '.join(title.split(' ')[:30])
|
|
||||||
if not title:
|
|
||||||
continue
|
|
||||||
entries.append((date, title, entry.link))
|
|
||||||
entries.sort(reverse=True)
|
|
||||||
entries = entries[:limit]
|
|
||||||
cache.set(key, (entries, now))
|
|
||||||
duration = datetime.datetime.utcnow()-now
|
|
||||||
logger.debug('loaded RSS feed %r in %s seconds', self.url, duration.seconds)
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def cache_key(self):
|
|
||||||
return hashlib.md5(self.url).hexdigest()
|
|
||||||
|
|
||||||
def get_feed_entries(self, limit=10, timeout=3600):
|
|
||||||
key = self.cache_key()
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
entries, stamp = cache.get(key, (None, None))
|
|
||||||
if entries is None or now-stamp > datetime.timedelta(seconds=timeout):
|
|
||||||
utils.launch_in_thread(key, self.load, now, limit)
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('feed')
|
|
||||||
verbose_name_plural = _('feeds')
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<form method="post" class="feed-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
<ul class='feed-list'>
|
|
||||||
{% for subwidget in form.feeds %}
|
|
||||||
<li class='feed-list-item'>
|
|
||||||
{{ subwidget.tag }}
|
|
||||||
<label for="{{subwidget.attrs.id}}_{{subwidget.index}}">
|
|
||||||
{{subwidget.choice_label}}
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<input type="submit" value="{% trans "Validate" %}" name="{{ submit }}">
|
|
||||||
</form>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<ul class="show-user-feeds">
|
|
||||||
{% for entry in entries %}
|
|
||||||
<li class="show-user-feeds-{{ entry.feed_name|slugify}}{% if entry.css_classes %} {{ entry.css_classes }}{% endif %}"
|
|
||||||
{% if entry.color_hex %}style="color: #{{ entry.color_hex}}"{% endif %}>
|
|
||||||
<a href="{{ entry.link }}" {% if entry.color_hex %}style="color: #{{ entry.color_hex}}"{% endif %}>{{ entry.title|safe }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
|
@ -1,23 +0,0 @@
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def launch_in_thread(key, function, *args, **kwargs):
|
|
||||||
'''Launch a function in a thread, prevent launching the same function many times using a lock'''
|
|
||||||
key = 'thread-' + key
|
|
||||||
created = cache.add(key, 1)
|
|
||||||
if created:
|
|
||||||
logger.debug('launching thread for %s', key)
|
|
||||||
def f():
|
|
||||||
try:
|
|
||||||
function(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
cache.delete(key)
|
|
||||||
thread = threading.Thread(target=f)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from django.forms.widgets import SubWidget, SelectMultiple
|
|
||||||
from django.forms.util import flatatt
|
|
||||||
from django.utils.html import conditional_escape
|
|
||||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
class CheckboxInput(SubWidget):
|
|
||||||
"""
|
|
||||||
An object used by CheckboxRenderer that represents a single
|
|
||||||
<input type='checkbox'>.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, value, attrs, choice, index):
|
|
||||||
self.name, self.value = name, value
|
|
||||||
self.attrs = attrs
|
|
||||||
self.choice_value = force_unicode(choice[0])
|
|
||||||
self.choice_label = force_unicode(choice[1])
|
|
||||||
self.index = index
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.render()
|
|
||||||
|
|
||||||
def render(self, name=None, value=None, attrs=None, choices=()):
|
|
||||||
name = name or self.name
|
|
||||||
value = value or self.value
|
|
||||||
attrs = attrs or self.attrs
|
|
||||||
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
|
|
||||||
else:
|
|
||||||
label_for = ''
|
|
||||||
choice_label = conditional_escape(force_unicode(self.choice_label))
|
|
||||||
return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), choice_label))
|
|
||||||
|
|
||||||
def is_checked(self):
|
|
||||||
return self.choice_value in self.value
|
|
||||||
|
|
||||||
def tag(self):
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
|
|
||||||
final_attrs = dict(self.attrs, type='checkbox', name=self.name, value=self.choice_value)
|
|
||||||
if self.is_checked():
|
|
||||||
final_attrs['checked'] = 'checked'
|
|
||||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
||||||
|
|
||||||
class CheckboxRenderer(StrAndUnicode):
|
|
||||||
def __init__(self, name, value, attrs, choices):
|
|
||||||
self.name, self.value, self.attrs = name, value, attrs
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for i, choice in enumerate(self.choices):
|
|
||||||
yield CheckboxInput(self.name, self.value, self.attrs.copy(), choice, i)
|
|
||||||
|
|
||||||
def __getitem__(self, idx):
|
|
||||||
choice = self.choices[idx] # Let the IndexError propogate
|
|
||||||
return CheckboxInput(self.name, self.value, self.attrs.copy(), choice, idx)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.render()
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
"""Outputs a <ul> for this set of checkbox fields."""
|
|
||||||
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
|
|
||||||
% force_unicode(w) for w in self]))
|
|
||||||
|
|
||||||
class CheckboxMultipleSelect(SelectMultiple):
|
|
||||||
"""
|
|
||||||
Checkbox multi select field that enables iteration of each checkbox
|
|
||||||
Similar to django.forms.widgets.RadioSelect
|
|
||||||
"""
|
|
||||||
renderer = CheckboxRenderer
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# Override the default renderer if we were passed one.
|
|
||||||
renderer = kwargs.pop('renderer', None)
|
|
||||||
if renderer:
|
|
||||||
self.renderer = renderer
|
|
||||||
super(CheckboxMultipleSelect, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def subwidgets(self, name, value, attrs=None, choices=()):
|
|
||||||
for widget in self.get_renderer(name, value, attrs, choices):
|
|
||||||
yield widget
|
|
||||||
|
|
||||||
def get_renderer(self, name, value, attrs=None, choices=()):
|
|
||||||
"""Returns an instance of the renderer."""
|
|
||||||
if value is None: value = ''
|
|
||||||
str_values = set([force_unicode(v) for v in value]) # Normalize to string.
|
|
||||||
if attrs is None:
|
|
||||||
attrs = {}
|
|
||||||
if 'id' not in attrs:
|
|
||||||
attrs['id'] = name
|
|
||||||
final_attrs = self.build_attrs(attrs)
|
|
||||||
choices = list(chain(self.choices, choices))
|
|
||||||
return self.renderer(name, str_values, final_attrs, choices)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
return self.get_renderer(name, value, attrs, choices).render()
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
if id_:
|
|
||||||
id_ += '_0'
|
|
||||||
return id_
|
|
|
@ -1,20 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
from cms.plugin_pool import plugin_pool
|
|
||||||
|
|
||||||
|
|
||||||
from portail_citoyen2.cms_plugins import FormPluginBase
|
|
||||||
from . import models, forms
|
|
||||||
|
|
||||||
|
|
||||||
class PasserelleRegisterPlugin(FormPluginBase):
|
|
||||||
model = models.PasserelleRegisterPlugin
|
|
||||||
name = _('passerelle register plugin')
|
|
||||||
render_template = 'passerelle_register_plugin/plugin.html'
|
|
||||||
text_enabled = True
|
|
||||||
no_cancel_button = False
|
|
||||||
form_class = forms.PasserelleRegisterForm
|
|
||||||
|
|
||||||
|
|
||||||
plugin_pool.register_plugin(PasserelleRegisterPlugin)
|
|
|
@ -1,58 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
|
|
||||||
|
|
||||||
import widgets
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class PasserelleRegisterForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.request = kwargs.pop('request')
|
|
||||||
self.user = self.request.user
|
|
||||||
instance = self.instance = kwargs.pop('plugin_instance')
|
|
||||||
try:
|
|
||||||
subscriptions = self.subscriptions = self.instance.get_subscriptions(user=self.user.email)
|
|
||||||
except:
|
|
||||||
subscriptions = self.subscriptions = []
|
|
||||||
logger.warning('unable to retrieve subscriptions for %r: %r',
|
|
||||||
self.user, self.user.email, exc_info=True)
|
|
||||||
super(PasserelleRegisterForm, self).__init__(*args, **kwargs)
|
|
||||||
self.all_choices = set()
|
|
||||||
for subscription in subscriptions:
|
|
||||||
if not instance.check_ressource(subscription['name']):
|
|
||||||
continue
|
|
||||||
choices = []
|
|
||||||
initial = []
|
|
||||||
for transport in subscription['transports']['available']:
|
|
||||||
if not instance.check_transport(transport):
|
|
||||||
continue
|
|
||||||
self.all_choices.add(transport)
|
|
||||||
choices.append((transport, transport))
|
|
||||||
if transport in subscription['transports']['defined']:
|
|
||||||
initial.append(transport)
|
|
||||||
self.fields[instance.simplify(subscription['name'])] = \
|
|
||||||
forms.MultipleChoiceField(label=subscription['description'],
|
|
||||||
choices=choices, initial=initial,
|
|
||||||
widget=widgets.CheckboxMultipleSelect,
|
|
||||||
required=False)
|
|
||||||
__init__.need_request = True
|
|
||||||
__init__.need_plugin_instance = True
|
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
cleaned_data = self.cleaned_data
|
|
||||||
subscriptions = []
|
|
||||||
for key, value in cleaned_data.iteritems():
|
|
||||||
subscriptions.append({
|
|
||||||
'name': key,
|
|
||||||
'transports': value
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
self.instance.set_subscriptions(subscriptions, user=self.user.email)
|
|
||||||
except:
|
|
||||||
subscriptions = self.subscriptions = []
|
|
||||||
logger.warning('unable to save subscriptions for %r: %r',
|
|
||||||
self.user, self.user.email, exc_info=True)
|
|
|
@ -1,43 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-09-10 15:01+0200\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
|
|
||||||
#: cms_plugins.py:36
|
|
||||||
msgid "passerelle register plugin"
|
|
||||||
msgstr "Plugin de gestion d'abonnements"
|
|
||||||
|
|
||||||
#: models.py:20
|
|
||||||
msgid "passerelle url"
|
|
||||||
msgstr "URL du service d'abonnement"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "ressources restrictions"
|
|
||||||
msgstr "Restriction sur les ressources ouvertes aux abonnements"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "transports restrictions"
|
|
||||||
msgstr "Restriction sur les modes d'envoi"
|
|
||||||
|
|
||||||
#: templates/passerelle_register_plugin/plugin.html:14
|
|
||||||
msgid "Name"
|
|
||||||
msgstr "Nom"
|
|
||||||
|
|
||||||
#: templates/passerelle_register_plugin/plugin.html:37
|
|
||||||
msgid "Validate"
|
|
||||||
msgstr "Valider"
|
|
|
@ -1,57 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.utils import datetime_utils as datetime
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
# Adding model 'PasserelleRegisterPlugin'
|
|
||||||
db.create_table(u'passerelle_register_plugin_passerelleregisterplugin', (
|
|
||||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
|
|
||||||
('passerelle_url', self.gf('django.db.models.fields.URLField')(max_length=128)),
|
|
||||||
('ressources_restrictions', self.gf('django.db.models.fields.TextField')(blank=True)),
|
|
||||||
('transports_restrictions', self.gf('django.db.models.fields.TextField')(blank=True)),
|
|
||||||
))
|
|
||||||
db.send_create_signal(u'passerelle_register_plugin', ['PasserelleRegisterPlugin'])
|
|
||||||
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
# Deleting model 'PasserelleRegisterPlugin'
|
|
||||||
db.delete_table(u'passerelle_register_plugin_passerelleregisterplugin')
|
|
||||||
|
|
||||||
|
|
||||||
models = {
|
|
||||||
'cms.cmsplugin': {
|
|
||||||
'Meta': {'object_name': 'CMSPlugin'},
|
|
||||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
|
||||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
|
||||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
|
|
||||||
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
|
|
||||||
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
|
||||||
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
|
||||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
|
|
||||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
|
|
||||||
},
|
|
||||||
'cms.placeholder': {
|
|
||||||
'Meta': {'object_name': 'Placeholder'},
|
|
||||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
|
|
||||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
|
|
||||||
},
|
|
||||||
u'passerelle_register_plugin.passerelleregisterplugin': {
|
|
||||||
'Meta': {'object_name': 'PasserelleRegisterPlugin', '_ormbases': ['cms.CMSPlugin']},
|
|
||||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
|
|
||||||
'passerelle_url': ('django.db.models.fields.URLField', [], {'max_length': '128'}),
|
|
||||||
'ressources_restrictions': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
|
||||||
'transports_restrictions': ('django.db.models.fields.TextField', [], {'blank': 'True'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['passerelle_register_plugin']
|
|
|
@ -1,122 +0,0 @@
|
||||||
import requests
|
|
||||||
import requests.exceptions
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
|
|
||||||
from cms.models import CMSPlugin
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PasserelleRegisterPlugin(CMSPlugin):
|
|
||||||
passerelle_url = models.URLField(verbose_name=_('passerelle url'),
|
|
||||||
max_length=128)
|
|
||||||
ressources_restrictions = models.TextField(verbose_name=_('ressources restrictions'), blank=True)
|
|
||||||
transports_restrictions = models.TextField(verbose_name=_('transports restrictions'), blank=True)
|
|
||||||
|
|
||||||
__ressources_restrictions = None
|
|
||||||
__transports_restrictions = None
|
|
||||||
|
|
||||||
def simplify(self, name):
|
|
||||||
return slugify(name.strip())
|
|
||||||
|
|
||||||
def get_ressources_restrictions(self):
|
|
||||||
if self.__ressources_restrictions is None:
|
|
||||||
self.__ressources_restrictions = []
|
|
||||||
for name in filter(None, map(self.simplify, self.ressources_restrictions.strip().split(','))):
|
|
||||||
self.__ressources_restrictions.append(name)
|
|
||||||
return self.__ressources_restrictions
|
|
||||||
|
|
||||||
def get_transports_restrictions(self):
|
|
||||||
if self.__transports_restrictions is None:
|
|
||||||
self.__transports_restrictions = []
|
|
||||||
for name in filter(None, map(self.simplify, self.transports_restrictions.strip().split(','))):
|
|
||||||
self.__transports_restrictions.append(name)
|
|
||||||
return self.__transports_restrictions
|
|
||||||
|
|
||||||
|
|
||||||
def check_ressource(self, ressource):
|
|
||||||
restrictions = self.get_ressources_restrictions()
|
|
||||||
if restrictions and self.simplify(ressource) not in restrictions:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check_transport(self, transport):
|
|
||||||
restrictions = self.get_transports_restrictions()
|
|
||||||
if restrictions and self.simplify(transport) not in restrictions:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def key(self, **kwargs):
|
|
||||||
return 'passerelle_register_subscriptions_'+slugify(self.passerelle_url+repr(sorted(kwargs.iteritems())))
|
|
||||||
|
|
||||||
def get_subscriptions(self, **kwargs):
|
|
||||||
key = self.key(**kwargs)
|
|
||||||
early_response = cache.get(key)
|
|
||||||
if early_response is not None:
|
|
||||||
logger.debug('got subscriptions from cache')
|
|
||||||
return early_response
|
|
||||||
http_response = requests.get(self.passerelle_url, params=kwargs)
|
|
||||||
json_response = http_response.json()
|
|
||||||
if json_response['err'] == 0:
|
|
||||||
logger.debug('got subscriptions from %s: %r', self.passerelle_url,
|
|
||||||
json_response['data'])
|
|
||||||
cache.set(key, json_response['data'])
|
|
||||||
return json_response['data']
|
|
||||||
else:
|
|
||||||
logger.error('got subscriptions from %(url)s failed: %(json)r',
|
|
||||||
{ 'url': self.passerelle_url,
|
|
||||||
'json': json_response })
|
|
||||||
return []
|
|
||||||
|
|
||||||
def set_subscriptions(self, subscriptions, **kwargs):
|
|
||||||
old_subscriptions = self.get_subscriptions(**kwargs)
|
|
||||||
index = {}
|
|
||||||
for subscription in subscriptions:
|
|
||||||
index[subscription['name']] = subscription
|
|
||||||
post = []
|
|
||||||
for subscription in old_subscriptions:
|
|
||||||
new_defined = set(subscription['transports']['defined'])
|
|
||||||
if self.simplify(subscription['name']) in index:
|
|
||||||
new_subscription = index[subscription['name']]
|
|
||||||
for transport in subscription['transports']['available']:
|
|
||||||
stransport = self.simplify(transport)
|
|
||||||
if self.check_transport(transport):
|
|
||||||
if stransport in new_subscription['transports']:
|
|
||||||
new_defined.add(transport)
|
|
||||||
else:
|
|
||||||
new_defined.discard(transport)
|
|
||||||
post.append(dict(name=subscription['name'],
|
|
||||||
transports=list(new_defined)))
|
|
||||||
headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
|
|
||||||
try:
|
|
||||||
response = requests.post(self.passerelle_url, params=kwargs,
|
|
||||||
data=json.dumps(post), headers=headers)
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.HTTPError:
|
|
||||||
logger.error(u'set subscriptions on %s returned an HTTP error code: %s',
|
|
||||||
response.request.url, response.status_code)
|
|
||||||
except requests.exceptions.RequestException, e:
|
|
||||||
logger.error(u'set subscriptions on %s failed with exception: %s',
|
|
||||||
response.request.url, e)
|
|
||||||
else:
|
|
||||||
logger.debug(u'set subscriptions on %s: post %r', response.request.url,
|
|
||||||
post)
|
|
||||||
logger.debug(u'set subscriptions on %s: response %r',
|
|
||||||
response.request.url, response.content)
|
|
||||||
cache.delete(self.key(**kwargs))
|
|
||||||
if response.json()['err'] != 0:
|
|
||||||
logger.error(u'set subscriptions on %s returned an error: %r',
|
|
||||||
response.request.url, response.json())
|
|
||||||
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u'<PasserelleRegisterPlugin %s>' % self.passerelle_url
|
|
|
@ -1,39 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div id="passerelle-register-plugin-{{ instance.id }}" class="passerelle-register-plugin">
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
{% for field in form %}
|
|
||||||
{{ field.errors }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<table id="passerelle-register-plugin-table-{{ instance.id }}">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>{% trans "Name" %}</td>
|
|
||||||
{% for choice in form.all_choices %}
|
|
||||||
<td id="passerelle-register-plugin-header-{{choice|slugify}}-{{instance.id}}" class="passerelle-register-plugin-header-{{choice|slugify}}">
|
|
||||||
{{ choice }}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for field in form %}
|
|
||||||
<tr class="{% for checkbox in field %}{% if checkbox.is_checked %}passerelle-register-plugin-checked-{{ checkbox.choice_value|slugify }} {% endif %}{% endfor %}">
|
|
||||||
<td><label>{{ field.label }}</label></td>
|
|
||||||
{% for choice in form.all_choices %}
|
|
||||||
{% for checkbox in field %}
|
|
||||||
{% if checkbox.choice_value == choice %}
|
|
||||||
<td>{{ checkbox.tag }}</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<input type="submit" name="{{ submit }}" value="{% trans "Validate" %}"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
|
@ -1,16 +0,0 @@
|
||||||
"""
|
|
||||||
This file demonstrates writing tests using the unittest module. These will pass
|
|
||||||
when you run "manage.py test".
|
|
||||||
|
|
||||||
Replace this with more appropriate tests for your application.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
|
||||||
def test_basic_addition(self):
|
|
||||||
"""
|
|
||||||
Tests that 1 + 1 always equals 2.
|
|
||||||
"""
|
|
||||||
self.assertEqual(1 + 1, 2)
|
|
|
@ -1 +0,0 @@
|
||||||
# Create your views here.
|
|
|
@ -1,104 +0,0 @@
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from django.forms.widgets import SubWidget, SelectMultiple
|
|
||||||
from django.forms.util import flatatt
|
|
||||||
from django.utils.html import conditional_escape
|
|
||||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
class CheckboxInput(SubWidget):
|
|
||||||
"""
|
|
||||||
An object used by CheckboxRenderer that represents a single
|
|
||||||
<input type='checkbox'>.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, value, attrs, choice, index):
|
|
||||||
self.name, self.value = name, value
|
|
||||||
self.attrs = attrs
|
|
||||||
self.choice_value = force_unicode(choice[0])
|
|
||||||
self.choice_label = force_unicode(choice[1])
|
|
||||||
self.index = index
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.render()
|
|
||||||
|
|
||||||
def render(self, name=None, value=None, attrs=None, choices=()):
|
|
||||||
name = name or self.name
|
|
||||||
value = value or self.value
|
|
||||||
attrs = attrs or self.attrs
|
|
||||||
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
|
|
||||||
else:
|
|
||||||
label_for = ''
|
|
||||||
choice_label = conditional_escape(force_unicode(self.choice_label))
|
|
||||||
return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), choice_label))
|
|
||||||
|
|
||||||
def is_checked(self):
|
|
||||||
return self.choice_value in self.value
|
|
||||||
|
|
||||||
def tag(self):
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
|
|
||||||
final_attrs = dict(self.attrs, type='checkbox', name=self.name, value=self.choice_value)
|
|
||||||
if self.is_checked():
|
|
||||||
final_attrs['checked'] = 'checked'
|
|
||||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
||||||
|
|
||||||
class CheckboxRenderer(StrAndUnicode):
|
|
||||||
def __init__(self, name, value, attrs, choices):
|
|
||||||
self.name, self.value, self.attrs = name, value, attrs
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for i, choice in enumerate(self.choices):
|
|
||||||
yield CheckboxInput(self.name, self.value, self.attrs.copy(), choice, i)
|
|
||||||
|
|
||||||
def __getitem__(self, idx):
|
|
||||||
choice = self.choices[idx] # Let the IndexError propogate
|
|
||||||
return CheckboxInput(self.name, self.value, self.attrs.copy(), choice, idx)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.render()
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
"""Outputs a <ul> for this set of checkbox fields."""
|
|
||||||
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
|
|
||||||
% force_unicode(w) for w in self]))
|
|
||||||
|
|
||||||
class CheckboxMultipleSelect(SelectMultiple):
|
|
||||||
"""
|
|
||||||
Checkbox multi select field that enables iteration of each checkbox
|
|
||||||
Similar to django.forms.widgets.RadioSelect
|
|
||||||
"""
|
|
||||||
renderer = CheckboxRenderer
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# Override the default renderer if we were passed one.
|
|
||||||
renderer = kwargs.pop('renderer', None)
|
|
||||||
if renderer:
|
|
||||||
self.renderer = renderer
|
|
||||||
super(CheckboxMultipleSelect, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def subwidgets(self, name, value, attrs=None, choices=()):
|
|
||||||
for widget in self.get_renderer(name, value, attrs, choices):
|
|
||||||
yield widget
|
|
||||||
|
|
||||||
def get_renderer(self, name, value, attrs=None, choices=()):
|
|
||||||
"""Returns an instance of the renderer."""
|
|
||||||
if value is None: value = ''
|
|
||||||
str_values = set([force_unicode(v) for v in value]) # Normalize to string.
|
|
||||||
if attrs is None:
|
|
||||||
attrs = {}
|
|
||||||
if 'id' not in attrs:
|
|
||||||
attrs['id'] = name
|
|
||||||
final_attrs = self.build_attrs(attrs)
|
|
||||||
choices = list(chain(self.choices, choices))
|
|
||||||
return self.renderer(name, str_values, final_attrs, choices)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
|
||||||
return self.get_renderer(name, value, attrs, choices).render()
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
|
||||||
if id_:
|
|
||||||
id_ += '_0'
|
|
||||||
return id_
|
|
|
@ -1,49 +0,0 @@
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.forms import ModelForm
|
|
||||||
|
|
||||||
|
|
||||||
from cms.plugin_base import CMSPluginBase
|
|
||||||
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
class FormPluginBase(CMSPluginBase):
|
|
||||||
form_class = None
|
|
||||||
no_cancel_button = True
|
|
||||||
submit_text = None
|
|
||||||
add_form_prefix = True
|
|
||||||
cache = False
|
|
||||||
|
|
||||||
def get_form_class(self, request, context, instance, placeholder):
|
|
||||||
return self.form_class
|
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
|
||||||
request = context['request']
|
|
||||||
class_name = self.__class__.__name__.lower()
|
|
||||||
context['submit_name'] = submit = 'submit-{class_name}-{instance_id}'.format(
|
|
||||||
class_name=class_name,
|
|
||||||
instance_id=instance.id)
|
|
||||||
context['submit_value'] = self.submit_text or _('Modify')
|
|
||||||
context['instance'] = instance
|
|
||||||
context['no_cancel_button'] = self.no_cancel_button
|
|
||||||
kwargs = {}
|
|
||||||
if self.add_form_prefix:
|
|
||||||
kwargs['prefix'] = class_name
|
|
||||||
form_class = self.get_form_class(request, context, instance,
|
|
||||||
placeholder)
|
|
||||||
if issubclass(form_class, ModelForm):
|
|
||||||
if not hasattr(self, 'get_object'):
|
|
||||||
raise ImproperlyConfigured('Your plugin class is missing a get_object method but use a ModelForm')
|
|
||||||
kwargs['instance'] = context['object'] = self.get_object(request, context, instance, placeholder)
|
|
||||||
if utils.callable_has_arg(form_class.__init__, 'plugin_instance'):
|
|
||||||
kwargs['plugin_instance'] = instance
|
|
||||||
if utils.callable_has_arg(form_class.__init__, 'request'):
|
|
||||||
kwargs['request'] = request
|
|
||||||
if request.method == 'POST' and submit in request.POST:
|
|
||||||
form = form_class(data=request.POST, **kwargs)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
else:
|
|
||||||
form = form_class(**kwargs)
|
|
||||||
context['form'] = form
|
|
||||||
return context
|
|
|
@ -1,7 +0,0 @@
|
||||||
from . import app_settings
|
|
||||||
|
|
||||||
def template_vars(request):
|
|
||||||
ctx = app_settings.TEMPLATE_VARS.copy()
|
|
||||||
if app_settings.PORTAIL_ADMIN_URL:
|
|
||||||
ctx['PORTAIL_ADMIN_URL'] = app_settings.PORTAIL_ADMIN_URL
|
|
||||||
return ctx
|
|
|
@ -1,73 +0,0 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from admin_tools.dashboard import modules, Dashboard, AppIndexDashboard
|
|
||||||
|
|
||||||
|
|
||||||
class CustomIndexDashboard(Dashboard):
|
|
||||||
"""
|
|
||||||
Custom index dashboard for Compte Orleans
|
|
||||||
"""
|
|
||||||
def init_with_context(self, context):
|
|
||||||
# append an app list module for "Applications"
|
|
||||||
self.children.append(modules.ModelList(
|
|
||||||
_('Contents'),
|
|
||||||
models=(
|
|
||||||
'cms.models.pagemodel.Page',
|
|
||||||
'data_source_plugin.models.DataSource',
|
|
||||||
'feed_plugin.models.Feed',
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
self.children.append(modules.ModelList(
|
|
||||||
_('Announces'),
|
|
||||||
models=(
|
|
||||||
'portail_citoyen_announces.*',
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
# append a recent actions module
|
|
||||||
self.children.append(modules.RecentActions(_('Recent Actions'), 5))
|
|
||||||
|
|
||||||
# append another link list module for "support".
|
|
||||||
self.children.append(modules.LinkList(
|
|
||||||
_('Support'),
|
|
||||||
children=[
|
|
||||||
{
|
|
||||||
'title': _('Project site'),
|
|
||||||
'url': 'https://dev.entrouvert.org/projects/portail-citoyen/',
|
|
||||||
'external': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': _('Report a bug'),
|
|
||||||
'url': 'https://dev.entrouvert.org/projects/portail-citoyen/issues/new',
|
|
||||||
'external': True,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class CustomAppIndexDashboard(AppIndexDashboard):
|
|
||||||
"""
|
|
||||||
Custom app index dashboard for Compte Orleans
|
|
||||||
"""
|
|
||||||
|
|
||||||
# we disable title because its redundant with the model list module
|
|
||||||
title = ''
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
AppIndexDashboard.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
# append a model list module and a recent actions module
|
|
||||||
self.children += [
|
|
||||||
modules.ModelList(self.app_title, self.models),
|
|
||||||
modules.RecentActions(
|
|
||||||
_('Recent Actions'),
|
|
||||||
include_list=self.get_app_content_types(),
|
|
||||||
limit=5
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def init_with_context(self, context):
|
|
||||||
"""
|
|
||||||
Use this method if you need to access the request context.
|
|
||||||
"""
|
|
||||||
return super(CustomAppIndexDashboard, self).init_with_context(context)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,72 +0,0 @@
|
||||||
# Translation of portail-citoyen strings
|
|
||||||
# Copyright (C) 2013 Entr'ouvert
|
|
||||||
# This file is distributed under the same license as the portail-citoyen package.
|
|
||||||
# Benjamin Dauvergne <bdauvergne@entrouvert.com>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: portail_citoyen2 0.1.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2014-03-26 15:15+0100\n"
|
|
||||||
"PO-Revision-Date: 2014-03-26 15:15+0100\n"
|
|
||||||
"Last-Translator: Benjamin Dauvergne <bdauvergne@entrouvert.com>\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
|
|
||||||
#: cms_plugins.py:26
|
|
||||||
msgid "Modify"
|
|
||||||
msgstr "Modifier"
|
|
||||||
|
|
||||||
#: dashboard.py:12
|
|
||||||
msgid "Contents"
|
|
||||||
msgstr "Contenus"
|
|
||||||
|
|
||||||
#: dashboard.py:21
|
|
||||||
msgid "Announces"
|
|
||||||
msgstr "Annonces"
|
|
||||||
|
|
||||||
#: dashboard.py:28 dashboard.py:63
|
|
||||||
msgid "Recent Actions"
|
|
||||||
msgstr "Actions récentes"
|
|
||||||
|
|
||||||
#: dashboard.py:32
|
|
||||||
msgid "Support"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: dashboard.py:35
|
|
||||||
msgid "Project site"
|
|
||||||
msgstr "Site du projet"
|
|
||||||
|
|
||||||
#: dashboard.py:40
|
|
||||||
msgid "Report a bug"
|
|
||||||
msgstr "Reporter un bug"
|
|
||||||
|
|
||||||
#: models.py:17
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Required, %s characters or fewer. Only letters, numbers, and @, ., +, -, or "
|
|
||||||
"_ characters."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: settings.py:45
|
|
||||||
msgid "French"
|
|
||||||
msgstr "Français"
|
|
||||||
|
|
||||||
#: templates/admin/base_site.html:4 templates/admin/base_site.html.py:19
|
|
||||||
msgid "Citizen portal administration"
|
|
||||||
msgstr "Administration du portail citoyen"
|
|
||||||
|
|
||||||
#: templates/admin/base_site.html:17
|
|
||||||
msgid "Administration portal"
|
|
||||||
msgstr "Portail d'administration"
|
|
||||||
|
|
||||||
#: templates/portail_citoyen/form.html:7
|
|
||||||
msgid "Submit"
|
|
||||||
msgstr "Envoyer"
|
|
||||||
|
|
||||||
#: templates/portail_citoyen/form.html:9
|
|
||||||
msgid "Back"
|
|
||||||
msgstr "Retour"
|
|
|
@ -1,4 +0,0 @@
|
||||||
from admin_tools.menu import Menu
|
|
||||||
|
|
||||||
class CustomMenu(Menu):
|
|
||||||
pass
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from south.utils import datetime_utils as datetime
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
pass
|
|
||||||
|
|
||||||
models = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['portail_citoyen2']
|
|
|
@ -1,35 +0,0 @@
|
||||||
from django.core.validators import MaxLengthValidator
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.db.models.signals import class_prepared
|
|
||||||
|
|
||||||
MAX_USERNAME_LENGTH = 255
|
|
||||||
|
|
||||||
def longer_username_signal(sender, *args, **kwargs):
|
|
||||||
if (sender.__name__ == "User" and
|
|
||||||
sender.__module__ == "django.contrib.auth.models"):
|
|
||||||
patch_user_model(sender)
|
|
||||||
class_prepared.connect(longer_username_signal)
|
|
||||||
|
|
||||||
def patch_user_model(model):
|
|
||||||
field = model._meta.get_field("username")
|
|
||||||
|
|
||||||
field.max_length = MAX_USERNAME_LENGTH
|
|
||||||
field.help_text = _("Required, %s characters or fewer. Only letters, "
|
|
||||||
"numbers, and @, ., +, -, or _ "
|
|
||||||
"characters." % MAX_USERNAME_LENGTH)
|
|
||||||
|
|
||||||
# patch model field validator because validator doesn't change if we change
|
|
||||||
# max_length
|
|
||||||
for v in field.validators:
|
|
||||||
if isinstance(v, MaxLengthValidator):
|
|
||||||
v.limit_value = MAX_USERNAME_LENGTH
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
# https://github.com/GoodCloud/django-longer-username/issues/1
|
|
||||||
# django 1.3.X loads User model before class_prepared signal is connected
|
|
||||||
# so we patch model after it's prepared
|
|
||||||
|
|
||||||
# check if User model is patched
|
|
||||||
if User._meta.get_field("username").max_length != MAX_USERNAME_LENGTH:
|
|
||||||
patch_user_model(User)
|
|
|
@ -1,63 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.contrib.auth.management import _get_all_permissions
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.contenttypes.management import update_contenttypes
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
def create_proxy_permissions(app, created_models, verbosity, **kwargs):
|
|
||||||
"""
|
|
||||||
Creates permissions for proxy models which are not created automatically
|
|
||||||
by `django.contrib.auth.management.create_permissions`.
|
|
||||||
see https://code.djangoproject.com/ticket/11154
|
|
||||||
This method is inspired by `django.contrib.auth.managment.create_permissions`.
|
|
||||||
|
|
||||||
Since we can't rely on `get_for_model' we must fallback to `get_by_natural_key`.
|
|
||||||
However, this method doesn't automatically create missing `ContentType` so
|
|
||||||
we must ensure all the model's `ContentType` are created before running this method.
|
|
||||||
We do so by unregistering the `update_contenttypes` `post_syncdb` signal and calling
|
|
||||||
it in here just before doing everything.
|
|
||||||
"""
|
|
||||||
update_contenttypes(app, created_models, verbosity, **kwargs)
|
|
||||||
app_models = models.get_models(app)
|
|
||||||
# This will hold the permissions we're looking for as
|
|
||||||
# (content_type, (codename, name))
|
|
||||||
searched_perms = list()
|
|
||||||
# The codenames and ctypes that should exist.
|
|
||||||
ctypes = set()
|
|
||||||
for model in app_models:
|
|
||||||
opts = model._meta
|
|
||||||
if opts.proxy:
|
|
||||||
# We can't use `get_for_model` here since it doesn't return
|
|
||||||
# the correct `ContentType` for proxy models.
|
|
||||||
# see https://code.djangoproject.com/ticket/17648
|
|
||||||
app_label, model = opts.app_label, opts.object_name.lower()
|
|
||||||
ctype = ContentType.objects.get_by_natural_key(app_label, model)
|
|
||||||
ctypes.add(ctype)
|
|
||||||
for perm in _get_all_permissions(opts, ctype):
|
|
||||||
searched_perms.append((ctype, perm))
|
|
||||||
|
|
||||||
# Find all the Permissions that have a content_type for a model we're
|
|
||||||
# looking for. We don't need to check for codenames since we already have
|
|
||||||
# a list of the ones we're going to create.
|
|
||||||
all_perms = set(Permission.objects.filter(
|
|
||||||
content_type__in=ctypes,
|
|
||||||
).values_list(
|
|
||||||
"content_type", "codename"
|
|
||||||
))
|
|
||||||
|
|
||||||
objs = [
|
|
||||||
Permission(codename=codename, name=name, content_type=ctype)
|
|
||||||
for ctype, (codename, name) in searched_perms
|
|
||||||
if (ctype.pk, codename) not in all_perms
|
|
||||||
]
|
|
||||||
Permission.objects.bulk_create(objs)
|
|
||||||
if verbosity >= 2:
|
|
||||||
for obj in objs:
|
|
||||||
sys.stdout.write("Adding permission '%s'" % obj)
|
|
||||||
|
|
||||||
models.signals.post_syncdb.connect(create_proxy_permissions)
|
|
||||||
# see `create_proxy_permissions` docstring to understand why we unregister
|
|
||||||
# this signal handler.
|
|
||||||
models.signals.post_syncdb.disconnect(update_contenttypes)
|
|
|
@ -1,320 +0,0 @@
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
import os
|
|
||||||
import logging.handlers
|
|
||||||
|
|
||||||
gettext_noop = lambda s: s
|
|
||||||
# Python dotted path to the WSGI application used by Django's runserver.
|
|
||||||
WSGI_APPLICATION = 'portail_citoyen2.wsgi.application'
|
|
||||||
DEBUG = 'DEBUG' in os.environ
|
|
||||||
DEBUG_PROPAGATE_EXCEPTIONS = 'DEBUG_PROPAGATE_EXCEPTIONS' in os.environ
|
|
||||||
TEMPLATE_DEBUG = DEBUG
|
|
||||||
|
|
||||||
PROJECT_PATH = os.path.join(os.path.dirname(__file__))
|
|
||||||
PROJECT_NAME = 'portail-citoyen2'
|
|
||||||
|
|
||||||
ADMINS = ()
|
|
||||||
if 'ADMINS' in os.environ:
|
|
||||||
ADMINS = filter(None, os.environ.get('ADMINS').split(':'))
|
|
||||||
ADMINS = [ admin.split(';') for admin in ADMINS ]
|
|
||||||
for admin in ADMINS:
|
|
||||||
assert len(admin) == 2, 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon'
|
|
||||||
assert '@' in admin[1], 'ADMINS setting pairs second value must be emails'
|
|
||||||
|
|
||||||
MANAGERS = ADMINS
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': os.environ.get('DATABASE_ENGINE', 'django.db.backends.sqlite3'),
|
|
||||||
'NAME': os.environ.get('DATABASE_NAME', os.path.join(PROJECT_PATH, '..', PROJECT_NAME + '.db')),
|
|
||||||
'USER': os.environ.get('DATABASE_USER', ''),
|
|
||||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD', ''),
|
|
||||||
'HOST': os.environ.get('DATABASE_HOST', ''),
|
|
||||||
'PORT': os.environ.get('DATABASE_PORT', '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Hey Entr'ouvert is in France !!
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
|
||||||
LANGUAGE_CODE = 'fr'
|
|
||||||
SITE_ID = 1
|
|
||||||
USE_I18N = True
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
LANGUAGES = (
|
|
||||||
('fr', gettext_noop('French')),
|
|
||||||
)
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
# Static files
|
|
||||||
|
|
||||||
STATIC_ROOT = os.environ.get('STATIC_ROOT', '/var/lib/%s/static' % PROJECT_NAME)
|
|
||||||
STATIC_URL = os.environ.get('STATIC_URL', '/static/')
|
|
||||||
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', '/var/lib/%s/media' % PROJECT_NAME)
|
|
||||||
MEDIA_URL = os.environ.get('MEDIA_URL', '/media/')
|
|
||||||
|
|
||||||
# passerelle address & apikey
|
|
||||||
PASSERELLE_URL = os.environ.get('PASSERELLE_URL', '')
|
|
||||||
PASSERELLE_APIKEY = os.environ.get('PASSERELLE_APIKEY', '')
|
|
||||||
|
|
||||||
if 'STATICFILES_DIRS' in os.environ:
|
|
||||||
STATICFILES_DIRS = os.environ['STATICFILES_DIRS'].split(':')
|
|
||||||
|
|
||||||
TEMPLATE_LOADERS = (
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
)
|
|
||||||
|
|
||||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
|
||||||
'django.core.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'allauth.account.context_processors.account',
|
|
||||||
'allauth.socialaccount.context_processors.socialaccount',
|
|
||||||
'django.core.context_processors.debug',
|
|
||||||
'django.core.context_processors.i18n',
|
|
||||||
'django.core.context_processors.media',
|
|
||||||
'django.core.context_processors.static',
|
|
||||||
'django.core.context_processors.tz',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'cms.context_processors.media',
|
|
||||||
'sekizai.context_processors.sekizai',
|
|
||||||
'portail_citoyen2.context_processors.template_vars',
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.http.ConditionalGetMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.transaction.TransactionMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
'cms.middleware.page.CurrentPageMiddleware',
|
|
||||||
'cms.middleware.user.CurrentUserMiddleware',
|
|
||||||
'cms.middleware.toolbar.ToolbarMiddleware',
|
|
||||||
'entrouvert.djommon.middleware.VersionMiddleware',
|
|
||||||
)
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'portail_citoyen2.urls'
|
|
||||||
|
|
||||||
TEMPLATE_DIRS = ['/var/lib/%s/templates' % PROJECT_NAME, os.path.join(PROJECT_PATH, 'templates')]
|
|
||||||
if os.environ.get('TEMPLATE_DIRS'):
|
|
||||||
TEMPLATE_DIRS = os.environ['TEMPLATE_DIRS'].split(':') + TEMPLATE_DIRS
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'admin_tools',
|
|
||||||
'admin_tools.theming',
|
|
||||||
'admin_tools.menu',
|
|
||||||
'admin_tools.dashboard',
|
|
||||||
'django.contrib.admin',
|
|
||||||
'south',
|
|
||||||
'mptt',
|
|
||||||
'sekizai',
|
|
||||||
'cms',
|
|
||||||
'menus',
|
|
||||||
'djangocms_text_ckeditor',
|
|
||||||
# 'cmsplugin_text_wrapper',
|
|
||||||
# 'cms_ajax_text_plugin',
|
|
||||||
'passerelle_register_plugin',
|
|
||||||
'feed_plugin',
|
|
||||||
'data_source_plugin',
|
|
||||||
'allauth',
|
|
||||||
'allauth.account',
|
|
||||||
'allauth.socialaccount',
|
|
||||||
# ... include the providers you want to enable:
|
|
||||||
'portail_citoyen2.allauth_authentic2',
|
|
||||||
'portail_citoyen2',
|
|
||||||
)
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
|
||||||
)
|
|
||||||
# auth and allauth settings
|
|
||||||
LOGIN_REDIRECT_URL = os.environ.get('LOGIN_REDIRECT_URL', '/')
|
|
||||||
LOGOUT_URL = os.environ.get('LOGOUT_URL', '/accounts/logout/')
|
|
||||||
LOGIN_URL = os.environ.get('LOGIN_URL', '/accounts/authentic2/login/?process=login')
|
|
||||||
SOCIALACCOUNT_QUERY_EMAIL = True
|
|
||||||
SOCIALACCOUNT_PROVIDERS = {
|
|
||||||
'authentic2': {
|
|
||||||
'URL': 'http://localhost:9000/idp/oauth2/',
|
|
||||||
'SCOPE': ['read', 'write'],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
SOCIALACOUNT_AUTO_SIGNUP = True
|
|
||||||
ACCOUNT_LOGOUT_ON_GET = True
|
|
||||||
ACCOUNT_UNIQUE_EMAIL = False
|
|
||||||
|
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
|
|
||||||
|
|
||||||
# sessions
|
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
|
||||||
SESSION_COOKIE_NAME = 'portail-citoyen'
|
|
||||||
SESSION_COOKIE_PATH = os.environ.get('SESSION_COOKIE_PATH', '/')
|
|
||||||
SESSION_COOKIE_SECURE = 'SESSION_COOKIE_SECURE' in os.environ
|
|
||||||
|
|
||||||
# email settings
|
|
||||||
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost')
|
|
||||||
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
|
|
||||||
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
|
|
||||||
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25))
|
|
||||||
EMAIL_SUBJECT_PREFIX = os.environ.get('EMAIL_SUBJECT_PREFIX', '[Portail citoyen]')
|
|
||||||
EMAIL_USE_TLS = 'EMAIL_USE_TLS' in os.environ
|
|
||||||
SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'root@localhost')
|
|
||||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'ne-pas-repondre@portail-citoyen.fr')
|
|
||||||
|
|
||||||
# web & network settings
|
|
||||||
if 'ALLOWED_HOSTS' in os.environ:
|
|
||||||
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(':')
|
|
||||||
else:
|
|
||||||
ALLOWED_HOSTS = ('127.0.0.1', 'localhost')
|
|
||||||
USE_X_FORWARDED_HOST = 'USE_X_FORWARDED_HOST' in os.environ
|
|
||||||
|
|
||||||
if 'INTERNAL_IPS' in os.environ:
|
|
||||||
INTERNAL_IPS = os.environ['INTERNAL_IPS'].split(':')
|
|
||||||
else:
|
|
||||||
INTERNAL_IPS = ('127.0.0.1')
|
|
||||||
|
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY', '0!=(1kc6kri-ui+tmj@mr+*0bvj!(p*r0duu2n=)7@!p=pvf9n')
|
|
||||||
|
|
||||||
|
|
||||||
SAML_METADATA_ROOT = 'metadata'
|
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': True,
|
|
||||||
'formatters': {
|
|
||||||
'syslog': {
|
|
||||||
'format': 'portail-citoyen(pid=%(process)d) %(levelname)s %(name)s: %(message)s',
|
|
||||||
},
|
|
||||||
'syslog_debug': {
|
|
||||||
'format': 'portail-citoyen(pid=%(process)d) %(levelname)s %(asctime)s t_%(thread)s %(name)s: %(message)s',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
'syslog': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'entrouvert.logging.handlers.SysLogHandler',
|
|
||||||
'formatter': 'syslog_debug' if DEBUG else 'syslog',
|
|
||||||
'facility': logging.handlers.SysLogHandler.LOG_LOCAL0,
|
|
||||||
'address': '/dev/log',
|
|
||||||
'max_length': 999,
|
|
||||||
},
|
|
||||||
'mail_admins': {
|
|
||||||
'level': 'ERROR',
|
|
||||||
'class': 'django.utils.log.AdminEmailHandler',
|
|
||||||
'filters': [],
|
|
||||||
},
|
|
||||||
'console': {
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'syslog_debug',
|
|
||||||
'level': 'DEBUG',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
'requests': {
|
|
||||||
'handlers': ['mail_admins','syslog'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'portail_citoyen2': {
|
|
||||||
'handlers': ['mail_admins','syslog'],
|
|
||||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'django': {
|
|
||||||
'handlers': ['mail_admins','syslog'],
|
|
||||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'django.db': {
|
|
||||||
'handlers': ['mail_admins','syslog'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'': {
|
|
||||||
'handlers': ['mail_admins','syslog'],
|
|
||||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SOUTH_TESTS_MIGRATE = False
|
|
||||||
|
|
||||||
# Admin tools
|
|
||||||
ADMIN_TOOLS_INDEX_DASHBOARD = 'portail_citoyen2.dashboard.CustomIndexDashboard'
|
|
||||||
ADMIN_TOOLS_APP_INDEX_DASHBOARD = 'portail_citoyen2.dashboard.CustomAppIndexDashboard'
|
|
||||||
ADMIN_TOOLS_MENU = 'portail_citoyen2.menu.CustomMenu'
|
|
||||||
ADMIN_TOOLS_THEMING_CSS = 'portail_citoyen/css/admin.css'
|
|
||||||
|
|
||||||
|
|
||||||
# cms settings
|
|
||||||
CMS_TEMPLATES = (
|
|
||||||
('portail_citoyen/base_two_columns.html', 'Canevas sur deux colonnes'),
|
|
||||||
('portail_citoyen/base_one_column.html', 'Canevas sur une colonne'),
|
|
||||||
('portail_citoyen/base_help.html', 'Canevas de l\'aide'),
|
|
||||||
)
|
|
||||||
if 'CMS_TEMPLATES' in os.environ:
|
|
||||||
CMS_TEMPLATES = map(lambda x: x.split(';'),
|
|
||||||
os.environ('CMS_TEMPLATES').split(':'))
|
|
||||||
CMS_REDIRECTS = True
|
|
||||||
CMS_TEXT_WRAPPERS = (
|
|
||||||
('block', {
|
|
||||||
'render_template': 'portail_citoyen/block.html',
|
|
||||||
'extra_context': {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
CMS_TEXT_WRAPPER_CLASSES = []
|
|
||||||
# necessary for plugin displaying a form to have a fresh csrftoken
|
|
||||||
CMS_PLACEHOLDER_CACHE = False
|
|
||||||
CMS_PAGE_CACHE = False
|
|
||||||
|
|
||||||
# Do we use memcached ?
|
|
||||||
if 'USE_MEMCACHED' in os.environ:
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
|
||||||
'LOCATION': '127.0.0.1:11211',
|
|
||||||
'KEY_PREFIX': 'portail-citoyen',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
|
||||||
|
|
||||||
|
|
||||||
if 'USE_DEBUG_TOOLBAR' in os.environ:
|
|
||||||
try:
|
|
||||||
import debug_toolbar
|
|
||||||
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
|
|
||||||
INSTALLED_APPS += ('debug_toolbar',)
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {'INTERCEPT_REDIRECTS': False}
|
|
||||||
except ImportError:
|
|
||||||
print "Debug toolbar missing, not loaded"
|
|
||||||
|
|
||||||
# extract any key starting with setting
|
|
||||||
for key in os.environ:
|
|
||||||
if key.startswith('SETTING_'):
|
|
||||||
setting_key = key[len('SETTING_'):]
|
|
||||||
value = os.environ[key]
|
|
||||||
try:
|
|
||||||
value = int(value)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
globals()[setting_key] = value
|
|
||||||
|
|
||||||
# try to import local_settings.py (useless, in theory)
|
|
||||||
try:
|
|
||||||
from local_settings import *
|
|
||||||
except ImportError, e:
|
|
||||||
if 'local_settings' in e.args[0]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not CMS_TEMPLATES:
|
|
||||||
raise ImproperlyConfigured('You must define CMS_TEMPLATES')
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,46 +0,0 @@
|
||||||
/**
|
|
||||||
* theming styles
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#header {
|
|
||||||
background: url(/static/admin_tools/images/admin-tools.png) 0 0 repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header #branding h1 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 5px 10px;
|
|
||||||
/* text-indent: -9999px;
|
|
||||||
background: transparent url(../images/logo-portail-citoyen.png) 10px 5px no-repeat;
|
|
||||||
height: 31px;
|
|
||||||
width: 93px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
div.breadcrumbs {
|
|
||||||
display: block;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: 0;
|
|
||||||
background-position: 0 -8px;
|
|
||||||
border-bottom: 1px solid #ededed;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.breadcrumbs a {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
width: 980px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector select {
|
|
||||||
width: 470px;
|
|
||||||
height: 17.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-available, .selector-chosen {
|
|
||||||
float: left;
|
|
||||||
width: 470px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,994 +0,0 @@
|
||||||
@font-face {
|
|
||||||
font-family: 'Museo500';
|
|
||||||
src: url(Museo500-Regular.otf);
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'MuseoSlab';
|
|
||||||
src: url(Museo_Slab.otf);
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body { margin: 0; font-family: arial, sans-serif; font-size: 13px;}
|
|
||||||
a { text-decoration: none; }
|
|
||||||
a:hover { text-decoration: underline; }
|
|
||||||
h1, h2, h3 { margin-top: 0; }
|
|
||||||
|
|
||||||
div#single-title, #nav,
|
|
||||||
#top, h2 {
|
|
||||||
font-family: MuseoSlab, sans-serif;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3, h4 {
|
|
||||||
font-family: Museo500, sans-serif;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
a {
|
|
||||||
-webkit-transition: color 200ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* layout */
|
|
||||||
|
|
||||||
body {
|
|
||||||
overflow-x: hidden;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#page {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#header {
|
|
||||||
background-color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 0 0 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
margin-left: -500px;
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div#top {
|
|
||||||
height: 456px;
|
|
||||||
width: 1500px;
|
|
||||||
margin: 0 auto 0 auto;
|
|
||||||
margin-left: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#top h1 {
|
|
||||||
width: 10em;
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 3em;
|
|
||||||
padding-left: 2em;
|
|
||||||
margin-left: 20px;
|
|
||||||
font-size: 120%;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#top h1 a {
|
|
||||||
color: white;
|
|
||||||
text-shadow: #6374AB 0px 0px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#top a img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#main-content-wrapper {
|
|
||||||
position: relative;
|
|
||||||
z-index: 100;
|
|
||||||
width: 1000px;
|
|
||||||
margin: 200px auto 0px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#main-content {
|
|
||||||
margin: 0;
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#footer {
|
|
||||||
clear: both;
|
|
||||||
background: white;
|
|
||||||
padding: 0px;
|
|
||||||
width: 1000px;
|
|
||||||
margin: 10px auto 0 auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
p#legal {
|
|
||||||
font-size: small;
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 5em;
|
|
||||||
font-size: 70%;
|
|
||||||
display: inline-block;
|
|
||||||
padding-top: 5px;
|
|
||||||
border-top: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
position: relative;
|
|
||||||
margin:0;
|
|
||||||
color: rgb(58, 58, 58);
|
|
||||||
}
|
|
||||||
|
|
||||||
div#content a {
|
|
||||||
color: #37a7da;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#content a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu {
|
|
||||||
font-size: 130%;
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px 0 32px 0;
|
|
||||||
-webkit-border-top-right-radius: 5px;
|
|
||||||
-moz-border-radius-topright: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li {
|
|
||||||
display: inline;
|
|
||||||
margin: 0px 10px 0 0;
|
|
||||||
padding: 5px;
|
|
||||||
background: #37a7da;
|
|
||||||
border: 5px solid transparent;
|
|
||||||
border-width: 2px 5px;
|
|
||||||
-webkit-transition: all .2s ease-in-out;
|
|
||||||
-moz-transition: all .2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li a {
|
|
||||||
color: white;
|
|
||||||
text-transform: uppercase;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li:hover {
|
|
||||||
background-color: #f4bc03;
|
|
||||||
border-color: #f4bc03;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li:hover a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li.selected, #menu li.ancestor {
|
|
||||||
background: #515151;
|
|
||||||
border-color: #515151;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu li.selected a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#left {
|
|
||||||
float: left;
|
|
||||||
width: 49.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#right {
|
|
||||||
float: right;
|
|
||||||
width: 49.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
br.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
#error-404, #error-500, #content .block, #password-changed {
|
|
||||||
background: white;
|
|
||||||
font-size: 110%;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .block h2 {
|
|
||||||
background: transparent;
|
|
||||||
font-weight: normal;
|
|
||||||
color: white;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 6px 10px 6px 10px;
|
|
||||||
color: #333;
|
|
||||||
font-size: 130%;
|
|
||||||
cursor: default; /* someday, perhaps, cursor: move */
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .block h2.feeds {
|
|
||||||
background-image: url(Picto-Bulle.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .block h2.newsletters {
|
|
||||||
background-image: url(Picto-coeur.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .block h3,
|
|
||||||
#content .block p {
|
|
||||||
margin: 1ex 10px;
|
|
||||||
padding-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .demarches ul,
|
|
||||||
#content ul.mes-demarches {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .demarches ul {
|
|
||||||
-webkit-column-count: 2;
|
|
||||||
-moz-column-count: 2;
|
|
||||||
column-count: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content ul.mes-demarches li,
|
|
||||||
#content .demarches ul li {
|
|
||||||
margin: 1ex 0 10px 1ex;
|
|
||||||
padding-left: 10px;
|
|
||||||
-webkit-column-break-inside: avoid;
|
|
||||||
-moz-column-break-inside: avoid;
|
|
||||||
column-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .demarches .toutes-les-demarches {
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .toutes-les-demarches a {
|
|
||||||
font-size: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content .toutes-les-demarches a:before {
|
|
||||||
content: "▹ ";
|
|
||||||
}
|
|
||||||
|
|
||||||
#commune-selector {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content #futurs-demarches {
|
|
||||||
overflow-y: hidden;
|
|
||||||
margin: 5px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content #futurs-demarches.selected {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#single-title {
|
|
||||||
border: 1px solid #a5a7aa;
|
|
||||||
border-width: 1px 0px;
|
|
||||||
font-size: 110%;
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page #main-content {
|
|
||||||
background: white;
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 10px 10px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page #main-content form div input {
|
|
||||||
display: block;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-bottom: 2ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#welcome {
|
|
||||||
text-align: justify;
|
|
||||||
margin: 0 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#welcome h2 {
|
|
||||||
margin: 1ex 0;
|
|
||||||
font-size: 300%;
|
|
||||||
background: transparent url(e54.png) left center no-repeat;
|
|
||||||
padding-left: 70px;
|
|
||||||
text-align: left;
|
|
||||||
width: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#welcome {
|
|
||||||
float: left;
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.helptext {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right {
|
|
||||||
width: 30%;
|
|
||||||
float: right;
|
|
||||||
margin: 1ex auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right form {
|
|
||||||
text-align: left;
|
|
||||||
background: white;
|
|
||||||
margin: 10px 10px;
|
|
||||||
padding: 10px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
-moz-box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
-webkit-box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right form div label {
|
|
||||||
width: 14em;
|
|
||||||
display: block;
|
|
||||||
padding-top: 3px;
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right form div input {
|
|
||||||
width: 16em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right form div.form-field-required label:after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
body.narrow-page div.right form > input {
|
|
||||||
display: block;
|
|
||||||
margin: 1em auto 0 auto;
|
|
||||||
background: #37a7da;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 3px 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.login-actions {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.region-header {
|
|
||||||
width: 1000px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks {
|
|
||||||
background: #ffffff;
|
|
||||||
position: relative;
|
|
||||||
float: right;
|
|
||||||
width: 250px;
|
|
||||||
padding: 5px 5px 5px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks span {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #888;
|
|
||||||
background: url(button_connexion.gif) left center no-repeat;
|
|
||||||
border: 1px solid #e9e9e9;
|
|
||||||
line-height: 20px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a {
|
|
||||||
color: #888;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a.logout {
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a.restricted {
|
|
||||||
color: white;
|
|
||||||
float: right;
|
|
||||||
padding: 0 1ex;
|
|
||||||
border: 1px outset #888;
|
|
||||||
background: #37a7da;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#username {
|
|
||||||
float: right;
|
|
||||||
padding: 5px 5px 5px 5px;
|
|
||||||
background: white;
|
|
||||||
line-height: 20px;
|
|
||||||
height: 22px;
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.newsList {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.newsList li.abonne {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.abonne {
|
|
||||||
padding-left: 20px;
|
|
||||||
background: transparent url(Validation.png) center left no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nonAbonne {
|
|
||||||
padding-left: 20px;
|
|
||||||
background: transparent url(Annulation.png) center left no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* page de profil */
|
|
||||||
#my-informations {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-informations p {
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-informations select,
|
|
||||||
#my-informations input {
|
|
||||||
margin-left: 10px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* forms error reporting */
|
|
||||||
|
|
||||||
.errorlist {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.errorlist li {
|
|
||||||
display: block;
|
|
||||||
color: #f44;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-field-required label:after {
|
|
||||||
content: '*';
|
|
||||||
color: #D90024;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.errorlist + p {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.passerelle-register-plugin input + label {
|
|
||||||
background: transparent url(Annulation.png) center left no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passerelle-register-plugin input:checked + label {
|
|
||||||
background: transparent url(Validation.png) center left no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passerelle-register-plugin td input {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
.passerelle-register-plugin td label {
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.block form {
|
|
||||||
padding: 0 1ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.announces {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.announces thead td {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.announces thead th {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.announces tbody td {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.announces tbody th {
|
|
||||||
width: 35%;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: -140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks > a, #agglolinks > span > a {
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks a, #agglolinks span {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
color: white;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
padding: 5px 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks a.agglo-mon-agglo, #agglolinks a.agglo-mes-e-services {
|
|
||||||
background: #37a7da;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks a.agglo-connaitre {
|
|
||||||
background: #d90024;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks a.agglo-vivre {
|
|
||||||
background: #1f4791;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks a.agglo-entreprendre {
|
|
||||||
background: #a3d117;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks span.agglo-mes-e-services > a {
|
|
||||||
background: #37a7da;
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.agglo-autres-services-menu {
|
|
||||||
display: none;
|
|
||||||
position: relative;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 10px;
|
|
||||||
margin-top: 0px;
|
|
||||||
width: 160px;
|
|
||||||
z-index: 10;
|
|
||||||
background: rgba(246, 132, 35, 0.5);
|
|
||||||
border-bottom: 5px solid #37a7da;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.agglo-autres-services-menu li {
|
|
||||||
list-style: none;
|
|
||||||
background: #FFA824;
|
|
||||||
opacity: 0.9;
|
|
||||||
-webkit-transition: all .2s ease-in-out;
|
|
||||||
-moz-transition: all .2s ease-in-out;
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
border-left: 5px solid #37a7da;
|
|
||||||
border-right: 5px solid #37a7da;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.agglo-autres-services-menu li:hover {
|
|
||||||
background: #f4bc03;
|
|
||||||
opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks ul.agglo-autres-services-menu li a {
|
|
||||||
display: block;
|
|
||||||
margin-left: 0;
|
|
||||||
-webkit-transition: all .2s ease-in-out;
|
|
||||||
-moz-transition: all .2s ease-in-out;
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks ul.agglo-autres-services-menu li a:hover {
|
|
||||||
letter-spacing: 1px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agglolinks span.agglo-mes-e-services:hover ul.agglo-autres-services-menu {
|
|
||||||
float: right;
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chapeau {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mon compte */
|
|
||||||
#my-informations-form {
|
|
||||||
width: 90%;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-informations-form input[type~=text] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#id_edit-profile-email_wrap, #id_edit-profile-address_wrap {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
#id_edit-profile-first_name_wrap, #id_edit-profile-phone_wrap, #id_edit-profile-postal_code_wrap {
|
|
||||||
width: 45%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#id_edit-profile-last_name_wrap, #id_edit-profile-mobile_wrap, #id_edit-profile-city_wrap {
|
|
||||||
width: 45%;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* pied de page */
|
|
||||||
#footer-menu {
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
top: 20px;
|
|
||||||
left: 20px;
|
|
||||||
width: 435px;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-menu-leaf {
|
|
||||||
display: inline;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
padding-right: 20px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-menu-leaf-link, .footer-menu-leaf-link:hover {
|
|
||||||
display: block;
|
|
||||||
width: 125px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
background: #ffffff;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #515151;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-password {
|
|
||||||
padding-bottom: 1ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-password p {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#my-password p a {
|
|
||||||
color: inherit;
|
|
||||||
padding: 1ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.show-user-feeds {
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.show-user-feeds li {
|
|
||||||
list-style: disc;
|
|
||||||
background: url(mediathk.png) left center no-repeat;
|
|
||||||
min-height: 30px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.show-user-feeds li.aquarium { background-image: url(aquarium.png); }
|
|
||||||
ul.show-user-feeds li.conservatoire { background-image: url(conservatoire.png); }
|
|
||||||
ul.show-user-feeds li.ecolothk { background-image: url(ecolothk.png); }
|
|
||||||
ul.show-user-feeds li.facebook { background-image: url(facebook.png); }
|
|
||||||
ul.show-user-feeds li.lattara { background-image: url(lattara.png); }
|
|
||||||
ul.show-user-feeds li.mediathk { background-image: url(mediathk.png); }
|
|
||||||
ul.show-user-feeds li.opendata { background-image: url(opendata.png); }
|
|
||||||
ul.show-user-feeds li.planet { background-image: url(planet.png); }
|
|
||||||
|
|
||||||
#id_new_password1_help_text, #id_password1_help_text {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 90%;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* saml post page */
|
|
||||||
.post-redirect {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages {
|
|
||||||
margin-left: 210px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages li.warning {
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages li.error {
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages li.info {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* registration form */
|
|
||||||
img.captcha {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#id_captcha_1 {
|
|
||||||
width: 13em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* pimping up */
|
|
||||||
h1#logo img {
|
|
||||||
-webkit-transition: all .2s ease-in-out;
|
|
||||||
-moz-transition: all .2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1#logo img:hover {
|
|
||||||
/*
|
|
||||||
-webkit-transform: scale(1.05);
|
|
||||||
-moz-transform: scale(1.05);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#nav {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0px 0;
|
|
||||||
width: 180px;
|
|
||||||
margin: 17px auto;
|
|
||||||
font-size: 110%;
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav li {
|
|
||||||
margin: 1ex 0;
|
|
||||||
padding: 1ex;
|
|
||||||
border: 1px solid #a5a7aa;
|
|
||||||
border-width: 1px 0px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav a {
|
|
||||||
color: #404041;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav li.selected:after,
|
|
||||||
#nav li.ancestor:after,
|
|
||||||
#nav li a:hover,
|
|
||||||
#nav li.selected a,
|
|
||||||
#nav li.ancestor a {
|
|
||||||
color: #e0007a;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.block {
|
|
||||||
text-align: left;
|
|
||||||
background: white;
|
|
||||||
margin: 10px 10px;
|
|
||||||
padding: 10px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
-moz-box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
-webkit-box-shadow: 0px 2px 3px rgba(0, 0, 0, .4);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2#welcome-title {
|
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
select,
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"] {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
background: white url(field-shade.png) top left repeat-x;
|
|
||||||
padding: 1px;
|
|
||||||
border-radius: 2px;
|
|
||||||
-moz-border-radius: 2px;
|
|
||||||
-webkit-border-radius: 2px;
|
|
||||||
color: black;
|
|
||||||
-webkit-transition: background 200ms ease-out;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"]:focus,
|
|
||||||
input[type="password"]:focus {
|
|
||||||
border: 1px solid #888;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#registration {
|
|
||||||
width: 40em;
|
|
||||||
text-align: justify;
|
|
||||||
margin: 2em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#registration form {
|
|
||||||
margin: 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#registration label {
|
|
||||||
display: block;
|
|
||||||
width: 15em;
|
|
||||||
float: left;
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 1em;
|
|
||||||
color: #777;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#registration ul.errorlist {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 16em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#registration input[type="submit"] {
|
|
||||||
margin-left: 17em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#toplinks {
|
|
||||||
background: #ffffff;
|
|
||||||
position: absolute;
|
|
||||||
top: 3em;
|
|
||||||
right: 0;
|
|
||||||
width: 40%;
|
|
||||||
padding: 5px 5px 5px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks span {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #888;
|
|
||||||
background: url(button_connexion.gif) left center no-repeat;
|
|
||||||
border: 1px solid #e9e9e9;
|
|
||||||
line-height: 20px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks span.logged-in {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a {
|
|
||||||
color: #888;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toplinks a.restricted {
|
|
||||||
color: white;
|
|
||||||
float: right;
|
|
||||||
padding: 0.5ex 1ex;
|
|
||||||
border: 1px outset #888;
|
|
||||||
background: #672290;
|
|
||||||
position: absolute;
|
|
||||||
top: -3em;
|
|
||||||
right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#toplinks a.logout {
|
|
||||||
background: url(button_connexion.gif) left center no-repeat;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 1px;
|
|
||||||
padding-left: 30px;
|
|
||||||
border: 1px solid #e9e9e9;
|
|
||||||
padding-right: 1em;
|
|
||||||
margin-right: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#real-content {
|
|
||||||
padding-left: 200px;
|
|
||||||
text-align: justify;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#help-content {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#help-menu-content li.selected > a {
|
|
||||||
color: #000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.account-management-plugin {
|
|
||||||
padding-left: 1em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.account-management-plugin li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block form label {
|
|
||||||
color: #777;
|
|
||||||
padding-top: 1ex;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block form select,
|
|
||||||
.block form input[type="text"],
|
|
||||||
.block form input[type="password"] {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block ul.feed-list {
|
|
||||||
padding: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block li.feed-list-item {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block li.feed-list-item label {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.fullname {
|
|
||||||
display: inline;
|
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue