views.py 89.5 KB
Newer Older
1 2 3
#!/usr/bin/env python
# -*- encoding: utf-8 -*-

4
### Imports
Vincent Le gallic's avatar
Vincent Le gallic committed
5
import django
6 7

# Les objets de réponse HTTP
8
from django.http import HttpResponse, HttpResponseRedirect
9
# Pour renvoyer facilement un template
10 11
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
12
# Pour bypasser le test de Cross-Site Request Forgery
Vincent Le gallic's avatar
Vincent Le gallic committed
13
from django.views.decorators.csrf import csrf_exempt
14 15
# Pour stocker des messages dans la session courante
from django.contrib import messages
16
# Les formulaires
17
import note.forms as forms
18 19
# Pour html-escaper, notamment
import django.utils.html
20

21 22
# Les paramètres django
import settings
23

24
# Modules standard utiles
25
import random
26 27
import time
import random
28
import socket, ssl
29
import json
Vincent Le gallic's avatar
Vincent Le gallic committed
30 31
import re
import subprocess
32
import os, shutil
33 34
import base64
import threading
35
import urllib
36

37
from django.utils.importlib import import_module
38 39
#: Ce module est importé avec la méthode spéciale de Django
#: pour conserver ces valeurs entre deux requêtes HTTP du client
40
keep_alive = import_module('keep_alive')
41

42

43 44
"""Génération d'erreurs/warnings/success messages.
   
Vincent Le gallic's avatar
Vincent Le gallic committed
45
   Ils sont enregistrés dans la session (django.contrib.messages)
46
   pour être affichés à la prochaine page chargée (juste après en général).
Vincent Le gallic's avatar
Vincent Le gallic committed
47 48
   
   """
49 50 51

def _add_message(request, messagelevel, message):
    """Enregistre un message dans la session avec django.contrib.messages.add_message"""
52
    messages.add_message(request, messagelevel, message)
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

def _add_error(request, message):
    """Enregistre une erreur"""
    _add_message(request, messages.ERROR, message)

def _add_warning(request, message):
    """Enregistre un warning"""
    _add_message(request, messages.WARNING, message)

def _add_success(request, message):
    """Enregistre un succès"""
    _add_message(request, messages.SUCCESS, message)



68 69
"""Utilitaires de communication avec le serveur NK2015"""

70
class NKError(Exception):
71
    """Classe de base d'une erreur survenue pendant la communication
72 73 74
       avec le servuer NK2015.
       
       """
75
    def __init__(self, msg):
76
        Exception.__init__(self)
77
        self.msg = msg
78 79 80 81 82 83
    def __str__(self):
        return str(self.msg)
    def __unicode__(self):
        return unicode(self.msg)

class NKRefused(NKError):
84
    """Levée en cas de connection refused."""
85 86 87
    pass

class NKHelloFailed(NKError):
88
    """Levée en cas d'échec au hello."""
89 90 91
    pass

class NKUnknownError(NKError):
92 93 94 95 96
    """Levée en cas d'autre erreur."""
    pass

class NKDeadServer(NKError):
    """Levée quand le serveur ne répond plus."""
97 98
    pass

99 100
def full_read(sock):
    """Lit sur la socket jusqu'à ce que l'output soit déJSON-izable"""
101
    output = ""
102
    tries = 0
103
    while True:
104
        output += sock.read()
105 106 107 108
        try:
            return json.loads(output)
        except:
            pass
109 110 111 112 113
        if output == "":
            tries += 1
        if tries == settings.NK_CONNECTION_MAXTRIES:
            # Au bout d'un moment, on laisse tomber
            raise NKDeadServer("Server NK2015 not responding")
114
        time.sleep(0.01)
115
    
116

117
def _is_success_code(cod):
118
    """Dit si un code de retour est un succès ou non"""
119
    return cod == 0 or 100 <= cod < 200
120

121
def connect_NK():
122 123
    """Connecte une socket au servuer NK2015 et la renvoie après avoir effectué le hello.
       Lève une erreur en cas d'échec"""
Vincent Le gallic's avatar
Vincent Le gallic committed
124
    sock = socket.socket()
125 126
    try:
        # On établit la connexion sur port 4242
127
        sock.connect((settings.NK2015_IP, settings.NK2015_PORT))
128
        # On passe en SSL
129
        sock = ssl.wrap_socket(sock, ca_certs='../keys/ca_.crt')
130
        # On fait un hello
131
        sock.write(json.dumps(["hello", "HTTP Django"]))
132
        # On récupère la réponse du hello
133
        out = full_read(sock)
134 135 136
    except Exception as exc:
        # Si on a foiré quelque part, c'est que le serveur est down
        raise NKRefused(str(exc))
137
    if out["retcode"] == 0:
138
        return sock
139
    elif out["retcode"] == 11:
140 141 142
        raise NKHelloFailed(out["errmsg"])
    else:
        raise NKUnknownError(out["errmsg"])
143

144 145 146
def _gerer_NKError(request, exc):
    """Fait ce qu'il faut en fonction de l'erreur qui a eu lieu pendant la communication avec le serveur NK2015."""
    if isinstance(exc, NKRefused):
147
        _add_error(request, settings.ERRMSG_NK2015_DOWN)
148
        return HttpResponseRedirect(_gen_redirect_postlogin(request))
149
    if isinstance(exc, NKHelloFailed):
150
        _add_error(request, settings.ERRMSG_HELLO_FAILED)
151
        return HttpResponseRedirect(_gen_redirect_postlogin(request))
