Commit eb6d7aee authored by Nicolas Dandrimont's avatar Nicolas Dandrimont

Utilisation un peu plus propre de django

- Utilisation de la version 1.2b1 avec entre autres son support confortable des
  bases de données multiples (yay les sessions dans sqlite et les prises dans
  pgsql) [./settings.py]

   o Le routage des données dans les différentes bases de données se fait avec
   un Routeur personnalisé [./database.py] (sinon, tout va dans la base default
   SQLite)

- Utilisation du framework d'authentification de django

   o Les utilisateurs sont récupérés dynamiquement avec leurs groupes depuis la
   base LDAP, puis sont stockés dans la base SQLite (modèle User) [./login.py].

   o Les groupes sont synchronisés à chaque login, et créés si nécessaire à la volée.

   o Les permissions sont stockées directement dans la base de données SQLite.
   Chaque application peut définir des permissions personnalisées dans ses
   modèles. [./apps/prises/models.py]

   o Les permissions peuvent être utilisées avec les décorateurs de
   django.contrib.auth.decorators (login_required, permission_required, ...)

   o Les templates comprennent automatiquement l'objet "user", dès lors qu'ils
   ont été rendus avec un Context particulier (RequestContext, cf les appels à
   render_to_response dans [./accueil.py, ./apps/prises/views.py,
   ./apps/dummy/views.py])

