Add a screen in the admin to launch commands. Fix bug #4 (https://github.com/IRI-Research/plan4learning/issues/4).
authorymh <ymh.work@gmail.com>
Fri, 11 Oct 2013 11:46:18 +0200
changeset 145 7c6fe1dab213
parent 144 8c32ea1310de
child 146 3d20cdc9be3d
Add a screen in the admin to launch commands. Fix bug #4 (https://github.com/IRI-Research/plan4learning/issues/4).
doc/administration.rst
src/p4l/admin.py
src/p4l/admin/__init__.py
src/p4l/admin/components.py
src/p4l/admin/forms.py
src/p4l/admin/sites.py
src/p4l/admin/views.py
src/p4l/config.py.tmpl
src/p4l/decorators.py
src/p4l/forms.py
src/p4l/locale/en/LC_MESSAGES/django.mo
src/p4l/locale/en/LC_MESSAGES/django.po
src/p4l/locale/es/LC_MESSAGES/django.mo
src/p4l/locale/es/LC_MESSAGES/django.po
src/p4l/locale/fr/LC_MESSAGES/django.mo
src/p4l/locale/fr/LC_MESSAGES/django.po
src/p4l/management/commands/dump_record.py
src/p4l/management/commands/import_record.py
src/p4l/models/__init__.py
src/p4l/settings.py
src/p4l/static/p4l/css/img/ajax-loader.gif
src/p4l/static/p4l/css/img/flags.png
src/p4l/static/p4l/css/p4l.css
src/p4l/static/p4l/css/p4l_admin.css
src/p4l/static/p4l/img/ajax-loader.gif
src/p4l/static/p4l/img/flags.png
src/p4l/templateloaders.py
src/p4l/templates/admin/app_index.html
src/p4l/templates/admin/index.html
src/p4l/templates/p4l/admin/confirm_run_script.html
src/p4l/urls.py
src/p4l/utils.py
src/p4l/views.py
--- a/doc/administration.rst	Tue Oct 08 17:25:39 2013 +0200
+++ b/doc/administration.rst	Fri Oct 11 11:46:18 2013 +0200
@@ -27,7 +27,7 @@
 Commande d'administration
 =========================
 
-Voici une liste des commandes ser vant à gérer l'import et l'export des notices (``Record``). 
+Voici une liste des commandes servant à gérer l'import et l'export des notices (``Record``). 
 
 
 .. _admin-import-record:
@@ -242,13 +242,16 @@
 Cette commande est fournie par le module Django ``Haystack``. Sa documentation se trouve à l'adresse suivante : http://django-haystack.readthedocs.org/en/v2.1.0/management_commands.html
 
 
-console d'administration / gestion des utilisateurs
-===================================================
+console d'administration
+========================
 
 Le back-office offre une console d'administration donnant accès en particulier à la gestion des utilisateurs.
 On y accède par le lien ``admin`` dans l'en-tête des pages si on est connecté en tant qu'administrateur ou bien en allant directement à l'adresse ``<racine du site>/p4l/admin/``.
 
 
+gestion des utilisateurs
+------------------------
+
 L'administration des utilisateurs se fait à l'adresse suivante : ``<racine du site>/p4l/admin/p4l/user/``.
 
 L'administration des groupes d'utilisateurs se fait à l'adresse suivante: ``<racine du site>/p4l/admin/auth/group/``.
@@ -263,4 +266,45 @@
 
 Pour faciliter la gestion de ces permissions, le plus simple est de créer un groupe ``utilisateurs``. On affectera à ce groupe toutes les permissions sur les objects de l'application ``p4l``.
 il suffira ensuite de mettre les utilisateurs dans ce groupe (champ ``Groupes`` dans l'interface d'édition des utilisateurs). L'utilisateur héritera alors des parmissions du groupe.