152 153
    if isinstance(exc, NKDeadServer):
        _add_error(request, settings.ERRMSG_NK2015_NOT_RESPONDING)
154
        return HttpResponseRedirect(_gen_redirect_postlogin(request))
155
    if isinstance(exc, NKUnknownError):
156 157
        erreur = settings.ERRMSG_UNKOWNERROR + "\n"
        erreur += str(exc)
158
        _add_error(request, erreur)
159
        return HttpResponseRedirect(_gen_redirect_postlogin(request))
160
    else:
161 162 163
        typ = django.utils.html.escape(str(type(exc)))
        s = django.utils.html.escape(str(exc))
        return HttpResponse("La gestion de cette erreur n'est pas prévue :\n%s : %s" % (typ, s))
164

165
def login_NK(request, username, password, form, masque=[[], [], False]):
Vincent Le gallic's avatar
Vincent Le gallic committed
166
    """Ouvre une connexion au serveur NK2015 par username/password
167
       Renvoie dans tous les cas un objet HttpResponse[Redirect] utilisable directement"""
168
    try:
Vincent Le gallic's avatar
Vincent Le gallic committed
169
        sock = connect_NK()
170
        data = [username, password, "bdd", masque]
171 172
        paquet = ["login", data]
        sock.write(json.dumps(paquet))
173 174
        out = full_read(sock)
        retcode, errmsg = out["retcode"], out["errmsg"]
175
    except NKError as exc:
176
        return _gerer_NKError(request, exc)
177
    if retcode == 0:
Vincent Le gallic's avatar
Vincent Le gallic committed
178
        # login réussi
179
        request.session["logged"] = "ok"
180
        # On demande au serveur quelles sont les pages auxquelles on a le droit d'accéder
181
        try:
182
            sock.write(json.dumps(["django_get_accessible_pages"]))
183
            out = full_read(sock)
184
            if _is_success_code(out["retcode"]):
185
                pages = [i for i in out["msg"] if i[0]!="Index"] # On ignore la page d'index, inutile car le lien est déjà là
186 187
            else:
                _add_error(request, out["errmsg"])
188
                return HttpResponseRedirect(settings.NOTE_LOGIN_URL)
189 190
        except NKError as exc:
            return _gerer_NKError(request, exc)
191
        save_pages = []
192
        for p in pages:
193 194 195
            save_pages.append({
                    "name": p[0],
                    "link": p[1],
196
                    "full_link": "%s%s/" % (settings.NOTE_ROOT_URL, p[1]),
197
                    })
198
        request.session["pages"] = save_pages
199
        sock.write(json.dumps(["whoami"]))
200 201 202
        out = full_read(sock)
        whoami = out["msg"]
        request.session["whoami"] = whoami
203 204
        # On conserve la connexion au serveur NK2015
        keep_alive.CONNS[whoami["idbde"]] = sock
205 206 207 208
        # On redirige vers /index, sauf si on était en train de se reloguer en venant d'un endroit particulier
        index_fallback = '%sindex/' % (settings.NOTE_ROOT_URL,)
        next = request.GET.get("next", index_fallback)
        return HttpResponseRedirect(next)
Vincent Le gallic's avatar
Vincent Le gallic committed
209
    else:
210
        _add_error(request, errmsg)
211 212 213 214
        try:
            del request.session["logged"]
        except: # Si on ne s'est encore jamais logué, la valeur n'existe pas
            pass
215
        variables = _fundamental_variables()
216
        variables["form"] = form
217
        return render_to_response('note/login.html', variables, context_instance=RequestContext(request))
Vincent Le gallic's avatar
Vincent Le gallic committed
218

219
def _get_socket(request, idbde):
220
    """Récupère la socket dans :py:mod:`keep_alive` ou bien renvoie un redirect de fallback."""
221 222 223 224
    if keep_alive.CONNS.has_key(idbde):
        return (True, keep_alive.CONNS[idbde])
    else:
        _add_error(request, settings.ERRMSG_NOSOCKET)
225
        return (MyHttpResponseRedirect(_gen_redirect_postlogin(request)), None)
226 227

def socket_still_alive(request):
228
    """Récupère dans :py:mod:`keep_alive` la socket de communication avec le serveur NK2015
229 230 231 232 233
       et vérifie que la session est toujours active.
        * En cas de réussite, renvoie ``(True, <la socket de connexion>)``.
        * En cas d'échec, renvoie ``(False, <un objet HttpResponse prêt à l'emploi>)``."""
    idbde = request.session["whoami"]["idbde"]
    gotit, sock = _get_socket(request, idbde)
234 235 236
    if not gotit:
        # gotit est en fait un MyHttpResponseRedirect de fallback
        return (False, gotit)
Vincent Le gallic's avatar
Vincent Le gallic committed
237
    try:
238
        sock.write(json.dumps(["mayi", "alive"]))
239
        out = full_read(sock)
240
    except NKError as exc:
241
        return (False, _gerer_NKError(request, exc))
242
    retcode, still_alive = out["retcode"], out["msg"]
243
    if retcode == 0:
244 245 246 247 248 249
        if still_alive:
            return (True, sock)
        else:
            _add_error(request, settings.ERRMSG_NK2015_SESSION_EXPIRED)
    else:
        _add_error(request, out["errmsg"])
250
    form = forms.LoginForm(label_suffix=" :")
251 252 253
    variables = _fundamental_variables()
    variables["form"] = form
    return (False, render_to_response('note/login.html', variables, context_instance=RequestContext(request)))
254

255 256 257 258


