= API du serveur d'écoute de la NoteKfet2015 = ''Cette page explique le protocole de communication entre le serveur de la !NoteKfet2015 et un de ses clients. Si ce n'est pas ce que vous cherchiez, allez demander votre chemin [[../|ici]].'' <> Codé en python, le serveur tourne en permanence et écoute sur le port 4242 (par défaut) et traite les requêtes envoyées par les clients. Il est le seul à s'interfacer avec la base {{{PostgreSQL}}}, et tous les clients doivent lui parler à lui. Il est responsable de l'authentification et du système de droits. Les clients (comme l'interface HTTP) n'ont pas à s'en soucier et peuvent éventuellement faire croire à l'utilisateur qu'il a plus/moins de droit que ce qu'il a réellement. Toute communication se fait en SSL. Avant même un {{{hello}}} ou un {{{login}}}, le serveur passe la connexion en SSL et le client doit le faire aussi s'il veut pouvoir communiquer correctement. La clé et le certificat sont stockés dans les chemins d'accès correspondants ({{{keyfile}}} (qui est (ou sera) enlevé du dépôt {{{git}}}) et {{{certfile}}}) modifiables dans {{{config.py}}} = Système de droits = Il y a des utilisateurs spéciaux (leurs informations sont stockées en JSON dans le fichier dont le path est dans {{{config.authfile}}}) et des utilisateurs normaux stockés dans la base de données. Les droits sont stockés pour chaque utilisateur sous forme d'une chaîne de caractères, où les droits sont séparés par des virgules et un droit est un mot matchant {{{[a-z][a-z_]+}}}. Exemple de contenu du champ droits : {{{"login,search"}}} Des aliases servent à donner plusieurs droits en un seul. (On ne peut pas faire d'alias d'alias) En général ils ont le même nom que la fonction qu'ils permettent d'exécuter. * Les droits spécifiques des utilisateurs spéciaux (ils peuvent en plus avoir tous ceux de bdd): * die * who * adduser * deluser * users * surdroits (= comme si il avait tous les surdroits = il peut modifier les droits bdd) * speak * broadcast * Les droits des utilisateurs bdd : (NB : pour les surdroits c'est exactement la même chose) (une catégorie = un alias) * basic * myself (c'est un droit particulier qui signifie "je veux pouvoir accéder à mon propre compte". Quand on se connecte "pour laisser" on peut demander à ne pas l'avoir) * login * preinscriptions * dons * activites * invites * note * get_boutons * inscriptions * consos * get_photo * transferts * credits * retraits * quick_search * historique_transactions * transactions * comptes * search * adherents_weak (permet de modifier un compte mais pas nom/prénom) * aliases * historique_pseudo * update_photo * boutons * create_bouton * update_bouton * delete_bouton * admin * activites_admin * invites_admin * adherents_strong * full_search * overforced * forced (c'est juste un trick pour que overforced => forced) * Les droits qui n'ont pas d'alias : * wei * overforced * transactions_admin * chgpass * supprimer_compte NB : les alias {{{"root"}}} et {{{"all"}}}, hardcodés, donnent '''tous''' les droits. Dans les faits, les droits sont chargés en mémoire depuis la BDD au moment du login et sont conservés dans une variable python. Certaines actions (notamment une modification des droits) vident cette variable qui sera donc repuisé dans la BDD dès qu'il y en aura besoin. [tout ceci n'étant valable que pour les users bdd, les special users gardent leur droits pendant toute une session] = API = == Protocole de communication == === Protocole === Tous les objets sont envoyés entre le client et le serveur encodés en [[http://www.json.org/|JSON]] (grâce à la [[http://docs.python.org/2/library/json.html|librairie python]] du même nom). Le protocole est asymétrique. Voici la descriptions des objets envoyés dans chaque sens : * client -> serveur * {{{["commande"]}}} * {{{["commande", ]}}} Une fois décodé, {{{}}} est un objet qui varie selon la commande. * serveur -> client * {{{ {"retcode": , "errmsg":"un message d'erreur", "msg":} }}} (où objet est ce qu'on cherche à envoyer et {{{n}}} un code de retour) === Les différents codes de retour possibles === * succès * 0 * 101 = ce message est broadcasté * 102 = ce message provient d'un autre client * 103 = tentative de création d'un bouton déjà existant (donc il n'a pas été dupliqué) * 110 = l'invité a été ajouté parce que tu as les droits invites_admin, mais en temps normal il aurait été refusé (précisions dans l'{{{errmsg}}}) * 130 = activité validée, mais il y a conflit avec d'autres activités (spécifiées dans l'{{{errmsg}}}) * échec * 1 = hello attendu avant * 2 = paquet mal formé (non dé-JSON-isable) * 3 = la commande attend un paramètre * 4 = le paramètre est incorrect pour la commande (erreur de type, de nombre de champs, champs interdits ou mal remplis...) * 5 = login failed * 6 = tentative de suppression d'un user inexistant * 7 = tentative de self-suppression * 8 = tentative d'envoi de message à un client inexistant ou non-joignable * 9 = challenge RSA non décodable * 10 = challenge RSA refusé (cooldown non terminé) * 11 = client incompatible * 12 = pseudo déjà pris * 13 = tentative d'inscription d'un preid inexistant * 14 = adhésion pour cette année déjà faite * 16 = pas de manpage correspondante * 255 = nom de commande hors ascii * 300 = transaction échouée pour cause de solde trop négatif (et pas assez de droits forcé) * 301 = tentative de transaction sur un idbde<0 * 302 = tentative de transaction avec quantite<0 * 303 = transaction échouée (bouton ou compte inexistant) * 304 = transaction échouée, compte pas à jour d'adhésion * 305 = tentative de transfert avec un montant<0 * 306 = transaction échouée, compte bloqué * 310 = ne peut pas valider une transaction valide * 311 = ne peut pas dévalider une transaction invalide * 312 = ne peut pas valider/dévalider une transaction cantinvalidate=true * 313 = ne peut pas valider/dévalider une transaction trop ancienne * 314 = ne peut pas valider/dévalider une transaction qu'on n'aurait pas pu réaliser * 402 = la modification de ce mot de passe est impossible (supreme) * 403 = droits insuffisants * 404 = Not found (ça peut être la commande, un idbde,...) * 410 = ne peut pas supprimer le compte, solde non nul * 411 = ne peut pas supprimer le compte, il a trop de droits * et le "length required" :-( -- NativeeKing <> * J'y ai pensé en rajoutant le 412. Trop tard. Je vais pas changer les codes d'erreur du protocole ^^ D'autant que la length est jamais required :p -- [[Wiki20-100]] <> * 412 = ne peut pas supprimer le compte, il est déjà supprimé * 414 = requête trop longue * 500 = recherche mal formulée, tu demandes à rechercher dans les alias mais tu ne donnes pas d'alias à rechercher * 501 = recherche mal formulée, tu demandes à rechercher dans l'historique mais tu ne donnes pas d'historique à rechercher * 555 = Erreur interne (c'est-à-dire, dans la BDD) * 666 = j'ai crashé sans trop savoir pourquoi, je t'envoie l'erreur (ne devrait pas se produire en prod :D) * 701 = activité à durée négative * 710 = impossible d'inviter à cette activité (non validée, ou pas de liste ou liste imprimée) * 711 = un special user ne peut pas inviter sans préciser le responsable * 712 = liste non ouverte à l'heure actuelle * 713 = responsable en négatif, il ne peut pas inviter * 714 = vous n'êtes pas le responsable de cet invité, vous ne pouvez pas le supprimer * 715 = cette personne a déjà été invitée à cette activité * 720 = ne peut pas supprimer l'activité parce qu'il y a des invités * 721 = ne peut pas supprimer l'activité parce que vous n'êtes pas le responsable * 722 = ne peut pas supprimer l'activité parce qu'elle est validée * 731 = ne peut pas modifier l'activité parce que vous n'êtes pas le responsable * 732 = ne peut pas modifier l'activité parce qu'elle est validée * 740 = erreur à la génération du pdf de la liste d'invités * 801 = photo trop grosse * 802 = format de photo non autorisé * 803 = échec de b64-décodage de la photo * 804 = erreur pendant le redimensionnement de la photo * 805 = erreur pendant la conversion en .png * 806 = erreur pendant la suppression du fichier non-.png == Les commandes == === Commandes basiques === ==== hello ==== Droit nécessaire : aucun (même pas d'authentification nécessaire) Attend en paramètre une chaîne de caractère qui est la version du client. {{{ ["hello", "v0.0"] }}} ==== help ==== Droit nécessaire : aucun (même pas d'authentification nécessaire) N'attend aucun paramètre. Renvoie la liste des commandes existantes. {{{ ["help"] }}} ==== man ==== Droit nécessaire : aucun (même pas d'authentification nécessaire) Attend en paramètre une chaîne de caractère. Renvoie de l'aide sur la commande fournie. {{{ ["man", "login"] }}} ==== login ==== Droit nécessaire : login Attend en paramètre une liste. {{{ [, , , ] }}} * pouvant être "bdd" ou "secial" * est à transmettre en clair (d'où l'intérêt du SSL). * est une liste de la forme : * Si on est en "special" ["droit1", "droit2", …] * Si on est en "bdd" [["droit1", "droit2", …], ["surdroit1", "surdroit2", …], ] ( étant un booléen) Le masque est la liste des droits qu'on ne *veut pas* pour cette session. (Habituellement, on mettra donc [[], [], False]). * Il serait bien que login renvoie la liste des droits de l'utilisateur -- ValentinSamir <> * Je veux bien, ça demande à peine une ligne de plus dans le code, mais je suis pas sûr que ce soit utile. Jette un œil à la commande mayi. Si tu veux vraiment, je peux le rajouter -- [[Wiki20-100]] <> {{{ ["login", ["20-100", "plop", "bdd", [[], [], true]]] }}} En se connectant de cette façon, on ne pourra pas utiliser les droits supreme, même si on les a. ==== myconnection ==== Droit nécessaire : aucun (même pas d'authentification nécessaire) N'attend aucun paramètre. Renvoie les informations sur la connection courante: ip, port émetteur (+ userid, username s'il y a lieu). {{{ ["myconnection"] }}} === Commandes des utilisateurs spéciaux === ==== die ==== Droit nécessaire : die (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Éteint le serveur. {{{ ["die"] }}} ==== adduser ==== Droit nécessaire : adduser (utilisateurs spéciaux uniquement) Attend en paramètre une liste [, , ] Ajoute/met à jour un utilisateur spécial. * est en clair. Si on fourni "-", il ne sera pas modifié (ne marche que si on update un utilisateur existant) * est une liste de strings qui sont les nouveaux droits de l'user. {{{ ["adduser", ["vincent", "plop", ["login", "die"]]] }}} ==== deluser ==== Droit nécessaire : deluser (utilisateurs spéciaux uniquement) Attend en paramètre un string (le pseudo de l'user). Supprime l'utilisateur spécial (NB : on ne peut pas se supprimer soi-même). {{{ ["deluser", "vincent"] }}} ==== users ==== Droit nécessaire : users (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Donne la liste des utilisateurs spéciaux existants avec leurs droits. {{{ ["users"] }}} === Commandes who === ==== who ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Donne la liste des clients connectés avec leur ip,port (+ userid, username s'il y a lieu). N'est en réalité qu'un alias de "whowith {}" {{{ ["who"] }}} ==== whowith ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) Attend en paramètre un dico {"ip": , "userid": , "username": , "client": }, chacune des clés pouvant être absente. (en revanche, fournir une des trois liste vide serait stupide). Donne la liste des clients connectés qui vérifient la propriété : (client.ip in listip) AND (client.userid in listid) AND (client.username in listnames) AND (client.version in listclient). Si une clé est omise, aucun filtrage n'est fait sur ce paramètre. {{{ ["whowith", {"userid": ["vincent"], "client": ["manual"]}] }}} ==== whospecial ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Alias de {{{ ["who", {"userid": ["special"]}] }}} ==== whomanualclient ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Alias de {{{["who", {"client": ["manual"]}] }}} ==== whohttpclient ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Alias de {{{ ["who", {"client": ["http"]}] }}} ==== whononeclient ==== Droit nécessaire : who (utilisateurs spéciaux uniquement) N'attend aucun paramètre. Alias de {{{ ["who", {"client": ["None"]}] }}} === Commandes de communications === ==== client_speak ==== Droit nécessaire : speak (utilisateurs spéciaux uniquement) Attend en paramètre : {{{ [, ] }}} * {{{}}} est l'{{{idServer}}} du destinataire * {{{}}} est un message, quelconque du moment qu'il est dé-JSONizable Envoie le message (re-JSONizé) au server numéro {{{}}}, précédé de {{{"Message from () :\n"}}}. Répond {{{"Done"}}} ou {{{"Ce client n'existe pas ou a été déconnecté."}}}. {{{ ["client_speak", [1, "Salut n°1"]] }}} ==== client_broadcast ==== Droit nécessaire : broadcast (utilisateurs spéciaux uniquement) Attend en paramètre : un message, quelconque du moment qu'il est dé-JSONizable Envoie le message (re-JSONizé) à tous les clients connectés précédé de {{{"Broadcast from () :\n"}}} {{{ ["client_broadcast", "Attention tout le monde, je reboote le serveur."] }}} == Les fonctions de note == === Recherche et affichages === ==== search ==== Droit nécessaire : search (ou full_search pour certains champs) Fait une recherche sur les comptes. Peut nécessiter les droits full_search en fonction des champs demandés. data = [flags, liste_de_fields, terme_de_recherche] OU data = [flags, ] * les flags possibles sont : * o cherche aussi dans les comptes qui ne sont pas à jour d'adhésion * a affiche les alias * A cherche aussi dans les alias * h affiche l'historique des pseudos * H cherche aussi dans l'historique des pseudos * b ne cherche que les match sur le début du mot (LIKE 'terme%') * i insensible à la casse (ILIKE) * x exact match (LIKE 'terme') (le comportement par défaut est LIKE '%terme%') (x écrase b) * les fields : idbde, pseudo, nom, prenom, mail, fonction, idcrans, commentaire * fields nécessitant full_search : tel, adresse, pbsante, numsecu Une recherche est faite sur tous les champs avec les options précisées par les flags puis est renvoyée la liste des [idbde, nom, prenom, pseudo, mail, solde, section] qui matchent. Attention, si plusieurs champs de recherche sont fournis, le test est une disjonction. Les idbde<0 sont ignorés. {{{ ["search", ["ai", {"idbde": "2", "nom": "hello"}]] }}} Renverra tous les comptes (en affichant leurs aliases) dont l'idbde contient un 2 OU dont le nom contient "hello" (sans tenir compte de la casse) {{{ ["search", ["bi", ["pseudo", "nom"], "Ch"]] }}} Renverra tous les comptes dont le nom OU le pseudo commence par "ch" (aussi case insensitive) ==== quick_search ==== Droit nécessaire : search data = [, ] Effectue une recherche simple : * sur les pseudos, les alias et l'historique * avec un filtre begin * case insensitive * on a juste le choix de préciser old ou pas (par défaut, on ne va pas chercher les comptes non à jour) * cas particulier : si term est le forme #qqc, on ne fait rien de tout ça mais on cherche sur les idbde Ne renvoie que ce qui a matché et l'idbde correspondant et l'appelle "terme" (que ce soit un pseudo, alias ou historique) Rajoute également un champ "was" qui peut être "pseudo", "alias" ou "historique" pour qu'on puisse savoir ce qui a matché (pour une mise en forme différente, par exemple) Donne également des infos sur la négativité du compte, dans le champ "negatif" : * 0 : en positif * 1 : solde_negatif > solde > solde_tres_negatif * 2 : solde_tres_negatif > solde > solde_pas_plus_negatif (forced sera nécessaire) * 3 : solde_pas_plus_negatif > solde (overforced sera nécessaire) Les idbde<0 sont ignorés. {{{ ["quick_search", ["plo"]] }}} Renvoie une liste de dictionnaires : {{{ {"idbde": , "negatif": , "nom": , "prenom": , "solde": , "terme": , "was": <"pseudo" ou "alias" ou "historique">} }}} où commence par "plo" (sans tenir compte de la casse) {{{ ["quick_search", ["#3", "o"]] }}} Renvoie une liste de dictionnaires analogues mais où "was" sera forcément "idbde" et commencera par 3. De plus, des comptes qui ne sont pas à jour d'adhésion sont susceptibles d'être renvoyés. === whoami === Droit nécessaire : myself N'attend aucun paramètre. Renvoie la totalité des données de l'utilisateur courant (pas la photo). Pas de vérification de droits puisqu'un user peut tout voir sur son compte. Ça marche aussi pour un user spécial même si ce qui est affiché est loin d'être passionnant… {{{ ["whoami"] }}} ==== compte ou adherent ==== Droit nécessaire : adherents_weak Attend en paramètre : un entier Renvoie les informations détaillées du compte. N'affiche pas {{{numsecu}}} ni {{{pbsante}}} si l'utilisateur n'a pas les droits wei. {{{ ["compte", 1] }}} === Modifications === ==== update_photo ==== Droit nécessaire : adherents_weak (si c'est la photo d'un autre) ou myself si c'est la sienne Attend en paramètre une liste [, ]. Ensuite il faut lui envoyer (avec un deuxième send) la base64 du fichier qu'on veut envoyer. (elle a intérêt à ne pas dépasser taille, sinon on va avoir des problèmes…) Le fichier est décodé à l'arrivée, convertit en png et sotcké dans {{{/home/note/Note_Kfet_2015_server/photos/.png}}} La liste des format acceptés est dans config.py (ImageMagick est utilisé pour faire la conversion). {{{ ["update_photo", [1, "VGhlIGFuc3dlciB0byBsaWZlLCB0aGUgdW5pdmVyc2UgYW5kIGV2ZXJ5dGhpbmcuCg==", "bmp"]] }}} ==== preinscrire ==== Droit nécessaire : preinscriptions (inclus dans "basic") Attend en paramètre un dictionnaire du genre {"nom":"Dupond","prenom":"Jean","mail":"dupond@crans.org", ...} Seuls les champs "nom", "prenom" et "mail" sont obligatoires. Enregistre une préinscription (dans la table du même nom). Remplace tous les champs non renseignés par leur valeur par défaut. Vérifie que nom et prenom sont non vides et les met en casse {{{.title()}}}, et fait une vérification (assez faible) sur le format de l'adresse mail. {{{ ["preinscrire", {"type": personne", "nom": "Dupond", "prenom": "Jean", "mail": "plouf@crans.org", "section": "42A0", "sexe": "M", "normalien": true}] }}} ==== inscrire ==== Droit nécessaire : inscriptions (inclus dans "note") Attend en paramètre une liste [, , ] où est l'identifiant de la préinscription qu'on cherche à valider, contient des données sur l'adhérent (dont au moins "wei" (booléen), et "annee" (entier), ainsi que "section" si il n'a pas été fourni à la préinscription). * paiement est lui-même une liste [, , ] * = montant (en plus de l'adhésion) versé sur la note * = "cheque" ou "especes" ou "virement" ou "soge" * = {"nom": , "prenom": , "banque": } {{{ ["inscrire" [1, {"wei": true, "annee": 2015, "adresse": "G999"}, [1000, "cheque", {"nom": "Dupond", "prenom": "Jean", "banque": "Sogé"}]]] }}} ==== alias ==== Droit nécessaire : aliases Attend en paramètre : {{{ [, ] }}} Ajoute un alias à un compte. {{{ ["alias", [5, "toto"]] }}} === unalias === Droit nécessaire : aliases Attend en paramètre : un alias ou un idbde Enlève un alias, ou tous les alias d'un compte. {{{ ["unalias", "toto"] }}} {{{ ["unalias", 5] }}} === Boutons === ==== get_boutons ==== Droit nécessaire : get_boutons (inclus dans "note") Attend en paramètre une liste {{{ [, ] }}} (deux strings). Renvoie tous les boutons (sous forme d'une liste de dictionnaires) contenant term dans leur label et qui sont dans la catégorie categ. Si term est vide, il laisse tout passer, si categ est vide alors aucun tri n'est fait dans les catégories. {{{ ["get_boutons", ["", ""]] }}} Récupère tous les boutons ==== create_bouton ==== Droit nécessaire : create_bouton (inclus dans "boutons") Attend en paramètre un dico contenant les clés {{{"label"}}}, {{{"montant"}}}, {{{"destinataire"}}} et {{{"categorie"}}}. Ajoute un bouton à la table (sauf si categorie n'existe pas encore, si destinataire n'est pas un club, ou si un bouton identique existe déjà). {{{ ["create_bouton", {"label": "Pinte", "montant": 200, "destinataire": 0, "categorie": "Alcool"}] }}} ==== update_bouton ==== Droit nécessaire : update_bouton (inclus dans "boutons") Attend en paramètre un dico contenant au moins la clé "id". Update un bouton avec le dico (à condition qu'il existe). {{{ ["update_bouton", {"id": 3, "montant": 400, "destinataire": 5}] }}} ==== delete_bouton ==== Droit nécessaire : delete_bouton (inclus dans "boutons") Attend en paramètre un entier. (id) Supprime le bouton n°id. {{{ ["delete_bouton", 3] }}} === Transactions === ==== consos ==== Droit nécessaire : consos (inclus dans "note") + forced ou overforced pour débiter des gens en négatif. Attend en paramètre un liste de {{{ [, , ] }}} Fait consommer tous les boutons à tous les comptes. Renvoie une liste de {{{[, , ]}}} correspondant au succès des différentes transactions demandées. Si un {{{}}} ou une {{{}}} est <0 alors un message d'insulte est envoyé et rien n'est fait. Si un compte ou un bouton n'existe pas, un message d'insulte est envoyé (à chaque fois qu'on passe dessus) mais le reste est effectué. Si des comtes sont en négatif (au sens de la config), des avertissements sont envoyés et les transactions correspondantes sont enregistrées en {{{valide=false}}} (le reste continue à s'exécuter). Les consos sont faites dans l'ordre de la liste, donc un adhérent peut devenir négatif au milieu. {{{ ["consos", [[1, 1, 3], [3, 6, 1]]] }}} ==== transferts ==== Droit nécessaire : transferts (inclus dans "note") + forced ou overforced pour débiter des gens en négatif. Attend en paramètre : {{{ [, , , ] }}} Effectue le transfert de chacun des émetteurs vers chacun des destinataires. Si un compte n'existe pas, un message d'insulte est envoyé (à chaque fois qu'on passe dessus) mais le reste est effectué. Si des comtes sont en négatif (au sens de la config), des avertissements sont envoyés et les transactions correspondantes sont enregistrées en {{{valide=false}}} (le reste continue à s'exécuter). Les transferts sont faits dans l'ordre de la liste, donc un adhérent peut devenir négatif au milieu. {{{ ["transferts", [[1, 3], [2, 4], 1500, "Hanjo"]] }}} ==== crediter ou credit ==== Droit nécessaire : credits Attend en paramètre : {{{ [, , , ] }}} Avec {{{ = {"nom":, "prenom":, "banque":, "motif":} }}} Fait un crédit (pas de multi-crédit). {{{}}} peut rester vide pour un crédit espèces; dans tous les cas, {{{"motif"}}} est facultatif. {{{ ["crediter", [1, 10000, "cheque", {"nom" : "Passoire", "prenom" : "Toto", "banque" : "Sogé", "motif" : "Toto a eu raison d'adhérer au BDE"}]] }}} ==== retirer ou retrait ==== Droit nécessaire : retraits Attend en paramètre : {{{ [, , , ] }}} Avec {{{ = {"nom":, "prenom":, "banque":, "motif":} }}} Fait un retrait (pas de multi-retrait). {{{}}} peut rester vide pour un retrait espèces; dans tous les cas, {{{"motif"}}} est facultatif. {{{ ["retirer", [1, 450, "especes", {"motif" : "Pour aller payer un prima"}]] }}} ==== dons ==== Droit nécessaire : dons Attend en paramètre : {{{ [, , ] }}} Effectue des dons de l'utilisateur courant vers les destinataires. (pas accessible aux special users) On ne peut pas faire un don si son solde après transaction est <0. (0 hardcodé, contrairement au solde "être en négatif") {{{ ["dons", [[2065], "2000", "Participation anniversaire Kfet"] }}} == Pas encore documentées mais ça viendra == * historique_pseudo * search_historique_pseudo * chgpass * update_compte * get_last_modified_photo * get_photo * get_preinscription * get_preinscriptions * del_preinscription * get_default_pseudo * readherer * get_boutons_categories * get_un_bouton * get_clubs * historique_transactions * valider_transaction * devalider_transaction * get_activites * get_activite * add_activite * update_activite * del_activite * valider_activite * devalider_activite * add_invite * del_invite * get_invites * django_get_accessible_pages * mayi Si vous tenez absolument à savoir tout de suite comment ça marche, vous pouvez essayer en tâtonnant, utiliser [[#man|man]]. Sinon, vous pouvez toujours aller voir la [[http://bde2.crans.org/doc/note/backend/|doc complète]]