- 
+
+
+Lancement d'un script
+---------------------
+Il est possible de lancer un script à partir de l'adresse suivante : ``<racine du site>/p4l/admin/confirm_script``.
+
+Le script qui est exécuté est configuré par la propriété ``ADMIN_SCRIPT`` dans la configuration de l'application (``src/p4l/config.py``).
+Cette propriété est un dictionnaire dont les clés sont les arguments du constructeur de subprocess.Popen.
+Tous les arguments et le fonctionnement de cet objet sont détaillés à l'adresse suivante : http://docs.python.org/2/library/subprocess.html#popen-constructor
+Tous les arguments sont configurables sauf les suivants : ``stdout``, ``stderr``, ``bufsize``, ``close_fds``, ``preexec_fn``.
+Cependant les quatres suivants seront les plus utiles:
+  * `args`: soit une séquence d'arguments de programme, soit une chaine de caractères
+  * `cwd`: le chemin du reepertoire de travail. Par défaut : ``None``
+  * `env`: dictionnire donnant les variables d'evironement positinnées durant l'éxeecution du script.
+
+Il est recommandé que ``args`` soit une liste d'arguments et non une simple chaîne de caractères.
+
+L'example suivant démontre comment on peut configurer cette propriété pour lancer le dump des notices avec la commande ``dump_record``.
+
+.. code-block :: python
+
+ADMIN_SCRIPT = {
+    'args' : [ sys.executable, "manage.py", "dump_record", "--newline", "-j", "/tmp/script_dump.rdf.bz2"],
+    'cwd' : "<chemin absolu des sources l'application>/src",
+    'env' : {'PYTHONPATH': '<chemin absolu de l'environement virtuel>/lib/python2.7/site-packages'}
+}
+
+
+Plusieurs points sont à noter:
+
+  * L'utilisation de cette fonctionnalité est à priori réservé pour une application installé sous Unix. (cela peut fonctionner sous Windows, mais cela n'a pas été testé)
+  * La fermeture de la fenêtre du navigateur ne stoppe pas la commande
+  * En particulier si la session de l'utilisateur expire ou bien que la fenêtre du browser est fermée, il n'y a plus possibilité de stopper le processus à partir d'un browser.
+    Le processus devra être interompu par les moyens habituels directement sur le serveur
+  * La commande est lancée dans le contexte du serveur web. Elle est donc executé par l'utilisateur du serveur web et hérite de ces droits d'accès.
+  * Tout démarrage du serveur web stoppe la commande.
+  * La commande partage les ressources du serveurs web. Attention donc à ne pas lancer des commandes trop gourmandes en ressources, cela peut avoir des conséquences sur la stabilité du serveur web et sa disponibilité.
+  * L'affichage de la sortie de la commande dans le browser se fait ligne par ligne.
+    Si la sortie de la commande ne comporte pas de caractère de retour à la ligne (``"\n"``) rien ne s'affichera avant la fin de la commande.
+  * Les sorties erreur et standard sont affichée ensemble sans différentiation.
+
+ 
\ No newline at end of file
--- a/src/p4l/admin.py	Tue Oct 08 17:25:39 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright IRI (c) 2013
-#
-# contact@iri.centrepompidou.fr
-#
-# This software is governed by the CeCILL-B license under French law and
-# abiding by the rules of distribution of free software.  You can  use, 
-# modify and/ or redistribute the software under the terms of the CeCILL-B
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info". 
-#
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty  and the software's author,  the holder of the
-# economic rights,  and the successive licensors  have only  limited
-# liability. 
-#
-# In this respect, the user's attention is drawn to the risks associated
-# with loading,  using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean  that it is complicated to manipulate,  and  that  also
-# therefore means  that it is reserved for developers  and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or 
-# data to be ensured and,  more generally, to use and operate it in the 
-# same conditions as regards security. 
-#
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL-B license and that you accept its terms.
-#
-
-from django.contrib import admin
-from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
-from django.utils.translation import gettext_lazy as _
-from p4l.models import User
-from p4l.forms import UserChangeForm, UserCreationform
-
-class UserAdmin(AuthUserAdmin):
-    form = UserChangeForm
-    add_form = UserCreationform
-    fieldsets = tuple(list(AuthUserAdmin.fieldsets) + [(_('language'), {'fields':('language',)})])
-
-
-admin.site.register(User, UserAdmin)
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/admin/__init__.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+
+__all__ = ['site']
+
+from django.contrib.auth.admin import GroupAdmin
+from django.contrib.auth.models import Group
+
+from p4l.admin.components import UserAdmin
+from p4l.admin.sites import site
+from p4l.models import User
+
+
+site.register(User, UserAdmin)
+site.register(Group, GroupAdmin)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/admin/components.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+
+
+'''
+Created on Oct 9, 2013
+
+@author: ymh
+'''
+
+from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
+from django.utils.translation import gettext_lazy as _
+
+from p4l.admin.forms import UserChangeForm, UserCreationform
+
+
+class UserAdmin(AuthUserAdmin):
+    form = UserChangeForm
+    add_form = UserCreationform
+    fieldsets = tuple(list(AuthUserAdmin.fieldsets) + [(_('language'), {'fields':('language',)})])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/admin/forms.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.contrib.auth.forms import (UserChangeForm as AuthUserChangeForm, 
+    UserCreationForm as AuthUserCreationForm)
+from django.core.exceptions import ValidationError
+from django.forms.fields import ChoiceField
+from django.utils.translation import ugettext as _
+
+
+User = get_user_model()
+
+class UserCreationform(AuthUserCreationForm):
+    class Meta:
+        model = User
+        
+    def clean_username(self):
+        # Since User.username is unique, this check is redundant,
+        # but it sets a nicer error message than the ORM. See #13147.
+        username = self.cleaned_data["username"]
+        try:
+            User.objects.get(username=username)
+        except User.DoesNotExist:
+            return username
+        raise ValidationError(self.error_messages['duplicate_username'])    
+    
+
+class UserChangeForm(AuthUserChangeForm):
+    language = ChoiceField(label=_("language"), choices=[(k,_(v)) for k,v in settings.LANGUAGES], initial=settings.LANGUAGE_CODE[:2])
+    class Meta:
+        model = User
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/admin/sites.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+
+
+'''
+Created on Oct 9, 2013
+
+@author: ymh
+'''
+
+from django.conf.urls import patterns, url
+from django.contrib.admin import AdminSite as DjangoAdminSite
+
+from p4l.admin.views import RunScriptView, ConfirmScriptView, KillScriptView
+from p4l.decorators import is_staff
+
+
+class AdminSite(DjangoAdminSite):
+    
+    login_template = "registration/login.html"
+
+    
+    def get_urls(self):
+        urlpatterns = DjangoAdminSite.get_urls(self)
+        
+        urlpatterns += patterns('',
+            url(r'^confirm_script$', is_staff(ConfirmScriptView.as_view()), name='confirm_script'),
+            url(r'^run_script$', is_staff(RunScriptView.as_view()), name='run_script'),
+            url(r'^kill_script$', is_staff(KillScriptView.as_view()), name='kill_script')
+        )
+        
+        return urlpatterns
+
+site = AdminSite()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/admin/views.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+'''
+Created on Oct 9, 2013
+
+@author: ymh
+'''
+
+import itertools
+import os
+import signal
+from subprocess import PIPE, Popen, STDOUT
+import time
+import uuid
+
+from django.conf import settings
+from django.http.response import StreamingHttpResponse, HttpResponse
+from django.utils.translation import ugettext
+from django.views.generic.base import TemplateView, View
+
+
+class ConfirmScriptView(TemplateView):
+    template_name = "p4l/admin/confirm_run_script.html"
+    
+    def get_context_data(self, **kwargs):
+        return {
+            'command_line' : " ".join(getattr(settings,"ADMIN_SCRIPT", {}).get('command',"")),
+            'env' : repr(getattr(settings,"ADMIN_SCRIPT", {}).get('env',{})),
+            'cwd' : repr(getattr(settings,"ADMIN_SCRIPT", {}).get('cwd',"")),
+        }
+
+class RunScriptView(View):
+    
+    def __init__(self, **kwargs):
+        View.__init__(self, **kwargs)
+        self.boundary = "--BOUNDARY--==--%s" % str(uuid.uuid4())
+
+    
+    def get(self, request):
+        resp = StreamingHttpResponse()
+        
+        command_kwargs = {
+            'shell':False,
+            'env':None,
+            'cwd':None
+        }
+        admin_script = getattr(settings,"ADMIN_SCRIPT", {})
+        command = admin_script.get('args',"")
+        
+        if not command:
+            return resp
+
+        command_kwargs.update(admin_script,
+            stdout=PIPE,
+            stderr=STDOUT,
+            bufsize=0,
+            close_fds=True,
+            preexec_fn=os.setsid
+        )
+
+        resp['Connection'] = "Keep-Alive"
+        doc_start = [
+             '<!DOCTYPE html>',
+             '<html lang="en">',
+             '<head>',
+             '<meta charset="utf-8">',
+             '<title>output</title>',
+             '<style>body {font-family: monospace; white-space: pre;}</style>',
+             '</head>',
+             '<body>',
+        ]
+        
+        doc_end = [
+             '<script>parent.done();</script>',
+             '</html>',
+             '</body>'
+        ]
+        
+        scroll_to_bottom = '<script type="text/javascript">window.scrollBy(0,50);</script>'
+
+        process = Popen(**command_kwargs)
+         
+        # Save the pid in the user's session (a thread-safe place)
+        request.session['pid'] = process.pid
+ 
+        def read_output():
+            for line in iter(process.stdout.readline, b''):
+                yield "%s%s" %(line, scroll_to_bottom)
+             
+        resp.streaming_content = itertools.chain(doc_start, read_output(), doc_end)
+        
+        return resp
+
+
+def check_pid(pid):        
+    """ Check For the existence of a unix pid. """
+    try:
+        os.kill(pid, 0)
+    except OSError:
+        return False
+    else:
+        return True
+
+class KillScriptView(View):
+    
+    def get(self, request):
+        
+        resp = HttpResponse()
+        
+        pid = request.session.get('pid', None)
+        
+        if not pid:
+            resp.content = ugettext("No active process to kill")
+        else:
+            os.kill(pid, signal.SIGINT)
+            i = 0
+            while i <= settings.SCRIPT_MAX_WAIT and check_pid(pid):
+                time.sleep(settings.SCRIPT_WAIT)
+                i += 1
+
+            if check_pid(pid):
+                os.killpg(pid, signal.SIGKILL)
+            
+            resp.content = ugettext("Success: The process was killed successfully.")
+            
+        return resp
--- a/src/p4l/config.py.tmpl	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/config.py.tmpl	Fri Oct 11 11:46:18 2013 +0200
@@ -165,3 +165,5 @@
 # url of the sesame repository containing all the rdf referentials
 SPARQL_QUERY_ENDPOINT = "http://localhost:8080/openrdf-sesame/repositories/plan4learning"
 