"""Les méthodes pour faire des requêtes au serveur NK2015
   
259
   La convention est la suivante :
260
    - en cas de réussite, on renvoie (True, <l'objet>)
261 262 263
    - en cas d'échec, (<un HttpReponseRedirect>, None)
   En fait le HttpResponseRedirect est un MyHttpResponseRedirect qui est exactement
    la même chose sauf qu'il est interprêté comme un False dans les tests."""
264

265 266 267 268 269 270
class MyHttpResponseRedirect(HttpResponseRedirect):
    """Pour que les redirections soient considérées comme un échec dans les questions
       "Ai-je réussi à obtenir cet objet que je voulais ?" """
    def __nonzero__(self):
        return False

271
def _get_activites(sock, isadmin, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,), computecandelete=False, whoami=None, mine=False):
272
    """Récupère la liste des activités"""
273
    if mine:
274
        flags = "m"
275
    else:
276
        flags = "A" * isadmin
277
    sock.write(json.dumps(["get_activites", ["", flags]]))
278
    out = full_read(sock)
279
    if _is_success_code(out["retcode"]):
280
        liste_activites = out["msg"]
281 282
        for i in range(len(liste_activites)):
            # on post-processe le champ "invitable"
283
            liste_activites[i]["caninvite"], liste_activites[i]["fails"] = liste_activites[i]["invitable"]
284 285 286 287
            if computecandelete:
                # On détermine si il est possible de supprimer l'activité
                # (indépendamment du fait qu'elle contient peut-être des invités, ça ce sera vérifié plus tard)
                # On peut supprimer si (on en est respo ET elle est pas validée) OU (on est admin)
288 289
                liste_activites[i]["candelete"] = isadmin or (liste_activites[i]["responsable"] == whoami["idbde"] and
                                                              liste_activites[i]["validepar"] == -100)
290
        return (True, liste_activites)
291 292
    else:
        _add_error(request, out["errmsg"])
293
        return (MyHttpResponseRedirect(fallback), None)
294

295
def _get_activite(sock, idact, request, fallback='%sactivites/' % (settings.NOTE_ROOT_URL,), computecandelete=False, whoami=None, isadmin=False):
296
    """Récupère une activité"""
297
    sock.write(json.dumps(["get_activite", idact]))
298 299 300 301 302
    out = full_read(sock)
    fallback = MyHttpResponseRedirect(fallback)
    if out["retcode"] == 404:
        _add_error(request, settings.ERRMSG_IDACT_FAIL % (idact))
        return (fallback, None)
303 304
    elif not _is_success_code(out["retcode"]):
        _add_error(request, out["errmsg"])
305
        return (fallback, None)
306
    else:
307
        activite = out["msg"]
308 309 310 311
        if computecandelete:
                # On détermine si il est possible de supprimer l'activité
                # (indépendamment du fait qu'elle contient peut-être des invités, ça ce sera vérifié plus tard)
                # On peut supprimer si (on en est respo ET elle est pas validée) OU (on est admin)
312 313
                activite["candelete"] = isadmin or (activite["responsable"] == whoami["idbde"] and
                                                    activite[i]["validepar"] == -100)
314
        return (True, out["msg"])
315

316
def _get_invites(sock, idact, isadmin, request, fallback='%sactivites/' % (settings.NOTE_ROOT_URL,)):
317 318
    """Récupère la liste des invités d'une activité.
       Il ne peut pas y avoir d'échec 404, au pire la liste est vide."""
319 320
    flags = "A" * isadmin
    data = [idact, flags]
321
    sock.write(json.dumps(["get_invites", data]))
322
    out = full_read(sock)
323
    if _is_success_code(out["retcode"]):
324
        return (True, out["msg"])
325 326
    else:
        _add_error(request, out["errmsg"])
327
        return (MyHttpResponseRedirect(fallback), None)
328

329
def _get_variable_droits(sock, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL)):
330
    """Récupère les droits existants
331
       renvoie une paire (<liste de (<alias qui le donne>, <droit>), <liste de droits sans alias>)"""
332
    sock.write(json.dumps(["mayi", "droits"]))
333
    out = full_read(sock)
334 335 336
    if _is_success_code(out["retcode"]):
        # il faut organiser un peu tout ça :
        dicodroits = out["msg"]
337 338
        keys = dicodroits["_keys"]
        noalias = dicodroits["_noalias"]
339 340
        del dicodroits["_keys"]
        del dicodroits["_noalias"]
341 342
        droits = [(key, dicodroits[key]) for key in keys]
        return (True, droits, noalias)
343 344
    else:
        _add_error(request, out["errmsg"])
345
        return (MyHttpResponseRedirect(fallback), None, None)
346

347
def _get_compte(sock, idbde, request, fallback='%scomptes/' % (settings.NOTE_ROOT_URL,)):
348
    """Récupère un compte"""
349
    sock.write(json.dumps(["compte", idbde]))
350 351 352 353 354
    out = full_read(sock)
    fallback = MyHttpResponseRedirect(fallback)
    if out["retcode"] == 404:
        _add_error(request, settings.ERRMSG_IDBDE_FAIL % (idbde))
        return (fallback, None)
355 356
    elif not _is_success_code(out["retcode"]):
        _add_error(request, out["errmsg"])
357
        return (fallback, None)
358
    else:
359
        return (True, out["msg"])
360