- Utilisation de l'interface d'administration automagique de django

   o Cette interface permet d'accéder directement aux données gérées dans l'ORM
   de django. (https://intranet2.crans.org/admin/ pour les nounous)

   o On peut gérer quels groupes ont quels droits dans les différentes applications.

   o Chaque application peut enregistrer une interface d'administration
   personnalisée pour ses modèles. [./apps/prises/admin.py]

- Utilisation de l'ORM de django pour l'application prises

   o Définition du modèle SQL [./apps/prises/models.py]

   o Définition des champs de formulaires [./apps/prises/forms.py]

   o Utilisation de ModelFormSets pour construire et valider les formulaires
   automatiquement [./apps/prises/views.py]

Il faut encore mettre à jour l'application d'impression pour utiliser les
modèles django avant de la réactiver.
parent bcb687ac
......@@ -22,15 +22,16 @@
#
import django.shortcuts
from django.template import RequestContext
import settings
from login import require_droits
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
@require_droits()
@login_required
def view(request):
'''Affiche toutes les applications disponibles en fonction des
droits de la personne connectée.'''
session = request.session
apps = list(settings.INTRANET_APPS)
apps.sort(lambda x,y: cmp(x['category'], y['category']))
return django.shortcuts.render_to_response("accueil.html", locals())
return django.shortcuts.render_to_response("accueil.html", locals(), context_instance=RequestContext(request))
# Create your views here.
import django.shortcuts
from login import require_droits
from django.contrib.auth.decorators import login_required
@require_droits()
from django.template import RequestContext
@login_required
def bonjour(request):
return django.shortcuts.render_to_response("dummy/bonjour.html", {"session" : request.session })
return django.shortcuts.render_to_response("dummy/bonjour.html", {"session" : request.session }, context_instance=RequestContext(request))
from django.contrib import admin
from intranet.apps.prises.models import Prise
class PriseAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'crans', 'prise_crans', 'crous', 'prise_crous', 'commentaire')
list_display_links = ('__unicode__',)
list_editable = ('commentaire',)
list_filter = ('batiment', 'cablage_effectue', 'crans', 'crous')
admin.site.register(Prise, PriseAdmin)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# FORMS.PY -- Formulaires Django pour les prises
#
# Copyright (C) 2010 Nicolas Dandrimont
# Authors: Nicolas Dandrimont <olasd@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.forms.models import modelformset_factory
from models import Prise
ModifPriseFormSet = modelformset_factory(Prise, fields=('crans', 'crous'), extra=0)
ValidPriseFormSet = modelformset_factory(Prise, fields=('cablage_effectue', ), extra=0)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# MODELS.PY -- Modèles de base de données Django pour les prises
#
# Copyright (C) 2010 Nicolas Dandrimont
# Authors: Nicolas Dandrimont <olasd@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db import models
# Create your models here.
class Prise(models.Model):
class Meta:
db_table = "prises"
ordering = ["batiment", "chambre"]
permissions = (
("can_view", "Peut visualiser les prises"),
("can_change", "Peut modifier les prises"),
("can_validate", "Peut valider les câblages"),
)
batiment = models.CharField(max_length=1, editable = False)
chambre = models.CharField(max_length=4, editable = False)
prise_crans = models.IntegerField(blank=True, null=True)
prise_crous = models.IntegerField(blank=True, null=True)
crans = models.BooleanField()
crous = models.BooleanField()
commentaire = models.CharField(max_length=1024, blank=True, null=True)
cablage_effectue = models.BooleanField()
def toggle(self):
"""Passe une prise du Cr@ns au CROUS ou vice-versa..."""
self.crans = not self.crans
self.crous = not self.crous
self.cablage_effectue = not self.cablage_effectue
def __unicode__(self):
return u"<Prise chambre=%s%s>" % (
self.batiment.upper(),
self.chambre,
)
......@@ -5,9 +5,7 @@ import views
urlpatterns = patterns('',
url('^$', views.redirect_to_view, name="root"),
url('^view/(.+)?$', views.view, name="view"),
url('^do_prises$', views.do_prises, name="do_prises"),
url('^validate/(.+)?$', views.validate, name="validate"),
url('^do_validate$', views.do_validate, name="do_validate")
)
......@@ -23,157 +23,74 @@
#
#
from __future__ import with_statement
import re
import sys
import psycopg2, psycopg2.extensions
import django.shortcuts
from login import require_droits
from django.template import RequestContext
from django.contrib.auth.decorators import login_required, permission_required
CNX = psycopg2.connect(database='switchs', host='pgsql.adm.crans.org', user='crans')
from models import Prise
from forms import ModifPriseFormSet, ValidPriseFormSet
PAGES = [
('view', 'Modification'),
('validate', 'Validation'),
]
class LoggingCursor(psycopg2.extensions.cursor):
"""Curseur avec ajout du logging"""
def execute(self, sql, args=None):
print >> sys.stderr, self.mogrify(sql, args)
try:
psycopg2.extensions.cursor.execute(self, sql, args)
except Exception, exc:
print >> sys.stderr, "%s: %s" % (exc.__class__.__name__, exc)
raise
class CursorMaker(object):
"""'Context manager' pour l'utilisation d'un curseur vers la base
de données proprement"""
def __init__(self, connection = CNX):
self.cnx = connection
self.cur = None
def __enter__(self):
self.cur = self.cnx.cursor(cursor_factory=LoggingCursor)
return self.cur
def __exit__(self, exc_type, exc_value, exc_traceback):
if exc_type is None:
# Tout s'est bien passé, on ferme le curseur proprement...
self.cnx.commit()
self.cur.close()
else:
# On a eu un souci, effectuons un peu de nettoyage...
self.cnx.rollback()
self.cur.close()
def get_batiments():
"""Récupère la liste des bâtiments"""
from django.db import connections
# On récupère la liste des batiments
with CursorMaker() as cur:
cur.execute('SELECT DISTINCT batiment FROM prises')
BATIMENTS = [ l[0] for l in cur.fetchall() ]
def get_bat(batiment):
"""Renvoie une liste des chambres dans le bâtiment."""
with CursorMaker() as cur:
cur.execute("SELECT * FROM prises WHERE (batiment=%s) ORDER by chambre", batiment)
return cur.fetchall()
def update_chbre(bat, chbre, cnx):
"""modifie la connexion d'une chambre"""
champs = ["crans", "crous", "commentaire", "cablage_effectue"]
champs_select = ','.join(champs)
select = "SELECT %s FROM prises WHERE (batiment=%%s AND chambre=%%s)" % champs_select
update = "UPDATE prises SET (%s) = (%s) WHERE (batiment=%%s AND chambre=%%s)" % (champs_select, ','.join(["%s"] * len(champs)))
with CursorMaker() as cur:
cur.execute(select, (bat, chbre))
crans, crous, commentaire, cablage_effectue = cur.fetchall()[0]
if cnx == 'crans':
if crans: return 0
cablage_effectue = (crans == cablage_effectue)
crans = True
crous = False
elif cnx == 'crous':
if crous: return 0
cablage_effectue = (crous == cablage_effectue)
crans = False
crous = True
else: raise ValueError("Connexion `%s' invalide" % cnx)
with CursorMaker() as cur:
cur.execute(update, (crans, crous, commentaire, cablage_effectue, bat, chbre))
@require_droits()
cursor = connections['switchs'].cursor()
batiments = cursor.execute('SELECT DISTINCT batiment FROM prises')
return [batiment[0] for batiment in cursor.fetchall()]
@login_required
def redirect_to_view(request):
"""Redirige vers la page d'accueil"""
return django.shortcuts.redirect("view")
@require_droits(['nounou', 'cableur', 'bureau', 'crous'])
@permission_required('prises.can_view')
def view(request, batiment = None):
"""Affiche les différents batiments et les différentes chambres."""
cur_page = "view"
chbres = {}
batiments = BATIMENTS
session = request.session
# On récupère chaque étage du batiment (si on travaille sur un bâtiment)
batiments = get_batiments()
if batiment:
for chbre in get_bat(batiment):
floor = int(re.search(r'\d+', chbre[1]).group()) / 100
if chbres.has_key(floor):
chbres[floor].append(chbre)
else:
chbres[floor] = [ chbre ]
chbres_by_etage = chbres.items()
chbres_by_etage.sort(lambda x,y: cmp (y[0], x[0]))
# On a peut-être des données à récupérer
if request.method == "POST":
if request.user.has_perm('prises.can_change'):
formset = ModifPriseFormSet(request.POST, queryset = Prise.objects.filter(batiment = batiment).order_by('chambre'))
if formset.is_valid():
instances = formset.save(commit=False)
for instance in instances:
instance.cablage_effectue = not instance.cablage_effectue
instance.save()
else:
raise Exception("You're doin it wrong", formset.errors)
# On affiche les prises du bâtiment
formset = ModifPriseFormSet(queryset = Prise.objects.filter(batiment = batiment).order_by('chambre'))
for form in formset.forms:
form.etage = form.instance.chambre[0]
l = locals()
l['PAGES'] = PAGES
return django.shortcuts.render_to_response("prises/prises.html", l)
return django.shortcuts.render_to_response("prises/prises.html", l, context_instance=RequestContext(request))
@require_droits(['nounou', 'cableur', 'bureau'])
@permission_required('prises.can_view')
def validate(request, batiment = None):
"""Permet de valider des câblages."""
cur_page = "validate"
chambres = []
with CursorMaker() as cur:
cur.execute("SELECT DISTINCT batiment FROM prises WHERE cablage_effectue=FALSE")
batiments = [l[0] for l in cur.fetchall()]
session = request.session
batiments = get_batiments()
if batiment:
with CursorMaker() as cur:
cur.execute("SELECT chambre,prise_crans,prise_crous,commentaire,crans,crous FROM prises WHERE (batiment=%s AND cablage_effectue=FALSE) ORDER by chambre", batiment)
fa = cur.fetchall()
for chambre, prise_crans, prise_crous, commentaire, crans, crous in fa:
chambres.append({ "chambre": chambre,
"crans": crans,
"crous": crous,
"prise_crans": prise_crans,
"prise_crous": prise_crous,
"commentaire": commentaire })
if request.method == "POST":
if request.user.has_perm('prises.can_validate'):
formset = ValidPriseFormSet(request.POST, queryset = Prise.objects.filter(batiment = batiment, cablage_effectue = False).order_by('crans', 'prise_crous'))
if formset.is_valid():
formset.save()
else:
raise Exception("You're doin it wrong", formset.errors)
# On affiche les prises du bâtiment
formset = ValidPriseFormSet(queryset = Prise.objects.filter(batiment = batiment, cablage_effectue = False).order_by('crans', 'prise_crous'))
l = locals()
l['PAGES'] = PAGES
return django.shortcuts.render_to_response("prises/validate.html", l)
@require_droits(['nounou', 'cableur', 'bureau', 'crous'])
def do_prises(request):
"""Récupère le formulaire, l'analyse et stocke les données dans la
base"""
modifs = []
pref = len('input_')
for name, val in request.POST.items():
if val:
bat, chbre = name[pref], name[(pref + 1):]
modifs.append( (bat, chbre, val) )
update_chbre( bat, chbre, val )
return django.shortcuts.render_to_response("prises/done_prises.html", { "modifs": modifs, "session": request.session} )
@require_droits(['nounou', 'bureau'])
def do_validate(request):
"""Effectue la validation du câblage."""
pass
return django.shortcuts.render_to_response("prises/validate.html", l, context_instance=RequestContext(request))
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# DATABASE.PY -- Gestion des bases de données multiples dans l'intranet
#
# Copyright (C) 2010 Nicolas Dandrimont
# Authors: Nicolas Dandrimont <olasd@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import settings
class Router(object):
"""Effectue le routage entre les différentes bases de données
Actuellement, redirige toutes les requetes de l'application
`prises' vers la base de données idoine"""
def db_for_read(self, model, **hints):
"""Base de données à utiliser pour la lecture"""
if model._meta.app_label == "prises":
return "switchs"
return None
def db_for_write(self, model, **hints):
"""Base de données à utiliser pour l'écriture"""
if model._meta.app_label == "prises":
return "switchs"
return None
def allow_syncdb(self, db, model):
"""Autoriser l'écriture du modèle donné dans la base de données ?"""
if db == "switchs":
if model._meta.app_label == "prises":
return True
return False
else:
if model._meta.app_label == "prises":
return False
return None
This diff is collapsed.
......@@ -17,12 +17,22 @@ ADMINS = (
MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '/usr/local/django/var/intranet.sql' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/usr/local/django/var/intranet.sql',
},
'switchs': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'switchs',
'HOST': 'pgsql.adm.crans.org',
'USER': 'crans',
},
}
DATABASE_ROUTERS = [
'intranet.database.Router',
]
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
......@@ -39,7 +49,7 @@ SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = False
USE_I18N = True
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
......@@ -53,7 +63,7 @@ MEDIA_URL = 'https://intranet2.crans.org/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
#ADMIN_MEDIA_PREFIX = '/media/'
ADMIN_MEDIA_PREFIX = '/media/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = '$u9k=cu^7hs7=-l!i$=n@t$yu91(%owdi+str2*4%=eofqm-6f'
......@@ -80,10 +90,18 @@ TEMPLATE_DIRS = (
# Don't forget to use absolute paths, not relative paths.
)
LOGIN_URL = "/login"
LOGIN_REDIRECT_URL = "/"
AUTHENTICATION_BACKENDS = (
'intranet.login.LDAPUserBackend',
)
INTRANET_APPS = (
{'name':'dummy', 'category':'Beta'},
{'name':'prises', 'category':'Administration'},
{'name':'impression', 'category': 'Services'},
# {'name':'impression', 'category': 'Services'},
)
INSTALLED_APPS = (
......@@ -91,4 +109,5 @@ INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
) + tuple( 'intranet.apps.%s' % app['name'] for app in INTRANET_APPS )
......@@ -11,19 +11,18 @@
<div class="block">
<img src="/static/img/logo_crans.png" alt="Logo Cr@ns" height="98px"/>
<h2>Connexion</h2>
{% if message %}
<div id="message">
{{ message }}
</div>
{% if form.errors %}
<div id="message">Your username and password didn't match. Please try again.</div>
{% endif %}
<form method="post" action="{% url do_login %}">
<label for="login">Login :</label>
<input type="text" id="login" name="login" />
<form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
{{ form.username.label_tag }}
{{ form.username }}
<br/>
<label for="password">Password :</label>
<input type="password" id="password" name="password"/>
{{ form.password.label_tag }}
{{ form.password }}
<br/>
<input type="hidden" name="next" value="{{ np }}" />
<input type="hidden" name="next" value="{{ next }}" />
<div class="liens">
<input type="submit" value="Login" id="bouton" />
<br/>
......
{% extends "prises/template.html" %}
{% block prises_content %}
{% if chbres_by_etage %}
{% if formset %}
<script type="text/javascript">
<!--
function maj(chbre) {
var actuel = document.getElementById('actuel_' + chbre);
var item = document.getElementById('item_' + chbre);
var obj = document.getElementsByName('input_' + chbre)[0];
if (obj.value == '')
function maj(id) {
var parts = id.split('-')
var actuel = document.getElementById('actuel_' + id);
var item = document.getElementById('item_' + id);
var crans = document.getElementsByName('form-' + parts[1] + '-crans')[0];
var crous = document.getElementsByName('form-' + parts[1] + '-crous')[0];
if (crans.value == 'true')
{
if (actuel.innerHTML == 'crans')
obj.value = 'crous'
else
obj.value = 'crans';
}
crans.value = '';
crous.value = 'true';
}
else
obj.value = '';
if (obj.value != '')
{
crans.value = 'true';
crous.value = '';
}
if ((crans.value == 'true' && actuel.innerHTML != 'crans') ||
(crous.value == 'true' && actuel.innerHTML != 'crous'))
item.style.backgroundColor = '#ff1010'
else
item.style.backgroundColor = '';
......@@ -26,26 +31,33 @@ function maj(chbre) {
//-->
</script>
<form id="chambres" action="{% url do_prises %}" method="post">
{% regroup formset.forms by etage as etages %}
<form id="chambres" action="" method="post">
{{ formset.management_form }}
<ul>
{% for etage, chbres_e in chbres_by_etage %}
{% for etage in etages %}
{% ifnotequal currentloop.first True %} <hr /> {% endifnotequal %}
<li class="fieldset">
<span class="fieldlegend">{{ etage }}° étage</span>
<span class="fieldlegend">{{ etage.grouper }}° étage</span>
<ul class="icones">
{% for bat, chbre, p_crans, p_crous, crans, crous, comm, status in chbres_e %}
<li class="chambre_{% if crans %}crans{% else %}crous{% endif %}" id="item_{{ bat }}{{ chbre }}"
onclick="maj('{{ bat }}{{ chbre }}');">
{% for chambre in etage.list %}
<li class="chambre_{% if chambre.instance.crans %}crans{% else %}crous{% endif %}" id="item_{{ chambre.id.html_name }}"
onclick="maj('{{ chambre.id.html_name }}');">
<ul>
<li> Chambre&nbsp;: {{ bat }}{{ chbre }} </li>
<li> Chambre&nbsp;: {{ chambre.instance.batiment|upper }}{{ chambre.instance.chambre }} </li>
<li> Actuellement&nbsp;:
<span id="actuel_{{ bat }}{{ chbre }}">{% if crans %}crans{% else %}crous{% endif %}</span>
{% if status %}<img src="/static/img/checkmark.png" alt="(ok)"/>
<span id="actuel_{{ chambre.id.html_name }}">{% if chambre.instance.crans %}crans{% else %}crous{% endif %}</span>
{% if chambre.instance.cablage_effectue %}<img src="/static/img/checkmark.png" alt="(ok)"/>
{% else %}<img src="/static/img/error.png" alt="(en attente)" />{% endif %}
</li>
<li style="visibility:hidden"><input type="hidden" name="input_{{bat}}{{chbre}}" value="" /></li>
{% if comm %}
<li style="max-width:10em"> Commentaire&nbsp;: {{ comm }} </li>
<li style="visibility:hidden">
<input type="hidden" name="{{ chambre.crans.html_name }}" value="{% if chambre.instance.crans %}true{% endif %}" />
<input type="hidden" name="{{ chambre.crous.html_name }}" value="{% if chambre.instance.crous %}true{% endif %}" />
{{ chambre.id }}
</li>
{% if chambre.instance.commentaire %}
<li style="max-width:10em"> Commentaire&nbsp;: {{ chambre.instance.commentaire }} </li>
{% endif %}
</ul>
</li>
......
{% extends "prises/template.html" %}
{% block prises_content %}
{% if chambres %}
<form id="chambres" action="{% url do_validate %}" method="post">
{% if formset.forms %}
<form id="chambres" action="" method="post">
<ul class="icones">
{% for chambre in chambres %}
<li class="chambre_{% if chambre.crans %}crans{% else %}crous{% endif %}">
<li style="visibility:hidden">{{ formset.management_form }}</li>
{% for form in formset.forms %}
<li class="chambre_{% if form.instance.crans %}crans{% else %}crous{% endif %}">
<ul>
<li>Chambre {{ batiment|upper }}{{ chambre.chambre }}</li>
<li>{% if chambre.crans %}
{{chambre.prise_crous}} -> {{chambre.prise_crans}}
<li style="visibility:hidden">{{ form.id }}</li>
<li>Chambre {{ batiment|upper }}{{ form.instance.chambre }}</li>
<li>{% if form.instance.crans %}
{{ form.instance.prise_crous }} -> {{ form.instance.prise_crans }}
{% else %}
{{chambre.prise_crans}} -> {{chambre.prise_crous}}
{{ form.instance.prise_crans }} -> {{ form.instance.prise_crous }}
{% endif %}</li>
<li><input type="checkbox" name="done_{{ batiment }}{{ chambre.chambre }}" /></li>
{% for field in form.visible_fields %}
<li>{{ field }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
......
......@@ -16,7 +16,7 @@
<a href="/">
<img id="main_topContentLogo" src="/static/img/logo_crans.png" alt="Revenir &agrave; l'accueil" style="position: absolute; left:30px; top: 30px;" /></a>
<ul id="main_topContentMenu">
<li>{{ session.adherent }}{% if session.domaine %}@{{ session.domaine }}{% endif %}</li>
<li>{{ user.username }}</li>
<li><a href="/">Page d'accueil</a></li>
<li class="last"><a href="{% url logout %}">D&eacute;connexion</a></li>
</ul>
......
from django.conf.urls.defaults import include, patterns, url
import settings
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Les pages existantes
url('^$', 'intranet.accueil.view'),