+# cf http://docs.python.org/2/library/subprocess.html#popen-constructor
+ADMIN_COMMAND_SCRIPT = {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/decorators.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+from django.contrib.auth import REDIRECT_FIELD_NAME
+
+'''
+Created on Oct 10, 2013
+
+@author: ymh
+'''
+from django.contrib.auth.decorators import user_passes_test
+
+
+def is_staff(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
+    """
+    Decorator for views that checks that the user is logged in, redirecting
+    to the log-in page if necessary.
+    """
+    actual_decorator = user_passes_test(
+        lambda u: u.is_staff,
+        login_url=login_url,
+        redirect_field_name=redirect_field_name
+    )
+    if function:
+        return actual_decorator(function)
+    return actual_decorator
--- a/src/p4l/forms.py	Tue Oct 08 17:25:39 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright IRI (c) 2013
-#
-# contact@iri.centrepompidou.fr
-#
-# This software is governed by the CeCILL-B license under French law and
-# abiding by the rules of distribution of free software.  You can  use, 
-# modify and/ or redistribute the software under the terms of the CeCILL-B
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info". 
-#
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty  and the software's author,  the holder of the
-# economic rights,  and the successive licensors  have only  limited
-# liability. 
-#
-# In this respect, the user's attention is drawn to the risks associated
-# with loading,  using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean  that it is complicated to manipulate,  and  that  also
-# therefore means  that it is reserved for developers  and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or 
-# data to be ensured and,  more generally, to use and operate it in the 
-# same conditions as regards security. 
-#
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL-B license and that you accept its terms.
-#
-
-
-from django.conf import settings
-from django.contrib.auth import get_user_model
-from django.contrib.auth.forms import (UserChangeForm as AuthUserChangeForm, 
-    UserCreationForm as AuthUserCreationForm)
-from django.core.exceptions import ValidationError
-from django.forms.fields import ChoiceField
-from django.utils.translation import ugettext as _
-
-
-User = get_user_model()
-
-class UserCreationform(AuthUserCreationForm):
-    class Meta:
-        model = User
-        
-    def clean_username(self):
-        # Since User.username is unique, this check is redundant,
-        # but it sets a nicer error message than the ORM. See #13147.
-        username = self.cleaned_data["username"]
-        try:
-            User.objects.get(username=username)
-        except User.DoesNotExist:
-            return username
-        raise ValidationError(self.error_messages['duplicate_username'])    
-    
-
-class UserChangeForm(AuthUserChangeForm):
-    language = ChoiceField(label=_("language"), choices=[(k,_(v)) for k,v in settings.LANGUAGES], initial=settings.LANGUAGE_CODE[:2])
-    class Meta:
-        model = User
-
Binary file src/p4l/locale/en/LC_MESSAGES/django.mo has changed
--- a/src/p4l/locale/en/LC_MESSAGES/django.po	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/locale/en/LC_MESSAGES/django.po	Fri Oct 11 11:46:18 2013 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-09-24 06:09-0500\n"
+"POT-Creation-Date: 2013-10-11 10:22+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"
@@ -16,27 +16,35 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: admin.py:10 forms.py:34 templates/p4l/record_view.html:56
-msgid "language"
-msgstr "language"
-
-#: settings.py:42
+#: settings.py:76
 msgid "French"
 msgstr "French"
 
-#: settings.py:43
+#: settings.py:77
 msgid "English"
 msgstr "English"
 
-#: settings.py:44
+#: settings.py:78
 msgid "Spanish"
 msgstr "Spanish"
 
-#: semantictree/forms/widgets.py:39
+#: admin/components.py:50 admin/forms.py:62 templates/p4l/record_view.html:56
+msgid "language"
+msgstr "language"
+
+#: admin/views.py:143
+msgid "No active process to kill"
+msgstr "No active process to kill"
+
+#: admin/views.py:154
+msgid "Success: The process was killed successfully."
+msgstr "Success: The process was killed successfully."
+
+#: semantictree/forms/widgets.py:68
 msgid "Search"
 msgstr "Search"
 
-#: semantictree/forms/widgets.py:43 semantictree/forms/widgets.py:80
+#: semantictree/forms/widgets.py:72 semantictree/forms/widgets.py:109
 msgid ""
 "\"data-url\", \"data-query\", \"data-root-query\", \"data-childs-query\" and "
 "\"data-child-count-query\" must be set in CharField configuration"
@@ -44,69 +52,91 @@
 "\"data-url\", \"data-query\", \"data-root-query\", \"data-childs-query\" and "
 "\"data-child-count-query\" must be set in CharField configuration"
 
-#: semantictree/forms/widgets.py:44 semantictree/forms/widgets.py:81
+#: semantictree/forms/widgets.py:73 semantictree/forms/widgets.py:110
 msgid "Browse"
 msgstr "Browse"
 
-#: semantictree/forms/widgets.py:49 semantictree/forms/widgets.py:86
+#: semantictree/forms/widgets.py:78 semantictree/forms/widgets.py:115
 msgid "or"
 msgstr "or"
 
-#: templates/p4l/base.html:35
+#: templates/admin/app_index.html:7
+#: templates/p4l/admin/confirm_run_script.html:13
+msgid "Home"
+msgstr "Home"
+
+#: templates/admin/app_index.html:10
+#, python-format
+msgid "%(name)s"
+msgstr "%(name)s"
+
+#: templates/admin/index.html:10
+msgid "Commands"
+msgstr "Commands"
+
+#: templates/admin/index.html:12
+msgid "Launch Script"
+msgstr "Launch Script"
+
+#: templates/admin/index.html:13
+msgid "Launch"
+msgstr "Launch"
+
+#: templates/p4l/base.html:36
 msgid "logout"
 msgstr "logout"
 
-#: templates/p4l/base.html:37 templates/registration/login.html:4
+#: templates/p4l/base.html:38 templates/registration/login.html:4
 #: templates/registration/login.html:27
 msgid "login"
 msgstr "login"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:10
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:15
 msgid "Record List"
 msgstr "Record list"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:30
-#: templates/p4l/home.html:70
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:35
+#: templates/p4l/home.html:75
 msgid "Page"
 msgstr "Page"
 
-#: templates/p4l/home.html:14
+#: templates/p4l/home.html:19
 msgid "Query"
 msgstr "Query"
 
-#: templates/p4l/home.html:19
+#: templates/p4l/home.html:24
 msgid "New record"
 msgstr "New record"
 
-#: templates/p4l/home.html:27 templates/p4l/home.html.py:67
+#: templates/p4l/home.html:32 templates/p4l/home.html.py:72
 msgid "Previous"
 msgstr "Previous"
 
-#: templates/p4l/home.html:30 templates/p4l/home.html.py:70
+#: templates/p4l/home.html:35 templates/p4l/home.html.py:75
 msgid "on"
 msgstr "on"
 
-#: templates/p4l/home.html:33 templates/p4l/home.html.py:73
+#: templates/p4l/home.html:38 templates/p4l/home.html.py:78
 msgid "Next"
 msgstr "Next"
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:36
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:36
 msgid "identifier"
 msgstr "identifier"
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:64
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:64
 msgid "titles"
 msgstr "titles"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "dates"
 msgstr "dates"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "actions"
 msgstr "actions"
 
-#: templates/p4l/home.html:59
+#: templates/p4l/home.html:64
 msgid "No record"
 msgstr "No record"
 
@@ -118,30 +148,30 @@
 msgid "View"
 msgstr "View"
 
-#: templates/p4l/record_update_form.html:52 templates/p4l/translations.html:4
+#: templates/p4l/record_update_form.html:55 templates/p4l/translations.html:4
 msgid "Save"
 msgstr "Save"
 
-#: templates/p4l/record_update_form.html:56
+#: templates/p4l/record_update_form.html:59
 msgid "Cancel"
 msgstr "Cancel"
 
-#: templates/p4l/record_update_form.html:60
-#: templates/p4l/record_update_form.html:248
+#: templates/p4l/record_update_form.html:63
+#: templates/p4l/record_update_form.html:251
 #: templates/p4l/translations.html:20
 msgid "View the record"
 msgstr "View the record"
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid ""
 "Are your sure you want to delete this record ? This action is irreversible."
 msgstr ""
 "Are your sure you want to delete this record ? This action is irreversible."
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid "Delete the record"
 msgstr "Delete the record"
@@ -393,6 +423,46 @@
 msgid "An error occured. Somes datas may be incorrect or incomplete."
 msgstr "An error occured. Somes fields may be incorrect or incomplete."
 
+#: templates/p4l/admin/confirm_run_script.html:14
+msgid "Run script"
+msgstr "Run script"
+
+#: templates/p4l/admin/confirm_run_script.html:20
+msgid "Are you sure ?"
+msgstr "Are you sure ?"
+
+#: templates/p4l/admin/confirm_run_script.html:22
+msgid ""
+"The ADMIN_SCRIPT setting is not correctly configured. Please configure it to "
+"launch commands."
+msgstr ""
+"The ADMIN_SCRIPT setting is not correctly configured. Please configure it to "
+"launch commands."
+
+#: templates/p4l/admin/confirm_run_script.html:24
+msgid "Are you sure you want to run the script"
+msgstr "Are you sure you want to run the script"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "cwd:"
+msgstr "cwd:"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "env:"
+msgstr "env:"
+
+#: templates/p4l/admin/confirm_run_script.html:28
+msgid "Yes, I'm sure"
+msgstr "Yes, I'm sure"
+
+#: templates/p4l/admin/confirm_run_script.html:30
+msgid "Click to stop process (sends SIGINT then SIGKILL)"
+msgstr "Click to stop process (sends SIGINT then SIGKILL)"
+
+#: templates/p4l/admin/confirm_run_script.html:31
+msgid "Done"
+msgstr "Done"
+
 #: templates/registration/login.html:8
 msgid "Sorry, that's not a valid username or password."
 msgstr "Sorry, that's not a valid username or password."
Binary file src/p4l/locale/es/LC_MESSAGES/django.mo has changed
--- a/src/p4l/locale/es/LC_MESSAGES/django.po	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/locale/es/LC_MESSAGES/django.po	Fri Oct 11 11:46:18 2013 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-09-24 06:09-0500\n"
+"POT-Creation-Date: 2013-10-11 10:22+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"
@@ -17,27 +17,35 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: admin.py:10 forms.py:34 templates/p4l/record_view.html:56
-msgid "language"
-msgstr "language"
-
-#: settings.py:42
+#: settings.py:76
 msgid "French"
 msgstr "French"
 
-#: settings.py:43
+#: settings.py:77
 msgid "English"
 msgstr "English"
 
-#: settings.py:44
+#: settings.py:78
 msgid "Spanish"
 msgstr "Spanish"
 
-#: semantictree/forms/widgets.py:39
+#: admin/components.py:50 admin/forms.py:62 templates/p4l/record_view.html:56
+msgid "language"
+msgstr "language"
+
+#: admin/views.py:143
+msgid "No active process to kill"
+msgstr "No active process to kill"
+
+#: admin/views.py:154
+msgid "Success: The process was killed successfully."
+msgstr "Success: The process was killed successfully."
+
+#: semantictree/forms/widgets.py:68
 msgid "Search"
 msgstr "Search"
 
-#: semantictree/forms/widgets.py:43 semantictree/forms/widgets.py:80
+#: semantictree/forms/widgets.py:72 semantictree/forms/widgets.py:109
 msgid ""
 "\"data-url\", \"data-query\", \"data-root-query\", \"data-childs-query\" and "
 "\"data-child-count-query\" must be set in CharField configuration"
@@ -45,69 +53,91 @@
 "\"data-url\", \"data-query\", \"data-root-query\", \"data-childs-query\" and "
 "\"data-child-count-query\" must be set in CharField configuration"
 
-#: semantictree/forms/widgets.py:44 semantictree/forms/widgets.py:81
+#: semantictree/forms/widgets.py:73 semantictree/forms/widgets.py:110
 msgid "Browse"
 msgstr "Browse"
 
-#: semantictree/forms/widgets.py:49 semantictree/forms/widgets.py:86
+#: semantictree/forms/widgets.py:78 semantictree/forms/widgets.py:115
 msgid "or"
 msgstr "or"
 
-#: templates/p4l/base.html:35
+#: templates/admin/app_index.html:7
+#: templates/p4l/admin/confirm_run_script.html:13
+msgid "Home"
+msgstr "Home"
+
+#: templates/admin/app_index.html:10
+#, python-format
+msgid "%(name)s"
+msgstr "%(name)s"
+
+#: templates/admin/index.html:10
+msgid "Commands"
+msgstr "Commands"
+
+#: templates/admin/index.html:12
+msgid "Launch Script"
+msgstr "Launch Script"
+
+#: templates/admin/index.html:13
+msgid "Launch"
+msgstr "Launch"
+
+#: templates/p4l/base.html:36
 msgid "logout"
 msgstr "logout"
 
-#: templates/p4l/base.html:37 templates/registration/login.html:4
+#: templates/p4l/base.html:38 templates/registration/login.html:4
 #: templates/registration/login.html:27
 msgid "login"
 msgstr "login"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:10
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:15
 msgid "Record List"
 msgstr "Record list"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:30
-#: templates/p4l/home.html:70
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:35
+#: templates/p4l/home.html:75
 msgid "Page"
 msgstr "Page"
 
-#: templates/p4l/home.html:14
+#: templates/p4l/home.html:19
 msgid "Query"
 msgstr "Query"
 
-#: templates/p4l/home.html:19
+#: templates/p4l/home.html:24
 msgid "New record"
 msgstr "New record"
 
-#: templates/p4l/home.html:27 templates/p4l/home.html.py:67
+#: templates/p4l/home.html:32 templates/p4l/home.html.py:72
 msgid "Previous"
 msgstr "Previous"
 
-#: templates/p4l/home.html:30 templates/p4l/home.html.py:70
+#: templates/p4l/home.html:35 templates/p4l/home.html.py:75
 msgid "on"
 msgstr "on"
 
-#: templates/p4l/home.html:33 templates/p4l/home.html.py:73
+#: templates/p4l/home.html:38 templates/p4l/home.html.py:78
 msgid "Next"
 msgstr "Next"
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:36
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:36
 msgid "identifier"
 msgstr "identifier"
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:64
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:64
 msgid "titles"
 msgstr "titles"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "dates"
 msgstr "dates"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "actions"
 msgstr "actions"
 
-#: templates/p4l/home.html:59
+#: templates/p4l/home.html:64
 msgid "No record"
 msgstr "No record"
 
@@ -119,30 +149,30 @@
 msgid "View"
 msgstr "View"
 
-#: templates/p4l/record_update_form.html:52 templates/p4l/translations.html:4
+#: templates/p4l/record_update_form.html:55 templates/p4l/translations.html:4
 msgid "Save"
 msgstr "Save"
 
-#: templates/p4l/record_update_form.html:56
+#: templates/p4l/record_update_form.html:59
 msgid "Cancel"
 msgstr "Cancel"
 
-#: templates/p4l/record_update_form.html:60
-#: templates/p4l/record_update_form.html:248
+#: templates/p4l/record_update_form.html:63
+#: templates/p4l/record_update_form.html:251
 #: templates/p4l/translations.html:20
 msgid "View the record"
 msgstr "View the record"
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid ""
 "Are your sure you want to delete this record ? This action is irreversible."
 msgstr ""
 "Are your sure you want to delete this record ? This action is irreversible."
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid "Delete the record"
 msgstr "Delete the record"
@@ -394,6 +424,46 @@
 msgid "An error occured. Somes datas may be incorrect or incomplete."
 msgstr "An error occured. Somes fields may be incorrect or incomplete."
 
+#: templates/p4l/admin/confirm_run_script.html:14
+msgid "Run script"
+msgstr "Run script"
+
+#: templates/p4l/admin/confirm_run_script.html:20
+msgid "Are you sure ?"
+msgstr "Are you sure ?"
+
+#: templates/p4l/admin/confirm_run_script.html:22
+msgid ""
+"The ADMIN_SCRIPT setting is not correctly configured. Please configure it to "
+"launch commands."
+msgstr ""
+"The ADMIN_SCRIPT setting is not correctly configured. Please configure it to "
+"launch commands."
+
+#: templates/p4l/admin/confirm_run_script.html:24
+msgid "Are you sure you want to run the script"
+msgstr "Are you sure you want to run the script"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "cwd:"
+msgstr "cwd:"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "env:"
+msgstr "env:"
+
+#: templates/p4l/admin/confirm_run_script.html:28
+msgid "Yes, I'm sure"
+msgstr "Yes, I'm sure"
+
+#: templates/p4l/admin/confirm_run_script.html:30
+msgid "Click to stop process (sends SIGINT then SIGKILL)"
+msgstr "Click to stop process (sends SIGINT then SIGKILL)"
+
+#: templates/p4l/admin/confirm_run_script.html:31
+msgid "Done"
+msgstr "Done"
+
 #: templates/registration/login.html:8
 msgid "Sorry, that's not a valid username or password."
 msgstr "Sorry, that's not a valid username or password."
Binary file src/p4l/locale/fr/LC_MESSAGES/django.mo has changed
--- a/src/p4l/locale/fr/LC_MESSAGES/django.po	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/locale/fr/LC_MESSAGES/django.po	Fri Oct 11 11:46:18 2013 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-09-24 06:09-0500\n"
+"POT-Creation-Date: 2013-10-11 10:22+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"
@@ -17,27 +17,35 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
-#: admin.py:10 forms.py:34 templates/p4l/record_view.html:56
-msgid "language"
-msgstr "langue"
-
-#: settings.py:42
+#: settings.py:76
 msgid "French"
 msgstr "Français"
 
-#: settings.py:43
+#: settings.py:77
 msgid "English"
 msgstr "Anglais"
 
-#: settings.py:44
+#: settings.py:78
 msgid "Spanish"
 msgstr "Espagnol"
 
-#: semantictree/forms/widgets.py:39
+#: admin/components.py:50 admin/forms.py:62 templates/p4l/record_view.html:56
+msgid "language"
+msgstr "langue"
+
+#: admin/views.py:143
+msgid "No active process to kill"
+msgstr "Pas de processus actif à tuer"
+
+#: admin/views.py:154
+msgid "Success: The process was killed successfully."
+msgstr "Réussi : le process a été tué avec succès."
+
+#: semantictree/forms/widgets.py:68
 msgid "Search"
 msgstr "Chercher"
 
-#: semantictree/forms/widgets.py:43 semantictree/forms/widgets.py:80
+#: semantictree/forms/widgets.py:72 semantictree/forms/widgets.py:109
 msgid ""
 "\"data-url\", \"data-query\", \"data-root-query\", \"data-childs-query\" and "
 "\"data-child-count-query\" must be set in CharField configuration"
@@ -46,69 +54,91 @@
 "\"data-child-count-query\" doivent être renseignés dans la configuration "
 "CharField"
 
-#: semantictree/forms/widgets.py:44 semantictree/forms/widgets.py:81
+#: semantictree/forms/widgets.py:73 semantictree/forms/widgets.py:110
 msgid "Browse"
 msgstr "Parcourir"
 
-#: semantictree/forms/widgets.py:49 semantictree/forms/widgets.py:86
+#: semantictree/forms/widgets.py:78 semantictree/forms/widgets.py:115
 msgid "or"
 msgstr "ou"
 
-#: templates/p4l/base.html:35
+#: templates/admin/app_index.html:7
+#: templates/p4l/admin/confirm_run_script.html:13
+msgid "Home"
+msgstr "Accueil"
+
+#: templates/admin/app_index.html:10
+#, python-format
+msgid "%(name)s"
+msgstr "%(name)s"
+
+#: templates/admin/index.html:10
+msgid "Commands"
+msgstr "Commandes"
+
+#: templates/admin/index.html:12
+msgid "Launch Script"
+msgstr "Lancer Script"
+
+#: templates/admin/index.html:13
+msgid "Launch"
+msgstr "Lancer"
+
+#: templates/p4l/base.html:36
 msgid "logout"
 msgstr "deconnection"
 
-#: templates/p4l/base.html:37 templates/registration/login.html:4
+#: templates/p4l/base.html:38 templates/registration/login.html:4
 #: templates/registration/login.html:27
 msgid "login"
 msgstr "connection"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:10
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:15
 msgid "Record List"
 msgstr "Liste notice"
 
-#: templates/p4l/home.html:6 templates/p4l/home.html.py:30
-#: templates/p4l/home.html:70
+#: templates/p4l/home.html:6 templates/p4l/home.html.py:35
+#: templates/p4l/home.html:75
 msgid "Page"
 msgstr "Page"
 
-#: templates/p4l/home.html:14
+#: templates/p4l/home.html:19
 msgid "Query"
 msgstr "Requête"
 
-#: templates/p4l/home.html:19
+#: templates/p4l/home.html:24
 msgid "New record"
 msgstr "Nouvelle notice"
 
-#: templates/p4l/home.html:27 templates/p4l/home.html.py:67
+#: templates/p4l/home.html:32 templates/p4l/home.html.py:72
 msgid "Previous"
 msgstr "Préc."
 
-#: templates/p4l/home.html:30 templates/p4l/home.html.py:70
+#: templates/p4l/home.html:35 templates/p4l/home.html.py:75
 msgid "on"
 msgstr "sur"
 
-#: templates/p4l/home.html:33 templates/p4l/home.html.py:73
+#: templates/p4l/home.html:38 templates/p4l/home.html.py:78
 msgid "Next"
 msgstr "Suiv."
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:36
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:36
 msgid "identifier"
 msgstr "identifiant"
 
-#: templates/p4l/home.html:43 templates/p4l/record_view.html:64
+#: templates/p4l/home.html:48 templates/p4l/record_view.html:64
 msgid "titles"
 msgstr "titres"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "dates"
 msgstr "dates"
 
-#: templates/p4l/home.html:43
+#: templates/p4l/home.html:48
 msgid "actions"
 msgstr "actions"
 
-#: templates/p4l/home.html:59
+#: templates/p4l/home.html:64
 msgid "No record"
 msgstr "Pas de notice"
 
@@ -120,22 +150,22 @@
 msgid "View"
 msgstr "Vue"
 
-#: templates/p4l/record_update_form.html:52 templates/p4l/translations.html:4
+#: templates/p4l/record_update_form.html:55 templates/p4l/translations.html:4
 msgid "Save"
 msgstr "Enregistrer"
 
-#: templates/p4l/record_update_form.html:56
+#: templates/p4l/record_update_form.html:59
 msgid "Cancel"
 msgstr "Annuler"
 
-#: templates/p4l/record_update_form.html:60
-#: templates/p4l/record_update_form.html:248
+#: templates/p4l/record_update_form.html:63
+#: templates/p4l/record_update_form.html:251
 #: templates/p4l/translations.html:20
 msgid "View the record"
 msgstr "Voir la notice"
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid ""
 "Are your sure you want to delete this record ? This action is irreversible."
@@ -143,8 +173,8 @@
 "Êtes-vous sûr sur vouloir effacer cette notice ? Cette action est "
 "irréversible."
 
-#: templates/p4l/record_update_form.html:61
-#: templates/p4l/record_update_form.html:249 templates/p4l/record_view.html:12
+#: templates/p4l/record_update_form.html:64
+#: templates/p4l/record_update_form.html:252 templates/p4l/record_view.html:12
 #: templates/p4l/record_view.html.py:238
 msgid "Delete the record"
 msgstr "Effacer la notice"
@@ -397,6 +427,46 @@
 msgstr ""
 "Une erreur est survenue. Certaines données sont incorrectes ou incomplètes."
 
+#: templates/p4l/admin/confirm_run_script.html:14
+msgid "Run script"
+msgstr "Lancer script"
+
+#: templates/p4l/admin/confirm_run_script.html:20
+msgid "Are you sure ?"
+msgstr "Êtes-vous sûr ?"
+
+#: templates/p4l/admin/confirm_run_script.html:22
+msgid ""
+"The ADMIN_SCRIPT setting is not correctly configured. Please configure it to "
+"launch commands."
+msgstr ""
+"La propriété ADMIN_SCRIPT n'est pas correctement configuré. Configurez la avant "
+"de lancer la commande."
+
+#: templates/p4l/admin/confirm_run_script.html:24
+msgid "Are you sure you want to run the script"
+msgstr "Êtes-vous sûr que vous voulez bien lancer le script"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "cwd:"
+msgstr "cwd :"
+
+#: templates/p4l/admin/confirm_run_script.html:25
+msgid "env:"
+msgstr "env :"
+
+#: templates/p4l/admin/confirm_run_script.html:28
+msgid "Yes, I'm sure"
+msgstr "Oui, je suis sûr(e)"
+
+#: templates/p4l/admin/confirm_run_script.html:30
+msgid "Click to stop process (sends SIGINT then SIGKILL)"
+msgstr "Cliquer pour arrêter le processus (envoie SIGINT puis SIGKILL)"
+
+#: templates/p4l/admin/confirm_run_script.html:31
+msgid "Done"
+msgstr "Terminé"
+
 #: templates/registration/login.html:8
 msgid "Sorry, that's not a valid username or password."
 msgstr "Désolé, utilisateur ou mot de passe invalide"
--- a/src/p4l/management/commands/dump_record.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/management/commands/dump_record.py	Fri Oct 11 11:46:18 2013 +0200
@@ -90,6 +90,12 @@
             default=False,
             help= 'gzip compress' 
         ),
+        make_option('--newline',
+            dest= 'newline',
+            action='store_true',
+            default=False,
+            help= 'show progress with newlines' 
+        ),
     )
 
 
