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