361
def _get_historique_pseudo(sock, idbde, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
362
    """Récupère un compte et ses anciens pseudos"""
363
    gotit, compte = _get_compte(sock, idbde, request, fallback=fallback)
364
    if gotit:
365
        sock.write(json.dumps(["historique_pseudo", idbde]))
366
        out = full_read(sock)
367
        if _is_success_code(out["retcode"]):
368 369
            histos = out["msg"]
            return (True, compte, True, histos)
370 371
        else:
            _add_error(request, out["errmsg"])
372
            return (True, compte, MyHttpResponseRedirect(fallback), None)
373
    else:
374
        return (gotit, None, gotit, None)
375

376
def _get_aliases(sock, idbde, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
377
    """Récupère un compte et post-processe ses alias"""
378
    gotit, compte = _get_compte(sock, idbde, request)
379
    if gotit:
380
        compte["aliases"] = [{"id": alias[0], "alias": alias[1]} for alias in compte["aliases"]]
381 382
        return (True, compte)
    else:
383
        return (MyHttpResponseRedirect(fallback), None)
384

385
def _get_boutons_categories(sock, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
386
    sock.write(json.dumps(["get_boutons_categories"]))
387
    out = full_read(sock)
388
    if _is_success_code(out["retcode"]):
389
        return (True, out["msg"])
390 391
    else:
        _add_error(request, out["errmsg"])
392
        return (MyHttpResponseRedirect('%sindex' % (settings.NOTE_ROOT_URL,)), None)
393

394
def _get_boutons(sock, request, hidden=False, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
395
    """Récupère tous les boutons et les ordonne par (catégorie, label)"""
396 397 398 399 400
    options = ["", ""]
    if hidden:
        options.append("all")
    to_send = ["get_boutons", options]
    sock.write(json.dumps(to_send))
401
    out = full_read(sock)
402 403
    if _is_success_code(out["retcode"]):
        boutons = out["msg"]
404 405 406
        boutons.sort(lambda x, y: cmp((x["categorie"].lower(), x["label"].lower()),
                                     (y["categorie"].lower(), y["label"].lower())))
        return (True, boutons)
407 408
    else:
        _add_error(request, out["errmsg"])
409
        return (MyHttpResponseRedirect(fallback), None)
410

411
def _get_un_bouton(sock, idbouton, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
412
    """Récupère un bouton"""
413
    sock.write(json.dumps(["get_un_bouton", idbouton]))
414 415 416 417 418
    out = full_read(sock)
    fallback = MyHttpResponseRedirect(fallback)
    if out["retcode"] == 404:
        _add_error(request, settings.ERRMSG_IDBUTTON_FAIL % (idbouton))
        return (fallback, None)
419 420
    elif not _is_success_code(out["retcode"]):
        _add_error(request, out["errmsg"])
421
        return (fallback, None)
422
    else:
423
        return (True, out["msg"])
424

425
def _get_clubs(sock, request, fallback='%sindex/' % (settings.NOTE_ROOT_URL,)):
426
    """Récupère la liste des (idbde, pseudo) des clubs"""
427
    sock.write(json.dumps(["get_clubs"]))
428
    out = full_read(sock)
429
    if _is_success_code(out["retcode"]):
430
        return (True, out["msg"])
431 432
    else:
        _add_error(request, out["errmsg"])
433
        return (MyHttpResponseRedirect(fallback), None)
434

435
def _get_preinscription(sock, preid, request, fallback='%sinscriptions/' % (settings.NOTE_ROOT_URL,)):
436
    """Récupère une préinscription"""
437
    sock.write(json.dumps(["get_preinscription", preid]))
438 439 440 441 442
    out = full_read(sock)
    fallback = MyHttpResponseRedirect(fallback)
    if out["retcode"] == 404:
        _add_error(request, settings.ERRMSG_PREID_FAIL % (preid))
        return (fallback, None)
443 444
    elif not _is_success_code(out["retcode"]):
        _add_error(request, out["errmsg"])
445
        return (fallback, None)
446
    else:
447
        return (True, out["msg"])
448

449
def _get_preinscriptions(sock, request, fallback='%sindex' % (settings.NOTE_ROOT_URL,)):
450
    """Récupère la liste des préinscriptions"""
451
    sock.write(json.dumps(["get_preinscriptions"]))
452
    out = full_read(sock)
453
    if _is_success_code(out["retcode"]):
454
        return (True, out["msg"])
455
    else:
456
        return (MyHttpResponseRedirect(fallback), None)
457

458
def _prepare_variables(sock, idbde, request, form=False, whoami=None):
459 460 461
    """Prépare plein de variables pour l'affichage/la modification des comptes.
       (Le post-processing n'est pas forcément le même)
       Pour une fois, ne revenvoie pas un objet ou une paire, mais un dico des variables récupérées"""
462
    gotit, compte = _get_compte(sock, idbde, request)
463 464 465 466 467 468
    vars = {}
    if gotit:
        # on rend quelques champs un peu plus présentables
        # les listes
        compte["historiques"] = ", ".join(compte["historiques"])
        compte["aliases"] = ", ".join([al[1] for al in compte["aliases"]])
469
        compte["annees"] = ", ".join([str(i) for i in compte["annees"]])
470
        # l'id crans
471 472 473 474
        if compte["type"] == "club":
            compte["type_idcrans"] = "cid"
        elif compte["type"] == "personne":
            compte["type_idcrans"] = "aid"
475 476 477 478
        vars["compte"] = compte
        # pour les infos qui ne sont accessibles qu'aux droits wei
        vars["has_wei"] = compte.has_key("numsecu")
        # on aimerait connaître les droits qui existent
479
        gotit, vars["droits"], vars["droits_noalias"] = _get_variable_droits(sock, request)
480 481 482
        if not gotit:
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
483 484
        if form:
            # on a aussi besoin de connaître la liste exhaustive des droits du compte courant
485
            sock.write(json.dumps(["mayi", "full_rights"]))
486 487 488
            out = full_read(sock)
            whoami["full_overrights"] = out["msg"]["surdroits"]
            whoami["full_rights"] = out["msg"]["droits"]
489
            vars["whoami"] = whoami
490 491 492 493
        else:
            # on fournit la photo
            _provide_photo(sock, idbde)
            vars["urlphoto"] = _get_url_photo(idbde)
494 495
    return vars

496 497
def _del_activite(sock, request, idact):
    """Supprime une activité (le Redirect en cas d'échec est à la charge de l'appelant"""
498
    sock.write(json.dumps(["del_activite", [idact, "A"]])) # pareil, on demande toujours l'admin au cas où
499
    out = full_read(sock)
500
    if _is_success_code(out["retcode"]):
501
        _add_success(request, settings.SUCCMSG_DELACT)
502 503 504 505 506
        return True
    else:
        _add_error(request, out["errmsg"])
        return False

507
def _create_BoutonForm(sock, request, contenu=None, initial=None):
508
    """Un hack pour peupler les choices au runtime"""
509 510
    if initial == None:
        if contenu == None:
511
            form = forms.BoutonForm(label_suffix=" :")
512
        else:
513
            form = forms.BoutonForm(contenu, label_suffix=" :")
514
    else:
515
        form = forms.BoutonForm(label_suffix=" :", initial=initial)
516
    gotit, categories = _get_boutons_categories(sock, request)
517 518
    # gotit est en fait un MyHttpResponseRedirect de fallback
    if not gotit:
519 520 521
        return (gotit, None)
    form.fields['categorie'].choices = [[ca] * 2 for ca in categories]
    gotit, clubs = _get_clubs(sock, request)
522
    if not gotit:
523 524 525
        return (gotit, None)
    form.fields['destinataire'].choices = [[cl["idbde"], cl["pseudo"]] for cl in clubs]
    return (True, form)
526

527 528 529 530 531


"""Pour transformer un id en entier et enregistrer
   une erreur dans la session en cas d'échec.
   Renvoient toujours :
532 533
    - en cas de réussite, (<l'id>, None)
    - en cas d'échec, (None, <un objet HttpResponseRedirect de fallback)"""
534

535
def _cast_as_int(ident, request, errmsg, fallback):
536 537
    """Fonction appelée par toutes les autres _cast, effectue vraiment le boulot"""
    try:
538
        return(int(ident), None)
539 540 541 542
    except:
        _add_error(request, errmsg)
        return (None, HttpResponseRedirect(fallback))

543
def _cast_as_idact(idact, request, fallback='%sactivites/' % (settings.NOTE_ROOT_URL,)):
544 545
    """Essaye de transformer idact en entier.
       Si échoue, ajoute un message d'erreur.
546
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/activites/')>)"""
547
    return _cast_as_int(idact, request, settings.ERRMSG_IDACT_INVALID % (idact), fallback)
548

549
def _cast_as_idbde(idbde, request, fallback='%scomptes/' % (settings.NOTE_ROOT_URL,)):
550 551
    """Essaye de transformer idbde en entier.
       Si échoue, ajoute un message d'erreur.
552
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/comptes/')>)"""
553
    return _cast_as_int(idbde, request, settings.ERRMSG_IDBDE_INVALID % (idbde), fallback)
554

555
def _cast_as_idinv(idinv, request, (fallback_idact, fallback_admin)):
556 557
    """Essaye de transformer idinv en entier.
       Si échoue, ajoute un message d'erreur.
558
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/activites/<idact>/')>)"""
559
    return _cast_as_int(idinv, request, settings.ERRMSG_IDINV_INVALID % (idinv),
560
                        '%sactivites/%s%s/' % (settings.NOTE_ROOT_URL, fallback_idact, "/admin" * fallback_admin))
561

562
def _cast_as_idbouton(idbouton, request, fallback='%sboutons/' % (settings.NOTE_ROOT_URL,)):
563 564
    """Essaye de transformer idbbouton en entier.
       Si échoue, ajoute un message d'erreur.
565
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/boutons/')>)"""
566
    return _cast_as_int(idbouton, request, settings.ERRMSG_IDBUTTON_INVALID % (idbouton), fallback)
567

568
def _cast_as_preid(preid, request, fallback='%sinscriptions/' % (settings.NOTE_ROOT_URL,)):
569 570
    """Essaye de transformer idbbouton en entier.
       Si échoue, ajoute un message d'erreur.
571
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/boutons/')>)"""
572
    return _cast_as_int(preid, request, settings.ERRMSG_PREID_INVALID % (preid), fallback)
573

574 575 576 577 578
def _cast_as_idtransaction(idtransaction, request, fallback='%sconsos/' % (settings.NOTE_ROOT_URL,)):
    """Essaye de transformer idtransaction en entier.
       Si échoue, ajoute un message d'erreur.
       Renvoie dans tous les cas (<l'entier ou None>, <si None, un HttpResponseRedirect('<NOTE_ROOT_URL>/consos/')>)"""
    return _cast_as_int(idtransaction, request, settings.ERRMSG_IDTRANSACTION_INVALID % (idtransaction), fallback)
579 580 581

"""Définitions des views"""

582
def login_page(request):
Vincent Le gallic's avatar
Vincent Le gallic committed
583
    """Renvoie la page de login ou traite les données du formulaire de login"""
584
    if request.method == "POST":
Vincent Le gallic's avatar
Vincent Le gallic committed
585
        # on récupère le formulaire
586
        form = forms.LoginForm(request.POST, label_suffix=" :")
587
        if form.is_valid():
Vincent Le gallic's avatar
Vincent Le gallic committed
588 589
            username = form.cleaned_data["username"]
            password = form.cleaned_data["password"]
590
            masque_droits = form.cleaned_data["droits"]
591 592
            restricted = ["wei", "overforced", "transactions_admin", "chgpass", "comptes", "boutons", "admin"]
            dico_masques = {'all': [[], [], False], 'restricted': [restricted, restricted, True]}
Vincent Le gallic's avatar
Vincent Le gallic committed
593
            masque_droits = dico_masques[masque_droits]
594
            # On tente un login
595
            return login_NK(request, username, password, form, masque_droits)
596
    else:
597
        form = forms.LoginForm(label_suffix=" :")
598 599 600 601 602 603 604 605
    variables = _fundamental_variables()
    variables["form"] = form
    return render_to_response('note/login.html', variables, context_instance=RequestContext(request))

def _fundamental_variables():
    """Renvoie un dictionnaire contenant les variables incontournables qu'on doit avoir pour render un template."""
    return {"NOTE_ROOT_URL" : settings.NOTE_ROOT_URL,
            "NOTE_LOGIN_URL" : settings.NOTE_LOGIN_URL}
606

607 608 609 610 611
def _gen_redirect_postlogin(request):
    """Génère l'uri de redirection contenant ``"?next=<la page où on veut aller après le login>"``"""
    next_page = re.sub(r"\?.*", "", request.path) # On ne garde pas ce qui était éventuellement présent dans le get.
    return settings.NOTE_LOGIN_URL + "?%s" % (urllib.urlencode({"next" : next_page}),)

612
def standard_page(request, socket=False):
613
    """Fonction appelée pour générer quasiment toutes les pages (pas login).
614 615 616 617 618 619 620 621
       
       Elle récupère la socket dans :py:mod:`keep_alive` et vérifie que la session NK2015 n'a pas timeout.
       
       Renvoie ``(bool, sock_ou_response, variables_standard)`` avec les cas suivants :
        * ``(True, <une socket ouverte vers le serveur NK2015>, <un dico de variables de bases>)``
        * ``(False, <un Http object utilisable>, {})``
        * ``(True, None, <un dico de variables de base>)`` (dans le cas ``socket=False``)
       """
622
    if request.session.get("logged", "no") == "ok":
Vincent Le gallic's avatar
Vincent Le gallic committed
623
        # Le login a réussi
624
        whoami = request.session["whoami"]
625 626 627
        variables_standard = _fundamental_variables()
        variables_standard["pages"] = request.session["pages"]
        variables_standard["whoami"] = whoami
628
        if socket:
629
            success, sock_ou_response = socket_still_alive(request)
630
            if success:
631
                return (True, sock_ou_response, variables_standard)
632
            else:
633
                return (False, sock_ou_response, {})
634
        else:
635
            return (True, None, variables_standard)
636
    else:
637 638
        # Le cookie Django a expiré ou est invalide
        _add_error(request, settings.ERRMSG_DJANGO_SESSION_EXPIRED)
639
        return (False, HttpResponseRedirect(_gen_redirect_postlogin(request)), {})
640 641 642 643

def index(request):
    """La page qui ne sert à rien"""
    # On appelle la fonction standard
644
    success, sock_ou_response, variables = standard_page(request)
645
    if success:
646
        return render_to_response('note/index.html', variables, context_instance=RequestContext(request))
647 648 649 650
    else:
        # Une erreur a eu lieu
        response = sock_ou_response
        return response
651

652
def consos(request, double=None):
Vincent Le gallic's avatar
Vincent Le gallic committed
653
    """La page des consos"""
654
    # On appelle la fonction standard
655
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
656
    if success:
657
        sock = sock_ou_response
658
        variables = {"double_stack_mode" : double, "page_consos" : True}
659
        gotit, categories = _get_boutons_categories(sock, request)
660 661 662
        if not gotit:
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
663
        variables["categories"] = categories
664
        gotit, boutons = _get_boutons(sock, request)
665 666 667
        if not gotit:
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
668
        variables["boutons"] = [(categ, [b for b in boutons if b["categorie"] == categ]) for categ in categories]
669
        sock.write(json.dumps(["historique_transactions", "last"]))
670 671 672 673 674
        out = full_read(sock)
        if _is_success_code(out["retcode"]):
            variables["historique"] = out["msg"]
        else:
            _add_error(request, out["errmsg"])
675
        # Le formulaire de Crédit
676
        variables["credit_form"] = forms.CreditRetraitForm(prefix="credit_form", label_suffix=" :")
677
        # Le formulaire de Retrait
678
        variables["retrait_form"] = forms.CreditRetraitForm(prefix="retrait_form", label_suffix=" :")
679
        # Le formulaire de Transfert
680
        variables["transfert_form"] = forms.TransfertForm(prefix="transfert_form", label_suffix=" :")
681
        variables.update(variables_standard)
682
        return render_to_response('note/consos.html', variables, context_instance=RequestContext(request))
683
    else:
684 685 686
        # Une erreur a eu lieu
        response = sock_ou_response
        return response
Vincent Le gallic's avatar
Vincent Le gallic committed
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702

def dons(request):
    """La page des dons"""
    # On appelle la fonction standard
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
    if success:
        sock = sock_ou_response
        variables = {}
        # Le formulaire de Don
        variables["don_form"] = forms.TransfertForm(label_suffix=" :")
        variables.update(variables_standard)
        return render_to_response('note/dons.html', variables, context_instance=RequestContext(request))
    else:
        # Une erreur a eu lieu
        response = sock_ou_response
        return response
703

704
@csrf_exempt
705
def do_credit_retrait(request, action):
706 707 708
    types = {"especes": "Espèces", "cheque": "Chèque", "virement": "Virement bancaire"}
    actions_write = {"credit": "crédit", "retrait": "retrait"}
    actions_socket = {"credit": "crediter", "retrait": "retirer"}
709 710 711 712 713
    # On appelle la fonction standard
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
    if success:
        sock = sock_ou_response
        # on n'a pas besoin du prefix parce que le JS a filé les paramètre sans.
714
        form = forms.CreditRetraitForm(request.POST, label_suffix=" :")
715 716
        if form.is_valid():
            data = form.cleaned_data
717
            if data["type"] != "especes" and "" in [data["nom"], data["prenom"], data["banque"]]:
718 719
                return HttpResponse("""Pour un %s par %s, les champs Nom, Prénom et Banque doivent être spécifiés.""" % (actions_write[action], types[data["type"]]))
            to_send = [data["idbde"], data["montant"], data["type"],
720
                       {"nom": data["nom"], "prenom": data["prenom"], "banque": data["banque"], "commentaire" : data["commentaire"]}]
721 722
            paquet = [actions_socket[action], to_send]
            sock.write(json.dumps(paquet))
723 724 725
            out = full_read(sock)
            return HttpResponse(json.dumps(out))
        else:
726 727
            errmsg = "Ce %s est invalide :\n" % (actions_write[action])
            for (k,v) in form.errors.items():
728 729 730 731 732
                errmsg += "%s : %s\n" % (k,"".join([str(i) for i in v]))
            return HttpResponse(errmsg)
    else:
        return HttpResponse("Erreur à l'établissement de la connexion")

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
@csrf_exempt
def do_transfert(request):
    # On appelle la fonction standard
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
    if success:
        sock = sock_ou_response
        if request.method != "POST" or not request.POST.has_key("transfertdata"):
            return HttpResponse("Bad request", status=400)
        transfertdata = request.POST["transfertdata"]
        try:
            transfertdata = json.loads(transfertdata)
        except ValueError:
            return HttpResponse("Failed to decode JSON object.", status=500)
        if not( isinstance(transfertdata, list) and len(transfertdata) == 4 and
                [type(i) for i in transfertdata] == [list, list, unicode, unicode]):
            return HttpResponse("Bad parameter", status=500)
749
        emetteurs, destinataires, montant, commentaire = transfertdata
750 751 752 753 754 755 756 757 758
        try:
            montant = float(montant)
        except ValueError:
            return HttpResponse(json.dumps({"retcode" : 1111, "msg" : None, "errmsg" : """Transfert impossible : "%s" n'est pas un montant valide.""" % (montant,)}))
        if emetteurs == []:
            return HttpResponse(json.dumps({"retcode" : 1112, "msg" : None, "errmsg" : "Transfert impossible : pas d'émetteurs."}))
        if destinataires == []:
            return HttpResponse(json.dumps({"retcode" : 1113, "msg" : None, "errmsg" : "Transfert impossible : pas de destinataires."}))
        montant = int(montant * 100)
759
        paquet = ["transferts", [emetteurs, destinataires, montant, commentaire]]
760 761 762 763 764 765
        sock.write(json.dumps(paquet))
        out = full_read(sock)
        return HttpResponse(json.dumps(out))
    else:
        return HttpResponse("Erreur à l'établissement de la connexion")

766
def activites(request, admin=None):
767
    """Affichage des activités"""
768
    # On commence par appeller la fonction standard
769
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
770
    if success:
771 772
        sock = sock_ou_response
        asked_admin = (admin == "/admin")
773
        # on demande si on le droit d'être admin
774
        sock.write(json.dumps(["mayi", "activites_admin"]))
775
        hasadmin = full_read(sock)["msg"]
776 777
        # on est en mode administration si on en a le droit ET qu'on l'a demandé
        isadmin = asked_admin and hasadmin
778 779
        if request.method == "POST":
            return HttpResponse("Bad Request", status=400)
780
        else:
781
            gotit, liste_activites = _get_activites(sock, isadmin, request)
782 783 784
            if not gotit:
                # gotit est en fait un MyHttpResponseRedirect de fallback
                return gotit
785
            # On affiche la liste des activités en ajoutant les variables standard
786 787 788
            variables = {"activites": liste_activites,
                         "hasadmin": hasadmin,
                         "isadmin": isadmin}
789 790 791 792 793 794 795
            variables.update(variables_standard)
            return render_to_response('note/activites.html', variables, context_instance=RequestContext(request))
    else:
        # Une erreur a eu lieu
        response = sock_ou_response
        return response

796
def activite(request, idact=None, admin=None):
797 798
    """Affichage d'une activité pour y inviter"""
    # On commence par appeller la fonction standard
799
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
800
    if success:
801
        sock = sock_ou_response
802
        # On récupère le contexte
803
        idact, fallback_redirect = _cast_as_idact(idact, request)
804 805
        if fallback_redirect != None:
            return fallback_redirect
806
        asked_admin = (admin == "/admin")
807
        # On demande si on le droit d'être admin
808
        sock.write(json.dumps(["mayi", "activites_admin"]))
809
        hasadmin = full_read(sock)["msg"]
810 811 812
        # On est en mode administration si on en a le droit ET qu'on l'a demandé
        isadmin = asked_admin and hasadmin
        # On récupère l'activité
813
        gotit, activite = _get_activite(sock, idact, request)
814
        if not gotit:
815
            return gotit
816
        if request.method == "POST":
817
            form = forms.InviteForm(request.POST, label_suffix=" :")
818
            if form.is_valid():
819 820
                nom = form.cleaned_data["nom"]
                prenom = form.cleaned_data["prenom"]
821
                data = [nom, prenom, idact]
822 823 824 825 826
                if isadmin: # un admin doit préciser le reponsable (en cas d'échec ce sera lui le responsable)
                    try:
                        data.append(int(request.POST["idrespo"]))
                    except:
                        pass
827
                sock.write(json.dumps(["add_invite", [data, "A" * isadmin]]))
828 829 830
                out = full_read(sock)
                invitation_errors = out["errmsg"]
                invited = _is_success_code(out["retcode"])
831 832 833 834 835
                if invitation_errors:
                    if invited:
                        _add_warning(request, invitation_errors)
                    else:
                        _add_error(request, invitation_errors)
836
                else:
837
                    _add_success(request, settings.SUCCMSG_ADDINV)
838
            else:
839
                invited = False
840
            if invited: # invitation réussie, on fait un redirect
841
                return HttpResponseRedirect("%sactivites/%s%s/" % (settings.NOTE_ROOT_URL, idact, "/admin" * isadmin))
842
        else:
843
            form = forms.InviteForm(label_suffix=" :", initial=activite)
844
        gotit, liste_invites = _get_invites(sock, idact, isadmin, request)
845 846 847
        if not gotit:
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
848
        # on prépare les variables
849 850 851 852 853
        variables = {"activite": activite,
                     "form": form,
                     "liste_invites": liste_invites,
                     "hasadmin": hasadmin,
                     "isadmin": isadmin}
854 855
        variables.update(variables_standard)
        return render_to_response('note/invitation.html', variables, context_instance=RequestContext(request))
856
    else:
857 858 859
        # Une erreur a eu lieu
        response = sock_ou_response
        return response
860

861
def activite_gestion(request, idact=None, validation=None):
862 863
    """Page de gestion d'une activité"""
    # On commence par appeller la fonction standard
864
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
865
    if success:
866 867
        sock = sock_ou_response
        idact, fallback_redirect = _cast_as_idact(idact, request)
868 869
        if fallback_redirect != None:
            return fallback_redirect
870
        if (request.method == "GET") and (validation == "/delete"):
871
            # suppression de l'activité effectuée par une fonction dédiée
872
            _del_activite(sock, request, idact)
873
            return HttpResponseRedirect('%sactivites/' % (settings.NOTE_ROOT_URL,))
874 875
        gotit, activite = _get_activite(sock, idact, request, computecandelete=True, whoami=variables_standard["whoami"], isadmin=True)
        variables = {}
876 877
        if gotit:
            variables["activite"] = activite
878 879 880 881 882 883 884
            if validation == "/validate":
                action = "valider_activite"
            elif validation == "/invalidate":
                action = "devalider_activite"
            else:
                action = None
            if action:
885
                sock.write(json.dumps([action, idact]))
886
                out = full_read(sock)
887 888 889 890 891 892
                if _is_success_code(out["retcode"]):
                    if out["retcode"] == 0:
                        succmsg = settings.SUCCMSG_VALIDACT if (validation == "/validate") else settings.SUCCMSG_DEVALIDACT
                        _add_success(request, succmsg)
                    else:
                        _add_warning(request, out["errmsg"])
893
                else:
894
                    _add_error(request, out["errmsg"])
895
                return HttpResponseRedirect('%sactivites/%s/gestion/' % (settings.NOTE_ROOT_URL, idact))
896 897 898
            variables.update(variables_standard)
            return render_to_response('note/activite_gestion.html', variables, context_instance=RequestContext(request))
        else:
899 900
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
901 902 903 904 905
    else:
        # Une erreur a eu lieu
        response = sock_ou_response
        return response

906
def activite_gestion_modifier(request, idact=None):
907
    """Page pour voir/éditer une activité, en tant qu'admin"""
908
    # On appelle la fonction standard
909
    success, sock_ou_response, variables_standard = standard_page(request, socket=True)
910
    if success:
911 912 913
        sock = sock_ou_response
        variables = {}
        idact, fallback_redirect = _cast_as_idact(idact, request)
914 915
        if fallback_redirect != None:
            return fallback_redirect
916
        gotit, activite = _get_activite(sock, idact, request)
917
        if not gotit:
918 919
            # gotit est en fait un MyHttpResponseRedirect de fallback
            return gotit
920
        variables["activite"] = activite
921
        if request.method == "GET":
922
            form = forms.ActiviteForm(label_suffix=" :", initial=activite)
923 924 925 926
            variables["form"] = form
            variables.update(variables_standard)
            return render_to_response('note/activite_modifier.html', variables, context_instance=RequestContext(request))
        else:
927
            form = forms.ActiviteForm(request.POST, label_suffix=" :")
928 929 930 931
            variables["form"] = form
            if form.is_valid():
                keysact = activite.keys()
                # on regarde les champs qui sont différents
932 933
                actdata = {k: v for (k, v) in form.cleaned_data.items() if k in keysact and (v != activite[k])}
                if actdata != {}:
934 935 936 937
                    # On rajoute l'idact
                    actdata["id"] = idact
                    # On demande toujours à faire l'update en tant qu'admin
                    #  de toutes façon ça ne provoque pas d'erreur si on ne l'est pas
938
                    tosend = [actdata, "A"]