@@ -120,6 +126,7 @@
         limit = options.get("limit", -1)
         skip = options.get("skip", 0)
         batch = options.get("batch", 100)
+        newline = options.get("newline", False)
         
         qs = Record.objects.all().select_related(*[field.name for field in Record._meta.fields if isinstance(field, ForeignKey)]).prefetch_related(*([field.name for field in Record._meta.many_to_many] + [obj.get_accessor_name() for obj in Record._meta.get_all_related_objects()])).order_by('identifier')  # @UndefinedVariable
         
@@ -157,7 +164,7 @@
             writer.characters("\n")
             for n in range((total_records/batch)+1):
                 for i,r in enumerate(qs[n*batch:((n+1)*batch)]):
-                    progress_writer = show_progress(i+(n*batch)+1, total_records, "Exporting record %s" % r.identifier, 50, progress_writer) 
+                    progress_writer = show_progress(i+(n*batch)+1, total_records, "Exporting record %s" % r.identifier, 40, writer=progress_writer, newline=newline) 
                     graph = self.get_graph_from_object(r)
                     do_write = False
                     for line in graph.serialize(format="pretty-xml", encoding="utf-8").splitlines(True):
--- a/src/p4l/management/commands/import_record.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/management/commands/import_record.py	Fri Oct 11 11:46:18 2013 +0200
@@ -81,6 +81,12 @@
             default=False,
             help= 'index while importing' 
         ),
