initial package

This commit is contained in:
Jérôme Schneider 2014-04-01 15:59:05 +02:00
parent ed6c299cab
commit ddbd35be26
122 changed files with 506 additions and 10696 deletions

View File

@ -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
View File

@ -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
View File

@ -1,3 +0,0 @@
- Authentication is handled by authsaml2
- First user created must be superadmin
- User admin must be read-only

6
debian/changelog vendored Normal file
View File

@ -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

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
7

5
debian/conf/db.conf vendored Normal file
View File

@ -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'

41
debian/conf/nginx-example.conf vendored Normal file
View File

@ -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;
}
}

46
debian/conf/portail-citoyen2.conf vendored Normal file
View File

@ -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'

7
debian/conf/syncdb.sh vendored Executable file
View File

@ -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

41
debian/control vendored Normal file
View File

@ -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

1
debian/docs vendored Normal file
View File

@ -0,0 +1 @@
README

18
debian/portail-citoyen-manage vendored Executable file
View File

@ -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 "$@"

14
debian/portail-citoyen2.config vendored Normal file
View File

@ -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#

8
debian/portail-citoyen2.dirs vendored Normal file
View File

@ -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

1
debian/portail-citoyen2.docs vendored Normal file
View File

@ -0,0 +1 @@
README

178
debian/portail-citoyen2.init vendored Normal file
View File

@ -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

4
debian/portail-citoyen2.install vendored Normal file
View File

@ -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

87
debian/portail-citoyen2.postinst vendored Normal file
View File

@ -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

32
debian/portail-citoyen2.postrm vendored Normal file
View File

@ -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

View File

@ -0,0 +1 @@
usr/

14
debian/rules vendored Executable file
View File

@ -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

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

View File

@ -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&amp;timestamp=' + timestamp + '&amp;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>&amp;algo=<var>algo</var>&amp;timestamp=<var>ts</var>&amp;nonce=<var>nonce</var>&amp;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>

View File

@ -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

View File

@ -1,5 +0,0 @@
BASE=`dirname $0`
ENV=${ENV:-dev}
$BASE/run.sh loaddata --traceback initial_data

View File

@ -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'),
},
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -1 +0,0 @@
# Create your models here.

View File

@ -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)

View File

@ -1,5 +0,0 @@
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import Authentic2Provider
urlpatterns = default_urlpatterns(Authentic2Provider)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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']

View File

@ -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')

View File

@ -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);
}
});
});

View File

@ -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>

View File

@ -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 %}

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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']

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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 %}

View File

@ -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)

View File

@ -1 +0,0 @@
# Create your views here.

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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']

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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()

View File

@ -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_

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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']

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -1 +0,0 @@
# Create your views here.

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -1,4 +0,0 @@
from admin_tools.menu import Menu
class CustomMenu(Menu):
pass

View File

@ -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']

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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