+        make_option('--newline',
+            dest= 'newline',
+            action='store_true',
+            default=False,
+            help= 'show progress with newlines' 
+        ),
     )
 
     def __init__(self, *args, **kwargs):
@@ -115,7 +121,7 @@
         for _,elem in context:
             if elem.tag == "{%s}Record" % IIEP:
                 i += 1
-                writer = show_progress(i, total_records, "Processing record nb %d " % i, 50, writer=writer)
+                writer = show_progress(i, total_records, "Processing record nb %d " % i, 40, writer=writer, newline=self.newline)
                 try:
                     record_graph = get_empty_graph()
                     record_graph.parse(data=ET.tostring(elem, encoding='utf-8'), format='xml')                    
@@ -128,7 +134,7 @@
                 else:
                     transaction.commit()
 
-                if i%self.batch_size == 0:                    
+                if i%self.batch_size == 0:
                     reset_queries()
 
         return errors
@@ -155,6 +161,7 @@
         self.batch_size = options.get('batch_size', 50)
         self.preserve = options.get("preserve", False)
         self.index = options.get("index", False)
+        self.newline = options.get("newline", False)
         
         if not self.index:
             old_realtime_indexing = getattr(settings, "REALTIME_INDEXING", None)
--- a/src/p4l/models/__init__.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/models/__init__.py	Fri Oct 11 11:46:18 2013 +0200
@@ -43,4 +43,3 @@
     Periodical, Meeting, SubjectMeeting, Audience, Record)
 from p4l.models.user import User
 
-
--- a/src/p4l/settings.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/settings.py	Fri Oct 11 11:46:18 2013 +0200
@@ -33,6 +33,7 @@
 
 # Django settings for p4l project.
 from django.conf import global_settings
+import os
 
 DEBUG = True
 TEMPLATE_DEBUG = DEBUG
@@ -129,6 +130,7 @@
 TEMPLATE_LOADERS = (
     'django.template.loaders.filesystem.Loader',
     'django.template.loaders.app_directories.Loader',
+    'p4l.templateloaders.Loader'
 #     'django.template.loaders.eggs.Loader',
 )
 
@@ -156,6 +158,7 @@
     # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
     # Always use forward slashes, even on Windows.
     # Don't forget to use absolute paths, not relative paths.
+    os.path.join(os.path.dirname(__file__), 'templates'),
 )
 
 INSTALLED_APPS = (
@@ -455,6 +458,12 @@
 (u"Zhuang; Chuang", "za"),
 (u"Zulu", "zu")]
 
+# cf http://docs.python.org/2/library/subprocess.html#popen-constructor 
+ADMIN_SCRIPT = {}
+
+SCRIPT_WAIT = .250
+SCRIPT_MAX_WAIT = 40 # * SCRIPT_WAIT = 10 sec
+
 from config import *  # @UnusedWildImport
 
 if not "SRC_BASE_URL" in locals():
Binary file src/p4l/static/p4l/css/img/ajax-loader.gif has changed
Binary file src/p4l/static/p4l/css/img/flags.png has changed
--- a/src/p4l/static/p4l/css/p4l.css	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/static/p4l/css/p4l.css	Fri Oct 11 11:46:18 2013 +0200
@@ -1,11 +1,9 @@
 /*!
  * modif from bootstrap for p4l
  */
-
-html,
-body {
-	height: 100%;
-	/* The html and body elements cannot have any padding or margin. */
+html,body {
+  height: 100%;
+  /* The html and body elements cannot have any padding or margin. */
 }
 
 .container {
@@ -14,142 +12,159 @@
 }
 
 .record-table thead td:first-child {
-    width: 190px;
+  width: 190px;
 }
+
 .record-table tbody td:first-child:first-letter {
-    text-transform: uppercase;
+  text-transform: uppercase;
 }
+
 .table thead td:first-letter {
-    text-transform: uppercase;
+  text-transform: uppercase;
 }
+
 .table thead {
-    font-weight: bold;
+  font-weight: bold;
 }
-.two_buttons{
-	min-width: 100px;
+
+.two_buttons {
+  min-width: 100px;
 }
 
 footer {
-    padding-bottom: 20px;
-    border-top: 1px solid gray;
-}
-.bottom-5 {
-    margin-bottom: 5px;
+  padding-bottom: 20px;
+  border-top: 1px solid gray;
 }
-.after-plus {
-    margin-top: 5px;
-}
-.after-plus button {
-    margin-right: 5px;
+
+.bottom-5 {
+  margin-bottom: 5px;
 }
 
-.rotate{
-	transform:rotate(90deg);
-	-ms-transform:rotate(90deg);
-	-webkit-transform:rotate(90deg);
+.after-plus {
+  margin-top: 5px;
+}
+
+.after-plus button {
+  margin-right: 5px;
+}
+
+.rotate {
+  transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  -webkit-transform: rotate(90deg);
 }
 
 .spinner {
-    width: 14px;
-    height: 14px;
-    float: left;
-	background:url(./img/ajax-loader.gif) no-repeat center center;
-	border-radius: 50%;
-	opacity: .7;
-	margin-right: 4px;
+  width: 14px;
+  height: 14px;
+  float: left;
+  background: url("../img/ajax-loader.gif") no-repeat center center;
+  border-radius: 50%;
+  opacity: .7;
+  margin-right: 4px;
 }
 
 .grey-bottom {
-    border-bottom: 1px dotted #CCC;
+  border-bottom: 1px dotted #CCC;
 }
 
 .well {
-    margin-bottom: 5px;
-    padding: 4px 5px 8px 8px;
+  margin-bottom: 5px;
+  padding: 4px 5px 8px 8px;
 }
 
 label {
-    margin-bottom: 0px;
+  margin-bottom: 0px;
 }
 
 .edit-form-row:nth-child(odd) {
-     background: #EEE;
+  background: #EEE;
 }
 
 .break-word {
-	word-wrap: break-word;
+  word-wrap: break-word;
 }
 
 /* Flags */
 .flag {
-    width: 16px;
-    height: 11px;
-    padding: 0;
-    border: none;
-    background:url(./img/flags.png) no-repeat;
-    /* hack to hide the button text */
-    text-indent: -999em; /* Hide the text, works in most modern browsers */
-    font-size: 0px; /* works well in IE7. still a black line (basically the text) in IE6. */
-    /*display: block;*/ /* Negative text-indent works in IE(6? worked fine in 7) only if this is added. */
-    line-height: 0px; /* Another fix for IE6. */
+  width: 16px;
+  height: 11px;
+  padding: 0;
+  border: none;
+  background: url("../img/flags.png") no-repeat;
+  /* hack to hide the button text */
+  text-indent: -999em;
+  /* Hide the text, works in most modern browsers */
+  font-size: 0px;
+  /* works well in IE7. still a black line (basically the text) in IE6. */
+  /*display: block;*/
+  /* Negative text-indent works in IE(6? worked fine in 7) only if this is added. */
+  line-height: 0px; /* Another fix for IE6. */
 }
-.flag.flag-es {background-position: -16px 0}
-.flag.flag-fr {background-position: 0 -11px}
-.flag.flag-en {background-position: -16px -11px}
+
+.flag.flag-es {
+  background-position: -16px 0
+}
+
+.flag.flag-fr {
+  background-position: 0 -11px
+}
+
+.flag.flag-en {
+  background-position: -16px -11px
+}
 
 /* css class to avoid angular flicker */
-[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak
+  {
   display: none !important;
 }
 
-
 .version {
-    text-align: right;
-    color: white;
-    font-size:9px;
+  text-align: right;
+  color: white;
+  font-size: 9px;
 }
 
 .version:hover {
-    color: inherit;
-	text-decoration: none;
-    border-bottom-width: 0px;
-    border-bottom-style: none;
+  color: inherit;
+  text-decoration: none;
+  border-bottom-width: 0px;
+  border-bottom-style: none;
 }
 
 #wrapper {
-	min-height: 100%;
-	height: auto !important;
-	height: 100%;
-	/* Negative indent footer by it's height */
-	margin: 0 auto -70px;
+  min-height: 100%;
+  height: auto !important;
+  height: 100%;
+  /* Negative indent footer by it's height */
+  margin: 0 auto -70px;
 }
 
 /* Set the fixed height of the footer here */
-#push,
-footer {
-    height: 70px;
+#push,footer {
+  height: 70px;
 }
 
 .footer-img {
-	padding: 10px 0;
+  padding: 10px 0;
 }
 
 #search-input {
-	padding-right: 24px;
+  padding-right: 24px;
 }
 
 #search-input-cancel {
-    text-indent: -1000em;
-    top: 3px;
-    right: 0.5em;
+  text-indent: -1000em;
+  top: 3px;
+  right: 0.5em;
 }
 
 #langselect {
-	padding-bottom: 10px;
+  padding-bottom: 10px;
 }
 
-
 #copyright-version a {
-	text-decoration: none;
-	color: inherit;
+  text-decoration: none;
+  color: inherit;
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/static/p4l/css/p4l_admin.css	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,41 @@
+.literal {
+    background-color: #eeeeec;
+    padding: 1px;
+    font-family: monospace;
+}
+
+.terminal {
+    position: relative;
+    top: 0;
+    left: 0;
+    display: block;
+    font-family: monospace;
+    white-space: pre;
+    width: 60%;
+    height: 30em;
+    border: 1px solid black;
+    margin-top: 1em;
+    overflow: auto;
+    display: none;
+}
+
+
+
+#done {
+  display: none;
+  margin-top: 2px;
+}
+
+#done h1 {
+    color: red;
+}
+
+.script-control {
+  float: left;
+  margin-left: 10px;
+}
+
+.clear {
+  clear: both;
+}
+
Binary file src/p4l/static/p4l/img/ajax-loader.gif has changed
Binary file src/p4l/static/p4l/img/flags.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/templateloaders.py	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright IRI (c) 2013
+#
+# contact@iri.centrepompidou.fr
+#
+# This software is governed by the CeCILL-B license under French law and
+# abiding by the rules of distribution of free software.  You can  use, 
+# modify and/ or redistribute the software under the terms of the CeCILL-B
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info". 
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability. 
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or 
+# data to be ensured and,  more generally, to use and operate it in the 
+# same conditions as regards security. 
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL-B license and that you accept its terms.
+#
+
+'''
+Created on Oct 9, 2013
+
+From http://djangosnippets.org/snippets/1376/
+
+@author: ymh
+'''
+from os.path import dirname, join, abspath, isdir
+
+from django.core.exceptions import ImproperlyConfigured
+from django.db.models import get_app
+from django.template import TemplateDoesNotExist
+from django.template.loaders.filesystem import Loader as FilesystemLoader
+
+
+def get_template_vars(template_name):
+    app_name, template_name = template_name.split(":", 1)
+    try:
+        template_dir = abspath(join(dirname(get_app(app_name).__file__), 'templates'))
+    except ImproperlyConfigured:
+        raise TemplateDoesNotExist()
+    
+    return template_name, template_dir
+
+class Loader(FilesystemLoader):
+    
+    is_usable = True
+    
+    def get_template_sources(self, template_name, template_dirs=None):
+        if ":" not in template_name:
+            raise TemplateDoesNotExist()
+        template_name, template_dir = get_template_vars(template_name)
+        
+        if not isdir(template_dir):
+            raise TemplateDoesNotExist()
+
+        return [join(template_dir, template_name)]
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/templates/admin/app_index.html	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,16 @@
+{% extends "admin:admin/index.html" %}
+{% load i18n %}
+
+{% if not is_popup %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo;
+{% for app in app_list %}
+{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}
+{% endfor %}
+</div>
+{% endblock %}
+{% endif %}
+
+{% block sidebar %}{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/templates/admin/index.html	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,19 @@
+{% extends "admin:admin/index.html" %}
+{% load i18n %}
+
+{% block content %}
+<div style="float: left;">
+{{ block.super }}
+<div id="content-other">
+    <div class="module">
+        <table>
+            <caption>{% trans 'Commands' %}</caption>
+            <tr>
+                <th scope="row">{% trans 'Launch Script' %}</th>
+                <td><a href="{% url 'admin:confirm_script' %}" class="addlink">{% trans 'Launch' %}</a></td>
+            </tr>
+        </table>
+    </div>
+</div>
+</div>
+{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/p4l/templates/p4l/admin/confirm_run_script.html	Fri Oct 11 11:46:18 2013 +0200
@@ -0,0 +1,59 @@
+{% extends "admin/base_site.html" %}
+{% load i18n static %}
+
+{% block extrahead %}
+{{ block.super }}
+<script type="text/javascript" src="{% static 'p4l/lib/jquery-1.9.1.js' %}"></script>
+<link rel="stylesheet" href="{% static 'p4l/css/p4l_admin.css' %}">
+{% endblock %}
+
+{% if not is_popup %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Run script'  %}
+</div>
+{% endblock %}
+{% endif %}
+
+{% block content %}
+<h1>{% trans 'Are you sure ?' %}</h1>
+{% if not command_line %}
+<p>{% blocktrans %}The ADMIN_SCRIPT setting is not correctly configured. Please configure it to launch commands.{% endblocktrans %}
+{% else %}
+<p>{% blocktrans %}Are you sure you want to run the script{% endblocktrans %} <span class="literal">{{ command_line }}</span> ?</p>
+<p>( {% trans 'cwd:' %} <span class="literal">{{ pwd }}</span>, {% trans 'env:' %} <span class="literal">{{ env }}</span> )</p>
+<div>
+    <form method="get" id="confirm-form" action="{% url 'admin:run_script' %}" target="script-output">
+        <div class="script-control" id="script-submit"><input type="submit" id="confirm-form-submit" value="{% trans "Yes, I'm sure" %}" /></div>
+    </form>
+    <div class="script-control" id="kill-button-div"><button id="kill-button" class="button" title="{% trans 'Click to stop process (sends SIGINT then SIGKILL)' %}">Control-C</button></div>
+    <div class="script-control" id="done"><h1>{% trans 'Done' %}</h1></div>
+</div>
+<div class="clear" id="kill-result">&nbsp;</div>
+<iframe name="script-output" class="terminal" scrolling="auto"></iframe>
+<script type="text/javascript">
+function launch_script() {
+    $("#confirm-form-submit").attr("disabled","disabled");
+    $("#kill-button").removeAttr("disabled");
+    $('.terminal').show();
+};
+
+function done() {
+    $('#done').show();
+    $('#kill-button').attr("disabled","disabled");
+};
+$(function() {
+    $('#confirm-form').submit(function(e) {
+        launch_script();
+     });
+    $('#kill-button').attr("disabled","disabled").click(function(e) {
+        $("#kill-result").load("{% url 'admin:kill_script' %}");
+    });
+    
+});
+</script>
+{% endif %}
+{% endblock %}
+
+
--- a/src/p4l/urls.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/urls.py	Fri Oct 11 11:46:18 2013 +0200
@@ -32,12 +32,13 @@
 #
 
 from django.conf.urls import patterns, include, url
-from django.contrib import admin
+#from django.contrib import admin
 from django.contrib.auth import urls as auth_urls
 from django.contrib.auth.decorators import login_required
 
 from p4l.search.views import RecordSearchView
 from p4l.views import RecordDetailView, RecordEditView, RecordDeleteView
+from p4l import admin
 
 
 js_info_dict = {
@@ -45,7 +46,7 @@
     'domain': 'django',
 }
 
-admin.autodiscover()
+#admin.autodiscover()
 
 urlpatterns = patterns('',
     url(r'^$', login_required(RecordSearchView.as_view()), name='p4l_home'),
--- a/src/p4l/utils.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/utils.py	Fri Oct 11 11:46:18 2013 +0200
@@ -46,7 +46,7 @@
 
 logger = logging.getLogger(__name__)
 
-def show_progress(current_line, total_line, label, width, writer=None):
+def show_progress(current_line, total_line, label, width, writer=None, newline=False):
 
     if writer is None:
         writer = sys.stdout
@@ -60,7 +60,7 @@
 
     loader = u'[' + (u'=' * int(marks)) + (u' ' * int(spaces)) + u']'
         
-    s = u"%s %3d%% %*d/%d - %*s\r" % (loader, percent, len(str(total_line)), current_line, total_line, width, label[:width])
+    s = u"%s %3d%% %*d/%d - %*s%s" % (loader, percent, len(str(total_line)), current_line, total_line, width, label[:width], "\n" if newline else "\r")
     
     writer.write(s) #takes the header into account
     if percent >= 100:
--- a/src/p4l/views.py	Tue Oct 08 17:25:39 2013 +0200
+++ b/src/p4l/views.py	Fri Oct 11 11:46:18 2013 +0200
@@ -52,7 +52,7 @@
     'filter': 'dataquery',
     'root': 'datarootquery',
     'childs': 'datachildsquery',
-    'child-count': 'datachildcountquery'                            
+    'child-count': 'datachildcountquery'
 }
 
 
@@ -158,4 +158,5 @@
         rec = get_object_or_404(Record, identifier=slug)
         rec.delete()
         return redirect('p4l_home')
-        
\ No newline at end of file
+
+